Safe eval?

i have a project i’m working on where i’d like to support complex
boolean/relational requests, where those requests must be satisfied on the
context of defined objects… i’m loath to create an entire parser/scanner
just to evaluate these expression when ruby’s own is already written but also
don’t want to risk using eval for the obvious reason. so, for example, i’ll
have a command line option for a request:

prog.rb --request=‘a < 42 and b == true’

i can think of three approaches for evaluating such requests:

  1. eval

    request = ‘a < 42 and b == true’
    a = 42
    b = true
    eval request

  2. code generation using ruby to evaluate (this protects against evil evals)

    request = ‘a < 42 and b == true’
    a = 42
    b = true

    code = <<-code
    a = #{ a }
    b = #{ b }
    p(#{ request })
    code

    res = ruby -e '#{ code }'

    case res
    when /true/o
    when /false/o
    else
    end

  3. full blown racc parser with associated context/evaluation logic…

  • eval is attractive because i’d be done today, but it’d be too easy for someone
    to do

    prog.rb --request=‘a < 42 and b == true; raise “ha ha”’

  • code generation is attractive for the same reason but feels hackish and slow

  • the full blown racc parser just seems like alot of work to accomplish such a
    small thing… then again perhaps it wouldn’t be that bad…

can someone think of alternatives or variations that are simple and safe?

-a

···

EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
PHONE :: 303.497.6469
ADDRESS :: E/GC2 325 Broadway, Boulder, CO 80305-3328
URL :: Solar-Terrestrial Physics Data | NCEI
TRY :: for l in ruby perl;do $l -e “print "\x3a\x2d\x29\x0a"”;done
===============================================================================

1) eval

[...]

2) code generation using ruby to evaluate (this protects against evil
evals)

If you really think that you are protected against the evil eval then just
try

    request = 'a < 42 and b == true'

       request = '(a < 42 && b == true; %x{ls})'

then replace ls with what you want ...

Use $SAFE = 4 in a new thread

Guy Decoux

Ara.T.Howard wrote:

i have a project i’m working on where i’d like to support complex
boolean/relational requests, where those requests must be satisfied on the
context of defined objects… i’m loath to create an entire parser/scanner
just to evaluate these expression when ruby’s own is already written but also
don’t want to risk using eval for the obvious reason. so, for example, i’ll
have a command line option for a request:

prog.rb --request=‘a < 42 and b == true’

Just use this:

module Safe; end
class << Safe

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 occured.

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 {
     $-w = nil

     sandbox ||= Object.new.taint

     yield(sandbox) if block_given?

     $SAFE = 5
     eval(code, sandbox.send(:binding))
   }
   value = thread.value
   result = Marshal.load(Marshal.dump(thread.value))
 rescue Exception => error
   error = Marshal.load(Marshal.dump(error))
 end

 return result, error

end
end

def safe(*args, &block)
Safe::safe(*args, &block)
end

Regards,
Florian Gross

and this does protect against modifying globals… hmmm. simple (could be
done today) and safe… sounds better than writing a parser/scanner…

-a

···

On Tue, 11 May 2004, ts wrote:

  1. eval

[…]

  1. code generation using ruby to evaluate (this protects against evil
    evals)

If you really think that you are protected against the evil eval then just
try

request = 'a < 42 and b == true'
   request = '(a < 42 && b == true; %x{ls})'

then replace ls with what you want …

Use $SAFE = 4 in a new thread

EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
PHONE :: 303.497.6469
ADDRESS :: E/GC2 325 Broadway, Boulder, CO 80305-3328
URL :: Solar-Terrestrial Physics Data | NCEI
TRY :: for l in ruby perl;do $l -e “print "\x3a\x2d\x29\x0a"”;done
===============================================================================

Ara.T.Howard wrote:

i have a project i’m working on where i’d like to support complex
boolean/relational requests, where those requests must be satisfied on the
context of defined objects… i’m loath to create an entire parser/scanner
just to evaluate these expression when ruby’s own is already written but also
don’t want to risk using eval for the obvious reason. so, for example, i’ll
have a command line option for a request:

prog.rb --request=‘a < 42 and b == true’

Just use this:

that looks sweet! i’ll try it out later today… thanks!

-a

···

On Mon, 10 May 2004, Florian Gross wrote:

module Safe; end
class << Safe

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 occured.

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 {
     $-w = nil

     sandbox ||= Object.new.taint

     yield(sandbox) if block_given?

     $SAFE = 5
     eval(code, sandbox.send(:binding))
   }
   value = thread.value
   result = Marshal.load(Marshal.dump(thread.value))
 rescue Exception => error
   error = Marshal.load(Marshal.dump(error))
 end

 return result, error

end
end

def safe(*args, &block)
Safe::safe(*args, &block)
end

Regards,
Florian Gross

EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
PHONE :: 303.497.6469
ADDRESS :: E/GC2 325 Broadway, Boulder, CO 80305-3328
URL :: Solar-Terrestrial Physics Data | NCEI
TRY :: for l in ruby perl;do $l -e “print "\x3a\x2d\x29\x0a"”;done
===============================================================================

Just use this:

svg% cat b.rb
#!/usr/bin/ruby
module Safe; end
class << Safe
   def safe(code, sandbox=nil)
     error = nil

     begin
       thread = Thread.new {
         $-w = nil

         sandbox ||= Object.new.taint

         yield(sandbox) if block_given?

         $SAFE = 5
         eval(code, sandbox.send(:binding))
       }
       value = thread.value
       result = Marshal.load(Marshal.dump(thread.value))
     rescue Exception => error
       error = Marshal.load(Marshal.dump(error))
     end

     return result, error
   end
end

def safe(*args, &block)
   Safe::safe(*args, &block)
end

safe('
class << a =
   def _dump(a)
      $stderr.puts "More you make it complex, more it will be easy to break"
   end
end
a')
svg%

Guy Decoux

ts wrote:

safe(’
class << a =
def _dump(a)
$stderr.puts “More you make it complex, more it will be easy to break”
end
end
a’)

Heh, good one. Thanks for pointing this out.

Try this fixed version:

module Safe
extend self

def safe(code, sandbox=nil)
error, result = nil, nil

 begin
   thread = Thread.new do
     sandbox ||= Object.new.taint
     yield(sandbox) if block_given?

     $SAFE = 5
     $-w = nil

     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
end

def safe(*args, &block)
Safe.safe(*args, &block)
end

Regards,
Florian Gross

Try this fixed version:

I really don't know if I must post this :frowning:

svg% ls b.rb x.rb
ls: x.rb: No such file or directory
b.rb*
svg%

svg% cat b.rb
#!/usr/bin/ruby
module Safe
   extend self

   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)
     immediate_classes = [Fixnum, Symbol, NilClass, TrueClass,
       FalseClass]
     return obj if immediate_classes.any? { |klass| klass === obj }
     safe_dup = Object.instance_method(:dup).bind(obj)
     safe_dup.call
   end
end

def safe(*args, &block)
   Safe.safe(*args, &block)
end

b = safe('
class << s = "`mv b.rb x.rb`"
   def call
   end
end
a = Object.new
ObjectSpace.define_finalizer(a, s)
a
')
svg%

svg% b.rb
svg%

svg% ls b.rb x.rb
ls: b.rb: No such file or directory
x.rb*
svg%

Guy Decoux

Try this fixed version:

I really don’t know if I must post this :frowning:

yes, you must!

what do you do with your spare time guy!? watch out or a man in black wearing
dark glasses is going to knock on your door… :wink:

all this makes me start to think that i really must use a racc parser…
uggh

-a

···

On Wed, 12 May 2004, ts wrote:

svg% ls b.rb x.rb
ls: x.rb: No such file or directory
b.rb*
svg%

svg% cat b.rb
#!/usr/bin/ruby
module Safe
extend self

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)
immediate_classes = [Fixnum, Symbol, NilClass, TrueClass,
FalseClass]
return obj if immediate_classes.any? { |klass| klass === obj }
safe_dup = Object.instance_method(:dup).bind(obj)
safe_dup.call
end
end

def safe(*args, &block)
Safe.safe(*args, &block)
end

b = safe(’
class << s = “mv b.rb x.rb
def call
end
end
a = Object.new
ObjectSpace.define_finalizer(a, s)
a
')
svg%

svg% b.rb
svg%

svg% ls b.rb x.rb
ls: b.rb: No such file or directory
x.rb*
svg%

Guy Decoux

EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
PHONE :: 303.497.6469
ADDRESS :: E/GC2 325 Broadway, Boulder, CO 80305-3328
URL :: Solar-Terrestrial Physics Data | NCEI
TRY :: for l in ruby perl;do $l -e “print "\x3a\x2d\x29\x0a"”;done
===============================================================================

You are evil :slight_smile:

So, do you have a safe solution? In ruby-talk:99765 you mentioned
running a new thread with $SAFE=4. Show us the light (unless your dark
side took over already).

Guillaume.

···

On Wed, 2004-05-12 at 09:40, ts wrote:

Try this fixed version:

I really don’t know if I must post this :frowning:

ts wrote:

b = safe(’
class << s = “mv b.rb x.rb
def call
end
end
a = Object.new
ObjectSpace.define_finalizer(a, s)
a
')

Heh, I don’t actually regard this one as a bug of safe(), but more as
one of Ruby. I’m uncertain if matz agrees, however.

Personally, I have a more complete version of it that adds $SAFE-checks
to a lot of Ruby’s built-in methods. (All methods of GC,
ObjectSpace.(define|add)_finalizer, Thread.new / .fork / .start /
…critical=, set_trace_func)

I’m pretty sure that there are more cases like this where $SAFE isn’t
checked correctly in Ruby. If anybody wants to point out more of them, I
can try to come up with a way to secure them, but I’m unsure if this is
the best solution and if it will work all the time.

Actually, that’s the reason of using a $SAFE-level of 5 and not 4 as one
would probably expect. :slight_smile:

Here is the way I secure define_finalizer:

 ObjectSpace.module_eval do
   class << self
     old_finalizer = instance_method(:define_finalizer)
     define_method(:_define_finalizer) do |block, *args|
       raise(SecurityError, "Penalizing finalizing") if $SAFE > 1
       old_finalizer.bind(self).call(*args, &block)
     end
     def define_finalizer(*args, &block)
       _define_finalizer(block, *args)
     end

     alias :add_finalizer :define_finalizer
   end
 end

If anybody wants to have the complete version with all the other added
checks, just let me know. I’ll do some cleaning up and release the whole
thing in that case.

Regards,
Florian Gross

Ara.T.Howard wrote:

what do you do with your spare time guy!? watch out or a man in black wearing
dark glasses is going to knock on your door… :wink:

This comment is twice as scary coming from a .gov email address. :slight_smile:

Guy: Don’t take this the wrong way… I was once chatting with a couple
of other rubyists, and we were talking about how impressive your
knowledge was.

Then we started talking about how no one we had ever talked to had ever
seen you in person. So I submitted the tentative theory that you are
actually not one person at all; you are either a committee of several
computer science experts, or a network of Cray supercomputers.

Cheers,

Hal

Heh, I don't actually regard this one as a bug of safe(), but more as
one of Ruby. I'm uncertain if matz agrees, however.

Well, it's not really important for me : I was just able to run code not
expected.

Actually, that's the reason of using a $SAFE-level of 5 and not 4 as one
would probably expect. :slight_smile:

plruby run with $SAFE = 12 :slight_smile:

Guy Decoux

Convince INRA to organize and sponsor the next European Ruby Conf and
maybe we’ll know the truth…

Guillaume.

···

On Wed, 2004-05-12 at 12:03, Hal Fulton wrote:

Guy: Don’t take this the wrong way… I was once chatting with a couple
of other rubyists, and we were talking about how impressive your
knowledge was.

Then we started talking about how no one we had ever talked to had ever
seen you in person. So I submitted the tentative theory that you are
actually not one person at all; you are either a committee of several
computer science experts, or a network of Cray supercomputers.

what do you do with your spare time guy!? watch out or a man in black wearing
dark glasses is going to knock on your door… :wink:

This comment is twice as scary coming from a .gov email address. :slight_smile:

and the suit is just back from the dry cleaners… now where are my
ray-bans…

Guy: Don’t take this the wrong way… I was once chatting with a couple of
other rubyists, and we were talking about how impressive your knowledge was.

ditto. i tell ruby nubies here in house to include ‘ts’ when googling for
answers from c.l.r if they want to find the ‘right’ answer!

Then we started talking about how no one we had ever talked to had ever seen
you in person. So I submitted the tentative theory that you are actually not
one person at all; you are either a committee of several computer science
experts, or a network of Cray supercomputers.

or, most likely, a committee of computer science experts running a network of
Cray supercomputers

-a

···

On Thu, 13 May 2004, Hal Fulton wrote:

===============================================================================

EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
PHONE :: 303.497.6469
ADDRESS :: E/GC2 325 Broadway, Boulder, CO 80305-3328
URL :: Solar-Terrestrial Physics Data | NCEI
TRY :: for l in ruby perl;do $l -e “print "\x3a\x2d\x29\x0a"”;done
===============================================================================

What happens exactly when you set $SAFE with value greater than 4? Does
ruby act upon it or is it for your internal usage?

Guillaume.

···

On Thu, 2004-05-13 at 04:50, ts wrote:

plruby run with $SAFE = 12 :slight_smile:

What happens exactly when you set $SAFE with value greater than 4? Does

Nothing special

ruby act upon it or is it for your internal usage?

Look at proc_save_safe_level() and proc_get_safe_level() in
eval.c (1.9) :slight_smile:

Guy Decoux

000c => 4000 … what’s the point?

why to i feel like this is some sort of koan? is everyone laughing at me?
:wink:

-a

···

On Thu, 13 May 2004, ts wrote:

Look at proc_save_safe_level() and proc_get_safe_level() in eval.c (1.9)
:slight_smile:

EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
PHONE :: 303.497.6469
ADDRESS :: E/GC2 325 Broadway, Boulder, CO 80305-3328
URL :: Solar-Terrestrial Physics Data | NCEI
TRY :: for l in ruby perl;do $l -e “print "\x3a\x2d\x29\x0a"”;done
===============================================================================

[GALLIC HUMOUR FLIES THROUGH THE ROCKIES IN A BLACK HELICOPTER]

Ara.T.Howard wrote:

Look at proc_save_safe_level() and proc_get_safe_level() in eval.c (1.9)
:slight_smile:

000c => 4000 … what’s the point?

why to i feel like this is some sort of koan? is everyone laughing at me?
:wink:

Not until they see the connection between NOAA (under “Research is big here”)
and “Mork & Mindy” in this short page.
http://users.frii.com/geomanda/boulder/

$SAFE = 12 reminds me of the brilliant spoof documentary “This is Spinal Tap”
about a touring Rock band. One of the band members goes into too much detail
about how he has his amplifiers modified from Range 0…10 to 0…11 because 10
isn’t high enough for him.

···

On Thu, 13 May 2004, ts wrote:

=============================================================================

And to let others in on the joke, Guy sees that $SAFE is a 3-bit value,
12 is 1100 in binary (4-bits), the top bit is masked off (i.e. dropped)
and 0100 is the result. $SAFE = 12 becomes $SAFE = 4.
( $SAFE = 7 would be the maximum allowable )

Trust me; in binary world, this is funny :smiley:

daz

And to let others in on the joke, Guy sees that $SAFE is a 3-bit value,
12 is 1100 in binary (4-bits), the top bit is masked off (i.e. dropped)
  and 0100 is the result. $SAFE = 12 becomes $SAFE = 4.
( $SAFE = 7 would be the maximum allowable )

and there is something worst : imagine that I want to introduce a back-door
in plruby, I just need to run it with $SAFE = 8.

Then I can say that plruby is safe because it run with twice the value of
the original $SAFE (4), but I've the possibility to run code with another
value.

This just to say that I *VOLONTARY* distribute *ONLY* the source of
plruby, because I *REALLY* expect that someone else re-read it to find
the problems that it can exist.

Guy Decoux