bwrap 1.0.0.pre.alpha5 → 1.0.0.pre.beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dfb5f9bdbcf68df6068aebca204e72ef9272829b1f8db11c34e5a4da469cc2c7
4
- data.tar.gz: 5f19c25ecad9b7f4923f80a6d61c87c6382671667f6b39eed0198befbb8eccd2
3
+ metadata.gz: db6e7253c0a896975954c0d649c68321958ade750f891f353ba30b9ea58880cb
4
+ data.tar.gz: 90f384499e8727660b9810711e0237ce604f8ef219ed415c210db16bcd764804
5
5
  SHA512:
6
- metadata.gz: c9682aff2b43180fee0e8d7b2b7234d4bb516e86c85dcabc5b40a1047284f9f5bf9e0641dff80483e4645b8aa4bd06c9b9de8ba1b43cd78af3b77de1060c8ad6
7
- data.tar.gz: cadd731077ff175b86cf0d56f17dd1c2cb3123e5d9b1dcff17156da5d706d7da12274bc59297ae925c1f07ea5b3a325c999c942048c2c5b165755934ae16d2f8
6
+ metadata.gz: 03e6873b068e52bc0c9fbbc072f9fc1a411696ca3c09aabcee05c8e660e5a210030c13fab84348aee720e7bd431600a0b18c3be124dfcb955ac95c250d83aeec
7
+ data.tar.gz: 4183575c47d740dd74c88995add7b87366299e75b4a8edaa89c20d422cd73cbd957b411e5bd51b9c9eabcd3881d8af1ba85922ce806b2387d75a0f30d8abd377
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changes
2
2
 
3
+ ## 1.0.0-beta1 (12.12.2021)
4
+
5
+ * optimist gem is now optional dependency
6
+ * Added Config#env_paths and Config#add_env_path
7
+ * Added Config#command_inside_root
8
+ * Added Bwrap#run_inside_root convenience method
9
+ * Execution#command_available?: added env_path_var argument
10
+ * Execution#which: added env_path_var argument
11
+ * Be able to resolve /usr/bin/env to real executable
12
+ * Try to avoid duplicate library binds
13
+ * Added Config#extra_executables
14
+ * Added Config::Features::Bash
15
+
3
16
  ## 1.0.0-alpha5 (29.11.2021)
4
17
 
5
18
  * Execution#command_available?: support absolute paths
@@ -2,7 +2,11 @@
2
2
 
3
3
  require "bwrap/version"
4
4
 
5
- # bwrap argument related operations.
5
+ # Classes that are used for building arguments.
6
+ #
7
+ # @note Classes inside here are kind of pseudo-internal API.
8
+ # In future, there may be some use for classes inside here, but for now they are
9
+ # only used internally.
6
10
  module Bwrap::Args
7
11
  # Nya.
8
12
  end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bwrap/execution/path"
4
+ require "bwrap/output"
5
+ require_relative "../library"
6
+ require_relative "mime"
7
+
8
+ class Bwrap::Args::Bind
9
+ # TODO: documentation
10
+ #
11
+ # @api private
12
+ class Library
13
+ include Bwrap::Execution::Path
14
+ include Bwrap::Output
15
+
16
+ # @see Bwrap::Args::Construct#command=
17
+ #
18
+ # @see (see Bwrap::Args::Construct#command=)
19
+ attr_writer :command
20
+
21
+ # Instance of {Bwrap::Config}.
22
+ attr_writer :config
23
+
24
+ # Instance of {Bwrap::Args::Environment}.
25
+ attr_writer :environment
26
+
27
+ attr_writer :executable_name
28
+
29
+ attr_writer :executable_path
30
+
31
+ def initialize args
32
+ @args = args
33
+ end
34
+
35
+ def extra_executables_mounts
36
+ return unless @config&.extra_executables
37
+
38
+ @config.extra_executables.each do |executable|
39
+ @executable_name = resolve_executable_name executable
40
+ @executable_path = resolve_executable_path @executable_name, not_inside_root: true
41
+
42
+ @args.append %W{ --ro-bind #{@executable_path} #{@executable_path} }
43
+
44
+ resolve_executable_libraries
45
+ end
46
+ end
47
+
48
+ # Convenience method to call {#resolve_executable_libraries}.
49
+ #
50
+ # Used by {#handle_system_mounts}.
51
+ def libs_command_requires
52
+ @executable_name = resolve_executable_name @command
53
+ @executable_path = resolve_executable_path @executable_name
54
+
55
+ # Actually add the executable to be bound to the sandbox.
56
+ unless @config&.command_inside_root
57
+ @args.append %W{ --ro-bind #{@executable_path} #{@executable_path} }
58
+ end
59
+
60
+ resolve_executable_libraries
61
+ end
62
+
63
+ # Does some inspection to find out libraries given executable needs in order to work.
64
+ #
65
+ # @warning scanelf does not play with spaces in names well. This method assumes that libraries
66
+ # have no spaces in names, though binaries can have.
67
+ #
68
+ # @todo Ensure scanelf is available (and throw proper error if it is not, telling to not use
69
+ # full_system_mounts option.)
70
+ def resolve_executable_libraries
71
+ trace "Resolving executable libraries of #{@executable_path}"
72
+
73
+ # TODO: Put this behind additional flag for extra control/sanity.
74
+ # Some executables are shell scripts and similar. For them we need to use the interpreter.
75
+
76
+ mime = Mime.new @executable_name, @executable_path
77
+ return unless mime.resolve_mime_type
78
+
79
+ # Then find out required libraries
80
+
81
+ library_mounts = []
82
+
83
+ library_object = ::Bwrap::Args::Library.new
84
+ libraries = library_object.libraries_needed_by mime.executable_path
85
+
86
+ # TODO: following is bad?
87
+ #library_object.needed_libraries(mime.executable_path).each do |library|
88
+ libraries.each do |library|
89
+ library_mounts << "--ro-bind" << library << library
90
+ end
91
+
92
+ @args.append library_mounts
93
+ end
94
+
95
+ # Used by {#libs_command_requires}.
96
+ private def resolve_executable_name command
97
+ if command.is_a? String
98
+ return command
99
+ end
100
+
101
+ # Array-like.
102
+ if command.respond_to? :at
103
+ return command.at(0)
104
+ end
105
+
106
+ raise "Can’t recognize type of given command. Type: #{command.class}"
107
+ end
108
+
109
+ # @warning Requires environment paths to be resolved beforehand.
110
+ #
111
+ # Used by {#libs_command_requires}.
112
+ private def resolve_executable_path executable_name, not_inside_root: nil
113
+ if @config&.command_inside_root.nil? or not_inside_root
114
+ return which executable_name
115
+ end
116
+
117
+ paths = @environment.env_paths.map do |path|
118
+ "#{@config.root}/#{path}"
119
+ end
120
+ env_path = paths.join ":"
121
+
122
+ which executable_name, env_path_var: env_path
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bwrap/execution"
4
+ require "bwrap/output"
5
+
6
+ class Bwrap::Args::Bind
7
+ # Inner class to clean up namespace for implementation specific reasons.
8
+ #
9
+ # @api private
10
+ class Mime
11
+ include Bwrap::Execution
12
+ include Bwrap::Output
13
+
14
+ # Name given to {#initialize}.
15
+ attr_reader :executable_name
16
+
17
+ # Either path given to {#initialize} or one parsed from shebang.
18
+ attr_reader :executable_path
19
+
20
+ def initialize executable_name, executable_path
21
+ @executable_name = executable_name
22
+ @executable_path = executable_path
23
+ end
24
+
25
+ # Used by {Bwrap::Args::Bind::Library#libs_command_requires}.
26
+ #
27
+ # @return false if caller should also return
28
+ def resolve_mime_type
29
+ mime_type = execvalue %W{ file --brief --mime-type #{@executable_path} }
30
+ trace "Mime type of #{@executable_path} is #{mime_type}"
31
+ return true unless mime_type[0..6] == "text/x-"
32
+
33
+ shebang = File.open @executable_path, &:readline
34
+ if shebang[0..1] != "#!"
35
+ warn "Executable #{@executable_name} was recognized as #{mime_type} but does not have " \
36
+ "proper shebang line. Skipping automatic library mounts."
37
+ return false
38
+ end
39
+
40
+ resolve_real_executable shebang
41
+
42
+ true
43
+ end
44
+
45
+ private def resolve_real_executable shebang
46
+ command_line = shebang.delete_prefix("#!").strip
47
+ real_executable, args = command_line.split " ", 2
48
+
49
+ if [ "/usr/bin/env", "/bin/env" ].include? real_executable
50
+ # First argument is name of the executable, resolved from PATH.
51
+ executable_name = args.split(" ", 2).first
52
+ real_executable = which executable_name
53
+ end
54
+
55
+ @executable_path = real_executable
56
+ end
57
+ end
58
+ end
@@ -3,7 +3,7 @@
3
3
  require "bwrap/execution"
4
4
  require "bwrap/output"
5
5
  require_relative "args"
6
- require_relative "library"
6
+ require_relative "bind/library"
7
7
 
8
8
  # Bind arguments for bwrap.
9
9
  class Bwrap::Args::Bind
@@ -18,52 +18,12 @@ class Bwrap::Args::Bind
18
18
  # @see (see Bwrap::Args::Construct#command=)
19
19
  attr_writer :command
20
20
 
21
- # Instance of {Config}.
21
+ # Instance of {Bwrap::Config}.
22
22
  attr_writer :config
23
23
 
24
24
  # Instance of {Bwrap::Args::Environment}.
25
25
  attr_writer :environment
26
26
 
27
- # Inner class to clean up namespace for implementation specific reasons.
28
- #
29
- # @api internal
30
- class Mime
31
- include Bwrap::Execution
32
- include Bwrap::Output
33
-
34
- # Name given to {#initialize}.
35
- attr_reader :executable_name
36
-
37
- # Either path given to {#initialize} or one parsed from shebang.
38
- attr_reader :executable_path
39
-
40
- def initialize executable_name, executable_path
41
- @executable_name = executable_name
42
- @executable_path = executable_path
43
- end
44
-
45
- # Used by {Bwrap::Args::Bind#libs_command_requires}.
46
- #
47
- # @return false if caller should also return
48
- def resolve_mime_type
49
- mime_type = execvalue %W{ file --brief --mime-type #{@executable_path} }
50
- return true unless mime_type[0..6] == "text/x-"
51
-
52
- shebang = File.open @executable_path, &:readline
53
- if shebang[0..1] != "#!"
54
- warn "Executable #{@executable_name} was recognized as #{mime_type} but does not have " \
55
- "proper shebang line. Skipping automatic library mounts."
56
- return false
57
- end
58
-
59
- shebang = shebang.delete_prefix("#!").strip
60
- real_executable, _args = shebang.split " ", 2
61
- @executable_path = real_executable
62
-
63
- true
64
- end
65
- end
66
-
67
27
  # Arguments to bind /dev/dri from host to sandbox.
68
28
  def bind_dev_dri
69
29
  @args.append %w{ --dev-bind /dev/dri /dev/dri }
@@ -99,13 +59,13 @@ class Bwrap::Args::Bind
99
59
  end
100
60
 
101
61
  # Arguments to read-only bind whole system inside sandbox.
102
- def full_system_mounts
62
+ def handle_system_mounts
103
63
  bindir_mounts = []
104
64
  binaries_from = @config.binaries_from
105
65
  binaries_from.each do |path|
106
66
  bindir_mounts << "--ro-bind" << path << path
107
67
  end
108
- @environment["PATH"] = binaries_from.join(":")
68
+ @environment.add_to_path binaries_from
109
69
 
110
70
  @args.append bindir_mounts
111
71
 
@@ -117,10 +77,13 @@ class Bwrap::Args::Bind
117
77
 
118
78
  libdir_mounts
119
79
 
80
+ library_bind = construct_library_bind
81
+
82
+ library_bind.extra_executables_mounts
83
+
120
84
  return unless @config.full_system_mounts
121
85
 
122
- loader_binds
123
- libs_command_requires
86
+ library_bind.libs_command_requires
124
87
  end
125
88
 
126
89
  # These are something user can specify to do custom --ro-bind binds.
@@ -135,7 +98,12 @@ class Bwrap::Args::Bind
135
98
  @args.append binds
136
99
  end
137
100
 
138
- # Used by {#full_system_mounts}.
101
+ # Performs cleanup operations after execution.
102
+ def cleanup
103
+ Bwrap::Args::Library.clear_needed_libraries_cache
104
+ end
105
+
106
+ # Used by {#handle_system_mounts}.
139
107
  private def libdir_mounts
140
108
  return unless @config.libdir_mounts
141
109
 
@@ -155,49 +123,12 @@ class Bwrap::Args::Bind
155
123
  @args.append libdir_mounts
156
124
  end
157
125
 
158
- # Used by {#full_system_mounts}.
159
- private def loader_binds
160
- loader_mounts = []
161
- path = "/lib64/ld-linux-x86-64.so.2"
162
- loader_mounts << "--ro-bind" << path << path if File.exist? path
163
- path = "/lib/ld-linux.so.2"
164
- loader_mounts << "--ro-bind" << path << path if File.exist? path
165
-
166
- @args.append loader_mounts
167
- end
168
-
169
- # Does some inspection to find out libraries given executable needs in order to work.
170
- #
171
- # Used by {#full_system_mounts}.
172
- #
173
- # @warning scanelf does not play with spaces in names well. This method assumes that libraries
174
- # have no spaces in names, though binaries can have.
175
- #
176
- # @todo Ensure scanelf is available (and throw proper error if it is not, telling to not use
177
- # full_system_mounts option.)
178
- private def libs_command_requires
179
- executable_name = @command.is_a?(String) && @command || @command[0]
180
- executable_path = which executable_name
181
-
182
- # TODO: Put this behind additional flag for extra control/sanity.
183
- # Some executables are shell scripts and similar. For them we need to use the interpreter.
184
-
185
- mime = Mime.new executable_name, executable_path
186
- return unless mime.resolve_mime_type
187
-
188
- # Then find out required libraries
189
-
190
- library_mounts = []
191
-
192
- library_object = Bwrap::Args::Library.new
193
- libraries = library_object.libraries_needed_by mime.executable_path
194
-
195
- # TODO: following is bad?
196
- #library_object.needed_libraries(mime.executable_path).each do |library|
197
- libraries.each do |library|
198
- library_mounts << "--ro-bind" << library << library
199
- end
126
+ private def construct_library_bind
127
+ library_bind = Bwrap::Args::Bind::Library.new @args
128
+ library_bind.command = @command
129
+ library_bind.config = @config
130
+ library_bind.environment = @environment
200
131
 
201
- @args.append library_mounts
132
+ library_bind
202
133
  end
203
134
  end
@@ -22,7 +22,7 @@ class Bwrap::Args::Construct
22
22
  # setting {Config#full_system_mounts=} uses this to resolve some
23
23
  # additional data.
24
24
  #
25
- # @param command [Array, String] Command with arguments
25
+ # @param value [Array, String] Command with arguments
26
26
  attr_writer :command
27
27
 
28
28
  # Constructs arguments for bwrap execution.
@@ -35,7 +35,7 @@ class Bwrap::Args::Construct
35
35
  machine_id = @machine_id.machine_id
36
36
  @args.append machine_id if machine_id
37
37
  resolv_conf
38
- @bind.full_system_mounts
38
+ @bind.handle_system_mounts
39
39
  @features.feature_binds
40
40
  @bind.custom_read_only_binds
41
41
  create_user_dir
@@ -60,6 +60,7 @@ class Bwrap::Args::Construct
60
60
  # Performs cleanup operations after execution.
61
61
  def cleanup
62
62
  @machine_id&.cleanup
63
+ @bind&.cleanup
63
64
  end
64
65
 
65
66
  # Used by {#construct_bwrap_args}.
@@ -10,15 +10,46 @@ class Bwrap::Args::Environment < Hash
10
10
  # Instance of {Config}.
11
11
  attr_writer :config
12
12
 
13
- # Returns used environment variables.
13
+ # Returns used environment variables wrapped as bwrap arguments.
14
14
  def environment_variables
15
15
  if debug?
16
16
  debug "Passing following environment variables to bwrap:\n" \
17
17
  "#{self}"
18
18
  end
19
19
 
20
+ env_paths
21
+
20
22
  map do |key, value|
23
+ if key == "PATH" and value.respond_to? :join
24
+ value = value.join ":"
25
+ end
26
+
21
27
  [ "--setenv", key, value ]
22
28
  end
23
29
  end
30
+
31
+ # @return [Array] All environment paths added via {Config#add_env_path} and other parsing logic
32
+ def env_paths
33
+ if @config.env_paths.respond_to? :each
34
+ self["PATH"] ||= []
35
+
36
+ self["PATH"] |= @config.env_paths
37
+ end
38
+
39
+ self["PATH"]
40
+ end
41
+
42
+ # Adds given paths to PATH environment variable defined in the sandbox.
43
+ #
44
+ # @param elements [String, Array] Path(s) to be added added to PATH environment variable
45
+ def add_to_path elements
46
+ self["PATH"] ||= []
47
+
48
+ if elements.respond_to? :each
49
+ self["PATH"] += elements
50
+ else
51
+ # Expecting elements to be single path element as a string.
52
+ self["PATH"] << elements
53
+ end
54
+ end
24
55
  end
@@ -10,8 +10,30 @@ require_relative "library"
10
10
  class Bwrap::Args::Features < Hash
11
11
  include Bwrap::Output
12
12
 
13
- # @api internal
13
+ # Implementation for Bash feature set.
14
+ #
15
+ # @api private
16
+ class BashBinds
17
+ # Mounts stuff like /bin/bash.
18
+ def bash_mounts
19
+ mounts = []
20
+
21
+ if File.file? "/bin/bash"
22
+ mounts << "--ro-bind" << "/bin/bash" << "/bin/bash"
23
+ end
24
+ if File.file? "/usr/bin/bash"
25
+ mounts << "--ro-bind" << "/usr/bin/bash" << "/usr/bin/bash"
26
+ end
27
+
28
+ mounts
29
+ end
30
+ end
31
+
32
+ # Implementation for Ruby feature set.
33
+ #
34
+ # @api private
14
35
  class RubyBinds
36
+ # Returns mounts needed by Ruby feature set.
15
37
  attr_reader :mounts
16
38
 
17
39
  # Bind system paths so scripts works inside sandbox.
@@ -40,19 +62,34 @@ class Bwrap::Args::Features < Hash
40
62
  library_mounts << "--ro-bind" << requisite_library << requisite_library
41
63
  end
42
64
  end
65
+
66
+ library_mounts
43
67
  end
44
68
  end
45
69
 
46
- # {Array} of parameters passed to bwrap.
70
+ # `Array` of parameters passed to bwrap.
47
71
  attr_writer :args
48
72
 
49
73
  # Instance of {Config}.
50
74
  attr_writer :config
51
75
 
76
+ # Resolves binds required by different features.
77
+ #
78
+ # Currently implemented feature sets:
79
+ # - ruby
52
80
  def feature_binds
81
+ bash_binds
53
82
  ruby_binds
54
83
  end
55
84
 
85
+ private def bash_binds
86
+ return unless @config.features.bash.enabled?
87
+
88
+ binds = BashBinds.new
89
+
90
+ @args.append binds.bash_mounts
91
+ end
92
+
56
93
  # @note This does not allow development headers needed for compilation for now.
57
94
  # I’ll look at it after I have an use for it.
58
95
  private def ruby_binds
@@ -61,6 +98,6 @@ class Bwrap::Args::Features < Hash
61
98
  binds = RubyBinds.new
62
99
 
63
100
  @args.append binds.sitedir_mounts
64
- @args.append binds.stdlib_mounts @config.features.ruby.stdlib
101
+ @args.append binds.stdlib_mounts(@config.features.ruby.stdlib)
65
102
  end
66
103
  end
@@ -6,7 +6,7 @@ require_relative "args"
6
6
 
7
7
  # Class to clean up namespace for implementation specific reasons.
8
8
  #
9
- # @api internal
9
+ # @api private
10
10
  class Bwrap::Args::Library
11
11
  include Bwrap::Execution
12
12
  include Bwrap::Output
@@ -14,6 +14,11 @@ class Bwrap::Args::Library
14
14
  # NOTE: This caching can be made more efficient, but need to look at it later, what to do about it.
15
15
  @@needed_libraries_cache ||= []
16
16
 
17
+ # Empties {@@needed_libraries_cache}.
18
+ def self.clear_needed_libraries_cache
19
+ @@needed_libraries_cache.clear
20
+ end
21
+
17
22
  # Otherwise similar to {#needed_libraries}, but checks used libc to handle musl executables.
18
23
  #
19
24
  # @param executable [String] Path to the executable to find dependencies for
@@ -99,17 +104,17 @@ class Bwrap::Args::Library
99
104
  binary_path, libraries_line = line.split "::SEPARATOR::"
100
105
  libraries = libraries_line.split ","
101
106
 
102
- @needed_libraries += libraries
107
+ (@needed_libraries & libraries).each do |library|
108
+ verb "Binding #{library} as dependency of #{binary_path}"
109
+ end
110
+
111
+ @needed_libraries |= libraries
103
112
 
104
113
  # Also check if requisite libraries needs some libraries.
105
114
  inner = Bwrap::Args::Library.new
106
- @needed_libraries += inner.needed_libraries libraries
115
+ @needed_libraries |= inner.needed_libraries libraries
107
116
 
108
- # Mark library cached only after its dependencies have been also handled.
109
- libraries.each do |library|
110
- verb "Binding #{library} as dependency of #{binary_path}"
111
- @@needed_libraries_cache << library
112
- end
117
+ @@needed_libraries_cache |= libraries
113
118
  end
114
119
 
115
120
  # Used by {#musl_needed_libraries}.
@@ -120,11 +125,13 @@ class Bwrap::Args::Library
120
125
  matches = library_data.match(/(.*) \(0x[0-9a-f]+\)/)
121
126
  library_path = matches[1]
122
127
 
123
- @needed_libraries << library_path
128
+ unless @needed_libraries.include? library_path
129
+ @needed_libraries << library_path
130
+ end
124
131
 
125
132
  # Also check if requisite libraries needs some libraries.
126
133
  inner = Bwrap::Args::Library.new
127
- @needed_libraries += inner.musl_needed_libraries library_path
134
+ @needed_libraries |= inner.musl_needed_libraries library_path
128
135
  end
129
136
  end
130
137
  # class Library ended
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "securerandom"
4
+ require "tempfile"
4
5
 
5
6
  require "bwrap/output"
6
7
  require_relative "args"
@@ -51,7 +52,7 @@ class Bwrap::Args::MachineId
51
52
  debug "Using random machine id as /etc/machine-id"
52
53
 
53
54
  @machine_id_file = Tempfile.new "bwrap-random_machine_id-", @config.tmpdir
54
- @machine_id_file.write SecureRandom.uuid.gsub("-", "")
55
+ @machine_id_file.write SecureRandom.uuid.delete("-", "")
55
56
  @machine_id_file.flush
56
57
 
57
58
  %W{ --ro-bind-data #{machine_id_file.fileno} /etc/machine-id }