dia 1.5 → 2.0.0

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/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