Method added hook

A

aurelianito

Hi!

I'm looking for a way to prevent the redefinition of a method. I'm able
to check when a method is defined (redefining Module#method_added) but
I'm unable to halt the new definition. I'm also unable to know if the
method is already defined or not.

Any thoughts?

Thank's,
Aureliano.
 
A

aurelianito

Hello!
It's my (Aureliano) again.

I'm trying to implement something similar to the Java Security Manager
for Ruby. The project is currently in prealpha stage at rubyforge
(http://securedruby.rubyforge.org).

But Ruby is different from Java because it's much more dinamic. And I
like it because of it!. But it brings some issues regarding code
redefinition. What I'm trying to achieve is a way to run untrusted code
in the ruby interpreter and don't worry about possible damages to the
system. "Will this code erase all my home directory?" (or "format my
harddrive?", if I'm root) is a valid concern. Of course, in Ruby you
can always check the source code just looking at it. But, what if you
have a >100000 lines script that does something interesting but you
don't trust because your archinemesis wrote it? What if you wan't to
use "eval" to run user input code?

I think I'm able to stop direct file accesses but, because of the
dinamicity (does such word exist?) of ruby, malign code (malign as the
Austin Power archinemesis, Dr. Evil) might redefine methods to trick
our good and helpless code to do something evil for the human kind (ok,
I'm exaggerating a little bit here). For instance imagine this:

evil.rb:
class Object
def chomp a
# it's really evil.
"a name of a file that if's erased the world will be destroyed"
end
end

good_and_dumb.rb:
require "evil" # did I told you the dumb part?
FileUtils.delete(chomp "erase_to_save_the_world.doc")

Well, the world is doomed. But If we were able to stop evil to redefine
chomp, this wouldn't happen. That's why I need a way to selectively
stop method definition/redefinition/removal.

But I'm unable to find a way to implement it in a pure ruby way. Do you
know any? All the good and dumb ruby code in the world will thank you.

Please post back,
thank you very much,
Aureliano.
 
T

ts

a> But I'm unable to find a way to implement it in a pure ruby way.

use $SAFE


Guy Decoux
 
A

aurelianito

I want more granularity than what the five (0-4) $SAFE levels offer. I
want to be able to say "can write in file A but not in file B"
 
S

Sylvain Joyeux

I'm looking for a way to prevent the redefinition of a method. I'm able
to check when a method is defined (redefining Module#method_added) but
I'm unable to halt the new definition. I'm also unable to know if the
method is already defined or not.
You can freeze the class to avoid having any redefinition on it:
MyClass.freeze

This is not valid at method-level though
 
A

aurelianito

I'm looking for a way to prevent the redefinition of a method. I'm able
You can freeze the class to avoid having any redefinition on it:
MyClass.freeze

This is not valid at method-level though

I've tried it, but there is no unfreeze method :S. And I need to be
able to halt the redefinition SOME TIMES and some times NOT.
 
E

ES

aurelianito said:
I want more granularity than what the five (0-4) $SAFE levels offer. I
want to be able to say "can write in file A but not in file B"

$ chmod a-w a.txt
$ chmod a+w b.txt


Regarding the method hooks, one thing that you can do to complicate things
somewhat is to do something like this (not tested):

class SomeClass
def some_method(*a, &b)
# ...
end

alias_method :__safety__, :some_method

def self.method_added(sym)
unless @my_add
# Revert back to original
if sym == :some_method
alias_method :some_method, :__safety__
end
else
@my_add = false
end
end
end

However, the problem is that the user may simply #undef_method your
#method_added or do something equivalent. It is a neverending cycle.
#freeze helps a bit but there is a way around it, too.

irb(main):001:0> class Foo
irb(main):002:1> def foo
irb(main):003:2> puts 'foo'
irb(main):004:2> end
irb(main):005:1> end
=> nil
irb(main):006:0> Foo.freeze
=> Foo
irb(main):007:0> class Foo
irb(main):008:1> def foo
irb(main):009:2> puts 'foo'
irb(main):010:2> end
irb(main):011:1> end
TypeError: can't modify frozen class
from (irb):8
irb(main):012:0> x = Foo.dup
=> #<Class:0xb7cef2d4>
irb(main):013:0> Foo = x
(irb):13: warning: already initialized constant Foo
=> Foo
irb(main):014:0> class Foo
irb(main):015:1> def foo
irb(main):016:2> puts 'bar'
irb(main):017:2> end
irb(main):018:1> end
=> nil
irb(main):019:0> Foo.new.foo
bar
=> nil
irb(main):020:0>


The only thing you can do is to modify the Ruby interpreter. You will
need to hook into the method table modification code and intercept all
those calls. Then, for each call, inspect the arguments and no-op any
attempts to modify some defined methods (you should probably allow this
to be configurable).


Or, the simpler solution: A) do not run code that may be malicious and
B) use the operating system's security features such as file permissions

E
 
L

Logan Capaldo

You can freeze the class to avoid having any redefinition on it:
MyClass.freeze

This is not valid at method-level though

Just out of curiosity does anyone know what happens if I do (class <<
MyClass; self; end).freeze
?
 
A

aurelianito

Regarding the method hooks, one thing that you can do to complicate things
somewhat is to do something like this (not tested):

class SomeClass
def some_method(*a, &b)
# ...
end

alias_method :__safety__, :some_method

def self.method_added(sym)
unless @my_add
# Revert back to original
if sym == :some_method
alias_method :some_method, :__safety__
end
else
@my_add = false
end
end
end

Uou! Something like this is what I'm looking for! I can build my
ClassModificationPermission (or something like that) from this!
However, the problem is that the user may simply #undef_method your
#method_added or do something equivalent. It is a neverending cycle.
#freeze helps a bit but there is a way around it, too.

I may define the self.method_removed method to prevent the removal
(doing the same trick).
irb(main):001:0> class Foo
irb(main):002:1> def foo
irb(main):003:2> puts 'foo'
irb(main):004:2> end
irb(main):005:1> end
=> nil
irb(main):006:0> Foo.freeze
=> Foo
irb(main):007:0> class Foo
irb(main):008:1> def foo
irb(main):009:2> puts 'foo'
irb(main):010:2> end
irb(main):011:1> end
TypeError: can't modify frozen class
from (irb):8
irb(main):012:0> x = Foo.dup
=> #<Class:0xb7cef2d4>
irb(main):013:0> Foo = x
(irb):13: warning: already initialized constant Foo
=> Foo
irb(main):014:0> class Foo
irb(main):015:1> def foo
irb(main):016:2> puts 'bar'
irb(main):017:2> end
irb(main):018:1> end
=> nil
irb(main):019:0> Foo.new.foo
bar
=> nil
irb(main):020:0>

Isn't there a callback method for constant assignament? I'll look for
it.
If that's the case, I can do the same trick (again).
The only thing you can do is to modify the Ruby interpreter. You will
need to hook into the method table modification code and intercept all
those calls. Then, for each call, inspect the arguments and no-op any
attempts to modify some defined methods (you should probably allow this
to be configurable).

I'm trying not to modify the ruby interpreter. I want a pure Ruby
solution. I've been thinking to make my (future) lib a gem and
distribute this way. If I have to modify it, I've been thinking two
choices:
1 - Add pre-event callbacks to all the operations I need to prevent.
(It should be as simpler than your solution, but I'm just guessing).
These callbacks should be thread safe. Then I would implement the
permission check in these hooks.
2 - Force def, class, module and = to be METHODS. If they were methods
I would just redefine them to check if the operation is allowed and
call the older method. This would be pure ruby code (unless there is a
serious performance issue).

Thank's for your input,
now I have something to try.

Aureliano.
 
D

David A. Black

Hi --

Just out of curiosity does anyone know what happens if I do (class <<
MyClass; self; end).freeze
?

The same as when you do:

class << MyClass; freeze; end

:) It freeze MyClass's singleton class, so you can add class methods
to MyClass.


David
 
S

Sylvain Joyeux

Just out of curiosity does anyone know what happens if I do (class <<
MyClass; self; end).freeze
?
An you can also do class << my_object; freeze; end which blocks any
modification of the my_object metaclass
 
G

Gene Tani

Besides, you can dup a frozen object and alter it (but clone gives you
a still frozen object)

class Foo
def bar
print "bar"
end
end

Foo.freeze
Foo2=Foo.dup

class Foo2
def Foo2.bar
print "bar in Foo2"
end
end
p Foo2.bar
 
A

aurelianito

I can always override dup to freeze the duplicated object. Is it right?
can I break other things in the Ruby library?
 
G

Gene Tani

oops, I was just repeating what ES said. Maybe some combination of
freeze and making your class un-subclassable and your method
un-sendable, so that your privates don't get turned public.
 
A

aurelianito

I want more granularity than what the five (0-4) $SAFE levels offer. I
(...)

Regarding the method hooks, one thing that you can do to complicate things
somewhat is to do something like this (not tested):

class SomeClass
def some_method(*a, &b)
# ...
end

alias_method :__safety__, :some_method

def self.method_added(sym)
unless @my_add
# Revert back to original
if sym == :some_method
alias_method :some_method, :__safety__
end
else
@my_add = false
end
end
end

I've patched it a little bit and it worked nicely.

class Klass
def foo
puts "foo"
end

alias_method :__safety__, :foo

def self.method_added(sym)
if sym == :foo && ! caller.find { |call| /method_added/ =~ call }
alias_method :foo, :__safety__
end
end
end

class Klass
def foo
puts "evil"
end
end

a = Klass.new
a.foo // prints "foo"

Do you know if this solution is thread safe or not?,
Aureliano.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,184
Messages
2,570,975
Members
47,533
Latest member
medikillz39

Latest Threads

Top