Forum: Ruby code execution from file?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
A70b7da5a3a712e800100e61ef8d8917?d=identicon&s=25 akonsu (Guest)
on 2005-12-08 00:38
(Received via mailing list)
hello,

i have a text field in a database table (a text file, generally
speaking), with arbitrary Ruby code. i would like to execute this code
in my application's current environment. but i need to hide all my
variables except for some specific ones from this code.

so i was thinking to create a class with a static method that accepts
parameters and put the content of the file in to this method. and pass
the variables i want to expose as parameters.

how is this done in ruby? i have never done it.

in addition, how to execute this code with maximal security level?

also, what would happen if the code reopened classes that i have
created? is this possible? how to make it not do that?

basically i want the user to type some code in to my application and
run it but i want to protect against malicious users who want to erase
my hard disk, etc. ;-)

thanks for any help!
konstantin
A70b7da5a3a712e800100e61ef8d8917?d=identicon&s=25 akonsu (Guest)
on 2005-12-08 00:46
(Received via mailing list)
please forgive the term "class with a static method", i meant "class
with a class method". my brain is still in this c++/java/c# rut.
Cb48ca5059faf7409a5ab3745a964696?d=identicon&s=25 ara.t.howard (Guest)
on 2005-12-08 02:24
(Received via mailing list)
On Thu, 8 Dec 2005, ako... wrote:

>
> how is this done in ruby? i have never done it.
>
> in addition, how to execute this code with maximal security level?
>
> also, what would happen if the code reopened classes that i have created? is
> this possible? how to make it not do that?
>
> basically i want the user to type some code in to my application and run it
> but i want to protect against malicious users who want to erase my hard
> disk, etc. ;-)

     harp:~ > cat a.rb
     def run file, vars = {}
       code = IO::read file
       dumped = vars.inject({}){|d,kv| k, v = kv; d.update k =>
Marshal::dump(v)}
       Thread::new(vars, code) do |vars, code|
         $SAFE = 4
         program = ""
         dumped.each{|k,v| program << "#{ k } = Marshal::load
<<-__data__\n#{ v }\n__data__\n\n"}
         program << code
         eval program
       end.value
     end

     value = run "b.rb", "a" => "forty-two"
     p value

     value = run "c.rb", "a" => "42"
     p value


     harp:~ > cat b.rb
     a.upcase


     harp:~ > cat c.rb
     a.center 42


     harp:~ > ruby a.rb
     "FORTY-TWO"
     "                    42                    "

hth.


-a
A70b7da5a3a712e800100e61ef8d8917?d=identicon&s=25 akonsu (Guest)
on 2005-12-08 03:49
(Received via mailing list)
thank you very much for your help.

my application runs inside a web browser process, and creating a thread
does not seem scalable.

i tried to evaluate a file from inside a lambda and it seems to work. i
also was able to set safety level for lambda locally which effectively
restores $SAFE when lambda finishes. the only drawback of this was that
my environment is still visible from lambdas.

code = <<CODE
    v = MyClass.new('from text')
    v.mymethod
CODE

class MyClass
  def initialize(v) @v = v end
  def mymethod() puts "SAFE=#{$SAFE}, value=#{@v}" end
end

p = eval <<LAMBDA
    lambda {
    module Mod
    $SAFE = 3
    #{code}
    end
    }
LAMBDA

v = MyClass.new('original')
v.mymethod
p.call
v.mymethod
788622ab7e1f792ca653eeacbe5c1db3?d=identicon&s=25 rcoder (Guest)
on 2005-12-08 04:09
(Received via mailing list)
There are a number of mechanisms to "lock down" the execution for a
chunk of Ruby code, the most common being the built-in $SAFE global
variable. Unfortunately, due to a number of recently-published
vulnerabilities in the implementation of the $SAFE checks, it's
generally not considered a good way to insure that your application
will be protected from malignant users.

The best example I've seen to date of a truly safe Ruby interpreter has
to be why the lucky stiff's "Try Ruby" website
(http://tryruby.hobix.com/), which lets anyone interact with a
restricted IRb prompt via their browser and some slick Javascript. You
might email why_ directly, and see if he'd be willing to share some
tips with you (and the rest of us!).

At a bare minimum, you need to make sure the following built-in classes
and modules are either entirely hidden, or redefined to "safe"
versions:

Dir
File
FileStat
FileUtils
IO (at least methods like 'popen', 'fcntl', etc.)
ObjectSpace
Pathname
Process
GzipFile
Kernel ('fork', 'at_exit', 'caller', 'load', 'require', many more)

....and probaby others, as well. Really, it's a *hard* thing to make
arbitrary code execution safe, so unless you can re-use and share
effort with others, I'd caution you against doing this unless
absolutely necessary.

Good luck,

Lennon
Cb48ca5059faf7409a5ab3745a964696?d=identicon&s=25 ara.t.howard (Guest)
on 2005-12-08 04:41
(Received via mailing list)
On Thu, 8 Dec 2005, ako... wrote:

> thank you very much for your help.
>
> my application runs inside a web browser process, and creating a thread does
> not seem scalable.


but what if the user code creates one?  it's the only way to isolate
$SAFE and
your vars, see below...


> i tried to evaluate a file from inside a lambda and it seems to work. i also
> was able to set safety level for lambda locally which effectively restores
> $SAFE when lambda finishes. the only drawback of this was that my
> environment is still visible from lambdas.


also note that lambda will prevent __everything__ in it's scope from
__ever__
being garbage collected.  this is bad idea in a web app - assuming it's
persistant like fastcgi.


in any case be careful:

     harp:~ > cat a.rb
     code = IO::read "b.rb"

     p = eval <<LAMBDA
        lambda {
        module Mod
        $SAFE = 3
        #{code}
        end
        }
     LAMBDA

     p.call
     p "we won't see this!"


     harp:~ > cat b.rb
     exit!


     harp:~ > ruby a.rb


a thread will prevent you main program from exiting.


regards.

-a
A70b7da5a3a712e800100e61ef8d8917?d=identicon&s=25 akonsu (Guest)
on 2005-12-08 08:53
(Received via mailing list)
putting aside security issues i think i was able to isolate my
environment and pass only desired variables in to the code. i create a
temporary module and evaluate my code in its context. then i remove
this module (that is why i needed the external module A, i could not
figure out how to remove a module otherwise). experts, does this look
feasible? i do not know though what to do to ensure security...

thanks
konstantin

module A
  x = 'x'
  y = 'y'

  module T
    class << self
      def get_binding(p) binding end
    end
  end

  # eval('puts x', T.get_binding(x)) -> # fails
  puts eval('"variable #{p}"', T.get_binding(x))

  remove_const :T

  #puts eval('"variable #{p}"', T.get_binding(x)) -> # fails
end
A70b7da5a3a712e800100e61ef8d8917?d=identicon&s=25 akonsu (Guest)
on 2005-12-08 19:39
(Received via mailing list)
is there a way in ruby to set up a hook that would intercept calls to
any methods of a class?
Ce60c4f78a63b0695e4dafc4bd7964f7?d=identicon&s=25 vanek (Guest)
on 2005-12-09 14:17
(Received via mailing list)
The Facets library has a lot of cool methods,
one of which ('wrap_method') can intercept calls.

   http://facets.rubyforge.org/rdoc/index.html
This topic is locked and can not be replied to.