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 142
142: def initialize(&block)
143:
144: @root_set = RuleSet.new
145: @set = RuleSet.new
146: @current_set = @set
147:
148: add_rules(&block)
149: end
Adds a set of checks (rules) to this treechecker. Returns self.
# File lib/rufus/treechecker.rb, line 187
187: def add_rules(&block)
188:
189: instance_eval(&block) if block
190:
191: self
192: 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 163
163: def check(rubycode)
164:
165: sexp = parse(rubycode)
166:
167: #@root_checks.each do |meth, *args|
168: # send meth, sexp, args
169: #end
170: @root_set.check(sexp)
171:
172: do_check(sexp)
173: end
Return a copy of this TreeChecker instance
# File lib/rufus/treechecker.rb, line 177
177: def clone
178:
179: tc = TreeChecker.new
180: tc.instance_variable_set(:@root_set, @root_set.clone)
181: tc.instance_variable_set(:@set, @set.clone)
182: tc
183: end
Freezes the treechecker instance “in depth“
# File lib/rufus/treechecker.rb, line 196
196: def freeze
197: super
198: @root_set.freeze
199: @set.freeze
200: end
pretty-prints the sexp tree of the given rubycode
# File lib/rufus/treechecker.rb, line 129
129: def ptree(rubycode)
130: puts stree(rubycode)
131: 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 316
316: def at_root(&block)
317:
318: @current_set = @root_set
319: add_rules(&block)
320: @current_set = @set
321: end
The actual check method, check() is rather a bootstrap one...
# File lib/rufus/treechecker.rb, line 502
502: def do_check(sexp)
503:
504: @set.check(sexp)
505:
506: return unless sexp.is_a?(Array) # check over, seems fine...
507:
508: # check children
509:
510: sexp.each { |c| do_check c }
511: end
# File lib/rufus/treechecker.rb, line 490
490: def do_exclude_pair (first, args)
491:
492: args, message = extract_message(args)
493: args.each do |a|
494: expand_class(a).each do |c|
495: @current_set.exclude_pattern([ first, c ], message)
496: end
497: end
498: end
prevents access (calling methods and rebinding) to a class (or a list of classes
# File lib/rufus/treechecker.rb, line 408
408: def exclude_access_to(*args)
409: exclude_call_on *args
410: exclude_rebinding *args
411: end
Bans the usage of ‘alias’
# File lib/rufus/treechecker.rb, line 460
460: def exclude_alias
461:
462: @current_set.exclude_symbol(:alias, "'alias' is forbidden")
463: @current_set.exclude_symbol(:alias_method, "'alias_method' is forbidden")
464: end
Bans the use of backquotes
# File lib/rufus/treechecker.rb, line 477
477: def exclude_backquotes
478:
479: @current_set.exclude_symbol(:xstr, 'backquotes are forbidden')
480: end
# File lib/rufus/treechecker.rb, line 372
372: def exclude_call_on(*args)
373: do_exclude_pair(:call, args)
374: end
# File lib/rufus/treechecker.rb, line 376
376: def exclude_call_to(*args)
377: args, message = extract_message(args)
378: args.each { |a| @current_set.exclude_pattern([ :call, :any, a], message) }
379: 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 427
427: def exclude_class_tinkering (*args)
428:
429: @current_set.exclude_pattern(
430: [ :sclass ], 'opening the metaclass of an instance is forbidden')
431:
432: Array(args.last[:except]).each { |e|
433: expand_class(e).each do |c|
434: @current_set.accept_pattern([ :class, :any, c ])
435: end
436: } if args.last.is_a?(Hash)
437:
438: @current_set.exclude_pattern(
439: [ :class ], 'defining a class is forbidden')
440: end
Bans method definitions
# File lib/rufus/treechecker.rb, line 415
415: def exclude_def
416:
417: @current_set.exclude_symbol(:defn, 'method definitions are forbidden')
418: end
Bans the use of ‘eval’, ‘module_eval’ and ‘instance_eval‘
# File lib/rufus/treechecker.rb, line 468
468: def exclude_eval
469:
470: exclude_call_to(:eval, 'eval() is forbidden')
471: exclude_call_to(:module_eval, 'module_eval() is forbidden')
472: exclude_call_to(:instance_eval, 'instance_eval() is forbidden')
473: end
# File lib/rufus/treechecker.rb, line 359
359: def exclude_fcall(*args)
360: do_exclude_pair(:fcall, args)
361: end
# File lib/rufus/treechecker.rb, line 367
367: def exclude_fvcall(*args)
368: do_exclude_pair(:fcall, args)
369: do_exclude_pair(:vcall, args)
370: end
# File lib/rufus/treechecker.rb, line 381
381: def exclude_fvccall(*args)
382: exclude_fvcall(*args)
383: exclude_call_to(*args)
384: end
Bans referencing or setting the value of global variables
# File lib/rufus/treechecker.rb, line 452
452: def exclude_global_vars
453:
454: @current_set.exclude_symbol(:gvar, 'global vars are forbidden')
455: @current_set.exclude_symbol(:gasgn, 'global vars are forbidden')
456: 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 349
349: def exclude_head(head, message=nil)
350:
351: @current_set.exclude_pattern(head, message)
352: end
Bans the definition or the opening of modules
# File lib/rufus/treechecker.rb, line 444
444: def exclude_module_tinkering
445:
446: @current_set.exclude_symbol(
447: :module, 'defining or opening a module is forbidden')
448: end
Bans raise and throw
# File lib/rufus/treechecker.rb, line 484
484: def exclude_raise
485:
486: exclude_fvccall(:raise, 'raise is forbidden')
487: exclude_fvccall(:throw, 'throw is forbidden')
488: 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 395
395: def exclude_rebinding(*args)
396: args, message = extract_message(args)
397: args.each do |a|
398: expand_class(a).each do |c|
399: @current_set.exclude_pattern([ :lasgn, :any, c], message)
400: end
401: end
402: end
# File lib/rufus/treechecker.rb, line 354
354: def exclude_symbol(*args)
355: args, message = extract_message(args)
356: args.each { |a| @current_set.exclude_symbol(a, message) }
357: end
# File lib/rufus/treechecker.rb, line 363
363: def exclude_vcall(*args)
364: do_exclude_pair(:vcall, args)
365: end
# File lib/rufus/treechecker.rb, line 331
331: def expand_class(arg)
332:
333: if arg.is_a?(Class) or arg.is_a?(Module)
334: [ parse(arg.to_s), parse("::#{arg.to_s}") ]
335: else
336: [ arg ]
337: end
338: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.