How to have friends

January 17, 2007

First of all, welcome to all the lonely people who arrived on this page falsely brought here by this deceiving title.
The reason behind it is that I wondered how to make a Ruby equivalent to C++’s friend methods.

What we have to do is to catch any attempt to call a private or protected method, check that the caller is authorized to do so and then call it on his behalf.

Detecting a missed call

Ruby provides a method called missing_method. Overloading this method lets us catch (among other things) any attempt at calling a private method.

We have to make sure that this is indeed a call to a private method. We do so (even if this is certainly not the most efficient way) by checking that the method name passed as an argument is present in the private methods list returned by private_methods.

	def method_missing mthid
 		puts "method_missing : " + mthid.id2name
  		# Check that mthid is actually a private method
 		if private_methods.include? mthid.id2name
 			# Perform other checks...
 		end
 	end

Telling your friends from your foes

Checking that a private method exists is not enough, as you don’t want uninvited methods to be able to call private methods of your object. You make sure this doesn’t happen by keeping a list of who is allowed to call your private methods.
The problem is, how do you ensure that it doesn’t get compromised? Ruby’s strength lies in the fact that nothing is really fixed. Would this be the end of a beautiful story?

Of course not. First, because one can freeze objects, that is protect them from further alteration. Which is very useful if we want to maintain an access list, write a few methods to access it and make sure that no one will be able to add new methods so as to bypass security checks.

Once an access list exists, we have to make sure that any method that calls the private methods is actually whom it pretends it is. This is a touchy point, as we will see in the following part.

Finding who is the caller

At the moment, Ruby doesn’t provide a very nice methods to do this. There is a method named caller that returns a bunch of strings. Parsing them is quite tedious, and sometimes the relevant information is not available at all. An improved version of this method is one of the much anticipated features of the next versions of Ruby. Meanwhile, we have to do this in ourselves. And that involves delving into the code of the Ruby interpreter.
When evaluating code, Ruby uses a structure to remember where it comes from, and where it is going. This structure is called a FRAME and keeps track of information like what is the current file, the current line, and many other relevant things. The thing we are interested in, however, is that it also mentions the class and the method that were referred to. These frames are stored as a linked list, the head of which is stored in the global variable ruby_frame.
Analyzing that linked list should hence provide us with all the information we need to identify who has been calling the program. This is done by the C function below:

#include "ruby.h"
#include "env.h"
#include "node.h"

static VALUE getbt(VALUE self)
{
  struct FRAME *frame	= ruby_frame;
  VALUE	ary		= rb_ary_new();
  VALUE	temp;

 	for (; frame; frame=frame->prev) {
 		temp = rb_ary_new();
 		rb_ary_push(temp, frame->last_class);
 		if (frame->last_func)
 			rb_ary_push(temp, ID2SYM(frame->last_func));
 		rb_ary_push(ary, temp);
 	}
  	return ary;
 }

 void Init_sunwind()
 {
 	rb_define_method(rb_cObject, "backtrace", getbt, 0);
 }

Follow this link if it isn’t displayed properly.

Note that, unfortunately, things are not as easy as this. Someone could still trick our program into believing that access should bre granted when it should not. Inserting a fake entry in the linked list should do the trick. However, it is not as easy as writing ruby code, as a hacker would have to install a C library and call it. Also, increasing the complexity of our linked-list parsing function by checking other elements could help us deter a few attacks. This is left as an open question for future articles.

Compile the program as a Ruby library, and let’s move on to the next step : the actual Ruby code.

The final code

The program is made up of several classes : The first one, BuddyController, maintains the list of friend methods for each class in a hash table. The second one, Test, has a secret method sekret_method that is private. It declares two methods, one is the method_missing method we have talked about at the beginning of this post. The second one is a class method that is used to declare friends. The last class, TestBuddy, defines a single method that is declared as a friend of Test.

Note that, in order to keep things simple, there is no real access control to BuddyController. Anyone can register the method he wishes. A real world implementation would obviously proceed by calling backtrace instead of trusting the caller.

require "sunwind"

# Declaration
class TestBuddy
end

class Test
end

# This class maintains a list of friend methods
class BuddyController
	# The list
	@@friend_list = Hash.new
 	# Forbids instantiation
	private_class_method :new

	# Add a methdod's signature to the list of friends
	def BuddyController.add klass, str
		# Add it to the list
		if @@friend_list[klass].nil?
			@@friend_list[klass] = [str]
		else
			@@friend_list[klass] += str
		end
	end

	# Check wether or not a method is a friend
	def BuddyController.match klass, str
		if (!@@friend_list[klass].nil?) && (@@friend_list[klass].include? str)
			true
		else
			false
		end
	end

	def BuddyController.dump
		@@friend_list.each { |k,v|
			puts k.to_s + "=" + v.to_s
		}
	end
end

# Freeze BuddyController to prevent any alteration
BuddyController.freeze

# Alter class object so that every object can access the friend class method.
class Object
	# The class method used to declare methods as friends.
	def Object.friend klass, sym
		BuddyController.add self, klass.to_s + "#" + sym.to_s
	end
end

# A class with a private method, declares friends.
class Test
	private

	# When a method cannot be loaded, check wether or not it is private
	def method_missing mthid
		puts "method_missing : " + mthid.id2name
 		# Check that mthid is actually a private method
		if private_methods.include? mthid.id2name
			# Get the backtrace
			bt 		= backtrace
			# after the target function, method_missing and backtrace
			# have been called.
			klass, sym	= bt[2]
			tempvalue	= klass.to_s + "#" + sym.to_s

			if (BuddyController.match self.class, tempvalue)
				# Call the method id
				send(mthid)
			else
				puts "Unauthorized access, or nonexistent method."
			end
		end
	end

	def sekret_method
		puts "Hello from the sekret method!"
	end

	# Declare TestBuddy#do_test as a friend
	friend TestBuddy, :do_test
end

# Freeze test so that no one can add its own method as a friend
Test.freeze

# A class with a method that is friend to Test
class TestBuddy
	def do_test
		temp = Test.new
		temp.sekret_method
	end
end

# "Main" program
tb = TestBuddy.new
tb.do_test

Follow this link if it is not displayed properly.

This program is far from being perfect, as nothing has been done to handle arguments. In fact, many things need to be improved. Backtraces aren’t used in BuddyControllet and declaring Object.friend is not the best choice. It’s a call for a version 2, isn’t it?

RTracker is going public!

January 16, 2007

Last week, I have opened a sourceforge account for RTracker. Although the code is still not production quality, I believe the project is ripe enough to welcome a bunch of other developpers. Pieces of code should be published little by little, as fast as I can “clean” them.

Some demonstration videos are being uploaded on the website. They are not really beautiful, as a lot of work has to be done on the graphics design side, but they show what the software can do.

If you are interested in joining, please note that the project’s Unix name on sourceforge.net is rdtracker, as rtracker was already taken by some other project.

 

I feel the way to start this blog is to introduce a trick that is central to RTracker, namely the possibility given to the Ruby programer to add methods to its classes on the flight. Like almost everything else you can wish for, Ruby provides the user with a method that does just this : Module#define_method. There is, however, a problem : that method is private. Don’t worry, there is a hack to do this anyway:

self.class.send(:define_method, “method_name”) { method_code }

That is, one has to send a message calling define_method to the class of the current object. In order to illustrate this, let’s write a small profiler that shows the performances of a method call. Note that the purpose of this program is to demonstrate how Ruby works, as Ruby already provides a very interesting tool, the benchmark.

If one wants to measure the time taken by a method to run, the simplest thing to do is to use the method Time#now, which returns what time it is. By calling this method before and after executing the “profiled” method, one can infer how much time the method took.

The next step is to replace the method whose performances we want to measure by our own method, that captures the current time, run the actual method and then captures the time again in order to display the difference. Why replace it, instead of simply creating a new method? Simply because profiling is interesting when dealing with huge pieces of code, and that one cannot imagine replacing every occurence of a widely used method. The following code does that for us:

class Object
def Object.profile symbol
rename_symbol = (”rprof_” + symbol.to_s).to_sym
alias_method rename_symbol, symbol
# Define the new method so as to add timing
# code.

self
.send(:define_method, symbol.to_s) { |*args|
start_time = Time.nowself.send(rename_symbol, *args)
puts (Time.now - start_time).to_s + ” have elapsed”
}
puts “The new method ” + rename_symbol.to_s + ” has been created for method ” + symbol
end
end

Then, in order to monitor a method, you can just use it like:

class Test
def hello
puts “hello”
end
profile :hello
end

Calling hello on an instance of set gives you the timing for that call.

There is something I have implicitely assumed, and haven’t talked about: the fact that the method is a member of class Module and not of class Class. Yet, we managed to add a method to our class. This is due to the fact that the module Kernel is automatically included in class Object. We have therefore access to that method at class level.

Another problem is that we do not take into account the fact that the Time#now method itself takes some time. Of course, one can imagine writing something like:

@@time_overhead = Time.now - Time.now

and then turning the time calculation code into:

puts (Time.now - start_time - @@time_overhead).to_s + " seconds have elapsed."

But that is not really relevant as trying Time.now - Time.now several times in irb returns very different values. That is because of the fact that it is run on a multitasked operating system, and that it is not possible to ensure that there will always be the same amount of time between the two calls of Time#now. Which means that measures are relevant when their order is bigger than the order of the scheduler’s overhead. When the order is not so different, only the average over several values is relevant.

A fix to this problem could be to focus only on how much user time has elapsed. Which is beyond the scope of this post.

Follow

Get every new post delivered to your Inbox.