NodeJS虚拟机

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函数,就没有办法加载包,更不会加载一些危险的包,如vmfs

但是,如果你一定要给他们开放包可供引用,那么你可以将外部的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 函数,甚至加入一些你自己的包。

你可能感兴趣的:(JavaScript,虚拟机,nodejs)