Object taint bypassing in DL and Fiddle in Ruby (CVE-2013-2065)

There is a vulnerability in DL and Fiddle in Ruby where tainted strings can be used by system calls regardless of the $SAFE level set in Ruby. This vulnerability has been assigned the CVE identifier CVE-2013-2065.

Impact

Native functions exposed to Ruby with DL or Fiddle do not check the taint values set on the objects passed in. This can result in tainted objects being accepted as input when a SecurityError exception should be raised.

Impacted DL code will look something like this:

def my_function(user_input)
  handle    = DL.dlopen(nil)
  sys_cfunc = DL::CFunc.new(handle['system'], DL::TYPE_INT, 'system')
  sys       = DL::Function.new(sys_cfunc, [DL::TYPE_VOIDP])
  sys.call user_input
end

$SAFE = 1
my_function "uname -rs".taint

Impacted Fiddle code will look something like this:

def my_function(user_input)
  handle    = DL.dlopen(nil)
  sys = Fiddle::Function.new(handle['system'],
                             [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
  sys.call user_input
end

$SAFE = 1
my_function "uname -rs".taint

All users running an affected release should either upgrade or use one of the workarounds immediately.

Note that this does not prevent numeric memory offsets from being used as pointer values. Numbers cannot be tainted, so code passing a numeric memory offset cannot be checked. For example:

def my_function(input)
  handle    = DL.dlopen(nil)
  sys = Fiddle::Function.new(handle['system'],
                             [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
  sys.call input
end

$SAFE = 1
user_input = "uname -rs".taint
my_function DL::CPtr[user_input].to_i

In this case, the memory location is passed, and taintedness of the object cannot be determined by DL / Fiddle. In this case, please check the tainting of the user input before passing the memory location:

user_input = "uname -rs".taint
raise if $SAFE >= 1 && user_input.tainted?
my_function DL::CPtr[user_input].to_i

Workarounds

If you cannot upgrade Ruby, this monkey patch can be used as a workaround:

class Fiddle::Function
  alias :old_call :call
  def call(*args)
    if $SAFE >= 1 && args.any? { |x| x.tainted? }
      raise SecurityError, "tainted parameter not allowed"
    end
    old_call(*args)
  end
end

Affected versions

  • All ruby 1.9 versions prior to ruby 1.9.3 patchlevel 426
  • All ruby 2.0 versions prior to ruby 2.0.0 patchlevel 195
  • prior to trunk revision 40728

ruby 1.8 versions are not affected.

Credits

Thanks to Vit Ondruch for reporting this issue.

History

  • Originally published at 2013-05-14 13:00:00 (UTC)