1. Skip to navigation
  2. Skip to content

The ELC Community Blog

A knowledge exchange on Ruby on Rails and Agile Development


Safely exposing your app to a ruby Sandbox

by stevend on October 23, 2007

Creating wrapper classes for the sandbox

When creating my sandboxed game of Tictactoe (where a user can upload a new algorithm and play tictactoe against it), I wanted to expose only a small part of my application to user uploaded code. In the follow code, for example, I would want to provide user access to only a few methods of the

   1  Board
class:

   1  class Board < ActiveRecord::Base
   2    has_many :moves
   3    belongs_to :algorithm_x, :class_name => "Algorithm", :foreign_key => "algorithm_x_id"
   4    belongs_to :algorithm_o, :class_name => "Algorithm", :foreign_key => "algorithm_o_id"
   5  
   6    def make_move!(x, y)...
   7    def move_matrix...
   8    def log_info(msg)...
   9    def winner...
  10    def game_over...
  11    def make_computer_move!...
  12    def human_turn?...
  13  end

If I want to allow the user's code to access

   1  make_move, moves, move_matrix, log_info
only, I'd create a wrapper class as follows:

   1  class BoardWrapper
   2    def initialize(board); @board = board; end
   3    def make_move(x,y); @board.make_move(x,y); end
   4    def moves; @board.moves.collect {|m| MoveWrapper.new(m) }; end
   5    def move_matrix; @board.move_matrix; end
   6    def log_info(msg); @board.log_info(msg); end
   7  end

acts_as_wrapped_class

This is pretty cumbersome to build, so I built

   1  acts_as_wrapped_class
to make creating these wrappers easy. It does the following:

  • Automatically generate a wrapper class for each class marked as
       1  acts_as_wrapped_class
    
  • Dispatch methods that match (or don't match) a safelist or blacklist
  • Finds appropriate wrappers for return results (meaning if
       1  Board
    
    returns a
       1  Move
    
    then
       1  BoardWrapper
    
    returns a
       1  MoveWrapper
    
    )
  • Wrap the contents of arrays and hashes (same as above, but will work with arrays of
       1  Move
    
    , and Hashes containing
       1  Move
    
    )
  • Dispatch
       1  ===, hash, &lt;=&gt;
    
    methods directly to the wrapped objects. Compare two wrappers objects and get the same results as the two wrapped objects.

The above example is much shorter when written with acts_as_wrapped_class:

   1  class Board < ActiveRecord::Base
   2    acts_as_wrapped_class :methods => [:moves, :make_move!, :move_matrix, :log_info]
   3  
   4    def make_move!(x, y)...
   5    def move_matrix...
   6    ...
   7  end
   8  
   9  class Move < ActiveRecord::Base
  10    belongs_to :board
  11      
  12    acts_as_wrapped_class :methods => [:x_pos, :y_pos, :is_x, :created_at]
  13  end

Simple executing acts_as_wrapped_class inside the definition of

   1  Board
automatically defines the BoardWrapper class with checks on which methods are called. This is accomplished through undefining all the methods of BoardWrapper and defining a method_missing which checks the safelist/blacklist before dispatching the method call.

Try to access

   1  winner
on a BoardWrapper and it will throw an exception, because :winner isn't on the list of approved classes. Of course, you can call
   1  wrapper._wrapped_class
and get access to the original Board object, but if you've set up your sandbox correctly, the class
   1  Board
will not even be defined in the sandbox and will raise an exception.

View the RDOC for acts_as_wrapped_class for more detail.

acts_as_runnable_code

In order to make sandboxing user code even easier, I created another gem: acts_as_runnable_code. This gem helps you with the creation of the sandbox, the referencing of the wrapper classes, and automatic wrapping/unwrapping of data as it flows in and out of the sandbox. It assumes the following about your application

  • you have objects that store user uploaded code in them
  • you want to use your classes in the sandbox with reduced functionality provided by acts_as_wrapped_class
  • you want to evaluate an instance of user uploaded code within the context of some instance of a wrapped class

When writing tictactoe, I created an Algorithm model which stored user uploaded code in a database TEXT field. I also wanted to evaluate that code using the binding of the

   1  Board
object on which the game was being played (meaning the user code looks like "make_move!(1,1)" rather than "@board.make_move(1,1)").

   1  class Algorithm < ActiveRecord::Base
   2    acts_as_runnable_code
   3  end
   4  
   5  @board = Board.find(id)
   6  @board.algorithm_x.run_code(@board, :timeout => 1.0)

View the RDOC for acts_as_runnable_code gem.

To see tictactoe in action, create your own algorithm, and test the safety of the sandbox (scary!) visit tictactoe.mapleton.net

Comments

Eric Anderson at 1:22 PM on October 23 2007

I haven’t taken a close look at this post yet but it’s protection pretty much advisory in Ruby and not enforced? For example couldn’t I use instance_variable_get to get the underlying object and then call on that object directly? I guess you could override instance_varaible_get to look at the caller or something.

David Stevenson at 11:15 PM on October 23 2007

Eric, Of course you can get access to the underlying object outside of the sandbox, but not inside. While instance_variable_get is actually undefined for Wrapper objects, I provide a method ._wrapped_object to access the contained object.

This isn’t a concern inside the sandbox, because we control which classes are defined inside in the sandbox. Only Wrapper classes and core datatypes are defined there (this automatically done by the acts_as_runnable_code gem, nothing about the sandbox prevents you from defining whatever you want in there).

Hence, when the malicious code calls ._wrapped_object, the unwrapped object is returned (marshaled) into the sandbox… where it FAILS to unmarshal because that class isn’t even defined there!

rbd at 3:23 PM on October 13 2008

I am also interested in writing an interactive game using your gems. I have installed Ruby 1.8.5 and Sandbox 0.4 and RubyGems 0.9.4 and acts_as_wrapped_class , and acts_as_runnable_class.

But when I run test_acts_as_runnable_code.rb I get an error:

test_run_code(ActsAsRunnableCodeTest): NoMethodError: undefined method `set’ for #<sandbox::safe:0xb7bd7830> ./../lib/acts_as_runnable_code.rb:18:in `run_code’ test_acts_as_runnable_code.rb:86:in `test_run_code’

It seems there is no Sandbox.set method.

What version of Sandbox do you have running, for the tictactoe ? Perhaps I have installed the wrong version of Sandbox.

thanks!

上海翻译公司 at 11:35 AM on October 17 2008

专业翻译公司,位居翻译公司业界前列,拥有分翻译公司,包括南京翻译公司,北京翻译公司上海翻译公司承接上海业务,上海翻译公司,北京翻译公司承接北京业务,上海翻译公司和北京翻译公司并提供翻译报,广州翻译公司报价,广州翻译公司大全,深圳翻译公司报价,深圳翻译公司服务。

翻译公司 at 8:07 PM on November 23 2008

 

 

 

  北京华美达翻译公司是由清华、北大的几位专家教授组建而成。管理规范、翻译专业水平高并拥有一大批有经验的专业翻译人员。公司译员大多是毕业于清华、北大、北京外语大学、第二外国语学院等著名大学,都具有多年的翻译经验,具有很高的专业翻译水平。

  北京华美达翻译公司以清华大学的自强不息、厚德载物作为优良传统,以“客户至上”为指导思想,为客户提供最周到、最专业的服务,以优质、低价、高效的服务让客户满意,本公司因有一大批优秀的翻译人才而享有盛名,赢得了众多客户的认可和好评。

  北京华美达翻译公司提供包括英语翻译俄语翻译德语翻译日语翻译韩语翻译法语翻译西班牙语翻译阿拉伯语翻译意大利语翻译越语翻译葡萄牙语翻译匈牙利语翻译波斯语翻译、等,以及小语种的中外文翻译服务。并且涉及的专业范围广,如:机械设备、电子电气、仪器仪表、计算机、水利工程、医疗医药、生物化学、经济金融、法律、贸易等等及相关领域。同时北京华美达翻译公司提供同传口译交传等翻译服务。很多译员曾先后出色的完成一些重要会议、电话及谈判等的同传、口译业务,并赢得广大客户的依赖和好评。

</body> </html>

Add a comment


home | services | Ruby on Rails Development | code | blog | company