Mar 25, 2009

The Freaky Sandbox

One thing that can always be useful is the ability to run "unsafe" code inside a sandbox, where it can do limited damage. For a project I am working on, I wanted to do this very thing. Users would be able to upload their own code for execution on the server.

Naturally, this leads to a whole whack of security issues. If you just run code unchecked, users could get access to files, threads, processes, all sorts of things.
Fortunately there is this gem called the Freaky Sandbox which lets you run Ruby code, and it disables access to many security sensitive areas.

Unfortunately the gem doesn't work anymore. If you check out the source and install it, all sorts of bad things happen when you actually try to use the gem.
So before diving in to fix this, I decided to see if JRuby had a similar gem. Fortunately, it does. Now when I first tried it with JRuby 1.1.something it also didn't work, but I made some tweaks to it and now it does. Also with JRuby 1.2 RC1 it works (and also with RC2).
UPDATE (Mar. 28/10): The gem in the repositories no longer works, however you can check out the code to the javasand gem and install it using these instructions.

So now you can do fancy things like this:
require 'sandbox'

s = Sandbox.safe

File.open("untrusted_code.rb") do |f|
s.eval f.read
end
What it does is filters out things like Thread and Kernel.fork, and file manipulation, things like that. It also doesn't see any classes that you have defined unless you import them into the sandbox.

So how could this be useful? One thing I'm thinking of is to allow users to write custom scripts for a site. Of course most users wouldn't be able to take advantage of this feature, but if script writers made their scripts available then they could just grab it and install it.

You could try something like this:
sandbox = Sandbox.safe

sandbox.import RandomAPIClassThatIWrote

res = s.eval untrusted_code

# do stuff with res
Unfortunately importing modules doesn't seem to work, it gives a NullPointerException when you try. If I get some free time soon I may look into it and try to fix it, but for now I'll just have to live with importing classes.

Now the question is: is this perfectly safe? Well, no. The code you run in the sandbox can still have infinite loops in it which will halt your program. Unfortunately it is impossible to tell if arbitrary code will have infinite loops in it or not, so we're out of luck for fixing this problem. However what you can try is the following:
sb_thread = Thread.new do
s.eval untrusted_code
end

sleep TIMEOUT

sb_thread.raise if sb_thread.alive? # use raise since kill doesn't seem to work
Normally this is unsafe, because of blocking operations like synchronous IO and things like that. However with the sandbox, you cannot use the require keyword. You also have no access to the File or Dir classes (they are not defined inside the sandbox). So correct me if I'm wrong but unless you go and import these classes into the sandbox there are no unsafe things that could happen by just killing the thread.

3 comments:

Anonymous said...

sandbox = Sandbox.safe

sandbox.import RandomAPIClassThatIWrote

res = s.eval untrusted_code


I am use it

Brian Armstrong said...

Hey Rob,

Thanks for posting this. I'm following a similar path right now and just got JRuby's sandbox working (after discovering that the freak sandbox from _why is no longer maintained). JRuby's still seems buggy but I haven't look at it much - did you end up using it in the end?

Thanks,
Brian

Rob Britton said...

Hi Brian,

Yeah it doesn't seem to work at the moment with the repository version of the gem. You can try installing it using these instructions: http://flouri.sh/2009/4/4/how-to-set-up-the-jruby-sandbox, they worked fine for me.