Notes to self

Safe code evaluation in Ruby with $SAFE

As you probably know, you can use eval() to evaluate Ruby code from Ruby. But evaluating things that come from the outside of the program like user inputs can be dangerous. Why they can be dangerous you ask? eval() evaluates anything as we would program it ourselves. Basically anything can happen. That’s why it’s best to avoid eval() for such inputs altogether. But we can evaluate Ruby in a safer manner too; with $SAFE.

Let’s evaluate an operation that would delete a file:

irb> eval "File.delete 'file'"
=> 1

If a file called file exists, it would be deleted. Unfortunately I don’t like the idea that any user input can delete a file on my filesystem. That’s the time when using $SAFE is handy.

irb> $SAFE = 2
irb> eval "File.delete 'file'"
SecurityError: Insecure operation `delete' at level 2
	from (irb):2:in `eval'
	from (eval):1
	from (irb):2:in `eval'
	from (irb):2
	from /usr/bin/irb:11:in `'

What happened? This time I set $SAFE level to 2 which disallowed a file operation (as all levels higher than 2 do). $SAFE simply tells Ruby what level of security we require from our code. Note that this applies to any Ruby code, not only the code called from eval. We can rewrite it using proc to apply the security level only on evaluated code:

proc {
  $SAFE = 2
  eval @input

We can than further improve on our example by setting $SAFE run level to 2 only on the input coming from the outside:

proc {
  $SAFE = 2 if @input.tainted?
  eval @input

Here are all the levels explained:

  • Level 0: normal environment, the security system is not running
  • Level 1: dangerous data, disallows the use of tainted data, eval or with a tainted object fail
  • Level 2: dangerous program, prohibits the loading of program files from globally writable locations
  • Level 3: dangerous program, newly created objects are considered tainted
  • Level 4: dangerous program, many operations including exit(), file I/O, thread manipulation, redefining methods are restricted

Note that for $SAFE level 3 irb does not work at all and if a Ruby script is set to run setuid or setgid, $SAFE is automatically set to 1. It’s also important to remember that JRuby never supported $SAFE and that Ruby 2.1 removed the $SAFE‘s level 4.

Finally, can user actually set the level of $SAFE in his untrusted code to actually avoid restrictions? Well, yes and no. He can, but $SAFE can be only increased.

by Josef Strzibny