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?

Advertisements

One Response to “How to have friends”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: