From: Florian Gross Date: 2004-09-02T22:45:20+09:00 Subject: Re: Not just $SAFE, but damn $SAFE --------------070708030301010601050909 Content-Type: text/plain; charset=us-ascii; format=flowed Content-Transfer-Encoding: 7bit Aredridel wrote: > I've been toying with an IRC bot that takes input from users in channel, > evals it, and returns the result. > > Doing so safely has proved to be a challenge -- the biggest problem > being that you can't trust any method on the returned object. > > Here's our solution, and I'd love to know if anyone can break it. It is easily breakable because of singleton methods. I've attached safe.rb which ought to be secure now (hello ts ;)) that ObjectSpace.define_finalizer doesn't allow one to escape the sandbox anymore. However, as batsman already mentioned all this still doesn't help much against DoS attacks. (You have to protect against those on the OS level right now.) Regards, Florian Gross --------------070708030301010601050909 Content-Type: text/plain; name="safe.rb" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="safe.rb" module Safe extend self # 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? # FIXME: ENV and ARGV are globally made uselss ENV.replace Hash.new ARGV.replace Array.new $-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 } safe_call = lambda do |obj, method, *args| Object.instance_method(method).bind(obj).call(*args) end klass = safe_call[obj, :class] return nil if safe_call[klass, :tainted?] # 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.) result = safe_call[obj, :dup] # result can now be trusted which means that we can call # methods like instance_variables etc. directly result.instance_variables.each do |iv| value = result.instance_variable_get(iv) result.instance_variable_set(iv, secure_object(value)) end return result end end def safe(*args, &block) Safe.safe(*args, &block) end --------------070708030301010601050909--