13

我想使用该vm模块作为运行外部代码的安全方式。它工作得很好,但还有一个问题:

var UNKNOWN_CODE = "while(true){}";

var vm = require("vm");

var obj = {};
var ctx = vm.createContext(obj);

var script = vm.createScript(UNKNOWN_CODE);

script.runInNewContext(ctx);

console.log("finished"); //never executed

有什么方法可以取消执行(例如,如果它持续超过 5 秒)?

提前致谢!

4

5 回答 5

25

您需要在单独的进程中运行它,例如:

master.js:

var cluster = require('cluster');

cluster.setupMaster({
  exec : "runner.js",
  args : process.argv.slice(2),
  silent : false
});
//This will be fired when the forked process becomes online
cluster.on( "online", function(worker) {
    var timer = 0;

    worker.on( "message", function(msg) {
        clearTimeout(timer); //The worker responded in under 5 seconds, clear the timeout
        console.log(msg);
        worker.destroy(); //Don't leave him hanging 

    });
    timer = setTimeout( function() {
        worker.destroy(); //Give it 5 seconds to run, then abort it
        console.log("worker timed out");
    }, 5000);

    worker.send( 'while(true){}' ); //Send the code to run for the worker
});
cluster.fork();

亚军.js:

//The runner.js is ran in a separate process and just listens for the message which contains code to be executed
process.on('message', function( UNKNOWN_CODE ) {

    var vm = require("vm");

    var obj = {};
    var ctx = vm.createContext(obj);

    var script = vm.createScript(UNKNOWN_CODE);

    script.runInNewContext(ctx);

    process.send( "finished" ); //Send the finished message to the parent process
});

要运行此示例,请将这些文件放在同一文件夹和目录中并运行

node master.js

您应该会在 5 秒后看到“工作人员超时”消息。如果您将其更改为'while(false){}'worker 将立即执行代码,您应该会看到"finished"

集群文档

于 2012-07-29T21:24:57.753 回答
6

您可以将“脚本断路器”嵌入 UNKNOWN_CODE。就像是:

;setTimeout(function() { throw new Error("Execution time limit reached!") }, 2000);

所以,整个事情看起来像这样:

var UNKNOWN_CODE = "while(true){}";

var scriptBreaker = ';setTimeout(function() { throw new Error("Execution time limit reached!") }, 2000);';

var vm = require("vm");

var obj = {};
var ctx = vm.createContext(obj);

var script = vm.createScript(scriptBreaker + UNKNOWN_CODE);

try {
  script.runInNewContext(ctx);
  console.log("Finished");
}
catch (err) { 
  console.log("Timeout!"); 
  // Handle Timeout Error...
}

更新:

经过更多测试后,我得出结论,可靠的方法是使用指向 Esailija 的过程。但是,我的做法有点不同。

在主应用程序中,我有这样的代码:

var cp = require('child_process');

function runUnsafeScript(script, callback) {
 var worker = cp.fork('./script-runner', [script]);

 worker.on('message', function(data) {
  worker.kill();
  callback(false, data);
 });

 worker.on('exit', function (code, signal) {
  callback(new Error(code), false);
 });

 worker.on('error', function (err) {
  callback(err, false);
 });

 setTimeout(function killOnTimeOut() {
  worker.kill();
  callback(new Error("Timeout"), false);
 }, 5000);
}

在 script-runner.js 中,它如下所示:

var vm = require("vm");
var script = vm.createScript( process.argv[2] );
var obj = { sendResult:function (result) { process.send(result); process.exit(0); } };
var context = vm.createContext(obj);

script.runInNewContext(context);

process.on('uncaughtException', function(err) {
 process.exit(1);
});

这种方法可以实现以下目标:

  • 在有限的上下文中运行脚本
  • 避免死循环和异常问题
  • 同时运行许多(受硬件限制)不安全的脚本,这样它们就不会相互中断
  • 将脚本执行结果传递给主应用程序进行进一步处理
于 2014-03-18T22:19:43.653 回答
4

是的,这现在是可能的,因为我在 Node模块中添加了timeout参数支持。vm您可以简单地将毫秒超时值传递给runInNewContext(),如果代码未在指定的时间内完成执行,它将引发异常。

请注意,这并不意味着运行不受信任代码的任何类型的安全模型。这只是允许您超时您信任或以其他方式安全的代码。

var vm = require("vm");

try {
    vm.runInNewContext("while(true) {}", {}, "loop", 1000);
} catch (e) {
    // Exception thrown after 1000ms
}

console.log("finished"); // Will now be executed

正是您所期望的:

$ time ./node test.js
finished

real    0m1.069s
user    0m1.047s
sys     0m0.017s
于 2013-06-02T17:54:39.580 回答
3

您可能想检查Threads a Gogo。不幸的是,它还没有更新到 0.8.x。

但是,正如@Esailija 所提到的,除非它在另一个进程中,否则无法安全地运行外部代码

var Threads = require('threads_a_gogo');

var t = Threads.create();
t.eval("while(true) { console.log('.'); }");

setTimeout(function() {
  t.destroy();
  console.log('finished');
}, 1000);
于 2012-08-03T00:35:05.127 回答
0

在较新版本的 Node.js(v0.12及更高版本)中,您将能够将超时选项传递给vm.runInNewContext.

这还没有在任何稳定的节点版本中,但是如果您希望使用最新的不稳定版本(v0.11.13),您可以像这样传递一个超时参数:

vm.runInNewContext('while(1) {}', {}, {timeout: '1000'});

现在,在 1000 毫秒后,脚本将抛出超时错误。你可以像这样抓住它:

try {
    vm.runInNewContext('while(1) {}', {}, {timeout: '1000'});
}
catch(e) {
    console.log(e); // Script execution timed out.
}
于 2014-05-12T03:23:08.877 回答