Unsuccessful sandboxing using runInNewContext()

304 views
Skip to first unread message

Steve M

unread,
Aug 5, 2011, 6:31:57 PM8/5/11
to nodejs
Hi,

I'm currently building an app that will allow users to host node.js
code in a shared environment, with the only isolation between user
code being sandboxing, but my efforts have been fruitless so far in
trying to get this working using vm.runInNewContext().

The set up is an nginx web server proxying requests to a nodejs app.
The nodejs app is running a HTTP server and reading the user's code
from disk, then passing the code to runInNewContext to be executed:

// user's code is loaded from disk..
hostedCode = fs.readFileSync("/nodejs/foo.js", "utf8");

// vars passed to the hosted app so they can use it
var sandbox = {
require: require,
response: response,
request: request
};

// Run the hosted code
vm.runInNewContext(hostedCode, sandbox);

I've noticed that if there is code in hostedCode that, for instance,
replaces readFileSync by doing something like ..
require("fs").readFileSync = function() { return "replaced!"; }

.. then the code from the first example (loading hostedCode) ceases to
work because readFileSync has been wiped out globally.

Is this expected behavior? I would have thought that the code executed
in runInNewContext would not impact anything else?

Any help would be appreciated.

Steve M.

Mark Hahn

unread,
Aug 6, 2011, 2:53:44 AM8/6/11
to nod...@googlegroups.com
May I suggest child processes instead?  I wrote an app that spawned a lot of processes running separate copies of node and it worked well.  I passed request to them by creating a small version that only had the needed properties and then serializing it to send over stdin.  I wrote my own versions of result.writehead, result.write, and result.end that ran in the child process.  These routines just serialized the result data to send back over stdout.

Of course the child node processes were *very* isolated, much better than a sandbox.

--
Job Board: http://jobs.nodejs.org/
Posting guidelines: https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
You received this message because you are subscribed to the Google
Groups "nodejs" group.
To post to this group, send email to nod...@googlegroups.com
To unsubscribe from this group, send email to
nodejs+un...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/nodejs?hl=en?hl=en

Steve M

unread,
Aug 6, 2011, 3:36:08 AM8/6/11
to nodejs
I was hoping to avoid having to spawn individual nodejs processes to
avoid the overhead. It would be great if it was possible somehow to
have a 'pool' of nodejs processes running that scale up and down
automatically based on load, but I haven't seen any way to do this
within nodejs or with an external tool.

Of course I could write something myself, but as soon as I go down the
path of hacking nodejs for this, I have to keep it updated with every
nodejs update..

On Aug 6, 8:53 am, Mark Hahn <m...@boutiquing.com> wrote:
> May I suggest child processes instead?  I wrote an app that spawned a lot of
> processes running separate copies of node and it worked well.  I passed
> request to them by creating a small version that only had the needed
> properties and then serializing it to send over stdin.  I wrote my own
> versions of result.writehead, result.write, and result.end that ran in the
> child process.  These routines just serialized the result data to send back
> over stdout.
>
> Of course the child node processes were *very* isolated, much better than a
> sandbox.
>

Marak Squires

unread,
Aug 6, 2011, 3:43:46 AM8/6/11
to nod...@googlegroups.com
We've got most of the pieces you'll want to build something like this available @ http://github.com/nodejitsu Check out haibu and haibu-carapace. 

We'll also be adding distributed work-pool logic to hook.io sometime in the near future. 

- Marak

Mark Hahn

unread,
Aug 6, 2011, 3:44:27 AM8/6/11
to nod...@googlegroups.com
I do keep a pool or processes, although I don't do anything to decrease the size of it.  I've had no problem because I don't get large spikes of activity.  Of course it wouldn't be much work to shrink the size.

The only negative I can think of is the serializing overhead. I worry about this but I haven't measured it. 


On Sat, Aug 6, 2011 at 12:36 AM, Steve M <sm4...@gmail.com> wrote:

Mark Hahn

unread,
Aug 6, 2011, 3:47:53 AM8/6/11
to nod...@googlegroups.com
. It would be great if it was possible somehow to
have a 'pool' of nodejs processes running that scale up and down
automatically based on load, but I haven't seen any way to do this
within nodejs or with an external tool.

I just realized that my app doesn't have the requirement to start arbitrary node apps.  It might be impossible to pool processes for your use case.  I'd have to think about that.

Marcel Laverdet

unread,
Aug 6, 2011, 4:10:31 AM8/6/11
to nod...@googlegroups.com
> I was hoping to avoid having to spawn individual nodejs processes to
> avoid the overhead.

You are walking down a dark path. The JS sandbox is a very well-researched and hard to solve problem. See Caja, FBJS, SES, AdSafe, and many others. It's a little easier to solve in a modern environment like v8, but still the kind of stuff that research papers are built on.

You're just scratching the surface of the problems you will run into. Consider a more destructive application like this:
require.__proto__.__proto__.foo = 1;

Now in your host application every object will have a new key `foo` which will mess up all your loops and cause all kinds of chaos. I highly recommend biting your lip and creating child processes.

On Sat, Aug 6, 2011 at 12:36 AM, Steve M <sm4...@gmail.com> wrote:

mscdex

unread,
Aug 6, 2011, 4:18:24 AM8/6/11
to nodejs
On Aug 6, 3:36 am, Steve M <sm49...@gmail.com> wrote:
> I was hoping to avoid having to spawn individual nodejs processes to
> avoid the overhead. It would be great if it was possible somehow to
> have a 'pool' of nodejs processes running that scale up and down
> automatically based on load, but I haven't seen any way to do this
> within nodejs or with an external tool.

You're going to want to run them in a separate process, since you are
running arbitrary javascript. For example, someone could write a
script that locks up the process forever, which means your main script
would stop serving requests if you were running it within the same
process.

Steve M

unread,
Aug 6, 2011, 6:02:08 AM8/6/11
to nodejs
Thank you, everyone; very helpful feedback. For now I'll go with the
spawning of another process and if performance becomes an issue will
look into an alternate approach.

Laurie Harper

unread,
Aug 6, 2011, 6:55:50 AM8/6/11
to nod...@googlegroups.com
Be aware that that's not enough to run untrusted code safely. A user script that: gets hold of a reference to the 'fs' module can mess with your file system; gets hold of a reference to the 'net' module can flood you NIC; etc.

You want to spawn child processes that are sandboxed at the OS level too. Don't rely on your VM sandbox being secure, as it wont be -- even if you make it water-tight today, it'll spring a leak tomorrow.

Think *very* carefully about whether your really want to let arbitrary untrusted code run on your server, without using full virtualization. Then take a look at something like http://www.nodester.com/ 

L.

--
Job Board: http://jobs.nodejs.org/
Posting guidelines: https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
You received this message because you are subscribed to the Google
Groups "nodejs" group.
To post to this group, send email to nod...@googlegroups.com
To unsubscribe from this group, send email to
nodejs+un...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/nodejs?hl=en?hl=en


mscdex

unread,
Aug 6, 2011, 7:25:47 AM8/6/11
to nodejs
On Aug 6, 6:55 am, Laurie Harper <lau...@holoweb.net> wrote:
> Be aware that that's not enough to run untrusted code safely. A user script that: gets hold of a reference to the 'fs' module can mess with your file system; gets hold of a reference to the 'net' module can flood you NIC; etc.

Scripts run through a v8 javascript sandbox are pure javascript and
have no access to node's bindings.

Steve M

unread,
Aug 6, 2011, 8:11:03 AM8/6/11
to nodejs
I was actually planning on running a slightly stripped down version of
nodejs for client code by removing the NODE_SET_METHOD definitions in
the C++ source (e.g. removing open, read, write, etc.) That way the
user gets all V8 functionality, and most NodeJS functionality. And
because the C++ bindings are removed, the functionality cannot be re-
introduced.

On Aug 6, 1:25 pm, mscdex <msc...@gmail.com> wrote:
> On Aug 6, 6:55 am, Laurie Harper <lau...@holoweb.net> wrote:
>
> > Be aware that that's not enough to run untrusted code safely. A user script that: gets hold of a reference to the 'fs' module can mess with your file system; gets hold of a reference to the 'net' module can flood you NIC; etc.
>
> Scripts run through a v8 javascriptsandboxare pure javascript and

Bradley Meck

unread,
Aug 6, 2011, 8:35:37 AM8/6/11
to nod...@googlegroups.com
Oh, that is true, but you can do odd things if you pass in any information (in or out). Generally you do want something to communicate with so you have to pass in something or other.

1. Override Function, eval, Function.prototype.call, Function.prototype.apply, toString, valueOf, and toJSON as hooks.
2. Add getter / setters for all values as hooks into possible places to poison any context this is run in (aka outside the sandbox access or into sandbox access === baaad).
3. Override prototypes to repeat this if any of them is called (aka viral).
4. Always check your function's .caller chain and use a try/catch (the exception can leak nice info) (dont use arguments.callee in case you got stuck in strict mode).
5. Type coerce anything given to you and see if it uses the same built in native/host objects (it may have come from another context to infect!).
6. Function.toString and see if it mixes the ""this" keyword with globals (this one is amazing)!

Mariusz Nowak

unread,
Aug 7, 2011, 5:12:53 AM8/7/11
to nod...@googlegroups.com
@Steve M

As a side note, it would be possible to achieve what you want with vm, however it would be a bit more complicated.
Firstly you would need to write your own Module (require/exports logic) or somehow port one that Node uses so it can be sandboxed in other contexts, it's important that context will use module loader, that was *declared* in that context.

The problem with your code is that you pass 'require' from your initial context to other context that you want to sandbox, *global context is also resolved lexically*, if hosted code runs require that you passed, require returns object from context in which it was declared, that's the reason host gets fs object from your initial context, and can mess it up.

Additionally you will probably need to use undocumented vm.createContext and vm.runInContext methods, as only with them (as far as I know) you can share same context between different vm calls - https://github.com/joyent/node/issues/1353




Reply all
Reply to author
Forward
0 new messages