分享

NodeJS虚拟机

 Frank_Chia 2018-04-05

2016-12-21 by 程序猿

NodeJS 中有一个名为 vm 的包,用来创建运行 NodeJS 代码(JavaScript, ECMAScript)的虚拟机。


var vm = require('vm');


这个vm的方法不多:

vm.Script
vm.createScript
vm.createContext
vm.runInDebugContext
vm.runInContext
vm.runInNewContext
vm.runInThisContext
vm.isContext

详情查看 NodeJS文档: VM

简述

简单地来说,这个包里面有Script(脚本)、Context(上下文)这两种对象。


上下文中含有当前可以操作的各种对象,因此又可以称为运行环境。如果我们生成一个新的上下文并与当前上下文隔离,那么就相等于我们建立了一个沙箱,在沙箱中运行的NodeJS脚本将无法影响外部的环境。


关于Script这个对象,其实存在感不是很强,因为它直接由字符串构造,在很多时候可以直接用代码字符串代替。


在沙箱中运行NodeJS代码var vm = require('vm');
var sandbox = vm.createContext({}); // Empty Context
var code = 'var x = 1;'
vm.runInContext(code, sandbox);
console.log(sandbox); // {x: 1}
NodeJS评测机

可以针对NodeJS设计一类评测机,它可以直接检查沙箱内的变量来判断程序是否正确。


这个时候,初始的上下文就相当于输入数据。


var vm = require('vm');
var testcases = [];
for (var i = 0; i < 1000; i++) {
var a = Math.random() * 1000;
var b = Math.random() * 1000;
testcases.push({
input: {
a: a,
b: b
},
output: {
ans: a + b,
}
})
}
var code = 'ans = a + b + (a < 5 ? 1: 0)';
testcases.forEach((e, i) => {
var sandbox = vm.createContext(e.input);
vm.runInContext(code, sandbox, {time: 1000}); // time limit: 1000ms
for(var key in e.output){
if(e.output[key] != sandbox[key]){
console.log(`testing failed in case ${i}`);
break;
}
}
})

上面这个程序生成了1000组随机数,并逐个建立沙盒,然后在其中运行代码,最后检查运行后的环境中某些变量是否符合要求。

这里是一个很简单的 A + B Problem, 但这里代码加入了一个扰动,当 a < 5 时答案会错误,这是刻意制造的错误,用于演示错误发生的情况。

可以看到,这样的待测试代码里面并不依赖I/O,评测系统的用户在提交代码的时候无须包括操作stdin, stdout的代码。就像写一个函数一样。


重载 Require

尽管当你不做任何事的时候,当用户代码包含var x = require(XXX); 试图加载包的时候,将会报ReferenceError: require is not defined 的错误。


因为他们的沙盒中根本就没有这个require函数,就没有办法加载包,更不会加载一些危险的包,如vm与fs。


但是,如果你一定要给他们开放包可供引用,那么你可以将外部的require函数扔到沙箱中:


var sandbox = vm.createContext({
require: require
})

这样用户代码里包含 require 函数将不会报错,会正确执行。


为了安全,你决定过滤一些危险的包:


var myRequire(package){
if(package == 'fs' || package == 'vm') return {};
return require(package);
}
var sandbox = vm.createContext({
require: myRequire
});

这样用户在引用 fs, vm 这些包的时候就会发现,得到的是一个空对象,自然也无法调用其中的方法了。


过滤的逻辑非常自由,你可以在里面加钩子,甚至重载整个 require 函数,甚至加入一些你自己的包。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多