Parent

Namespace

Files

Class/Module Index [+]

Quicksearch

Rufus::TreeChecker

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

featured exclusion methods

call / vcall / fcall ?

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.

low-level rules

higher level rules

Those rules take no arguments

a bit further

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

Constants

VERSION

Public Class Methods

new(&block) click to toggle source

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

Public Instance Methods

add_rules(&block) click to toggle source

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
check(rubycode) click to toggle source

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
clone() click to toggle source

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
freeze() click to toggle source

freezes the treechecker instance "in depth"

# File lib/rufus/treechecker.rb, line 206
def freeze
  super
  @root_set.freeze
  @set.freeze
end
ptree(rubycode) click to toggle source

pretty-prints the sexp tree of the given rubycode

# File lib/rufus/treechecker.rb, line 133
def ptree (rubycode)
  puts stree(rubycode)
end
stree(rubycode) click to toggle source

returns the pretty-printed string of the given rubycode (thanks ruby_parser).

# File lib/rufus/treechecker.rb, line 141
def stree (rubycode)
  "#{rubycode.inspect}\n =>\n#{parse(rubycode).inspect}"
end
to_s() click to toggle source
# File lib/rufus/treechecker.rb, line 157
def to_s
  s = "#{self.class} (#{self.object_id})\n"
  s << "root_set :\n"
  s << @root_set.to_s
  s << "set :\n"
  s << @set.to_s
end

Protected Instance Methods

at_root(&block) click to toggle source

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
do_check(sexp) click to toggle source

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
do_exclude_pair(first, args) click to toggle source
# 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
exclude_access_to(*args) click to toggle source

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
exclude_alias() click to toggle source

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
exclude_backquotes() click to toggle source

bans the use of backquotes

# File lib/rufus/treechecker.rb, line 497
def exclude_backquotes

  @current_set.exclude_symbol(:xstr, 'backquotes are forbidden')
end
exclude_call_on(*args) click to toggle source
# File lib/rufus/treechecker.rb, line 384
def exclude_call_on (*args)
  do_exclude_pair(:call, args)
end
exclude_call_to(*args) click to toggle source
# 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
exclude_class_tinkering(*args) click to toggle source

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
exclude_def() click to toggle source

bans method definitions

# File lib/rufus/treechecker.rb, line 429
def exclude_def

  @current_set.exclude_symbol(:defn, 'method definitions are forbidden')
end
exclude_eval() click to toggle source

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
exclude_fcall(*args) click to toggle source
# File lib/rufus/treechecker.rb, line 371
def exclude_fcall (*args)
  do_exclude_pair(:fcall, args)
end
exclude_fvcall(*args) click to toggle source
# File lib/rufus/treechecker.rb, line 379
def exclude_fvcall (*args)
  do_exclude_pair(:fcall, args)
  do_exclude_pair(:vcall, args)
end
exclude_fvccall(*args) click to toggle source
# File lib/rufus/treechecker.rb, line 393
def exclude_fvccall (*args)
  exclude_fvcall(*args)
  exclude_call_to(*args)
end
exclude_global_vars() click to toggle source

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
exclude_head(head, message=nil) click to toggle source

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
exclude_module_tinkering() click to toggle source

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
exclude_raise() click to toggle source

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
exclude_rebinding(*args) click to toggle source

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
exclude_symbol(*args) click to toggle source
# 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
exclude_vcall(*args) click to toggle source
# File lib/rufus/treechecker.rb, line 375
def exclude_vcall (*args)
  do_exclude_pair(:vcall, args)
end
expand_class(arg) click to toggle source
# 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
extract_message(args) click to toggle source
# 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
parse(rubycode) click to toggle source

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

[Validate]

Generated with the Darkfish Rdoc Generator 2.