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:
eval
request = ‘a < 42 and b == true’
a = 42
b = true
eval request
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
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
===============================================================================
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
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
===============================================================================
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
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).
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.
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.
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…
This comment is twice as scary coming from a .gov email address.
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.
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…
This comment is twice as scary coming from a .gov email address.
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
[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)
000c => 4000 … what’s the point?
why to i feel like this is some sort of koan? is everyone laughing at me?
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.
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 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.