Product SiteDocumentation Site

2.1.3. $SAFE

Ruby interpreter can run in restricted execution mode with several levels of checking, controlled by global variable $SAFE. There are 5 possible levels: 0,1,2,3,4 with 0 being default safe level. $SAFE is thread-local and its value can only be increased (at least in theory - in practice there are well known ways how to work around restricted code execution or decrease a safe level. See Section 2.1.3.1, “Security considerations of $SAFE”). Safe level can be changed by assigning to $SAFE or with -T<level> argument.
Safe levels have following restrictions:
level 0
strings from streams/environment/ARGV are tainted (default)
level 1
dangerous operations on tainted values are forbidden (such as eval, require etc.)
level 2
adds to the level 1 also restrictions on directory, file and process operations
level 3
in addition all created objects are tainted and untrusted
level 4
code running in this level cannot change trusted objects, direct output is also restricted. This safe level is deprecated since Ruby 2.1
There is a lack of documentation of what is restricted in each safe level. For more exhausting description refer to Programming Ruby: Pragmatic programmer`s guide.

2.1.3.1. Security considerations of $SAFE

Design of restricted code execution based on $SAFE is inherently flawed. Blacklist approach is used to restrict operation on each level, which means any missed function creates a vulnerability. In past several security updates were related to restricted code execution and taint flag (see CVE-2005-2337, CVE-2006-3694, CVE-2008-3655, CVE-2008-3657, CVE-2011-1005, CVE-2012-4464,CVE-2012-4466 and CVE-2013-2065).

Warning

Design of restricted code execution based on $SAFE is inherently flawed and cannot be used to run untrusted code even at the highest safe level. It must not be used as mechanism to create a secure sandbox, as attacker will be able to work around the restrictions or decrease safe level.
One example of how exploitable the design is comes from CVE-2013-2065:
require 'fiddle'

$SAFE = 1
input = "uname -rs".taint
handle = DL.dlopen(nil)
sys = Fiddle::Function.new(handle['system'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
sys.call DL::CPtr[input].to_i
Even though safe level 1 should restrict execution of system commands, this can be bypassed using Fiddle library, which is an extension to translate a foreign function interface with Ruby. Exploit above bypasses safe level by passing input to system call as numeric memory offset. Since numbers as literals cannot be tainted, code cannot check taintedness of input.

Note

However, running application with higher safe level is still useful for catching unintended programming errors, such as executing eval on tainted string.