Object
TreeChecker relies on ruby_parser to turns a piece of ruby code (a string) into a bunch of sexpression and then TreeChecker will check that sexpression tree and raise a Rufus::SecurityException if an excluded pattern is spotted.
The TreeChecker is meant to be useful for people writing DSLs directly in Ruby (not via their own parser) that want to check and prevent bad things from happening in this code.
tc = Rufus::TreeChecker.new do
exclude_fvcall :abort
exclude_fvcall :exit, :exit!
end
tc.check("1 + 1; abort") # will raise a SecurityError
tc.check("puts (1..10).to_a.inspect") # OK
What the difference between those ? Well, here is how those various piece of code look like :
"exit" => [:vcall, :exit] "Kernel.exit" => [:call, [:const, :Kernel], :exit] "Kernel::exit" => [:call, [:const, :Kernel], :exit] "k.exit" => [:call, [:vcall, :k], :exit] "exit -1" => [:fcall, :exit, [:array, [:lit, -1]]]
Obviously :fcall could be labelled as "function call", :call is a call on to some instance, while vcall might either be a variable dereference or a function call with no arguments.
exclude_symbol : bans the usage of a given symbol (very low-level,
mostly used by other rules
Those rules take no arguments
exclude_access_to : prevents calling or rebinding a list of classes
exclude_eval : bans eval, module_eval and instance_eval
exclude_global_vars : bans calling or modifying global vars
exclude_alias : bans calls to alias and alias_method
exclude_vm_exiting : bans exit, abort, ...
exclude_raise : bans calls to raise or throw
It's possible to clone a TreeChecker and to add some more rules to it :
tc0 = Rufus::TreeChecker.new do # # calls to eval, module_eval and instance_eval are not allowed # exclude_eval end tc1 = tc0.clone tc1.add_rules do # # calls to any method on File and FileUtils classes are not allowed # exclude_call_on File, FileUtils end
initializes the TreeChecker, expects a block
# File lib/rufus/treechecker.rb, line 148 def initialize (&block) @root_set = RuleSet.new @set = RuleSet.new @current_set = @set add_rules(&block) end
adds a set of checks (rules) to this treechecker. Returns self.
# File lib/rufus/treechecker.rb, line 196 def add_rules (&block) instance_eval(&block) if block self end
Performs the check on the given String of ruby code. Will raise a Rufus::SecurityError if there is something excluded by the rules specified at the initialization of the TreeChecker instance.
# File lib/rufus/treechecker.rb, line 170 def check (rubycode) sexp = parse(rubycode) #@root_checks.each do |meth, *args| # send meth, sexp, args #end @root_set.check(sexp) do_check(sexp) end
return a copy of this TreeChecker instance
# File lib/rufus/treechecker.rb, line 185 def clone tc = TreeChecker.new tc.instance_variable_set(:@root_set, @root_set.clone) tc.instance_variable_set(:@set, @set.clone) tc end
freezes the treechecker instance "in depth"
# File lib/rufus/treechecker.rb, line 206 def freeze super @root_set.freeze @set.freeze end
pretty-prints the sexp tree of the given rubycode
# File lib/rufus/treechecker.rb, line 133 def ptree (rubycode) puts stree(rubycode) end
within the 'at_root' block, rules are added to the @root_checks, ie they are evaluated only for the toplevel (root) sexp.
# File lib/rufus/treechecker.rb, line 327 def at_root (&block) @current_set = @root_set add_rules(&block) @current_set = @set end
the actual check method, check() is rather a bootstrap one...
# File lib/rufus/treechecker.rb, line 524 def do_check (sexp) @set.check(sexp) return unless sexp.is_a?(Array) # check over, seems fine... # check children sexp.each { |c| do_check c } end
# File lib/rufus/treechecker.rb, line 511 def do_exclude_pair (first, args) args, message = extract_message(args) args.each do |a| expand_class(a).each do |c| @current_set.exclude_pattern([ first, c ], message) end end end
prevents access (calling methods and rebinding) to a class (or a list of classes
# File lib/rufus/treechecker.rb, line 421 def exclude_access_to (*args) exclude_call_on *args exclude_rebinding *args end
bans the usage of 'alias'
# File lib/rufus/treechecker.rb, line 478 def exclude_alias @current_set.exclude_symbol(:alias, "'alias' is forbidden") @current_set.exclude_symbol(:alias_method, "'alias_method' is forbidden") end
bans the use of backquotes
# File lib/rufus/treechecker.rb, line 497 def exclude_backquotes @current_set.exclude_symbol(:xstr, 'backquotes are forbidden') end
# File lib/rufus/treechecker.rb, line 384 def exclude_call_on (*args) do_exclude_pair(:call, args) end
# File lib/rufus/treechecker.rb, line 388 def exclude_call_to (*args) args, message = extract_message(args) args.each { |a| @current_set.exclude_pattern([ :call, :any, a], message) } end
bans the defintion and the [re]openening of classes
a list of exceptions (classes) can be passed. Subclassing those exceptions is permitted.
exclude_class_tinkering :except => [ String, Array ]
# File lib/rufus/treechecker.rb, line 442 def exclude_class_tinkering (*args) @current_set.exclude_pattern( [ :sclass ], 'opening the metaclass of an instance is forbidden') Array(args.last[:except]).each { |e| expand_class(e).each do |c| @current_set.accept_pattern([ :class, :any, c ]) end } if args.last.is_a?(Hash) @current_set.exclude_pattern( [ :class ], 'defining a class is forbidden') end
bans method definitions
# File lib/rufus/treechecker.rb, line 429 def exclude_def @current_set.exclude_symbol(:defn, 'method definitions are forbidden') end
bans the use of 'eval', 'module_eval' and 'instance_eval'
# File lib/rufus/treechecker.rb, line 487 def exclude_eval exclude_call_to(:eval, 'eval() is forbidden') exclude_call_to(:module_eval, 'module_eval() is forbidden') exclude_call_to(:instance_eval, 'instance_eval() is forbidden') end
# File lib/rufus/treechecker.rb, line 371 def exclude_fcall (*args) do_exclude_pair(:fcall, args) end
# File lib/rufus/treechecker.rb, line 379 def exclude_fvcall (*args) do_exclude_pair(:fcall, args) do_exclude_pair(:vcall, args) end
# File lib/rufus/treechecker.rb, line 393 def exclude_fvccall (*args) exclude_fvcall(*args) exclude_call_to(*args) end
bans referencing or setting the value of global variables
# File lib/rufus/treechecker.rb, line 469 def exclude_global_vars @current_set.exclude_symbol(:gvar, 'global vars are forbidden') @current_set.exclude_symbol(:gasgn, 'global vars are forbidden') end
adds a rule that will forbid sexps that begin with the given head
tc = TreeChecker.new do
exclude_head [ :block ]
end
tc.check('a = 2') # ok
tc.check('a = 2; b = 5') # will raise an error as it's a block
# File lib/rufus/treechecker.rb, line 361 def exclude_head (head, message=nil) @current_set.exclude_pattern(head, message) end
bans the definition or the opening of modules
# File lib/rufus/treechecker.rb, line 460 def exclude_module_tinkering @current_set.exclude_symbol( :module, 'defining or opening a module is forbidden') end
bans raise and throw
# File lib/rufus/treechecker.rb, line 505 def exclude_raise exclude_fvccall(:raise, 'raise is forbidden') exclude_fvccall(:throw, 'throw is forbidden') end
This rule :
exclude_rebinding Kernel
will raise a security error for those pieces of code :
k = Kernel k = ::Kernel
# File lib/rufus/treechecker.rb, line 408 def exclude_rebinding (*args) args, message = extract_message(args) args.each do |a| expand_class(a).each do |c| @current_set.exclude_pattern([ :lasgn, :any, c], message) end end end
# File lib/rufus/treechecker.rb, line 366 def exclude_symbol (*args) args, message = extract_message(args) args.each { |a| @current_set.exclude_symbol(a, message) } end
# File lib/rufus/treechecker.rb, line 375 def exclude_vcall (*args) do_exclude_pair(:vcall, args) end
# File lib/rufus/treechecker.rb, line 342 def expand_class (arg) if arg.is_a?(Class) or arg.is_a?(Module) [ parse(arg.to_s), parse("::#{arg.to_s}") ] else [ arg ] end end
# File lib/rufus/treechecker.rb, line 334 def extract_message (args) message = nil args = args.dup message = args.pop if args.last.is_a?(String) [ args, message ] end
a simple parse (relies on ruby_parser currently)
# File lib/rufus/treechecker.rb, line 538 def parse (rubycode) #(@parser ||= RubyParser.new).parse(rubycode).to_a # # parser goes ballistic after a while, seems having a new parser # each is not heavy at all RubyParser.new.parse(rubycode).to_a end
Generated with the Darkfish Rdoc Generator 2.