dia 1.5 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/TODO.md DELETED
@@ -1,8 +0,0 @@
1
- ## TODO
2
-
3
- ### 1.4
4
- * Dia::Sandbox.run() doesn't use @app to launch a process, but uses @app\_path which was removed in 1.3
5
- * If you're going to run a block under a sandbox, make Dia::Sandbox#run accept *args so they may be passed onto the block.
6
-
7
- ### 1.3
8
- * Remove link to experimental branch in gemspec before release
@@ -1,7 +0,0 @@
1
- module Dia
2
- module CommonAPI
3
- extend FFI::Library
4
- ffi_lib(%w(sandbox system libSystem.B.dylib))
5
- attach_function :sandbox_init, [ :pointer, :uint64, :pointer ], :int
6
- end
7
- end
@@ -1,208 +0,0 @@
1
- module Dia
2
-
3
- class Sandbox
4
-
5
- require('io/wait')
6
-
7
- include Dia::CommonAPI
8
-
9
- attr_reader :app
10
- attr_reader :profile
11
- attr_reader :pid
12
- attr_reader :blk
13
-
14
- # The constructer accepts a profile as the first parameter, and an
15
- # application path _or_ block as its second parameter.
16
- #
17
- # @example
18
- #
19
- # # Passing an application to the constructer ..
20
- # sandbox = Dia::Sandbox.new(Dia::Profiles::NO_OS_SERVICES, 'ping google.com')
21
- #
22
- # # Passing a block to the constructer ..
23
- # sandbox = Dia::Sandbox.new(Dia::Profiles::NO_OS_SERVICES) do
24
- # File.open('foo.txt', 'w') do |f|
25
- # f.puts "bar"
26
- # end
27
- # end
28
- #
29
- # @see Dia::Sandbox#run See Dia::Sandbox#run for executing the sandbox.
30
- #
31
- # @param [Constant] Profile The profile to be used when creating a
32
- # sandbox.
33
- #
34
- # @param [Proc] Proc A proc object you want to run under a
35
- # sandbox.
36
- # Omit the "Application" parameter if
37
- # passed.
38
- #
39
- # @param [String] Application The path to an application you want
40
- # to run under a sandbox.
41
- # Omit the "Proc" parameter if passed.
42
- # @return [Dia::SandBox] Returns an instance of Dia::SandBox
43
-
44
- def initialize(profile, app=nil, &blk)
45
- if (app && blk) || (app.nil? && blk.nil?)
46
- raise ArgumentError, 'Application or Proc object expected'
47
- end
48
-
49
- @app = app
50
- @blk = blk
51
- @profile = profile
52
- @pid = nil
53
- initialize_streams()
54
- end
55
-
56
- # The #exception_raised?() method returns true if an exception has been
57
- # raised in the last call to #run(), and false otherwise.
58
- #
59
- # @return [Boolean] Returns true or false.
60
- # @since 1.5
61
- def exception_raised?()
62
- !!exception()
63
- end
64
-
65
- # The exception() method returns the last exception raised after a
66
- # a call to #run(), or nil.
67
- #
68
- # Every call to #run() resets the variable storing the exception object
69
- # to nil, and it will only be a non-nil value if the last call to #run()
70
- # raised an exception.
71
- #
72
- # This method can be used if you need access(from the parent process)
73
- # to an exception raised in your sandbox.
74
- #
75
- # If the sandbox raises an exception rather quickly, you might need to
76
- # sleep(X) (0.3-0.5s on my machine) before the parent process
77
- # recieves the exception.
78
- #
79
- # @return [Exception, nil] Returns an instance or subclass instance of
80
- # Exception when successful, and nil when
81
- # there is no exception available.
82
- # @since 1.5
83
- def exception()
84
- @write.close()
85
- if @read.ready?()
86
- @e = Marshal.load(@read.readlines().join())
87
- end
88
- @read.close()
89
- initialize_streams()
90
- @e
91
- end
92
-
93
- # The run method will spawn a child process and run the application _or_
94
- # block supplied to the constructer under a sandbox.
95
- # This method will not block.
96
- #
97
- # @param [Arguments] Arguments A variable amount of arguments that will
98
- # be passed onto the block supplied to the
99
- # constructer. Optional.
100
- #
101
- # @raise [SystemCallError] In the case of running a block, a number
102
- # of subclasses of SystemCallError may be
103
- # raised if the block violates sandbox
104
- # restrictions.
105
- # The parent process will not be affected
106
- # and if you wish to catch exceptions you
107
- # should do so in your block.
108
- #
109
- # @raise [Dia::SandboxException] Will raise Dia::SandboxException in a
110
- # child process and exit if the sandbox
111
- # could not be initiated.
112
- #
113
- # @return [Fixnum] The Process ID(PID) that the sandboxed
114
- # application is being run under.
115
- def run(*args)
116
- @e = nil
117
- initialize_streams()
118
- @pid = fork do
119
- begin
120
- if sandbox_init(FFI::MemoryPointer.from_string(@profile),
121
- 0x0001,
122
- err = FFI::MemoryPointer.new(:pointer)) == -1
123
-
124
- raise(Dia::SandboxException, "Failed to initialize sandbox" \
125
- "(#{err.read_pointer.read_string})")
126
- end
127
-
128
- if @app
129
- exec(@app)
130
- else
131
- @blk.call(*args)
132
- end
133
-
134
- rescue SystemExit, Interrupt => e
135
- raise(e)
136
- rescue Exception => e
137
- @write.write(Marshal.dump(e))
138
- ensure
139
- @write.close()
140
- @read.close()
141
- end
142
- end
143
-
144
- # parent ..
145
- @thr = Process.detach(@pid)
146
- @pid
147
- end
148
-
149
- # The exit_status method will return the exit status of the child process
150
- # running in a sandbox.
151
- # This method *will* block until the child process exits.
152
- #
153
- # @return [Fixnum, nil] Returns the exit status of the process that ran
154
- # under a sandbox.
155
- # Returns nil if Dia::Sandbox#run has not
156
- # been called yet, or if the process stopped
157
- # abnormally(ie: through SIGKILL, or #terminate).
158
- # @since 1.5
159
- def exit_status()
160
- @thr.value().exitstatus() unless @thr.nil?
161
- end
162
-
163
- # The terminate method will send SIGKILL to a process running in a sandbox.
164
- # By doing so, it effectively terminates the sandbox.
165
- #
166
- # @raise [SystemCallError] It may raise a number of subclasses of
167
- # SystemCallError if a call to Process.kill
168
- # was unsuccessful ..
169
- #
170
- # @return [Fixnum, nil] It will return 1 when successful, and
171
- # it will return "nil" if Dia::Sandbox#run()
172
- # has not been called yet.
173
- def terminate()
174
- Process.kill('SIGKILL', @pid) unless @pid.nil?
175
- end
176
-
177
- # The running? method will return true if a sandbox is running,
178
- # and false otherwise.
179
- #
180
- # @raise [SystemCallError] It may raise a subclass of SystemCallError if
181
- # you do not have permission to send a signal
182
- # to the process running in a sandbox.
183
- #
184
- # @return [Boolean,nil] It will return true or false if the sandbox
185
- # is running or not, and it will return "nil"
186
- # if Dia::Sandbox#run has not been called yet.
187
- def running?()
188
- if @pid.nil?
189
- nil
190
- else
191
- begin
192
- Process.kill(0, @pid)
193
- true
194
- rescue Errno::ESRCH
195
- false
196
- end
197
- end
198
- end
199
-
200
- private
201
-
202
- def initialize_streams()
203
- @read, @write = IO.pipe()
204
- end
205
-
206
- end
207
-
208
- end
@@ -1,29 +0,0 @@
1
- BareTest.suite "Dia::Sandbox#running?", :tags => [ :running? ] do
2
-
3
- assert 'Confirm that Dia::Sandbox#running? returns true when a sandbox is running' do
4
- sandbox = Dia::Sandbox.new(Dia::Profiles::NO_OS_SERVICES) do
5
- sleep(20)
6
- end
7
-
8
- sandbox.run
9
- equal(true, sandbox.running?)
10
- sandbox.terminate
11
- end
12
-
13
- assert 'Confirm that Dia::Sandbox#running? returns false when a sandbox is not running' do
14
- sandbox = Dia::Sandbox.new(Dia::Profiles::NO_OS_SERVICES) do
15
- sleep(20)
16
- end
17
- sandbox.run
18
- sandbox.terminate
19
- sleep(1)
20
- equal(false, sandbox.running?)
21
- end
22
-
23
- assert("nil will be returned if Dia::Sandbox#run hasn't been called before a call to #running?()") do
24
- sandbox = Dia::Sandbox.new(Dia::Profiles::NO_INTERNET) do
25
- # ...
26
- end
27
- equal(nil, sandbox.running?)
28
- end
29
- end
@@ -1,55 +0,0 @@
1
- BareTest.suite('Dia::Sandbox#exception()') do
2
- assert('#exception() will return an exception raised in the sandbox') do
3
- sandbox = Dia::Sandbox.new(Dia::Profiles::NO_OS_SERVICES) do
4
- raise()
5
- end
6
-
7
- sandbox.run()
8
- sleep(0.1)
9
- equal(RuntimeError, sandbox.exception.class())
10
- end
11
-
12
- assert('#exception() returns nil if called before ' \
13
- 'Dia::Sandbox#run()') do
14
- sandbox = Dia::Sandbox.new(Dia::Profiles::NO_OS_SERVICES) do
15
- # ...
16
- end
17
- equal(nil, sandbox.exception())
18
- end
19
-
20
- assert('#exception() returns an exception object the first' \
21
- ' and second time it is called after a single call to #run()') do
22
- sandbox = Dia::Sandbox.new(Dia::Profiles::NO_OS_SERVICES) do
23
- raise()
24
- end
25
-
26
- sandbox.run()
27
- sleep(0.1)
28
- equal(RuntimeError, sandbox.exception().class)
29
- equal(RuntimeError, sandbox.exception().class)
30
- end
31
-
32
- assert('#exception() will be set to nil after the first call to ' \
33
- '#run raises an exception, and the second does not') do
34
-
35
- sandbox = Dia::Sandbox.new(Dia::Profiles::NO_OS_SERVICES) do
36
- raise()
37
- end
38
-
39
- sandbox.run()
40
- sleep(0.1)
41
- equal(RuntimeError, sandbox.exception().class)
42
- sandbox.instance_variable_set("@blk", proc { })
43
- sandbox.run()
44
- equal(nil, sandbox.exception())
45
- end
46
-
47
- assert('#exception() can marshal data containing one or more \n') do
48
- sandbox = Dia::Sandbox.new(Dia::Profiles::NO_OS_SERVICES) do
49
- fork {}
50
- end
51
- sandbox.run()
52
- sleep(0.5)
53
- equal(Errno::EPERM, sandbox.exception().class)
54
- end
55
- end
@@ -1,22 +0,0 @@
1
- BareTest.suite('Dia::Sandbox#exception_raised?()',
2
- :tags => [ :exception_raised] ) do
3
- assert('#exception_raised?() returns false when no exception has been ' \
4
- 'raised') do
5
- sandbox = Dia::Sandbox.new(Dia::Profiles::NO_OS_SERVICES) do
6
- # ...
7
- end
8
- sandbox.run()
9
- sleep(0.1)
10
- equal(false, sandbox.exception_raised?())
11
- end
12
-
13
- assert('#exception_raised?() returns true when an exception has been ' \
14
- 'raised') do
15
- sandbox = Dia::Sandbox.new(Dia::Profiles::NO_OS_SERVICES) do
16
- raise(StandardError, 'Exception')
17
- end
18
- sandbox.run()
19
- sleep(0.1)
20
- equal(true, sandbox.exception_raised?())
21
- end
22
- end
@@ -1,12 +0,0 @@
1
- BareTest.suite('Exceptions', :tags => [ :sandbox_init_exception ]) do
2
- assert('Dia::SandboxException is raised if a call to sandbox_init() fails') do
3
- sandbox = Dia::Sandbox.new(Dia::Profiles::NO_OS_SERVICES) do
4
- # ...
5
- end
6
- sandbox.instance_variable_set("@profile", "i am going to break")
7
- sandbox.run
8
- sleep(0.1)
9
- equal(Dia::SandboxException, sandbox.exception().class)
10
- end
11
- end
12
-
@@ -1,16 +0,0 @@
1
- BareTest.suite('Dia::Sandbox#exit_status') do
2
- assert('The exit status of a child process running under a sandbox is returned.') do
3
- sandbox = Dia::Sandbox.new(Dia::Profiles::NO_INTERNET) do
4
- exit(10)
5
- end
6
- sandbox.run
7
- equal(10, sandbox.exit_status)
8
- end
9
-
10
- assert("nil will be returned if Dia::Sandbox#run hasn't been called before a call to #exit_status") do
11
- sandbox = Dia::Sandbox.new(Dia::Profiles::NO_INTERNET) do
12
- end
13
- equal(nil, sandbox.exit_status)
14
- end
15
-
16
- end
@@ -1,34 +0,0 @@
1
- # See /test/suite/run_block_in_sandbox_test.rb for tests that confirm sandboxes are successfully created ..
2
- BareTest.suite 'Dia::Sandbox.new', :tags => [ :new ] do
3
-
4
- assert 'Passing no arguments to the constructer will raise an ArgumentError' do
5
- raises(ArgumentError) do
6
- Dia::Sandbox.new
7
- end
8
- end
9
-
10
- assert 'Passing only a profile to the constructer will raise an ArgumentError' do
11
- raises(ArgumentError) do
12
- Dia::Sandbox.new(Dia::Profiles::NO_INTERNET)
13
- end
14
- end
15
-
16
- assert 'Passing a profile, application path, and a block will raise an ArgumentError' do
17
- raises(ArgumentError) do
18
- Dia::Sandbox.new(Dia::Profiles::NO_OS_SERVICES, 'ls') do
19
- puts "foo"
20
- end
21
- end
22
- end
23
-
24
- assert 'Passing an application path and a profile will raise nothing' do
25
- Dia::Sandbox.new(Dia::Profiles::NO_OS_SERVICES, 'ls')
26
- end
27
-
28
- assert 'Passing a block and a profile will raise nothing' do
29
- Dia::Sandbox.new(Dia::Profiles::NO_OS_SERVICES) do
30
- puts "foo"
31
- end
32
- end
33
-
34
- end
@@ -1,132 +0,0 @@
1
- # TODO: Add assertion for Dia::Profiles::NO_OS_SERVICES
2
-
3
- BareTest.suite 'Dia::Sandbox#run', :tags => [ :run ] do
4
-
5
- setup do
6
- @reader, @writer = IO.pipe
7
- end
8
-
9
- assert 'A Ruby block will not be able to access the internet' do
10
-
11
- sandbox = Dia::Sandbox.new(Dia::Profiles::NO_INTERNET) do
12
- begin
13
- @reader.close
14
- TCPSocket.open('http://www.google.com', 80)
15
- @writer.write('false')
16
- rescue SocketError, SystemCallError => e
17
- @writer.write('true')
18
- end
19
- end
20
-
21
- # a child process is spawned, and the block passed to the constructer executed.
22
- sandbox.run
23
-
24
- # back in the parent.
25
- @writer.close
26
- successful = @reader.gets
27
- @reader.close
28
-
29
- equal('true', successful)
30
- end
31
-
32
- assert 'A Ruby block will not be able to write the filesystem' do
33
-
34
- sandbox = Dia::Sandbox.new(Dia::Profiles::NO_FILESYSTEM_WRITE) do
35
- begin
36
- @reader.close
37
- File.open('foo.txt', 'w')
38
- @writer.write('false')
39
- rescue SystemCallError => e
40
- @writer.write('true')
41
- end
42
- end
43
-
44
- # a child process is spawned, and the block passed to the constructer executed.
45
- sandbox.run
46
-
47
- # back in the parent.
48
- @writer.close
49
- successful = @reader.gets
50
- @reader.close
51
-
52
- equal('true', successful)
53
- end
54
-
55
- assert 'A Ruby block will not be able to write to the filesystem except when writing to /tmp' do
56
- sandbox = Dia::Sandbox.new(Dia::Profiles::NO_FILESYSTEM_WRITE_EXCEPT_TMP) do
57
- marshal = []
58
- begin
59
- marshal = Marshal.dump(marshal)
60
- @reader.close
61
- File.open('foo.txt', 'w')
62
- @writer.write('false')
63
- rescue SystemCallError => e
64
- marshal = Marshal.dump(Marshal.load(marshal) << 'true')
65
- end
66
-
67
- begin
68
- File.open('/tmp/foo.txt', 'w') do |f|
69
- f.puts 'foo'
70
- end
71
- @writer.write(marshal = Marshal.dump(Marshal.load(marshal) << 'true'))
72
- rescue SystemCallError => e
73
- @writer.write('false')
74
- end
75
- end
76
-
77
- # a child process is spawned, and the block passed to the constructer executed.
78
- sandbox.run
79
-
80
- # back in the parent.
81
- @writer.close
82
- successful = Marshal.load(@reader.gets)
83
- @reader.close
84
-
85
- equal(['true', 'true'], successful)
86
- end
87
-
88
- assert 'A Ruby block will not be able to do any socket based communication' do
89
- sandbox = Dia::Sandbox.new(Dia::Profiles::NO_NETWORKING) do
90
- begin
91
- @reader.close
92
- TCPSocket.open('http://www.youtube.com', 80)
93
- @writer.write('false')
94
- rescue SocketError => e
95
- @writer.write('true')
96
- end
97
- end
98
-
99
- # a child process is spawned, and the block passed to the constructer executed.
100
- sandbox.run
101
-
102
- # back in the parent.
103
- @writer.close
104
- successful = @reader.gets
105
- @reader.close
106
-
107
- equal('true', successful)
108
- end
109
-
110
- assert 'A Ruby block will be able to receive arguments through #run' do
111
- sandbox = Dia::Sandbox.new(Dia::Profiles::NO_INTERNET) do |foo, bar|
112
- @reader.close
113
- @writer.write(foo+bar)
114
- @writer.close
115
- end
116
- sandbox.run('foo', 'bar')
117
-
118
- # back in the parent..
119
- @writer.close
120
- answer = @reader.gets
121
- @reader.close
122
-
123
- equal('foobar', answer)
124
- end
125
-
126
- assert('A Ruby block will return the PID of the spawned child process after executing #run') do
127
- sandbox = Dia::Sandbox.new(Dia::Profiles::NO_INTERNET) do
128
- # ...
129
- end
130
- equal(Fixnum, sandbox.run.class)
131
- end
132
- end