rufus-treechecker 1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.txt +96 -0
- data/lib/rufus/treechecker.rb +529 -0
- data/test/ft_0_basic.rb +241 -0
- data/test/ft_1_old_treechecker.rb +72 -0
- data/test/test.rb +4 -0
- data/test/testmixin.rb +31 -0
- metadata +68 -0
data/README.txt
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
|
2
|
+
= 'rufus-treechecker'
|
3
|
+
|
4
|
+
== what is it ?
|
5
|
+
|
6
|
+
Initialize a Rufus::TreeChecker and pass some ruby code to make sure it's safe before calling eval().
|
7
|
+
|
8
|
+
|
9
|
+
== getting it
|
10
|
+
|
11
|
+
sudo gem install -y rufus-treechecker
|
12
|
+
|
13
|
+
or download[http://rubyforge.org/frs/?group_id=4812] it from RubyForge.
|
14
|
+
|
15
|
+
|
16
|
+
== usage
|
17
|
+
|
18
|
+
The treechecker uses ruby_parser (http://rubyforge.org/projects/parsetree)
|
19
|
+
to turn Ruby code into s-expressions, the treechecker then
|
20
|
+
checks this sexp tree and raises a Rufus::SecurityError if an excluded pattern
|
21
|
+
is spotted.
|
22
|
+
|
23
|
+
The excluded patterns are defined at the initialization of the TreeChecker
|
24
|
+
instance by listing rules.
|
25
|
+
|
26
|
+
require 'rubygems'
|
27
|
+
require 'rufus-treechecker'
|
28
|
+
|
29
|
+
tc = Rufus::TreeChecker.new do
|
30
|
+
exclude_fvcall :abort
|
31
|
+
exclude_fvcall :exit, :exit!
|
32
|
+
end
|
33
|
+
|
34
|
+
tc.check("1 + 1; abort") # will raise a SecurityError
|
35
|
+
tc.check("puts (1..10).to_a.inspect") # OK
|
36
|
+
|
37
|
+
|
38
|
+
Nice, but how do I know what to exclude ?
|
39
|
+
|
40
|
+
require 'rubygems'
|
41
|
+
require 'rufus-treechecker'
|
42
|
+
|
43
|
+
Rufus::TreeChecker.new.ptree('a = 5 + 6; puts a')
|
44
|
+
|
45
|
+
will yield
|
46
|
+
|
47
|
+
"a = 5 + 6; puts a"
|
48
|
+
=>
|
49
|
+
[:block,
|
50
|
+
[:lasgn, :a, [:call, [:lit, 5], :+, [:array, [:lit, 6]]]],
|
51
|
+
[:fcall, :puts, [:array, [:lvar, :a]]]
|
52
|
+
]
|
53
|
+
|
54
|
+
|
55
|
+
For more documentation, see http://github.com/jmettraux/rufus-treechecker/tree/master/lib/rufus/treechecker.rb
|
56
|
+
|
57
|
+
|
58
|
+
== dependencies
|
59
|
+
|
60
|
+
the 'rogue-parser' gem
|
61
|
+
|
62
|
+
|
63
|
+
== mailing list
|
64
|
+
|
65
|
+
On the Rufus-Ruby list[http://groups.google.com/group/rufus-ruby] :
|
66
|
+
|
67
|
+
http://groups.google.com/group/rufus-ruby
|
68
|
+
|
69
|
+
|
70
|
+
== issue tracker
|
71
|
+
|
72
|
+
http://rubyforge.org/tracker/?atid=18584&group_id=4812&func=browse
|
73
|
+
|
74
|
+
|
75
|
+
== source
|
76
|
+
|
77
|
+
http://github.com/jmettraux/rufus-treechecker
|
78
|
+
|
79
|
+
git clone git://github.com/jmettraux/rufus-treechecker.git
|
80
|
+
|
81
|
+
|
82
|
+
== author
|
83
|
+
|
84
|
+
John Mettraux, jmettraux@gmail.com,
|
85
|
+
http://jmettraux.wordpress.com
|
86
|
+
|
87
|
+
|
88
|
+
== the rest of Rufus
|
89
|
+
|
90
|
+
http://rufus.rubyforge.org
|
91
|
+
|
92
|
+
|
93
|
+
== license
|
94
|
+
|
95
|
+
MIT
|
96
|
+
|
@@ -0,0 +1,529 @@
|
|
1
|
+
#
|
2
|
+
#--
|
3
|
+
# Copyright (c) 2008, John Mettraux, jmettraux@gmail.com
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
#
|
24
|
+
|
25
|
+
#
|
26
|
+
# "made in Japan" (as opposed to "swiss made")
|
27
|
+
#
|
28
|
+
|
29
|
+
require 'ruby_parser' # gem 'rogue_parser'
|
30
|
+
|
31
|
+
|
32
|
+
module Rufus
|
33
|
+
|
34
|
+
#
|
35
|
+
# Instances of this error class are thrown when the ruby code being
|
36
|
+
# checked contains exclude stuff
|
37
|
+
#
|
38
|
+
class SecurityError < RuntimeError
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# TreeChecker relies on ruby_parser to turns a piece of ruby code (a string)
|
43
|
+
# into a bunch of sexpression and then TreeChecker will check that
|
44
|
+
# sexpression tree and raise a Rufus::SecurityException if an excluded
|
45
|
+
# pattern is spotted.
|
46
|
+
#
|
47
|
+
# The TreeChecker is meant to be useful for people writing DSLs directly
|
48
|
+
# in Ruby (not via their own parser) that want to check and prevent
|
49
|
+
# bad things from happening in this code.
|
50
|
+
#
|
51
|
+
# tc = Rufus::TreeChecker.new do
|
52
|
+
# exclude_fvcall :abort
|
53
|
+
# exclude_fvcall :exit, :exit!
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# tc.check("1 + 1; abort") # will raise a SecurityError
|
57
|
+
# tc.check("puts (1..10).to_a.inspect") # OK
|
58
|
+
#
|
59
|
+
#
|
60
|
+
# == featured exclusion methods
|
61
|
+
#
|
62
|
+
# === call / vcall / fcall ?
|
63
|
+
#
|
64
|
+
# What the difference between those ? Well, here is how those various piece
|
65
|
+
# of code look like :
|
66
|
+
#
|
67
|
+
# "exit" => [:vcall, :exit]
|
68
|
+
# "Kernel.exit" => [:call, [:const, :Kernel], :exit]
|
69
|
+
# "Kernel::exit" => [:call, [:const, :Kernel], :exit]
|
70
|
+
# "k.exit" => [:call, [:vcall, :k], :exit]
|
71
|
+
# "exit -1" => [:fcall, :exit, [:array, [:lit, -1]]]
|
72
|
+
#
|
73
|
+
# Obviously :fcall could be labelled as "function call", :call is a call
|
74
|
+
# on to some instance, while vcall might either be a variable dereference
|
75
|
+
# or a function call with no arguments.
|
76
|
+
#
|
77
|
+
# === low-level rules
|
78
|
+
#
|
79
|
+
# - exclude_symbol : bans the usage of a given symbol (very low-level,
|
80
|
+
# mostly used by other rules
|
81
|
+
# - exclude_head
|
82
|
+
# - exclude_fcall
|
83
|
+
# - exclude_vcall
|
84
|
+
# - exclude_fvcall
|
85
|
+
# - exclude_fvkcall
|
86
|
+
# - exclude_call_on
|
87
|
+
# - exclude_call_to
|
88
|
+
# - exclude_def
|
89
|
+
# - exclude_class_tinkering
|
90
|
+
# - exclude_module_tinkering
|
91
|
+
#
|
92
|
+
# - at_root
|
93
|
+
#
|
94
|
+
# === higher level rules
|
95
|
+
#
|
96
|
+
# Those rules take no arguments
|
97
|
+
#
|
98
|
+
# - exclude_eval : bans eval, module_eval and instance_eval
|
99
|
+
# - exclude_global_vars : bans calling or modifying global vars
|
100
|
+
# - exclude_alias : bans calls to alias and alias_method
|
101
|
+
# - exclude_vm_exiting : bans exit, abort, ...
|
102
|
+
# - exclude_raise : bans calls to raise or throw
|
103
|
+
#
|
104
|
+
#
|
105
|
+
# == a bit further
|
106
|
+
#
|
107
|
+
# It's possible to clone a TreeChecker and to add some more rules to it :
|
108
|
+
#
|
109
|
+
# tc0 = Rufus::TreeChecker.new do
|
110
|
+
# #
|
111
|
+
# # calls to eval, module_eval and instance_eval are not allowed
|
112
|
+
# #
|
113
|
+
# exclude_eval
|
114
|
+
# end
|
115
|
+
#
|
116
|
+
# tc1 = tc0.clone
|
117
|
+
# tc1.add_rules do
|
118
|
+
# #
|
119
|
+
# # calls to any method on File and FileUtils classes are not allowed
|
120
|
+
# #
|
121
|
+
# exclude_call_on File, FileUtils
|
122
|
+
# end
|
123
|
+
#
|
124
|
+
class TreeChecker
|
125
|
+
|
126
|
+
VERSION = '1.0'
|
127
|
+
|
128
|
+
#
|
129
|
+
# pretty-prints the sexp tree of the given rubycode
|
130
|
+
#
|
131
|
+
def ptree (rubycode)
|
132
|
+
puts
|
133
|
+
puts rubycode.inspect
|
134
|
+
puts " => "
|
135
|
+
puts parse(rubycode).inspect
|
136
|
+
end
|
137
|
+
|
138
|
+
#
|
139
|
+
# initializes the TreeChecker, expects a block
|
140
|
+
#
|
141
|
+
def initialize (&block)
|
142
|
+
|
143
|
+
@root_checks = []
|
144
|
+
@checks = []
|
145
|
+
|
146
|
+
@current_checks = @checks
|
147
|
+
|
148
|
+
add_rules(&block)
|
149
|
+
end
|
150
|
+
|
151
|
+
def to_s
|
152
|
+
p self.class
|
153
|
+
puts ' root_checks :'
|
154
|
+
puts @root_checks.collect { |r| r.inspect }.join("\n")
|
155
|
+
puts ' checks :'
|
156
|
+
puts @checks.collect { |r| r.inspect }.join("\n")
|
157
|
+
end
|
158
|
+
|
159
|
+
#
|
160
|
+
# Performs the check on the given String of ruby code. Will raise a
|
161
|
+
# Rufus::SecurityError if there is something excluded by the rules
|
162
|
+
# specified at the initialization of the TreeChecker instance.
|
163
|
+
#
|
164
|
+
def check (rubycode)
|
165
|
+
|
166
|
+
sexp = parse(rubycode)
|
167
|
+
|
168
|
+
@root_checks.each do |meth, *args|
|
169
|
+
send meth, sexp, args
|
170
|
+
end
|
171
|
+
|
172
|
+
do_check(sexp)
|
173
|
+
end
|
174
|
+
|
175
|
+
#
|
176
|
+
# return a copy of this TreeChecker instance
|
177
|
+
#
|
178
|
+
def clone
|
179
|
+
|
180
|
+
copy = TreeChecker.new
|
181
|
+
copy.instance_variable_set(:@checks, @checks.dup)
|
182
|
+
copy
|
183
|
+
end
|
184
|
+
|
185
|
+
#
|
186
|
+
# adds a set of checks (rules) to this treechecker. Returns self.
|
187
|
+
#
|
188
|
+
def add_rules (&block)
|
189
|
+
|
190
|
+
instance_eval(&block) if block
|
191
|
+
|
192
|
+
self
|
193
|
+
end
|
194
|
+
|
195
|
+
#
|
196
|
+
# freezes the treechecker instance "in depth"
|
197
|
+
#
|
198
|
+
def freeze
|
199
|
+
super
|
200
|
+
@root_checks.freeze
|
201
|
+
@root_checks.each { |c| c.freeze }
|
202
|
+
@checks.freeze
|
203
|
+
@checks.each { |c| c.freeze }
|
204
|
+
end
|
205
|
+
|
206
|
+
#
|
207
|
+
# generates a 'classic' tree checker
|
208
|
+
#
|
209
|
+
# Here is how it's built :
|
210
|
+
#
|
211
|
+
# return TreeChecker.new do
|
212
|
+
# exclude_fvkcall :abort
|
213
|
+
# exclude_fvkcall :exit, :exit!
|
214
|
+
# exclude_fvkcall :system
|
215
|
+
# exclude_eval
|
216
|
+
# exclude_alias
|
217
|
+
# exclude_global_vars
|
218
|
+
# exclude_call_on File, FileUtils
|
219
|
+
# exclude_class_tinkering
|
220
|
+
# exclude_module_tinkering
|
221
|
+
# end
|
222
|
+
#
|
223
|
+
def self.new_classic_tree_checker
|
224
|
+
|
225
|
+
return TreeChecker.new do
|
226
|
+
exclude_fvkcall :abort
|
227
|
+
exclude_fvkcall :exit, :exit!
|
228
|
+
exclude_fvkcall :system
|
229
|
+
exclude_eval
|
230
|
+
exclude_alias
|
231
|
+
exclude_global_vars
|
232
|
+
exclude_call_on File, FileUtils
|
233
|
+
exclude_class_tinkering
|
234
|
+
exclude_module_tinkering
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
protected
|
239
|
+
|
240
|
+
#--
|
241
|
+
# the methods used to define the checks
|
242
|
+
#++
|
243
|
+
|
244
|
+
#
|
245
|
+
# within the 'at_root' block, rules are added to the @root_checks, ie
|
246
|
+
# they are evaluated only for the toplevel (root) sexp.
|
247
|
+
#
|
248
|
+
def at_root (&block)
|
249
|
+
|
250
|
+
@current_checks = @root_checks
|
251
|
+
add_rules(&block)
|
252
|
+
@current_checks = @checks
|
253
|
+
end
|
254
|
+
|
255
|
+
#
|
256
|
+
# adds a rule that will forbid sexps that begin with the given head
|
257
|
+
#
|
258
|
+
# tc = TreeChecker.new do
|
259
|
+
# exclude_head [ :block ]
|
260
|
+
# end
|
261
|
+
#
|
262
|
+
# tc.check('a = 2') # ok
|
263
|
+
# tc.check('a = 2; b = 5') # will raise an error as it's a block
|
264
|
+
#
|
265
|
+
def exclude_head (head, message=nil)
|
266
|
+
|
267
|
+
@current_checks << [ :do_exclude_head, head, message ]
|
268
|
+
end
|
269
|
+
|
270
|
+
[
|
271
|
+
:exclude_symbol,
|
272
|
+
:exclude_fcall,
|
273
|
+
:exclude_vcall,
|
274
|
+
:exclude_fvcall,
|
275
|
+
:exclude_fvkcall,
|
276
|
+
:exclude_call_on,
|
277
|
+
:exclude_call_to
|
278
|
+
|
279
|
+
].each do |m|
|
280
|
+
class_eval <<-EOS
|
281
|
+
def #{m} (*args)
|
282
|
+
|
283
|
+
message = args.last.is_a?(String) ? args.pop : nil
|
284
|
+
|
285
|
+
args.each do |a|
|
286
|
+
|
287
|
+
a = [ Class, Module ].include?(a.class) ? \
|
288
|
+
parse(a.to_s) : a.to_sym
|
289
|
+
|
290
|
+
@current_checks << [ :do_#{m}, a, message ]
|
291
|
+
end
|
292
|
+
end
|
293
|
+
EOS
|
294
|
+
end
|
295
|
+
|
296
|
+
#
|
297
|
+
# bans method definitions
|
298
|
+
#
|
299
|
+
def exclude_def
|
300
|
+
|
301
|
+
@current_checks << [
|
302
|
+
:do_exclude_symbol, :defn, 'method definitions are forbidden' ]
|
303
|
+
end
|
304
|
+
|
305
|
+
#
|
306
|
+
# bans the defintion and the [re]openening of classes
|
307
|
+
#
|
308
|
+
# a list of exceptions (classes) can be passed. Subclassing those
|
309
|
+
# exceptions is permitted.
|
310
|
+
#
|
311
|
+
def exclude_class_tinkering (*exceptions)
|
312
|
+
|
313
|
+
#
|
314
|
+
# :class
|
315
|
+
|
316
|
+
@current_checks << [
|
317
|
+
:do_exclude_class_tinkering
|
318
|
+
] + exceptions.collect { |e| parse(e.to_s) }
|
319
|
+
|
320
|
+
#
|
321
|
+
# :sclass
|
322
|
+
|
323
|
+
@current_checks << [
|
324
|
+
:do_exclude_symbol,
|
325
|
+
:sclass,
|
326
|
+
'opening the metaclass of an instance is forbidde'
|
327
|
+
]
|
328
|
+
end
|
329
|
+
|
330
|
+
#
|
331
|
+
# bans the definition or the opening of modules
|
332
|
+
#
|
333
|
+
def exclude_module_tinkering
|
334
|
+
|
335
|
+
@current_checks << [
|
336
|
+
:do_exclude_symbol, :module, 'defining or opening a module is forbidden'
|
337
|
+
]
|
338
|
+
end
|
339
|
+
|
340
|
+
#
|
341
|
+
# bans referencing or setting the value of global variables
|
342
|
+
#
|
343
|
+
def exclude_global_vars
|
344
|
+
|
345
|
+
@current_checks << [
|
346
|
+
:do_exclude_symbol, :gvar, "global vars are forbidden" ]
|
347
|
+
@current_checks << [
|
348
|
+
:do_exclude_symbol, :gasgn, "global vars are forbidden" ]
|
349
|
+
end
|
350
|
+
|
351
|
+
#
|
352
|
+
# bans the usage of 'alias'
|
353
|
+
#
|
354
|
+
def exclude_alias
|
355
|
+
|
356
|
+
@current_checks << [
|
357
|
+
:do_exclude_symbol, :alias, "'alias' is forbidden" ]
|
358
|
+
@current_checks << [
|
359
|
+
:do_exclude_symbol, :alias_method, "'alias_method' is forbidden" ]
|
360
|
+
end
|
361
|
+
|
362
|
+
#
|
363
|
+
# bans the use of 'eval', 'module_eval' and 'instance_eval'
|
364
|
+
#
|
365
|
+
def exclude_eval
|
366
|
+
|
367
|
+
@current_checks << [
|
368
|
+
:do_exclude_fcall,
|
369
|
+
:eval,
|
370
|
+
"eval() is forbidden" ]
|
371
|
+
@current_checks << [
|
372
|
+
:do_exclude_call_to,
|
373
|
+
:instance_eval,
|
374
|
+
"instance_eval() is forbidden" ]
|
375
|
+
@current_checks << [
|
376
|
+
:do_exclude_call_to,
|
377
|
+
:module_eval,
|
378
|
+
"module_eval() is forbidden" ]
|
379
|
+
end
|
380
|
+
|
381
|
+
#
|
382
|
+
# bans the use of backquotes
|
383
|
+
#
|
384
|
+
def exclude_backquotes
|
385
|
+
@current_checks << [
|
386
|
+
:do_exclude_symbol, :xstr, "backquotes are forbidden" ]
|
387
|
+
end
|
388
|
+
|
389
|
+
#
|
390
|
+
# bans raise and throw
|
391
|
+
#
|
392
|
+
def exclude_raise
|
393
|
+
|
394
|
+
@current_checks << [ :do_exclude_fvkcall, :raise, "raise is forbidden" ]
|
395
|
+
@current_checks << [ :do_exclude_fvkcall, :throw, "throw is forbidden" ]
|
396
|
+
end
|
397
|
+
|
398
|
+
#
|
399
|
+
# the actual check method, check() is rather a bootstrap one...
|
400
|
+
#
|
401
|
+
def do_check (sexp)
|
402
|
+
|
403
|
+
@checks.each do |exclusion_method, *args|
|
404
|
+
send exclusion_method, sexp, args
|
405
|
+
end
|
406
|
+
|
407
|
+
return unless sexp.is_a?(Array) # check over, seems fine...
|
408
|
+
|
409
|
+
# check children
|
410
|
+
|
411
|
+
sexp.each { |c| do_check c }
|
412
|
+
end
|
413
|
+
|
414
|
+
#
|
415
|
+
# the methods that actually perform the checks
|
416
|
+
# (and potentially raise security exceptions)
|
417
|
+
|
418
|
+
#
|
419
|
+
# constructs a new set of arguments by inserting the newhead at the
|
420
|
+
# beginning of the arguments
|
421
|
+
#
|
422
|
+
def cons (newhead, args)
|
423
|
+
|
424
|
+
newhead = Array(newhead)
|
425
|
+
newhead << args[0]
|
426
|
+
|
427
|
+
[ newhead ] + (args[1, -1] || [])
|
428
|
+
end
|
429
|
+
|
430
|
+
def do_exclude_fcall (sexp, args)
|
431
|
+
|
432
|
+
do_exclude_head(sexp, cons(:fcall, args))
|
433
|
+
end
|
434
|
+
|
435
|
+
def do_exclude_vcall (sexp, args)
|
436
|
+
|
437
|
+
do_exclude_head(sexp, cons(:vcall, args))
|
438
|
+
end
|
439
|
+
|
440
|
+
#
|
441
|
+
# excludes :fcall and :vcall
|
442
|
+
#
|
443
|
+
def do_exclude_fvcall (sexp, args)
|
444
|
+
|
445
|
+
do_exclude_fcall(sexp, args)
|
446
|
+
do_exclude_vcall(sexp, args)
|
447
|
+
end
|
448
|
+
|
449
|
+
#
|
450
|
+
# excludes :fcall and :vcall and :call on Kernel
|
451
|
+
#
|
452
|
+
def do_exclude_fvkcall (sexp, args)
|
453
|
+
|
454
|
+
do_exclude_fvcall(sexp, args)
|
455
|
+
do_exclude_head(sexp, cons([ :call, [ :const, :Kernel ] ], args))
|
456
|
+
end
|
457
|
+
|
458
|
+
#
|
459
|
+
# raises a Rufus::SecurityError if the sexp is a reference to
|
460
|
+
# a certain symbol (like :gvar or :alias).
|
461
|
+
#
|
462
|
+
def do_exclude_symbol (sexp, args)
|
463
|
+
|
464
|
+
raise SecurityError.new(
|
465
|
+
args[1] || "symbol :#{excluded_symbol} is forbidden"
|
466
|
+
) if sexp == args[0]
|
467
|
+
end
|
468
|
+
|
469
|
+
#
|
470
|
+
# raises a security error if the sexp is a call on a given constant or
|
471
|
+
# module (class)
|
472
|
+
#
|
473
|
+
def do_exclude_call_on (sexp, args)
|
474
|
+
|
475
|
+
do_exclude_head(sexp, [ [:call, args[0]] ] + (args[1, -1] || []))
|
476
|
+
end
|
477
|
+
|
478
|
+
#
|
479
|
+
# raises a security error if a call to a given method of any instance
|
480
|
+
# is found
|
481
|
+
#
|
482
|
+
def do_exclude_call_to (sexp, args)
|
483
|
+
|
484
|
+
return unless sexp.is_a?(Array)
|
485
|
+
|
486
|
+
raise SecurityError.new(
|
487
|
+
args[1] || "calls to '#{args[0]}' are forbidden"
|
488
|
+
) if sexp[0] == :call and sexp[2] == args[0]
|
489
|
+
end
|
490
|
+
|
491
|
+
def do_exclude_head (sexp, args)
|
492
|
+
|
493
|
+
return unless sexp.is_a?(Array)
|
494
|
+
|
495
|
+
head = args[0]
|
496
|
+
|
497
|
+
raise SecurityError.new(
|
498
|
+
args[1] || "#{head.inspect} is forbidden"
|
499
|
+
) if sexp[0, head.length] == head
|
500
|
+
end
|
501
|
+
|
502
|
+
def do_exclude_class_tinkering (sexp, args)
|
503
|
+
|
504
|
+
return unless sexp.is_a?(Array) # lonely symbols are not class definitions
|
505
|
+
|
506
|
+
return unless sexp[0] == :class
|
507
|
+
|
508
|
+
raise SecurityError.new(
|
509
|
+
'defining or opening a class is forbidden'
|
510
|
+
) if args.length == 0 or ( ! args.include?(sexp[2]))
|
511
|
+
#
|
512
|
+
# raise error if there are no exceptions or
|
513
|
+
# if the parent class is not a member of the exception list
|
514
|
+
end
|
515
|
+
|
516
|
+
#
|
517
|
+
# a simple parse (relies on ruby_parser currently)
|
518
|
+
#
|
519
|
+
def parse (rubycode)
|
520
|
+
|
521
|
+
#(@parser ||= RubyParser.new).parse(rubycode).to_a
|
522
|
+
#
|
523
|
+
# parser goes ballistic after a while, seems having a new parser
|
524
|
+
# each is not heavy at all
|
525
|
+
|
526
|
+
RubyParser.new.parse(rubycode).to_a
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
data/test/ft_0_basic.rb
ADDED
@@ -0,0 +1,241 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# Testing rufus-treechecker
|
4
|
+
#
|
5
|
+
# jmettraux at gmail.org
|
6
|
+
#
|
7
|
+
# Fri Aug 29 10:13:33 JST 2008
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'testmixin'
|
11
|
+
|
12
|
+
|
13
|
+
class BasicTest < Test::Unit::TestCase
|
14
|
+
include TestMixin
|
15
|
+
|
16
|
+
|
17
|
+
def test_0
|
18
|
+
|
19
|
+
tc = Rufus::TreeChecker.new do
|
20
|
+
exclude_vcall :abort
|
21
|
+
exclude_fcall :abort
|
22
|
+
exclude_fvcall :exit, :exit!
|
23
|
+
exclude_call_to :exit
|
24
|
+
end
|
25
|
+
|
26
|
+
assert_nok(tc, 'exit')
|
27
|
+
assert_nok(tc, 'exit()')
|
28
|
+
assert_nok(tc, 'exit!')
|
29
|
+
assert_nok(tc, 'abort')
|
30
|
+
assert_nok(tc, 'abort()')
|
31
|
+
assert_nok(tc, 'Kernel.exit')
|
32
|
+
assert_nok(tc, 'Kernel.exit()')
|
33
|
+
assert_nok(tc, 'Kernel::exit')
|
34
|
+
assert_nok(tc, 'Kernel::exit()')
|
35
|
+
assert_nok(tc, '::Kernel.exit')
|
36
|
+
|
37
|
+
assert_ok(tc, '1 + 1')
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_0b_vm_exiting
|
41
|
+
|
42
|
+
# TODO : implement me !
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_1_global_vars
|
46
|
+
|
47
|
+
tc = Rufus::TreeChecker.new do
|
48
|
+
exclude_global_vars
|
49
|
+
end
|
50
|
+
|
51
|
+
assert_nok(tc, '$ENV')
|
52
|
+
assert_nok(tc, '$ENV = {}')
|
53
|
+
assert_nok(tc, "$ENV['HOME'] = 'away'")
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_2_aliases
|
57
|
+
|
58
|
+
tc = Rufus::TreeChecker.new do
|
59
|
+
exclude_alias
|
60
|
+
end
|
61
|
+
|
62
|
+
assert_nok(tc, 'alias :a :b')
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_3_exclude_calls_on
|
66
|
+
|
67
|
+
tc = Rufus::TreeChecker.new do
|
68
|
+
exclude_call_on File, FileUtils
|
69
|
+
exclude_call_on IO
|
70
|
+
end
|
71
|
+
|
72
|
+
assert_nok(tc, 'data = File.read("surf.txt")')
|
73
|
+
assert_nok(tc, 'f = File.new("surf.txt")')
|
74
|
+
assert_nok(tc, 'FileUtils.rm_f("bondzoi.txt")')
|
75
|
+
assert_nok(tc, 'IO.foreach("testfile") {|x| print "GOT ", x }')
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_4_exclude_def
|
79
|
+
|
80
|
+
tc = Rufus::TreeChecker.new do
|
81
|
+
exclude_def
|
82
|
+
end
|
83
|
+
|
84
|
+
assert_nok(tc, 'def drink; "water"; end')
|
85
|
+
assert_nok(tc, 'class Toto; def drink; "water"; end; end')
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_5_exclude_class_tinkering
|
89
|
+
|
90
|
+
tc = Rufus::TreeChecker.new do
|
91
|
+
exclude_class_tinkering
|
92
|
+
end
|
93
|
+
|
94
|
+
assert_nok(tc, 'class << instance; def length; 3; end; end')
|
95
|
+
assert_nok(tc, 'class Toto; end')
|
96
|
+
assert_nok(tc, 'class Alpha::Toto; end')
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_5b_exclude_class_tinkering_with_exceptions
|
100
|
+
|
101
|
+
tc = Rufus::TreeChecker.new do
|
102
|
+
exclude_class_tinkering String, Rufus::TreeChecker
|
103
|
+
end
|
104
|
+
|
105
|
+
assert_nok(tc, 'class String; def length; 3; end; end')
|
106
|
+
|
107
|
+
assert_ok(tc, 'class S2 < String; def length; 3; end; end')
|
108
|
+
assert_ok(tc, 'class Toto < Rufus::TreeChecker; def length; 3; end; end')
|
109
|
+
|
110
|
+
assert_nok(tc, 'class Toto; end')
|
111
|
+
assert_nok(tc, 'class Alpha::Toto; end')
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_5c_exclude_class_tinkering_with_exceptions
|
115
|
+
|
116
|
+
tc = Rufus::TreeChecker.new do
|
117
|
+
exclude_class_tinkering 'String', 'Rufus::TreeChecker'
|
118
|
+
end
|
119
|
+
|
120
|
+
assert_nok(tc, 'class String; def length; 3; end; end')
|
121
|
+
|
122
|
+
assert_ok(tc, 'class S2 < String; def length; 3; end; end')
|
123
|
+
assert_ok(tc, 'class Toto < Rufus::TreeChecker; def length; 3; end; end')
|
124
|
+
|
125
|
+
assert_nok(tc, 'class Toto; end')
|
126
|
+
assert_nok(tc, 'class Alpha::Toto; end')
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_6_exclude_module_tinkering
|
130
|
+
|
131
|
+
tc = Rufus::TreeChecker.new do
|
132
|
+
exclude_module_tinkering
|
133
|
+
end
|
134
|
+
|
135
|
+
assert_nok(tc, 'module Alpha; end')
|
136
|
+
assert_nok(tc, 'module Momo::Alpha; end')
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_7_exclude_eval
|
140
|
+
|
141
|
+
tc = Rufus::TreeChecker.new do
|
142
|
+
exclude_eval
|
143
|
+
end
|
144
|
+
|
145
|
+
assert_nok(tc, 'eval("code")')
|
146
|
+
assert_nok(tc, 'toto.instance_eval("code")')
|
147
|
+
assert_nok(tc, 'Toto.module_eval("code")')
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_8_exclude_backquotes
|
151
|
+
|
152
|
+
tc = Rufus::TreeChecker.new do
|
153
|
+
exclude_backquotes
|
154
|
+
end
|
155
|
+
|
156
|
+
assert_nok(tc, '`kill -9 whatever`')
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_9_exclude_raise_and_throw
|
160
|
+
|
161
|
+
tc = Rufus::TreeChecker.new do
|
162
|
+
exclude_raise
|
163
|
+
end
|
164
|
+
|
165
|
+
assert_nok(tc, 'raise')
|
166
|
+
assert_nok(tc, 'raise "error"')
|
167
|
+
assert_nok(tc, 'Kernel.raise')
|
168
|
+
assert_nok(tc, 'Kernel.raise "error"')
|
169
|
+
assert_ok(tc, 'Kernel.puts "error"')
|
170
|
+
assert_nok(tc, 'throw')
|
171
|
+
assert_nok(tc, 'throw :halt')
|
172
|
+
end
|
173
|
+
|
174
|
+
def test_10_exclude_public
|
175
|
+
|
176
|
+
tc = Rufus::TreeChecker.new do
|
177
|
+
exclude_fvcall :public
|
178
|
+
exclude_fvcall :protected
|
179
|
+
exclude_fvcall :private
|
180
|
+
end
|
181
|
+
|
182
|
+
assert_nok(tc, 'public')
|
183
|
+
assert_nok(tc, 'public :surf')
|
184
|
+
assert_nok(tc, 'class Toto; public :car; end')
|
185
|
+
assert_nok(tc, 'private')
|
186
|
+
assert_nok(tc, 'private :surf')
|
187
|
+
assert_nok(tc, 'class Toto; private :car; end')
|
188
|
+
end
|
189
|
+
|
190
|
+
def test_11_is_not
|
191
|
+
|
192
|
+
tc = Rufus::TreeChecker.new do
|
193
|
+
exclude_head [ :block ]
|
194
|
+
exclude_head [ :lasgn ]
|
195
|
+
exclude_head [ :dasgn_curr ]
|
196
|
+
end
|
197
|
+
|
198
|
+
assert_nok(tc, 'a; b; c')
|
199
|
+
assert_nok(tc, 'lambda { a; b; c }')
|
200
|
+
|
201
|
+
assert_nok(tc, 'a = 2')
|
202
|
+
assert_nok(tc, 'lambda { a = 2 }')
|
203
|
+
end
|
204
|
+
|
205
|
+
def test_12_at_root
|
206
|
+
|
207
|
+
tc = Rufus::TreeChecker.new do
|
208
|
+
at_root do
|
209
|
+
exclude_head [ :block ]
|
210
|
+
exclude_head [ :lasgn ]
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
assert_nok(tc, 'a; b; c')
|
215
|
+
assert_ok(tc, 'lambda { a; b; c }')
|
216
|
+
|
217
|
+
assert_nok(tc, 'a = 2')
|
218
|
+
assert_ok(tc, 'lambda { a = 2 }')
|
219
|
+
end
|
220
|
+
|
221
|
+
def test_12_freeze
|
222
|
+
|
223
|
+
# TODO : are there some rules with composite values ?
|
224
|
+
# can't remember of any :(
|
225
|
+
end
|
226
|
+
|
227
|
+
#def test_X
|
228
|
+
# tc = Rufus::TreeChecker.new do
|
229
|
+
# end
|
230
|
+
# #tc.ptree 'load "surf"'
|
231
|
+
# tc.ptree 'class Toto; load "nada"; end'
|
232
|
+
# tc.ptree 'class Toto; def m; load "nada"; end; end'
|
233
|
+
# tc.ptree 'class << toto; def m; load "nada"; end; end'
|
234
|
+
# #tc.ptree 'lambda { a; b; c }'
|
235
|
+
# #tc.ptree 'lambda { a = c }'
|
236
|
+
# #tc.ptree 'c = 0; a = c'
|
237
|
+
# #tc.ptree 'c = a = 0'
|
238
|
+
# tc.ptree 'a = 5 + 6; puts a'
|
239
|
+
#end
|
240
|
+
end
|
241
|
+
|
@@ -0,0 +1,72 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# Testing rufus-treechecker
|
4
|
+
#
|
5
|
+
# jmettraux at gmail.org
|
6
|
+
#
|
7
|
+
# Fri Aug 29 10:13:33 JST 2008
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'testmixin'
|
11
|
+
|
12
|
+
module Testy
|
13
|
+
class Tasty
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class OldTreeCheckerTest < Test::Unit::TestCase
|
18
|
+
include TestMixin
|
19
|
+
|
20
|
+
|
21
|
+
def test_0
|
22
|
+
|
23
|
+
tc = Rufus::TreeChecker.new do
|
24
|
+
exclude_fvkcall :abort
|
25
|
+
exclude_fvkcall :exit, :exit!
|
26
|
+
exclude_fvkcall :system
|
27
|
+
exclude_eval
|
28
|
+
exclude_alias
|
29
|
+
exclude_global_vars
|
30
|
+
exclude_call_on File, FileUtils
|
31
|
+
exclude_class_tinkering Testy::Tasty
|
32
|
+
exclude_module_tinkering
|
33
|
+
|
34
|
+
exclude_fvcall :public
|
35
|
+
exclude_fvcall :protected
|
36
|
+
exclude_fvcall :private
|
37
|
+
exclude_fcall :load
|
38
|
+
exclude_fcall :require
|
39
|
+
end
|
40
|
+
|
41
|
+
assert_nocompile tc, "def surf }"
|
42
|
+
|
43
|
+
assert_ok tc, "puts 'toto'"
|
44
|
+
|
45
|
+
assert_nok tc, "exit"
|
46
|
+
assert_nok tc, "puts $BATEAU"
|
47
|
+
assert_nok tc, "abort"
|
48
|
+
assert_nok tc, "abort; puts 'ok'"
|
49
|
+
assert_nok tc, "puts 'ok'; abort"
|
50
|
+
|
51
|
+
assert_nok tc, "exit 0"
|
52
|
+
assert_nok tc, "system('whatever')"
|
53
|
+
|
54
|
+
assert_nok tc, "alias :a :b"
|
55
|
+
assert_nok tc, "alias_method :a, :b"
|
56
|
+
|
57
|
+
assert_nok tc, "File.open('x')"
|
58
|
+
assert_nok tc, "FileUtils.rm('x')"
|
59
|
+
|
60
|
+
assert_nok tc, "eval 'nada'"
|
61
|
+
assert_nok tc, "M.module_eval 'nada'"
|
62
|
+
assert_nok tc, "o.instance_eval 'nada'"
|
63
|
+
|
64
|
+
assert_ok tc, "puts 'toto'"
|
65
|
+
|
66
|
+
assert_ok tc, "class Toto < Testy::Tasty\nend"
|
67
|
+
assert_nok tc, "class String\nend"
|
68
|
+
assert_nok tc, "module Whatever\nend"
|
69
|
+
assert_nok tc, "class << e\nend"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
data/test/test.rb
ADDED
data/test/testmixin.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# Testing rufus-treechecker
|
4
|
+
#
|
5
|
+
# jmettraux at gmail.org
|
6
|
+
#
|
7
|
+
# Fri Aug 29 18:30:03 JST 2008
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'test/unit'
|
11
|
+
require 'rubygems'
|
12
|
+
require 'rufus/treechecker'
|
13
|
+
|
14
|
+
|
15
|
+
module TestMixin
|
16
|
+
|
17
|
+
def assert_ok (tc, rubycode)
|
18
|
+
tc.check(rubycode)
|
19
|
+
end
|
20
|
+
def assert_nok (tc, rubycode)
|
21
|
+
assert_raise Rufus::SecurityError do
|
22
|
+
tc.check(rubycode)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
def assert_nocompile (tc, rubycode)
|
26
|
+
assert_raise Racc::ParseError do
|
27
|
+
tc.check(rubycode)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rufus-treechecker
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "1.0"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Mettraux
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-09-01 00:00:00 +09:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rogue_parser
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description:
|
26
|
+
email: john at openwfe dot org
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.txt
|
33
|
+
files:
|
34
|
+
- lib/rufus
|
35
|
+
- lib/rufus/treechecker.rb
|
36
|
+
- test/ft_0_basic.rb
|
37
|
+
- test/ft_1_old_treechecker.rb
|
38
|
+
- test/test.rb
|
39
|
+
- test/testmixin.rb
|
40
|
+
- README.txt
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://rufus.rubyforge.org/rufus-treechecker
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
requirements:
|
61
|
+
- rogue_parser
|
62
|
+
rubyforge_project: rufus
|
63
|
+
rubygems_version: 1.2.0
|
64
|
+
signing_key:
|
65
|
+
specification_version: 2
|
66
|
+
summary: checking ruby code before eval()
|
67
|
+
test_files:
|
68
|
+
- test/test.rb
|