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
orFile.open
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.