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 class: 1 Board
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 only, I'd create a wrapper class as follows: 1 make_move, moves, move_matrix, log_info
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 to make creating these wrappers easy. It does the following: 1 acts_as_wrapped_class
- 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
returns a1 Board
then1 Move
returns a1 BoardWrapper
)1 MoveWrapper
- Wrap the contents of arrays and hashes (same as above, but will work with arrays of
, and Hashes containing1 Move
)1 Move
- Dispatch
methods directly to the wrapped objects. Compare two wrappers objects and get the same results as the two wrapped objects.1 ===, hash, <=>
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 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. 1 Board
Try to access 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 winner
and get access to the original Board object, but if you've set up your sandbox correctly, the class 1 wrapper._wrapped_class
will not even be defined in the sandbox and will raise an exception. 1 Board
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 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 Board
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
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.
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!
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!
专业翻译公司,位居翻译公司业界前列,拥有分翻译公司,包括南京翻译公司,北京翻译公司,上海翻译公司承接上海业务,上海翻译公司,北京翻译公司承接北京业务,上海翻译公司和北京翻译公司并提供翻译报,广州翻译公司报价,广州翻译公司大全,深圳翻译公司报价,深圳翻译公司服务。
北京华美达翻译公司是由清华、北大的几位专家教授组建而成。管理规范、翻译专业水平高并拥有一大批有经验的专业翻译人员。公司译员大多是毕业于清华、北大、北京外语大学、第二外国语学院等著名大学,都具有多年的翻译经验,具有很高的专业翻译水平。
北京华美达翻译公司以清华大学的自强不息、厚德载物作为优良传统,以“客户至上”为指导思想,为客户提供最周到、最专业的服务,以优质、低价、高效的服务让客户满意,本公司因有一大批优秀的翻译人才而享有盛名,赢得了众多客户的认可和好评。
北京华美达翻译公司提供包括英语翻译、俄语翻译、德语翻译、日语翻译、韩语翻译、法语翻译、西班牙语翻译、阿拉伯语翻译、意大利语翻译、越语翻译、葡萄牙语翻译、匈牙利语翻译、波斯语翻译、等,以及小语种的中外文翻译服务。并且涉及的专业范围广,如:机械设备、电子电气、仪器仪表、计算机、水利工程、医疗医药、生物化学、经济金融、法律、贸易等等及相关领域。同时北京华美达翻译公司提供同传、口译和交传等翻译服务。很多译员曾先后出色的完成一些重要会议、电话及谈判等的同传、口译业务,并赢得广大客户的依赖和好评。
</body> </html>