Sandboxing Ruby
I have come to a point when I need to have a good sandbox to run untrusted ruby code in. The good news, I told myself, is it's been done before. But hey, not many people need a sandbox, not many people need a sandbox in ruby, and not many people need a sandbox in ruby that has access to some gems.
Lemme give you a bit of an overview on what's been done:
- MRI Ruby has $SAFE and taint. Well, I think taint is everywhere, but $SAFE is only in MRI. Which is ok, because honestly, $SAFE sucks.
- _why (who disappeared somewhat before my time) made the freaky freaky sandbox. So, that was cool. It was also a long time ago, and is not maintained anymore. Oh, and it was apparently very hackish, going into Ruby internals a lot.
- Then there was something called "javasand", which I haven't looked into much... because:
- The peepz who did try ruby and Rails for Zombies came along and recycled all that into jruby_sandbox.
And life is now good. Want a ruby sandbox? Run JRuby, use that gem. Bliss.
Wait, wait— not so fast. I still need to have access to some gems and libraries from within the sandbox.
And that's a problem because, in the sandbox, or so I was told, only Kernel#require exists. No RubyGems. Darn. I got stuck there for a while. But then (forgive the cliché) it hit:
Ruby 1.9 has baked-right-in rubygems support.
So, after setting the JRUBY_OPTS=--1.9
config, here's how you do things:
require "sandbox"
sand = Sandbox::Safe.new
sand.eval <<-RUBY
require "bundler"
Bundler.require :sandbox
RUBY
sand.activate!
sand.eval "# untrusted code"
I use my Gemfile to the most I can, so the relevant section looks like this:
group :sandbox do
platforms :jruby do
gem 'typhoeus'
gem 'kramdown'
gem 'nokogiri'
# ...
end
end
And everything just works peachy.
Written by Félix Saparelli
Related protips
4 Responses
Everything just worked peachy. In between 1.6.7.2 and 1.7.0.RC2, something changed and rubygems can't be accessed from within anymore. Or something. I need to redo my testing here.
What do you mean by saying $SAFE sucks? It's not safe for running untrusted code or it simply don't satisfy your needs? https://github.com/c42/secure in this gem $SAFE is used.
See:
- http://www.ruby-forum.com/topic/79295
- http://www.ruby-forum.com/topic/3985130
- http://stackoverflow.com/questions/3417521/what-vulnerabilities-are-possible-in-ruby-with-safe-1
- http://www.confreaks.com/videos/713-rubyconf2011-sandboxing-ruby-the-good-the-bad-and-the-fugly
- http://www.rubyflow.com/items/5642-dead-simple-code-sandboxing
My problem with $SAFE is you can't do anything Ruby'esque if $SAFE is too high but the lower settings aren't even close to being secure. You should be able to truly sandbox some Ruby code and let it do its thing using the full power of Ruby while blocking it from doing things like accessing the file system.
As well as a sentence on that gem's readme:
Getting rid of SAFE level 3, and moving everything into the kernel space.
I'm guessing the author has similar reservation about using $SAFE.
Furthermore, $SAFE is really only correctly implemented in MRI, and might disappear after 2.0. jRuby can be sandboxed using either the gem I mention, or (if you want to go one better) using the JVM's security policies. Rubinius doesn't implement $SAFE and (iirc/afaik) never will.
$SAFE is certainly not meant to be used to run untrusted code, it's meant to be used to throw exceptions when code does stuff you don't want it to. I.e. $SAFE is, by design, not secure.
If you don't want to use JRuby, consider using the 'trusted-sandbox' gem. It runs Ruby code within a fully controlled Docker container. You can disable network, set disk quotas, limit execution time, balance CPU with other running containers, set memory limits, etc. And the overhead is quite low.
You can find it here:
https://github.com/vaharoni/trusted-sandbox
Disclosure: this is a gem I built.