sandboxed 0.0.1
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/Gemfile +3 -0
- data/LICENSE +21 -0
- data/lib/sandboxed.rb +45 -0
- data/lib/sandboxed/compat.rb +5 -0
- data/lib/sandboxed/fiber.rb +73 -0
- data/lib/sandboxed/proc.rb +17 -0
- data/lib/sandboxed/version.rb +3 -0
- data/spec/sandboxed_spec.rb +64 -0
- metadata +86 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2011 Michael Klaus
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
data/lib/sandboxed.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'sandboxed/compat'
|
2
|
+
require 'sandboxed/proc'
|
3
|
+
require 'sandboxed/fiber'
|
4
|
+
|
5
|
+
module Kernel
|
6
|
+
|
7
|
+
# initialize worker fiber for sandboxed execution
|
8
|
+
SAFE_FIBER = Fiber.new do |msg|
|
9
|
+
while true
|
10
|
+
obj, m, args, block = msg
|
11
|
+
begin
|
12
|
+
msg = Fiber.yield(block ? obj.send(m, *args, &block) : obj.send(m, *args))
|
13
|
+
rescue Exception => e
|
14
|
+
msg = Fiber.yield(e)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end.freeze
|
18
|
+
|
19
|
+
def safe(*args, &block)
|
20
|
+
opts = args.last.is_a?(Hash) ? args.pop : {}
|
21
|
+
level = opts.delete(:level) || 4
|
22
|
+
ctx = opts.delete(:context) || eval('self', block.binding) # rebind to actual declaring object
|
23
|
+
args << opts unless opts.empty? # be nicer about passed in hashes
|
24
|
+
|
25
|
+
bound = block.bind(ctx) # TODO 1.8 compat is missing out here. How to?
|
26
|
+
Fiber.new do |l, c, b, a|
|
27
|
+
$SAFE = l
|
28
|
+
b.call *a
|
29
|
+
end.resume(level, ctx, bound, args)
|
30
|
+
end
|
31
|
+
|
32
|
+
def safe_method(*ms)
|
33
|
+
ms.each do |m|
|
34
|
+
safe_m = :"#{m}__safe_"
|
35
|
+
alias_method safe_m, m
|
36
|
+
define_method m do |*args, &block|
|
37
|
+
result = SAFE_FIBER.resume([self, safe_m, args, block])
|
38
|
+
result.is_a?(Exception) ? throw(result) : result # TODO find a better way
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# Poor Man's Fiber (API compatible Thread based Fiber implementation for Ruby 1.8)
|
2
|
+
# (c) 2008 Aman Gupta (tmm1)
|
3
|
+
|
4
|
+
unless defined? Fiber
|
5
|
+
require 'thread'
|
6
|
+
|
7
|
+
class FiberError < StandardError; end
|
8
|
+
|
9
|
+
class Fiber
|
10
|
+
def initialize
|
11
|
+
raise ArgumentError, 'new Fiber requires a block' unless block_given?
|
12
|
+
|
13
|
+
@yield = Queue.new
|
14
|
+
@resume = Queue.new
|
15
|
+
|
16
|
+
@thread = Thread.new{ @yield.push [yield(*@resume.pop)] }
|
17
|
+
@thread.abort_on_exception = true
|
18
|
+
@thread[:fiber] = self
|
19
|
+
end
|
20
|
+
attr_reader :thread
|
21
|
+
|
22
|
+
def resume *args
|
23
|
+
raise FiberError, 'dead fiber called' unless @thread.alive?
|
24
|
+
@resume.push(args)
|
25
|
+
result = @yield.pop
|
26
|
+
result.size > 1 ? result : result.first
|
27
|
+
end
|
28
|
+
|
29
|
+
def yield *args
|
30
|
+
@yield.push(args)
|
31
|
+
result = @resume.pop
|
32
|
+
result.size > 1 ? result : result.first
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.yield *args
|
36
|
+
raise FiberError, "can't yield from root fiber" unless fiber = Thread.current[:fiber]
|
37
|
+
fiber.yield(*args)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.current
|
41
|
+
Thread.current[:fiber] or raise FiberError, 'not inside a fiber'
|
42
|
+
end
|
43
|
+
|
44
|
+
def inspect
|
45
|
+
"#<#{self.class}:0x#{self.object_id.to_s(16)}>"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
if __FILE__ == $0
|
51
|
+
f = Fiber.new{ puts 'hi'; p Fiber.yield(1); puts 'bye'; :done }
|
52
|
+
p f.resume
|
53
|
+
p f.resume(2)
|
54
|
+
end
|
55
|
+
|
56
|
+
__END__
|
57
|
+
|
58
|
+
$ ruby fbr.rb
|
59
|
+
hi
|
60
|
+
1
|
61
|
+
2
|
62
|
+
bye
|
63
|
+
:done
|
64
|
+
|
65
|
+
$ ruby1.9 fbr.rb
|
66
|
+
hi
|
67
|
+
1
|
68
|
+
2
|
69
|
+
bye
|
70
|
+
:done
|
71
|
+
|
72
|
+
|
73
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
unless Proc.method_defined?(:bind)
|
2
|
+
|
3
|
+
# copied from active_support proc.h to reduce dependencies
|
4
|
+
class Proc
|
5
|
+
def bind(object)
|
6
|
+
block, time = self, Time.now
|
7
|
+
class << object; self; end.class_eval do
|
8
|
+
method_name = "__bind_#{time.to_i}_#{time.usec}"
|
9
|
+
define_method(method_name, &block)
|
10
|
+
method = instance_method(method_name)
|
11
|
+
remove_method(method_name)
|
12
|
+
method
|
13
|
+
end.bind(object)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'sandboxed'
|
2
|
+
|
3
|
+
class Ctx
|
4
|
+
attr_reader :log
|
5
|
+
def initialize
|
6
|
+
@log = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def internal_log(text)
|
10
|
+
@log << text
|
11
|
+
end
|
12
|
+
|
13
|
+
def sandbox_log(text)
|
14
|
+
@log << text
|
15
|
+
end
|
16
|
+
safe_method :sandbox_log
|
17
|
+
end
|
18
|
+
|
19
|
+
describe Kernel do
|
20
|
+
describe "#safe" do
|
21
|
+
it "should use :level => 4 as default" do
|
22
|
+
safe { $SAFE }.should == 4
|
23
|
+
end
|
24
|
+
it "should execute safe operations" do
|
25
|
+
safe { 2 + 2 }.should == 4
|
26
|
+
end
|
27
|
+
it "should not execute unsafe operations" do
|
28
|
+
lambda{ safe{puts 'foo'} }.should raise_error SecurityError
|
29
|
+
end
|
30
|
+
it "should allow access depending on the :level" do
|
31
|
+
lambda{ safe(:level => 0){'foo'.taint} }.should_not raise_error SecurityError
|
32
|
+
end
|
33
|
+
it "should execute on the context object" do
|
34
|
+
safe(:context => 'foo'){ reverse }.should == 'oof'
|
35
|
+
end
|
36
|
+
it "should pass in safe local variables" do
|
37
|
+
arr = [].untrust
|
38
|
+
safe(arr){ |a| a << 'foo'}.should == ['foo']
|
39
|
+
arr.should == ['foo']
|
40
|
+
end
|
41
|
+
it "should pass in unsafe local variables" do
|
42
|
+
arr = []
|
43
|
+
lambda{ safe(arr){|a| a << 'foo'} }.should raise_error SecurityError
|
44
|
+
arr.should == []
|
45
|
+
end
|
46
|
+
it "should pass in hash local variables and options" do
|
47
|
+
safe(:from => 2, :len => 3, :context => 'foobar'){ |h| self[h[:from],h[:len]] }.should == 'oba'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "#safe_method" do
|
52
|
+
before(:each) do
|
53
|
+
@ctx = Ctx.new
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should not execute unsafe methods" do
|
57
|
+
lambda{ safe(:context => @ctx){internal_log 'foo'} }.should raise_error SecurityError
|
58
|
+
end
|
59
|
+
it "should execute safe methods" do
|
60
|
+
safe(:context => @ctx){sandbox_log 'foo'}.should == ['foo']
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sandboxed
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Michael Klaus
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-04-11 00:00:00 +02:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :development
|
32
|
+
version_requirements: *id001
|
33
|
+
description: Execute code blocks in a $SAFE environment.
|
34
|
+
email:
|
35
|
+
- Michael.Klaus@gmx.net
|
36
|
+
executables: []
|
37
|
+
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files: []
|
41
|
+
|
42
|
+
files:
|
43
|
+
- lib/sandboxed.rb
|
44
|
+
- lib/sandboxed/compat.rb
|
45
|
+
- lib/sandboxed/version.rb
|
46
|
+
- lib/sandboxed/proc.rb
|
47
|
+
- lib/sandboxed/fiber.rb
|
48
|
+
- spec/sandboxed_spec.rb
|
49
|
+
- LICENSE
|
50
|
+
- Gemfile
|
51
|
+
has_rdoc: true
|
52
|
+
homepage: http://github.com/QaDeS/sandboxed
|
53
|
+
licenses: []
|
54
|
+
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
version: "0"
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
segments:
|
74
|
+
- 1
|
75
|
+
- 3
|
76
|
+
- 6
|
77
|
+
version: 1.3.6
|
78
|
+
requirements: []
|
79
|
+
|
80
|
+
rubyforge_project: sandboxed
|
81
|
+
rubygems_version: 1.3.7
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: A ruby execution sandbox
|
85
|
+
test_files: []
|
86
|
+
|