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

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.
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 }