shikashi-the-north 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ =begin
2
+
3
+ This file is part of the shikashi project, http://github.com/tario/shikashi
4
+
5
+ Copyright (c) 2009-2010 Roberto Dario Seminara <robertodarioseminara@gmail.com>
6
+
7
+ shikashi is free software: you can redistribute it and/or modify
8
+ it under the terms of the gnu general public license as published by
9
+ the free software foundation, either version 3 of the license, or
10
+ (at your option) any later version.
11
+
12
+ shikashi is distributed in the hope that it will be useful,
13
+ but without any warranty; without even the implied warranty of
14
+ merchantability or fitness for a particular purpose. see the
15
+ gnu general public license for more details.
16
+
17
+ you should have received a copy of the gnu general public license
18
+ along with shikashi. if not, see <http://www.gnu.org/licenses/>.
19
+
20
+ =end
21
+ module Shikashi
22
+ class Privileges
23
+ #Defines the permissions needed to declare classes within the sandbox
24
+ def allow_class_definitions
25
+ instances_of(Class).allow nil, :inherited, :method_added
26
+ allow_method "core#define_method".to_sym
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ =begin
2
+
3
+ This file is part of the shikashi project, http://github.com/tario/shikashi
4
+
5
+ Copyright (c) 2009-2010 Roberto Dario Seminara <robertodarioseminara@gmail.com>
6
+
7
+ shikashi is free software: you can redistribute it and/or modify
8
+ it under the terms of the gnu general public license as published by
9
+ the free software foundation, either version 3 of the license, or
10
+ (at your option) any later version.
11
+
12
+ shikashi is distributed in the hope that it will be useful,
13
+ but without any warranty; without even the implied warranty of
14
+ merchantability or fitness for a particular purpose. see the
15
+ gnu general public license for more details.
16
+
17
+ you should have received a copy of the gnu general public license
18
+ along with shikashi. if not, see <http://www.gnu.org/licenses/>.
19
+
20
+ =end
21
+ module Shikashi
22
+ class Privileges
23
+ #Define the permissions needed to raise exceptions within the sandbox
24
+ def allow_exceptions
25
+ allow_method :raise
26
+ methods_of(Exception).allow :backtrace, :set_backtrace, :exception
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ =begin
2
+
3
+ This file is part of the shikashi project, http://github.com/tario/shikashi
4
+
5
+ Copyright (c) 2009-2010 Roberto Dario Seminara <robertodarioseminara@gmail.com>
6
+
7
+ shikashi is free software: you can redistribute it and/or modify
8
+ it under the terms of the gnu general public license as published by
9
+ the free software foundation, either version 3 of the license, or
10
+ (at your option) any later version.
11
+
12
+ shikashi is distributed in the hope that it will be useful,
13
+ but without any warranty; without even the implied warranty of
14
+ merchantability or fitness for a particular purpose. see the
15
+ gnu general public license for more details.
16
+
17
+ you should have received a copy of the gnu general public license
18
+ along with shikashi. if not, see <http://www.gnu.org/licenses/>.
19
+
20
+ =end
21
+ module Shikashi
22
+ class Privileges
23
+ #Define the permissions needed to define singleton methods within the sandbox
24
+ def allow_singleton_methods
25
+ allow_method :singleton_method_added
26
+ allow_method "core#define_singleton_method".to_sym
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,530 @@
1
+ =begin
2
+
3
+ This file is part of the shikashi project, http://github.com/tario/shikashi
4
+
5
+ Copyright (c) 2009-2010 Roberto Dario Seminara <robertodarioseminara@gmail.com>
6
+
7
+ shikashi is free software: you can redistribute it and/or modify
8
+ it under the terms of the gnu general public license as published by
9
+ the free software foundation, either version 3 of the license, or
10
+ (at your option) any later version.
11
+
12
+ shikashi is distributed in the hope that it will be useful,
13
+ but without any warranty; without even the implied warranty of
14
+ merchantability or fitness for a particular purpose. see the
15
+ gnu general public license for more details.
16
+
17
+ you should have received a copy of the gnu general public license
18
+ along with shikashi. if not, see <http://www.gnu.org/licenses/>.
19
+
20
+ =end
21
+ require "rubygems"
22
+ require "evalhook"
23
+ require "shikashi/privileges"
24
+ require "shikashi/pick_argument"
25
+ require "getsource"
26
+ require "timeout"
27
+
28
+ module Shikashi
29
+
30
+ class << self
31
+ attr_accessor :global_binding
32
+ end
33
+
34
+ module Timeout
35
+
36
+
37
+ #raised when reach the timeout in a script execution restricted by timeout (see Sandbox#run)
38
+ class Error < Exception
39
+
40
+ end
41
+ end
42
+
43
+ #The sandbox class run the sandbox, because of internal behaviour only can be use one instance
44
+ #of sandbox by thread (each different thread may have its own sandbox running in the same time)
45
+ #
46
+ #= Example
47
+ #
48
+ # require "rubygems"
49
+ # require "shikashi"
50
+ #
51
+ # include Shikashi
52
+ #
53
+ # s = Sandbox.new
54
+ # priv = Privileges.new
55
+ # priv.allow_method :print
56
+ #
57
+ # s.run(priv, 'print "hello world\n"')
58
+ #
59
+ class Sandbox
60
+
61
+ #array of privileges of restricted code within sandbox
62
+ #
63
+ #Example
64
+ # sandbox.privileges[source].allow_method :raise
65
+ #
66
+ attr_reader :privileges
67
+ #Binding of execution, the default is a binding in a global context allowing the definition of module of classes
68
+ attr_reader :chain
69
+
70
+ attr_reader :hook_handler
71
+
72
+ #
73
+ # Same as Sandbox.new.run
74
+ #
75
+
76
+ def self.run(*args)
77
+ Sandbox.new.run(Shikashi.global_binding, *args)
78
+ end
79
+ #
80
+ # Generate a random source file name for the sandbox, used internally
81
+ #
82
+
83
+ def generate_id
84
+ "sandbox-#{rand(1000000)}"
85
+ end
86
+
87
+ def initialize
88
+ @privileges = Hash.new
89
+ @chain = Hash.new
90
+ @hook_handler_list = Array.new
91
+ @hook_handler = instantiate_evalhook_handler
92
+ @hook_handler.sandbox = self
93
+ @base_namespace = create_adhoc_base_namespace
94
+ @hook_handler.base_namespace = @base_namespace
95
+ end
96
+
97
+ # add a chain of sources, used internally
98
+ def add_source_chain(outer, inner)
99
+ @chain[inner] = outer
100
+ end
101
+
102
+ def base_namespace
103
+ @base_namespace
104
+ end
105
+
106
+ class Packet
107
+ def initialize(evalhook_packet, default_privileges, source) #:nodoc:
108
+ @evalhook_packet = evalhook_packet
109
+ @default_privileges = default_privileges
110
+ @source = source
111
+ end
112
+
113
+ #Run the code in the package
114
+ #
115
+ #call-seq: run(arguments)
116
+ #
117
+ #Arguments
118
+ #
119
+ # :binding Optional argument with the binding object of the context where the code is to be executed
120
+ # The default is a binding in the global context
121
+ # :timeout Optional argument to restrict the execution time of the script to a given value in seconds
122
+ def run(*args)
123
+ t = args.pick(:timeout) do nil end
124
+ binding_ = args.pick(Binding,:binding) do
125
+ nil
126
+ end
127
+
128
+ begin
129
+ timeout t do
130
+ @evalhook_packet.run(binding_, @source, 0)
131
+ end
132
+ rescue ::Timeout::Error
133
+ raise Shikashi::Timeout::Error
134
+ end
135
+ end
136
+
137
+ # Dispose the objects associated with this code package
138
+ def dispose
139
+ @evalhook_packet.dispose
140
+ end
141
+ end
142
+
143
+ class EvalhookHandler < EvalHook::HookHandler
144
+ attr_accessor :sandbox
145
+
146
+ def handle_xstr( str )
147
+ source = get_caller
148
+
149
+ privileges = sandbox.privileges[source]
150
+ if privileges
151
+ unless privileges.xstr_allowed?
152
+ raise SecurityError, "fobidden shell commands"
153
+ end
154
+ end
155
+
156
+ `#{str}`
157
+ end
158
+
159
+ def handle_gasgn( global_id, value )
160
+ source = get_caller
161
+
162
+ privileges = sandbox.privileges[source]
163
+ if privileges
164
+ unless privileges.global_write_allowed? global_id or
165
+ value.instance_of? String or
166
+ value.instance_of? Fixnum or
167
+ value.instance_of? Numeric or
168
+ value.instance_of? Float
169
+ raise SecurityError.new("Cannot assign global variable #{global_id}")
170
+ end
171
+ end
172
+
173
+ nil
174
+ end
175
+
176
+ def handle_gvar(global_id)
177
+ source = get_caller
178
+ privileges = sandbox.privileges[source]
179
+ if privileges
180
+ unless privileges.global_read_allowed? global_id
181
+ raise SecurityError, "cannot access global variable #{global_id}"
182
+ end
183
+ end
184
+
185
+ nil
186
+ end
187
+
188
+ def handle_const(name)
189
+ source = get_caller
190
+ privileges = sandbox.privileges[source]
191
+ if privileges
192
+ constants = sandbox.base_namespace.constants
193
+ unless constants.include? name or constants.include? name.to_sym
194
+ unless privileges.const_read_allowed? name.to_s
195
+ raise SecurityError, "cannot access constant #{name}"
196
+ end
197
+ end
198
+ end
199
+
200
+ const_value(sandbox.base_namespace.const_get(name))
201
+ end
202
+
203
+ def handle_cdecl(klass, const_id, value)
204
+ source = get_caller
205
+
206
+ privileges = sandbox.privileges[source]
207
+ if privileges
208
+ unless privileges.const_write_allowed? "#{klass}::#{const_id}"
209
+ if (klass == Object)
210
+
211
+ unless privileges.const_write_allowed? const_id.to_s or
212
+ value.instance_of? String or
213
+ value.instance_of? Fixnum or
214
+ value.instance_of? Numeric or
215
+ value.instance_of? Float
216
+
217
+ raise SecurityError.new("Cannot assign const #{const_id}")
218
+ end
219
+ else
220
+ raise SecurityError.new("Cannot assign const #{klass}::#{const_id}")
221
+ end
222
+ end
223
+ end
224
+
225
+ nil
226
+ end
227
+
228
+ def handle_method(klass, recv, method_name)
229
+ source = nil
230
+
231
+ method_id = 0
232
+
233
+ if method_name
234
+
235
+ source = self.get_caller
236
+ m = begin
237
+ klass.instance_method(method_name)
238
+ rescue
239
+ method_name = :method_missing
240
+ klass.instance_method(:method_missing)
241
+ end
242
+ dest_source = m.body.file
243
+
244
+ privileges = nil
245
+ if source != dest_source then
246
+ privileges = sandbox.privileges[source]
247
+
248
+ unless privileges then
249
+ raise SecurityError.new("Cannot invoke method #{method_name} on object of class #{klass}")
250
+ else
251
+ # privileges = privileges.dup
252
+ loop_source = source
253
+ loop_privileges = privileges
254
+
255
+ while loop_privileges and loop_source != dest_source
256
+ unless loop_privileges.allow?(klass,recv,method_name,method_id)
257
+ raise SecurityError.new("Cannot invoke method #{method_name} on object of class #{klass}")
258
+ end
259
+
260
+ loop_privileges = nil
261
+ loop_source = sandbox.chain[loop_source]
262
+
263
+ if dest_source then
264
+ loop_privileges = sandbox.privileges[loop_source]
265
+ else
266
+ loop_privileges = nil
267
+ end
268
+
269
+ end
270
+ end
271
+ end
272
+
273
+ return nil if method_name == :instance_eval
274
+ return nil if method_name == :binding
275
+
276
+ nil
277
+
278
+ end
279
+
280
+
281
+ end # if
282
+
283
+ def get_caller
284
+ caller_locations[2].path
285
+ end
286
+ end # Class
287
+
288
+ #Run the code in sandbox with the given privileges
289
+ # (see examples)
290
+ #
291
+ # Arguments
292
+ #
293
+ # :code Mandatory argument of class String with the code to execute restricted in the sandbox
294
+ # :privileges Optional argument of class Shikashi::Sandbox::Privileges to indicate the restrictions of the
295
+ # code executed in the sandbox. The default is an empty Privileges (absolutly no permission)
296
+ # Must be of class Privileges or passed as hash_key (:privileges => privileges)
297
+ # :binding Optional argument with the binding object of the context where the code is to be executed
298
+ # The default is a binding in the global context
299
+ # :source Optional argument to indicate the "source name", (visible in the backtraces). Only can
300
+ # be specified as hash parameter
301
+ # :timeout Optional argument to restrict the execution time of the script to a given value in seconds
302
+ # (accepts integer and decimal values), when timeout hits Shikashi::Timeout::Error is raised
303
+ # :base_namespace Alternate module to contain all classes and constants defined by the unprivileged code
304
+ # if not specified, by default, the base_namespace is created with the sandbox itself
305
+ # :no_base_namespace Specify to do not use a base_namespace (default false, not recommended to change)
306
+ # :encoding Specify the encoding of source (example: "utf-8"), the encoding also can be
307
+ # specified on header like a ruby normal source file
308
+ #
309
+ #The arguments can be passed in any order and using hash notation or not, examples:
310
+ #
311
+ # sandbox.run code, privileges
312
+ # sandbox.run code, :privileges => privileges
313
+ # sandbox.run :code => code, :privileges => privileges
314
+ # sandbox.run code, privileges, binding
315
+ # sandbox.run binding, code, privileges
316
+ # #etc
317
+ # sandbox.run binding, code, privileges, :source => source
318
+ # sandbox.run binding, :code => code, :privileges => privileges, :source => source
319
+ #
320
+ #Example:
321
+ #
322
+ # require "rubygems"
323
+ # require "shikashi"
324
+ #
325
+ # include Shikashi
326
+ #
327
+ # sandbox = Sandbox.new
328
+ # privileges = Privileges.new
329
+ # privileges.allow_method :print
330
+ # sandbox.run('print "hello world\n"', :privileges => privileges)
331
+ #
332
+ #Example 2:
333
+ # require "rubygems"
334
+ # require "shikashi"
335
+ #
336
+ # include Shikashi
337
+ #
338
+ # sandbox = Sandbox.new
339
+ # privileges = Privileges.new
340
+ # privileges.allow_method :print
341
+ # privileges.allow_method :singleton_method_added
342
+ #
343
+ # sandbox.run('
344
+ # def self.foo
345
+ # print "hello world\n"
346
+ # end
347
+ # ', :privileges => privileges)
348
+ #
349
+ #
350
+ def run(*args)
351
+ run_i(*args)
352
+ end
353
+
354
+ #Creates a packet of code with the given privileges to execute later as many times as neccessary
355
+ #
356
+ # (see examples)
357
+ #
358
+ # Arguments
359
+ #
360
+ # :code Mandatory argument of class String with the code to execute restricted in the sandbox
361
+ # :privileges Optional argument of class Shikashi::Sandbox::Privileges to indicate the restrictions of the
362
+ # code executed in the sandbox. The default is an empty Privileges (absolutly no permission)
363
+ # Must be of class Privileges or passed as hash_key (:privileges => privileges)
364
+ # :source Optional argument to indicate the "source name", (visible in the backtraces). Only can
365
+ # be specified as hash parameter
366
+ # :base_namespace Alternate module to contain all classes and constants defined by the unprivileged code
367
+ # if not specified, by default, the base_namespace is created with the sandbox itself
368
+ # :no_base_namespace Specify to do not use a base_namespace (default false, not recommended to change)
369
+ # :encoding Specify the encoding of source (example: "utf-8"), the encoding also can be
370
+ # specified on header like a ruby normal source file
371
+ #
372
+ # NOTE: arguments are the same as for Sandbox#run method, except for timeout and binding which can be
373
+ # used when calling Shikashi::Sandbox::Packet#run
374
+ #
375
+ #Example:
376
+ #
377
+ # require "rubygems"
378
+ # require "shikashi"
379
+ #
380
+ # include Shikashi
381
+ #
382
+ # sandbox = Sandbox.new
383
+ #
384
+ # privileges = Privileges.allow_method(:print)
385
+ #
386
+ # # this is equivallent to sandbox.run('print "hello world\n"')
387
+ # packet = sandbox.packet('print "hello world\n"', privileges)
388
+ # packet.run
389
+ #
390
+ def packet(*args)
391
+ code = args.pick(String,:code)
392
+ base_namespace = args.pick(:base_namespace) do nil end
393
+ no_base_namespace = args.pick(:no_base_namespace) do @no_base_namespace end
394
+ privileges_ = args.pick(Privileges,:privileges) do Privileges.new end
395
+ encoding = get_source_encoding(code) || args.pick(:encoding) do nil end
396
+
397
+ hook_handler = nil
398
+
399
+ if base_namespace
400
+ hook_handler = instantiate_evalhook_handler
401
+ hook_handler.base_namespace = base_namespace
402
+ hook_handler.sandbox = self
403
+ else
404
+ hook_handler = @hook_handler
405
+ base_namespace = hook_handler.base_namespace
406
+ end
407
+ source = args.pick(:source) do generate_id end
408
+
409
+ self.privileges[source] = privileges_
410
+
411
+ code = "nil;\n " + code
412
+
413
+ unless no_base_namespace
414
+ if (eval(base_namespace.to_s).instance_of? Module)
415
+ code = "module #{base_namespace}\n #{code}\n end\n"
416
+ else
417
+ code = "class #{base_namespace}\n #{code}\n end\n"
418
+ end
419
+ end
420
+
421
+ if encoding
422
+ code = "# encoding: #{encoding}\n" + code
423
+ end
424
+
425
+ evalhook_packet = @hook_handler.packet(code)
426
+ Shikashi::Sandbox::Packet.new(evalhook_packet, privileges_, source)
427
+ end
428
+
429
+ def create_hook_handler(*args)
430
+ hook_handler = instantiate_evalhook_handler
431
+ hook_handler.sandbox = self
432
+ @base_namespace = args.pick(:base_namespace) do create_adhoc_base_namespace end
433
+ hook_handler.base_namespace = @base_namespace
434
+
435
+ source = args.pick(:source) do generate_id end
436
+ privileges_ = args.pick(Privileges,:privileges) do Privileges.new end
437
+
438
+ self.privileges[source] = privileges_
439
+
440
+ hook_handler
441
+ end
442
+
443
+ def dispose
444
+ @hook_handler_list.each(&:dispose)
445
+ end
446
+ private
447
+
448
+ def instantiate_evalhook_handler
449
+ newhookhandler = EvalhookHandler.new
450
+ @hook_handler_list << newhookhandler
451
+ newhookhandler
452
+ end
453
+
454
+ def create_adhoc_base_namespace
455
+ rnd_module_name = "SandboxBasenamespace#{rand(100000000)}"
456
+
457
+ eval("module Shikashi::Sandbox::#{rnd_module_name}; end")
458
+ @base_namespace = eval("Shikashi::Sandbox::#{rnd_module_name}")
459
+ @base_namespace
460
+ end
461
+
462
+ def run_i(*args)
463
+
464
+ t = args.pick(:timeout) do nil end
465
+ raise Shikashi::Timeout::Error if t == 0
466
+ t = t || 0
467
+
468
+ if block_given?
469
+ yield
470
+ else
471
+ begin
472
+ timeout t do
473
+ privileges_ = args.pick(Privileges,:privileges) do Privileges.new end
474
+ code = args.pick(String,:code)
475
+ binding_ = args.pick(Binding,:binding) do Shikashi.global_binding end
476
+ source = args.pick(:source) do generate_id end
477
+ base_namespace = args.pick(:base_namespace) do nil end
478
+ no_base_namespace = args.pick(:no_base_namespace) do @no_base_namespace end
479
+ encoding = get_source_encoding(code) || args.pick(:encoding) do nil end
480
+
481
+ hook_handler = nil
482
+
483
+ if base_namespace
484
+ hook_handler = instantiate_evalhook_handler
485
+ hook_handler.base_namespace = base_namespace
486
+ hook_handler.sandbox = self
487
+ else
488
+ hook_handler = @hook_handler
489
+ base_namespace = hook_handler.base_namespace
490
+ end
491
+
492
+ self.privileges[source] = privileges_
493
+ code = "nil;\n " + code
494
+
495
+ unless no_base_namespace
496
+ if (eval(base_namespace.to_s).instance_of? Module)
497
+ code = "module #{base_namespace}\n #{code}\n end\n"
498
+ else
499
+ code = "class #{base_namespace}\n #{code}\n end\n"
500
+ end
501
+ end
502
+
503
+ if encoding
504
+ # preend encoding
505
+ code = "# encoding: #{encoding}\n" + code
506
+ end
507
+ hook_handler.evalhook(code, binding_, source)
508
+ end
509
+ rescue ::Timeout::Error
510
+ raise Shikashi::Timeout::Error
511
+ end
512
+ end
513
+ end
514
+
515
+ def get_source_encoding(code)
516
+ first_line = code.to_s.lines.first.to_s
517
+ m = first_line.match(/encoding:(.*)$/)
518
+ if m
519
+ m[1]
520
+ else
521
+ nil
522
+ end
523
+ end
524
+
525
+ end
526
+ end
527
+
528
+ Shikashi.global_binding = binding()
529
+
530
+