/
policy.rb
358 lines (295 loc) · 6.93 KB
/
policy.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
require 'set'
module RubyCop
# Visitor class for Ruby::Node subclasses. Determines whether the node is
# safe according to our rules.
class Policy
def initialize
@const_list = GrayList.new
initialize_const_blacklist
end
def inspect
'#<%s:0x%x>' % [self.class.name, object_id]
end
def blacklist_const(const)
@const_list.blacklist(const)
end
def const_allowed?(const)
@const_list.allow?(const)
end
def whitelist_const(const)
@const_list.whitelist(const)
end
def visit(node)
klass = node.class.ancestors.detect do |ancestor|
respond_to?("visit_#{ancestor.name.split('::').last}")
end
if klass
send("visit_#{klass.name.split('::').last}", node)
else
warn "unhandled node type: #{node.inspect}:#{node.class.name}"
true
end
end
def visit_Alias(node)
false # never allowed
end
def visit_Args(node)
node.elements.all? { |e| visit(e) }
end
def visit_Array(node)
node.elements.all? { |e| visit(e) }
end
def visit_Assoc(node)
visit(node.key) && visit(node.value)
end
def visit_Binary(node)
visit(node.lvalue) && visit(node.rvalue)
end
def visit_Block(node)
(node.params.nil? || visit(node.params)) && node.elements.all? { |e| visit(e) }
end
CALL_BLACKLIST = %w[
__send__
abort
alias_method
at_exit
autoload
binding
callcc
caller
class_eval
const_get
const_set
dup
eval
exec
exit
fail
fork
gets
global_variables
instance_eval
load
loop
method
module_eval
open
public_send
readline
readlines
redo
remove_const
require
retry
send
set_trace_func
sleep
spawn
srand
syscall
system
trap
undef
__callee__
__method__
].to_set.freeze
def visit_Call(node)
!CALL_BLACKLIST.include?(node.identifier.token.to_s) && [node.target, node.arguments, node.block].compact.all? { |e| visit(e) }
end
def visit_Case(node)
visit(node.expression) && visit(node.block)
end
def visit_ChainedBlock(node)
node.elements.all? { |e| visit(e) } && node.blocks.all? { |e| visit(e) } && (node.params.nil? || visit(node.params))
end
def visit_Class(node)
visit(node.const) && (node.superclass.nil? || visit(node.superclass)) && visit(node.body)
end
def visit_ClassVariable(node)
false # never allowed
end
def visit_ClassVariableAssignment(node)
false # never allowed
end
def visit_Char(node)
true
end
def visit_Constant(node)
const_allowed?(node.token)
end
def visit_ConstantAssignment(node)
visit(node.lvalue) && visit(node.rvalue)
end
def visit_Defined(node)
false # never allowed (though it's probably safe)
end
def visit_Else(node)
node.elements.all? { |e| visit(e) }
end
def visit_ExecutableString(node)
false # never allowed
end
def visit_Float(node)
true
end
def visit_For(node)
visit(node.variable) && visit(node.range) && visit(node.statements)
end
def visit_GlobalVariable(node)
false # never allowed
end
def visit_GlobalVariableAssignment(node)
false # never allowed
end
def visit_Hash(node)
node.assocs.nil? || node.assocs.all? { |e| visit(e) }
end
def visit_Identifier(node)
!CALL_BLACKLIST.include?(node.token)
end
def visit_If(node)
visit(node.expression) && node.elements.all? { |e| visit(e) } && node.blocks.all? { |e| visit(e) }
end
alias_method :visit_Unless, :visit_If
def visit_IfMod(node)
visit(node.expression) && node.elements.all? { |e| visit(e) }
end
alias_method :visit_UnlessMod, :visit_IfMod
def visit_IfOp(node)
visit(node.condition) && visit(node.then_part) && visit(node.else_part)
end
def visit_InstanceVariable(node)
true
end
def visit_InstanceVariableAssignment(node)
visit(node.rvalue)
end
def visit_Integer(node)
true
end
KEYWORD_WHITELIST = %w[
false
nil
self
true
].to_set.freeze
def visit_Keyword(node)
KEYWORD_WHITELIST.include?(node.token)
end
def visit_Label(node)
true
end
def visit_LocalVariableAssignment(node)
visit(node.rvalue)
end
def visit_Method(node)
[node.target, node.params, node.body].compact.all? { |e| visit(e) }
end
def visit_Module(node)
visit(node.const) && visit(node.body)
end
def visit_MultiAssignment(node)
visit(node.lvalue) && visit(node.rvalue)
end
def visit_MultiAssignmentList(node)
node.elements.all? { |e| visit(e) }
end
def visit_Params(node)
node.elements.all? { |e| visit(e) }
end
def visit_Program(node)
node.elements.all? { |e| visit(e) }
end
def visit_Range(node)
visit(node.min) && visit(node.max)
end
def visit_RescueMod(node)
node.elements.all? { |e| visit(e) } && visit(node.expression)
end
def visit_RescueParams(node)
node.elements.all? { |e| visit(e) }
end
def visit_SingletonClass(node)
visit(node.superclass) && visit(node.body)
end
def visit_SplatArg(node)
visit(node.arg)
end
def visit_Statements(node)
node.elements.all? { |e| visit(e) }
end
def visit_String(node)
# embedded strings can have statements in them, so check those
node.elements.reject { |e| e.is_a?(::String) }.all? { |e| visit(e) }
end
def visit_StringConcat(node)
node.elements.all? { |e| visit(e) }
end
def visit_Symbol(node)
true
end
def visit_Unary(node)
visit(node.operand)
end
def visit_Until(node)
false # never allowed
end
alias_method :visit_UntilMod, :visit_Until
def visit_When(node)
visit(node.expression) && node.elements.all? { |e| visit(e) }
end
def visit_While(node)
false # never allowed
end
alias_method :visit_WhileMod, :visit_While
private
CONST_BLACKLIST = %w[
ARGF
ARGV
Array
Base64
Class
Dir
ENV
Enumerable
Error
Exception
Fiber
File
FileUtils
GC
Gem
Hash
IO
IRB
Kernel
Module
Net
Object
ObjectSpace
OpenSSL
OpenURI
PLATFORM
Proc
Process
RUBY_COPYRIGHT
RUBY_DESCRIPTION
RUBY_ENGINE
RUBY_PATCHLEVEL
RUBY_PLATFORM
RUBY_RELEASE_DATE
RUBY_VERSION
Rails
STDERR
STDIN
STDOUT
String
TOPLEVEL_BINDING
Thread
VERSION
].freeze
def initialize_const_blacklist
CONST_BLACKLIST.each { |const| blacklist_const(const) }
end
end
end