2016-12-21 by 程序猿 NodeJS 中有一个名为 vm 的包,用来创建运行 NodeJS 代码(JavaScript, ECMAScript)的虚拟机。 var vm = require('vm'); 这个vm的方法不多: vm.Scriptvm.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 函数,甚至加入一些你自己的包。 |
|
来自: Frank_Chia > 《区块链》