Combine NodeJS Fibers + VM Sandbox

258 Views Asked by At

I want to run some untrusted code in Node that might look like this:

for (var i = 0; i < 5; i++){
    green_led(1);
    sleep(500);
    green_led(0);
    sleep(500);
}

Using Fibers, I got the synchronous behaviour working as expected:

var Fiber = require('fibers');

function sleep(ms){
    var fiber = Fiber.current;
    setTimeout(function(){ fiber.run(); }, ms);
    Fiber.yield();
}
function green_led(active){
    //...
}
Fiber(function(){
    for (var i = 0; i < 5; i++){
        green_led(1);
        sleep(500);
        green_led(0);
        sleep(500);
    }
}).run();

The difficulty is how to sandbox the code. Having to use Fibers makes it really complicated; I'm not really sure how to start. How might I get the above sandboxed with vm2? For example, the following obviously won't work:

var code = "\
for (var i = 0; i < 5; i++){\
    green_led(1);\
    sleep(500);\
    green_led(0);\
    sleep(500);\
}\
";
function sleep(ms){
    var fiber = Fiber.current;
    setTimeout(function(){ fiber.run(); }, ms);
    Fiber.yield();
}
function green_led(active){
    //...
}
Fiber(function(){
    vm.run(code);
}).run();

(It won't work because green_led and sleep are not visible to the sandboxed code in the VM).

How should this be done? Either...

  1. Perhaps everything should be run inside the VM, including Fibers and implementation of green_led etc?
  2. Or would it be better to keep code run by the VM minimal, and instead somehow whitelist/proxy green_led and sleep? Not exactly easy on the gray matter, it's hard enough understanding how Fibers work in the first place!
1

There are 1 best solutions below

0
On

It's actually quite straight-forward.

var Fiber = require('fibers');

const {VM} = require('vm2');
const vm = new VM({
    sandbox: {
        green_led: green_led,
        sleep: sleep
    }
});

function sleep(ms){
    var fiber = Fiber.current;
    setTimeout(function(){ fiber.run(); }, ms);
    Fiber.yield();
}
function green_led(active){
    //...
}

vm.run(
    `Fiber(function(){
        for (var i = 0; i < 5; i++){
            green_led(1);
            sleep(500);
            green_led(0);
            sleep(500);
        }
    }).run()`
);

The above approach passes in a reference to Fiber and the other functions sleep and green_led via the sandbox object. This could be done in other ways. For example, sleep and green_led could be defined within the string passed to vm.run(), and the vm could itself include fibers like so:

const {NodeVM} = require('vm2');
var vm = new NodeVM({
    require: {
        external: true,
    }
});
vm.run(
    `
    var Fiber = require("fibers");
    function sleep(ms){
        var fiber = Fiber.current;
        setTimeout(function(){ fiber.run(); }, ms);
        Fiber.yield();
    }
    function green_led(active){
        //...
    }
    Fiber(function(){
        for (var i = 0; i < 5; i++){
            green_led(1);
            sleep(500);
            green_led(0);
            sleep(500);
        }
    }).run()`
);

As per the documentation, notice the difference between VM and NodeVM. Out of the above two methods, only the first would be able to make use of the timeout feature. Also, the second method is not immune to while (true) {} etc.