Okay, there are the different $SAFE levels. But why not simply removing dangerous methods, like:
undef `
undef system
undef require
...
or replacing them by your own?
I guess, this is as secure as any $SAFE level (of course it depends on which methods you are removing). Or am I missing something? The problem is that this way you can't run other "good" code next to your "bad" code (as it is possible with $SAFE).
It would be very nice to execute some Ruby code in such a reduced environment without affecting the other "good" code:
BTW, is this possible to implement in Ruby or a C extension? I guess not. Or would it work with two (or multiple) anonymous modules, one for the good code, one for the bad code, and then by removing all methods/constants/global variables outside those two modules?
I'm not sure, but maybe you simply want a thread running at $SAFE>4
and another one accessing the clean data?
Not that I need this feature, but I'd like it
Here's an implementation:
# Runs passed code in a relatively safe sandboxed environment.
···
#
# You can pass a block which is called with the sandbox as its first
# argument to apply custom changes to the sandbox environment.
#
# Returns an Array with the result of the executed code and
# an exception, if one occurred.
#
# Example of usage:
#
# result, error = safe "1.0 / rand(10)"
# puts if error then
# "Error: #{error.inspect}"
# else
# result.inspect
# end
def safe(code, sandbox=nil)
error = nil
begin
thread = Thread.new do
$-w = nil
sandbox ||= Object.new.taint
yield(sandbox) if block_given?
$SAFE = 5
eval(code, sandbox.send(:binding))
end
value = thread.value
result = Marshal.load(Marshal.dump(thread.value))
rescue Exception => error
error = Marshal.load(Marshal.dump(error))
end
return result, error
end
However in current Ruby versions there is a way to escape the sandbox via ObjectSpace#define_finalizer. I suppose that this could be worked around by overloading it with a version that gets the $SAFE level of the caller via Binding.of_caller and then applies it to the passed-in handler via eval "$SAFE = #{caller_safe}", block.
But I'm not sure whether that solution would be 100% secure and I'd like to avoid adding a dependency to Binding.of_caller here.
Matz is aware of that problem (ts has discovered and reported it) and as far as I know it will be fixed in Ruby 1.8.2.
> $SAFE = 5
> eval(code, sandbox.send(:binding))
> end
> value = thread.value
> result = Marshal.load(Marshal.dump(thread.value))
it always an error to evaluate the result with a different value of $SAFE
Hm, right. It was an old version, sorry for that. This one should work correctly: (Except the ObjectSpace#define_finalizer problem)
# Runs passed code in a relatively safe sandboxed environment.
# # You can pass a block which is called with the sandbox as its first # argument to apply custom changes to the sandbox environment.
# # Returns an Array with the result of the executed code and
# an exception, if one occurred.
# # Example of usage:
# # result, error = safe "1.0 / rand(10)"
# puts if error then
# "Error: #{error.inspect}"
# else
# result.inspect
# end
def safe(code, sandbox = nil)
error, result = nil, nil
begin
thread = Thread.new do
sandbox ||= Object.new.taint
yield(sandbox) if block_given?
$-w = nil
$SAFE = 5
eval(code, sandbox.send(:binding))
end
result = secure_object(thread.value)
rescue Exception => error
error = secure_object(error)
end
return result, error
end
def secure_object(obj)
# We can't dup immediate values. But that's no problem
# because most of them can't have any singleton methods
# anyway. (nil, true and false can, but they can't be
# defined in safe contexts.)
immediate_classes = [Fixnum, Symbol, NilClass, TrueClass, FalseClass]
return obj if immediate_classes.any? { |klass| klass === obj }
# Dup won't copy any singleton methods and without any
# of them the Object will be safe. (But we can't call
# the Object's .dup because it might be evil already.)
safe_dup = Object.instance_method(:dup).bind(obj)
safe_dup.call
end
I believe this one to be safe, but I'd prefer to be proven the opposite by you instead of some malicious attacker.
I believe this one to be safe, but I'd prefer to be proven the opposite
by you instead of some malicious attacker.
it depend how you use the object after this ...
svg% cat b.rb
#!/usr/bin/ruby
def safe(code, sandbox = nil)
error, result = nil, nil
begin
thread = Thread.new do
sandbox ||= Object.new.taint
yield(sandbox) if block_given?
$-w = nil
$SAFE = 5
eval(code, sandbox.send(:binding))
end
result = secure_object(thread.value)
rescue Exception => error
error = secure_object(error)
end
return result, error
end
def secure_object(obj)
# We can't dup immediate values. But that's no problem
# because most of them can't have any singleton methods
# anyway. (nil, true and false can, but they can't be
# defined in safe contexts.)
immediate_classes = [Fixnum, Symbol, NilClass, TrueClass, FalseClass]
return obj if immediate_classes.any? { |klass| klass === obj }
# Dup won't copy any singleton methods and without any
# of them the Object will be safe. (But we can't call
# the Object's .dup because it might be evil already.)
safe_dup = Object.instance_method(:dup).bind(obj)
safe_dup.call
end
Hm, which means that I have to call secure_object recursively on all objects which the object itself references. (instance_variables, contents of Arrays)
Would this be enough or am I still overseeing something?
Hm, which means that I have to call secure_object recursively on all
objects which the object itself references. (instance_variables,
contents of Arrays)
You have found, aa was
svg% cat aa
a = Object.new
b = "sss"
class << b
def inspect
system("rm b.rb")
"hello :-)"
end
end
a.instance_variable_set("@a", b)
a
svg%
Would this be enough or am I still overseeing something?
Hm, which means that I have to call secure_object recursively on all objects which the object itself references. (instance_variables, contents of Arrays)
And secure_object needs to raise an Exception when
secure_tainted.bind(secure_class.bind(obj).call).call (Object is an instance of an insecure class).
Currently this also works:
safe "Class.new { def inspect; puts 'foo'; end }.new"