rufus-treechecker 1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|