bwrap 1.0.0.pre.alpha1 → 1.0.0.pre.alpha5

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: e4193a0724aceb154c2e9be0809366ed2998837beeeb577dc101a58e20dca97d
4
- data.tar.gz: a927a6a6b8cb415f3983440ca0437b852582227f5b163f34143ffb375dc96a40
3
+ metadata.gz: dfb5f9bdbcf68df6068aebca204e72ef9272829b1f8db11c34e5a4da469cc2c7
4
+ data.tar.gz: 5f19c25ecad9b7f4923f80a6d61c87c6382671667f6b39eed0198befbb8eccd2
5
5
  SHA512:
6
- metadata.gz: f2f49d20cf15a331d34439d71ef038645bc2a5520407cdb519ea8f5e9e32db526dd7524ad35939acd8098a328d6a3c1c2542d5fde44d0adcfd8ae26f2313d5fb
7
- data.tar.gz: ee295629e684ef280652e38a94e7731b6e3227a2bac1150a79da268f3d2972eaac889ac1928103edf9d022c84430898ebc53d008a6f181d412d3f1ede441d0a9
6
+ metadata.gz: c9682aff2b43180fee0e8d7b2b7234d4bb516e86c85dcabc5b40a1047284f9f5bf9e0641dff80483e4645b8aa4bd06c9b9de8ba1b43cd78af3b77de1060c8ad6
7
+ data.tar.gz: cadd731077ff175b86cf0d56f17dd1c2cb3123e5d9b1dcff17156da5d706d7da12274bc59297ae925c1f07ea5b3a325c999c942048c2c5b165755934ae16d2f8
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -1,7 +1,37 @@
1
1
  # Changes
2
2
 
3
+ ## 1.0.0-alpha5 (29.11.2021)
4
+
5
+ * Execution#command_available?: support absolute paths
6
+ * Execution#which: support absolute paths
7
+ * Many miscellaneous fixes
8
+
9
+ ## 1.0.0-alpha4 (22.11.2021)
10
+
11
+ * Allow use without home directory set
12
+ * Bwrap#parse_command_line_arguments is no longer run when Bwrap is initialized
13
+ * Made pulseaudio optional
14
+ * Changed --share-net to be added only if requested
15
+ * Added Config#root= to specify path used as writable root
16
+ * Added Config#full_system_mounts to control whether library loaders are mounted inside chroot
17
+ * Added Config#libdir_mounts
18
+ * Added Config#features
19
+
20
+ ## 1.0.0-alpha3 (14.11.2021)
21
+
22
+ * Avoid frozen string literal modification
23
+
24
+ ## 1.0.0-alpha2 (14.11.2021)
25
+
26
+ * Made gem to tell its license (ISC)
27
+ * Gem building now allows newer bundler versions than 1.17
28
+ * Added Config#xorg_application option
29
+ * Properly resolve /etc/resolv.conf
30
+
3
31
  ## 1.0.0-alpha1 (10.11.2021)
4
32
 
33
+ * First working version
34
+
5
35
  ## 0.1.0 (unreleased)
6
36
 
7
37
  * First, currently unfinished and unreleased version. Work in Progress.
data/README.md CHANGED
@@ -31,3 +31,5 @@ Please see [API documentation](https://www.rubydoc.info/gems/bwrap) for usage in
31
31
  ## Contributing
32
32
 
33
33
  Bug reports and pull requests are welcome at https://git.sr.ht/~smar/ruby-bwrap.
34
+
35
+ Gerrit instance https://gerrit.smar.fi/admin/repos/ruby-bwrap can also be used.
@@ -1,32 +1,90 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "bwrap/execution"
4
+ require "bwrap/output"
3
5
  require_relative "args"
6
+ require_relative "library"
4
7
 
5
8
  # Bind arguments for bwrap.
6
- module Bwrap::Args::Bind
9
+ class Bwrap::Args::Bind
10
+ include Bwrap::Execution
11
+ include Bwrap::Output
12
+
13
+ # Array of parameters passed to bwrap.
14
+ attr_writer :args
15
+
16
+ # @see Bwrap::Args::Construct#command=
17
+ #
18
+ # @see (see Bwrap::Args::Construct#command=)
19
+ attr_writer :command
20
+
21
+ # Instance of {Config}.
22
+ attr_writer :config
23
+
24
+ # Instance of {Bwrap::Args::Environment}.
25
+ attr_writer :environment
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
+
7
67
  # Arguments to bind /dev/dri from host to sandbox.
8
- private def bind_dev_dri
9
- %w{ --dev-bind /dev/dri /dev/dri }
68
+ def bind_dev_dri
69
+ @args.append %w{ --dev-bind /dev/dri /dev/dri }
10
70
  end
11
71
 
12
72
  # Arguments to bind /sys/dev/char from host to sandbox.
13
- private def bind_sys_dev_char
14
- %w{ --ro-bind /sys/dev/char /sys/dev/char }
73
+ def bind_sys_dev_char
74
+ @args.append %w{ --ro-bind /sys/dev/char /sys/dev/char }
15
75
  end
16
76
 
17
77
  # Arguments to bind /sys/devices/pci0000:00 from host to sandbox.
18
- private def bind_pci_devices
19
- %w{ --ro-bind /sys/devices/pci0000:00 /sys/devices/pci0000:00 }
78
+ def bind_pci_devices
79
+ @args.append %w{ --ro-bind /sys/devices/pci0000:00 /sys/devices/pci0000:00 }
20
80
  end
21
81
 
22
82
  # Arguments to bind home directory from sandbox directory (`#{@config.sandbox_directory}/home`)
23
83
  # as `/home/#{@config.user}`.
24
84
  #
25
85
  # @note Requires @config.user to be set.
26
- private def bind_home_directory
27
- unless @config.user
28
- raise "Tried to bind user directory without user being set."
29
- end
86
+ def bind_home_directory
87
+ return unless @config.user
30
88
 
31
89
  home_directory = "#{@config.sandbox_directory}/home"
32
90
 
@@ -36,11 +94,12 @@ module Bwrap::Args::Bind
36
94
 
37
95
  @environment["HOME"] = "/home/#{@config.user}"
38
96
 
39
- %W{ --bind #{home_directory} /home/#{@config.user} }
97
+ debug "Using #{home_directory} as /home/#{@config.user}"
98
+ @args.append %W{ --bind #{home_directory} /home/#{@config.user} }
40
99
  end
41
100
 
42
101
  # Arguments to read-only bind whole system inside sandbox.
43
- private def full_system_mounts
102
+ def full_system_mounts
44
103
  bindir_mounts = []
45
104
  binaries_from = @config.binaries_from
46
105
  binaries_from.each do |path|
@@ -48,6 +107,38 @@ module Bwrap::Args::Bind
48
107
  end
49
108
  @environment["PATH"] = binaries_from.join(":")
50
109
 
110
+ @args.append bindir_mounts
111
+
112
+ if debug?
113
+ debug "Using following bindir mounts:\n" \
114
+ "#{bindir_mounts}\n" \
115
+ "(Odd is key, even is value)"
116
+ end
117
+
118
+ libdir_mounts
119
+
120
+ return unless @config.full_system_mounts
121
+
122
+ loader_binds
123
+ libs_command_requires
124
+ end
125
+
126
+ # These are something user can specify to do custom --ro-bind binds.
127
+ def custom_read_only_binds
128
+ return unless @config.ro_binds
129
+
130
+ binds = []
131
+ @config.ro_binds.each do |source_path, destination_path|
132
+ binds << "--ro-bind" << source_path.to_s << destination_path.to_s
133
+ end
134
+
135
+ @args.append binds
136
+ end
137
+
138
+ # Used by {#full_system_mounts}.
139
+ private def libdir_mounts
140
+ return unless @config.libdir_mounts
141
+
51
142
  libdir_mounts = %w{
52
143
  --ro-bind /lib /lib
53
144
  --ro-bind /lib64 /lib64
@@ -55,24 +146,58 @@ module Bwrap::Args::Bind
55
146
  --ro-bind /usr/lib64 /usr/lib64
56
147
  }
57
148
 
58
- system_mounts = bindir_mounts + libdir_mounts
59
149
  if debug?
60
- debug "Using following system mounts:\n" \
61
- "#{system_mounts}\n" \
150
+ debug "Using following libdir mounts:\n" \
151
+ "#{libdir_mounts}\n" \
62
152
  "(Odd is key, even is value)"
63
153
  end
64
- system_mounts
154
+
155
+ @args.append libdir_mounts
65
156
  end
66
157
 
67
- # These are something user can specify to do custom --ro-bind binds.
68
- private def custom_read_only_binds
69
- return [] unless @config.ro_binds
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
70
165
 
71
- binds = []
72
- @config.ro_binds.each do |source_path, destination_path|
73
- binds << "--ro-bind" << source_path.to_s << destination_path.to_s
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
74
199
  end
75
200
 
76
- binds
201
+ @args.append library_mounts
77
202
  end
78
- end
203
+ end
@@ -5,46 +5,56 @@ require "tempfile"
5
5
  require "bwrap/output"
6
6
  require_relative "bind"
7
7
  require_relative "environment"
8
+ require_relative "features"
8
9
  require_relative "machine_id"
9
10
  require_relative "mount"
10
11
 
11
12
  # Constructs arguments for bwrap execution.
12
13
  class Bwrap::Args::Construct
13
14
  include Bwrap::Output
14
- include Bwrap::Args::Bind
15
15
  include Bwrap::Args::Mount
16
16
 
17
17
  attr_writer :config
18
18
 
19
+ # Command that is executed inside bwrap sandbox.
20
+ #
21
+ # @note This is not used for anything vital, but some things, like
22
+ # setting {Config#full_system_mounts=} uses this to resolve some
23
+ # additional data.
24
+ #
25
+ # @param command [Array, String] Command with arguments
26
+ attr_writer :command
27
+
19
28
  # Constructs arguments for bwrap execution.
20
29
  def construct_bwrap_args
21
- @environment = Bwrap::Args::Environment.new
22
- @environment.config = @config
23
- @machine_id = Bwrap::Args::MachineId.new
24
- @machine_id.config = @config
30
+ @args = []
31
+ create_objects
25
32
 
26
- [
27
- xauthority_args,
28
- @machine_id.machine_id,
29
- resolv_conf,
30
- full_system_mounts,
31
- custom_read_only_binds,
32
- create_user_dir,
33
- read_only_pulseaudio,
34
- dev_mount,
35
- bind_dev_dri,
36
- bind_sys_dev_char,
37
- bind_pci_devices,
38
- proc_mount,
39
- tmp_as_tmpfs,
40
- bind_home_directory,
41
- "--unshare-all",
42
- share_net,
43
- hostname,
44
- @environment.environment_variables,
45
- "--die-with-parent",
46
- "--new-session"
47
- ]
33
+ root_mount
34
+ xauthority_args
35
+ machine_id = @machine_id.machine_id
36
+ @args.append machine_id if machine_id
37
+ resolv_conf
38
+ @bind.full_system_mounts
39
+ @features.feature_binds
40
+ @bind.custom_read_only_binds
41
+ create_user_dir
42
+ read_only_pulseaudio
43
+ dev_mount
44
+ @bind.bind_dev_dri
45
+ @bind.bind_sys_dev_char
46
+ @bind.bind_pci_devices
47
+ proc_mount
48
+ tmp_as_tmpfs
49
+ @bind.bind_home_directory
50
+ @args.append "--unshare-all"
51
+ share_net
52
+ hostname
53
+ @args.append @environment.environment_variables
54
+ @args.append "--die-with-parent"
55
+ @args.append "--new-session"
56
+
57
+ @args.compact
48
58
  end
49
59
 
50
60
  # Performs cleanup operations after execution.
@@ -52,37 +62,64 @@ class Bwrap::Args::Construct
52
62
  @machine_id&.cleanup
53
63
  end
54
64
 
65
+ # Used by {#construct_bwrap_args}.
66
+ private def create_objects
67
+ @bind = Bwrap::Args::Bind.new
68
+ @bind.args = @args
69
+ @bind.command = @command
70
+ @bind.config = @config
71
+
72
+ @environment = Bwrap::Args::Environment.new
73
+ @environment.config = @config
74
+ @bind.environment = @environment
75
+
76
+ @features = Bwrap::Args::Features.new
77
+ @features.args = @args
78
+ @features.config = @config
79
+
80
+ @machine_id = Bwrap::Args::MachineId.new
81
+ @machine_id.config = @config
82
+ end
83
+
55
84
  # Arguments for generating .Xauthority file.
56
85
  private def xauthority_args
86
+ return unless @config.xorg_application
87
+
57
88
  xauth_args = %W{ --ro-bind #{Dir.home}/.Xauthority #{Dir.home}/.Xauthority }
58
89
  debug "Binding following .Xauthority file: #{Dir.home}/.Xauthority"
59
- xauth_args
90
+ @args.append xauth_args
60
91
  end
61
92
 
62
93
  # Arguments to read-only bind /etc/resolv.conf.
63
94
  private def resolv_conf
64
- #source_resolv_conf = "/etc/resolv.conf"
65
- source_resolv_conf = "/run/netconfig/resolv.conf"
95
+ # We can’t really bind symlinks, so let’s resolve real path to resolv.conf, in case it is symlinked.
96
+ source_resolv_conf = Pathname.new "/etc/resolv.conf"
97
+ source_resolv_conf = source_resolv_conf.realpath
98
+
66
99
  debug "Binding #{source_resolv_conf} as /etc/resolv.conf"
67
- %w{ --ro-bind /run/netconfig/resolv.conf /etc/resolv.conf }
100
+ @args.append %W{ --ro-bind #{source_resolv_conf} /etc/resolv.conf }
68
101
  end
69
102
 
70
103
  # Arguments to create `/run/user/#{uid}`.
71
104
  private def create_user_dir
72
105
  trace "Creating directory /run/user/#{uid}"
73
- %W{ --dir /run/user/#{uid} }
106
+ @args.append %W{ --dir /run/user/#{uid} }
74
107
  end
75
108
 
76
109
  # Arguments to bind necessary pulseaudio data for audio support.
77
110
  private def read_only_pulseaudio
111
+ return unless @config.audio.include? :pulseaudio
112
+
78
113
  debug "Binding pulseaudio"
79
- %W{ --ro-bind /run/user/#{uid}/pulse /run/user/#{uid}/pulse }
114
+ @args.append %W{ --ro-bind /run/user/#{uid}/pulse /run/user/#{uid}/pulse }
80
115
  end
81
116
 
82
117
  # Arguments to allow network connection inside sandbox.
83
118
  private def share_net
119
+ return unless @config.share_net
120
+
84
121
  verb "Sharing network"
85
- %w{ --share-net }
122
+ @args.append %w{ --share-net }
86
123
  end
87
124
 
88
125
  # Arguments to set hostname to whatever is configured.
@@ -90,7 +127,7 @@ class Bwrap::Args::Construct
90
127
  return unless @config.hostname
91
128
 
92
129
  debug "Setting hostname to #{@config.hostname}"
93
- %W{ --hostname #{@config.hostname} }
130
+ @args.append %W{ --hostname #{@config.hostname} }
94
131
  end
95
132
 
96
133
  # Returns current user id.
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bwrap/output"
4
+ require_relative "args"
5
+ require_relative "library"
6
+
7
+ # Feature parameter construction.
8
+ #
9
+ # @see Config::Features
10
+ class Bwrap::Args::Features < Hash
11
+ include Bwrap::Output
12
+
13
+ # @api internal
14
+ class RubyBinds
15
+ attr_reader :mounts
16
+
17
+ # Bind system paths so scripts works inside sandbox.
18
+ def sitedir_mounts
19
+ mounts = []
20
+ mounts << "--ro-bind" << RbConfig::CONFIG["sitedir"] << RbConfig::CONFIG["sitedir"]
21
+ mounts << "--ro-bind" << RbConfig::CONFIG["rubyhdrdir"] << RbConfig::CONFIG["rubyhdrdir"]
22
+ mounts << "--ro-bind" << RbConfig::CONFIG["rubylibdir"] << RbConfig::CONFIG["rubylibdir"]
23
+ mounts << "--ro-bind" << RbConfig::CONFIG["vendordir"] << RbConfig::CONFIG["vendordir"]
24
+
25
+ mounts
26
+ end
27
+
28
+ # Create binds for required system libraries.
29
+ #
30
+ # These are in path like /usr/lib64/ruby/2.5.0/x86_64-linux-gnu/,
31
+ # and as they are mostly shared libraries, they may have some extra
32
+ # dependencies that also need to be bound inside the sandbox.
33
+ def stdlib_mounts stdlib
34
+ library_mounts = []
35
+ library = Bwrap::Args::Library.new
36
+ stdlib.each do |lib|
37
+ path = "#{RbConfig::CONFIG["rubyarchdir"]}/#{lib}.so"
38
+
39
+ library.needed_libraries(path).each do |requisite_library|
40
+ library_mounts << "--ro-bind" << requisite_library << requisite_library
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ # {Array} of parameters passed to bwrap.
47
+ attr_writer :args
48
+
49
+ # Instance of {Config}.
50
+ attr_writer :config
51
+
52
+ def feature_binds
53
+ ruby_binds
54
+ end
55
+
56
+ # @note This does not allow development headers needed for compilation for now.
57
+ # I’ll look at it after I have an use for it.
58
+ private def ruby_binds
59
+ return unless @config.features.ruby.enabled?
60
+
61
+ binds = RubyBinds.new
62
+
63
+ @args.append binds.sitedir_mounts
64
+ @args.append binds.stdlib_mounts @config.features.ruby.stdlib
65
+ end
66
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bwrap/execution"
4
+ require "bwrap/output"
5
+ require_relative "args"
6
+
7
+ # Class to clean up namespace for implementation specific reasons.
8
+ #
9
+ # @api internal
10
+ class Bwrap::Args::Library
11
+ include Bwrap::Execution
12
+ include Bwrap::Output
13
+
14
+ # NOTE: This caching can be made more efficient, but need to look at it later, what to do about it.
15
+ @@needed_libraries_cache ||= []
16
+
17
+ # Otherwise similar to {#needed_libraries}, but checks used libc to handle musl executables.
18
+ #
19
+ # @param executable [String] Path to the executable to find dependencies for
20
+ def libraries_needed_by executable
21
+ # %i == interpreter, the library used to load the executable by kernel.
22
+ # %F == Path to given file.
23
+ output_format = "%i::SEPARATOR::%F"
24
+ scanelf_command = %W{ scanelf --nobanner --quiet --format #{output_format} }
25
+ scanelf_command << executable
26
+
27
+ data = execvalue scanelf_command
28
+ data = data.strip
29
+ interpreter, _executable_path = data.split "::SEPARATOR::"
30
+ interpreter = Pathname.new interpreter
31
+
32
+ if interpreter.basename.to_s[0..6] == "ld-musl"
33
+ musl_needed_libraries executable
34
+ else
35
+ # For glibc, scanelf can return full paths for us most of time.
36
+ needed_libraries executable
37
+ end
38
+ end
39
+
40
+ # @param binary_paths [String, Array] one or more paths to be resolved
41
+ #
42
+ # @todo Maybe caching should be done here too?
43
+ def musl_needed_libraries binary_paths
44
+ trace "Finding musl libraries #{binary_paths} requires"
45
+ @needed_libraries = []
46
+
47
+ if binary_paths.is_a? String
48
+ binary_paths = [ binary_paths ]
49
+ end
50
+
51
+ binary_paths.each do |binary_path|
52
+ output = execvalue %W{ ldd #{binary_path} }
53
+ lines = output.split "\n"
54
+ _interpreter_line = lines.shift
55
+
56
+ lines.each do |line|
57
+ parse_ldd_line line
58
+ end
59
+ end
60
+
61
+ @needed_libraries
62
+ end
63
+
64
+ # Used by {Bwrap::Args::Bind#libs_command_requires}.
65
+ #
66
+ # @param binary_paths [String, Array] one or more paths to be resolved
67
+ def needed_libraries binary_paths
68
+ trace "Finding libraries #{binary_paths} requires"
69
+ @needed_libraries = []
70
+
71
+ # %i == interpreter, the library used to load the executable by kernel.
72
+ output_format = "%F::SEPARATOR::%n"
73
+ scanelf_command = %W{ scanelf --nobanner --quiet --format #{output_format} --ldcache --needed }
74
+
75
+ if binary_paths.is_a? String
76
+ binary_paths = [ binary_paths ]
77
+ end
78
+
79
+ # Check if the exe is already resolved.
80
+ binary_paths.delete_if do |binary_path|
81
+ @@needed_libraries_cache.include? binary_path
82
+ end
83
+
84
+ return [] if binary_paths.empty?
85
+
86
+ data = execvalue(scanelf_command + binary_paths)
87
+ trace "scanelf found following libraries: #{data}"
88
+
89
+ lines = data.split "\n"
90
+ lines.each do |line|
91
+ parse_scanelf_line line
92
+ end
93
+
94
+ @needed_libraries
95
+ end
96
+
97
+ # Used by {#needed_libraries}.
98
+ private def parse_scanelf_line line
99
+ binary_path, libraries_line = line.split "::SEPARATOR::"
100
+ libraries = libraries_line.split ","
101
+
102
+ @needed_libraries += libraries
103
+
104
+ # Also check if requisite libraries needs some libraries.
105
+ inner = Bwrap::Args::Library.new
106
+ @needed_libraries += inner.needed_libraries libraries
107
+
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
113
+ end
114
+
115
+ # Used by {#musl_needed_libraries}.
116
+ private def parse_ldd_line line
117
+ line = line.strip
118
+ _library_name, library_data = line.split " => "
119
+
120
+ matches = library_data.match(/(.*) \(0x[0-9a-f]+\)/)
121
+ library_path = matches[1]
122
+
123
+ @needed_libraries << library_path
124
+
125
+ # Also check if requisite libraries needs some libraries.
126
+ inner = Bwrap::Args::Library.new
127
+ @needed_libraries += inner.musl_needed_libraries library_path
128
+ end
129
+ end
130
+ # class Library ended
@@ -24,7 +24,7 @@ class Bwrap::Args::MachineId
24
24
  # Returning [] means that execute() will ignore this fully.
25
25
  # Nil would be converted to empty string, causing spawn() to pass it as argument, causing
26
26
  # bwrap to misbehave.
27
- return [] unless @config.machine_id
27
+ return unless @config.machine_id
28
28
 
29
29
  machine_id = @config.machine_id
30
30
 
@@ -4,21 +4,29 @@ require_relative "args"
4
4
 
5
5
  # Bind arguments for bwrap.
6
6
  module Bwrap::Args::Mount
7
+ # Arguments for readwrite-binding {Config#root} as /.
8
+ private def root_mount
9
+ return unless @config.root
10
+
11
+ debug "Binding #{@config.root} as /"
12
+ @args.append %W{ --bind #{@config.root} / }
13
+ end
14
+
7
15
  # Arguments for mounting devtmpfs to /dev.
8
16
  private def dev_mount
9
17
  debug "Mounting new devtmpfs to /dev"
10
- %w{ --dev /dev }
18
+ @args.append %w{ --dev /dev }
11
19
  end
12
20
 
13
21
  # Arguments for mounting procfs to /proc.
14
22
  private def proc_mount
15
23
  debug "Mounting new procfs to /proc"
16
- %w{ --proc /proc }
24
+ @args.append %w{ --proc /proc }
17
25
  end
18
26
 
19
27
  # Arguments for mounting tmpfs to /tmp.
20
28
  private def tmp_as_tmpfs
21
29
  debug "Mounting tmpfs to /tmp"
22
- %w{ --tmpfs /tmp }
30
+ @args.append %w{ --tmpfs /tmp }
23
31
  end
24
32
  end
data/lib/bwrap/config.rb CHANGED
@@ -24,13 +24,54 @@ class Bwrap::Config
24
24
  # Given file as bound as /etc/machine_id.
25
25
  attr_accessor :machine_id
26
26
 
27
+ # Name of the user inside chroot.
28
+ #
29
+ # This is optional and defaults to no user.
27
30
  attr_accessor :user
28
31
 
32
+ # Set to true to indicate we’re running a X.org application, meaning we need to do some extra holes,
33
+ # like binding .Xauthority.
34
+ #
35
+ # @return [Boolean] Whether Xorg specific binds are used.
36
+ attr_accessor :xorg_application
37
+
38
+ # Array of audio schemes usable inside chroot.
39
+ #
40
+ # Currently supports:
41
+ # - :pulseaudio
42
+ #
43
+ attr_accessor :audio
44
+
45
+ # @return [Boolean] true if network should be shared from host.
46
+ attr_accessor :share_net
47
+
48
+ # TODO: This should cause Bind#full_system_mounts to mount /lib64/ld-linux-x86-64.so.2 and so on,
49
+ # probably according executable type of specified command. But that needs some magic.
50
+ #
51
+ # For now this just mounts all relevant files it can find.
52
+ #
53
+ # @return [Boolean] true if Linux library loaders are mounted inside chroot
54
+ attr_accessor :full_system_mounts
55
+
56
+ # Set to true if basic system directories, like /usr/lib and /usr/lib64,
57
+ # should be bound inside chroot.
58
+ #
59
+ # /usr/bin can be mounted using {Config#binaries_from=}.
60
+ #
61
+ # @return [Boolean] true if libdirs are mounted to the chroot
62
+ attr_accessor :libdir_mounts
63
+
29
64
  # Array of directories to be bind mounted and used to construct PATH environment variable.
30
65
  attr_reader :binaries_from
31
66
 
67
+ # TODO: Document this.
68
+ # TODO: I wonder if this should just be removed. I don’t know, this is a bit ...
69
+ # Well, I can see it can have some merit, but very hard to say.
32
70
  attr_reader :sandbox_directory
33
71
 
72
+ # Use given directory as root. End result is similar to classic chroot.
73
+ attr_reader :root
74
+
34
75
  # `Hash`[`Pathname`] => `Pathname` containing custom read-only binds.
35
76
  attr_reader :ro_binds
36
77
 
@@ -39,9 +80,63 @@ class Bwrap::Config
39
80
  # Defaults to Dir.tmpdir.
40
81
  attr_reader :tmpdir
41
82
 
83
+ # Methods to enable or disable feature sets to control various aspects of sandboxing.
84
+ class Features
85
+ # Defines Ruby feature set.
86
+ class Ruby
87
+ # @return [Array] list of needed libraries.
88
+ attr_reader :stdlib
89
+
90
+ def initialize
91
+ @stdlib = []
92
+ end
93
+
94
+ # @see enabled=
95
+ def enabled?
96
+ @enabled
97
+ end
98
+
99
+ # Enable Ruby feature set.
100
+ #
101
+ # Among others, binds RbConfig::CONFIG["sitedir"] so scripts works.
102
+ #
103
+ # @note This does not allow development headers needed for compilation for now.
104
+ # I’ll look at it after I have an use for it.
105
+ def enable
106
+ @enabled = true
107
+ end
108
+
109
+ # Disable Ruby feature set.
110
+ def disable
111
+ @enabled = false
112
+ end
113
+
114
+ # Extra libraries to be loaded from `RbConfig::CONFIG["rubyarchdir"]`.
115
+ #
116
+ # @note This is only required to be called if extra dependencies are necessary.
117
+ # For example, psych.so requires libyaml.so.
118
+ def stdlib= libs
119
+ # Just a little check to have error earlier.
120
+ libs.each do |lib|
121
+ unless File.exist? "#{RbConfig::CONFIG["rubyarchdir"]}/#{lib}.so"
122
+ raise "Library “#{lib}” passed to Bwrap::Config::Ruby.stdlib= does not exist."
123
+ end
124
+ end
125
+
126
+ @stdlib = libs
127
+ end
128
+ end
129
+
130
+ # @return [Ruby] Instance of feature class for Ruby
131
+ def ruby
132
+ @ruby ||= Ruby.new
133
+ end
134
+ end
135
+
42
136
  def initialize
43
137
  @binaries_from = []
44
138
  @tmpdir = Dir.tmpdir
139
+ @audio = []
45
140
  end
46
141
 
47
142
  def binaries_from= array
@@ -55,6 +150,17 @@ class Bwrap::Config
55
150
  end
56
151
  end
57
152
 
153
+ # Enable or disable feature sets to control various aspects of sandboxing.
154
+ #
155
+ # @example To enable Ruby feature set
156
+ # @config.features.ruby = true
157
+ #
158
+ # @see {Features} List of available features
159
+ # @return [Features] Object used to toggle features
160
+ def features
161
+ @features ||= ::Bwrap::Config::Features.new
162
+ end
163
+
58
164
  def sandbox_directory= directory
59
165
  unless Dir.exist? directory
60
166
  raise "Given sandbox directory #{directory} does not exist. Please create it beforehand and setup to your needs."
@@ -63,6 +169,15 @@ class Bwrap::Config
63
169
  @sandbox_directory = directory
64
170
  end
65
171
 
172
+ # Directory used as writable root, akin to classic chroot.
173
+ def root= directory
174
+ unless Dir.exist? directory
175
+ raise "Given root directory #{directory} does not exist. Please create it beforehand and set up to your needs."
176
+ end
177
+
178
+ @root = directory
179
+ end
180
+
66
181
  # Set given hash of paths to be bound with --ro-bind.
67
182
  #
68
183
  # Key is source path, value is destination path.
@@ -95,7 +95,7 @@ class Bwrap::Execution::Execute
95
95
  # Stub to instruct implementation in subclass.
96
96
  def self.prepend_rootcmd command, rootcmd:
97
97
  raise NotImplementedError, "If rootcmd execution is necessary, monkey patch Bwrap::Execution::Execute " \
98
- "to add “self.prepend_rootcmd(command, rootcmd:)” method."
98
+ "to add “self.prepend_rootcmd(command, rootcmd:)” method."
99
99
  end
100
100
 
101
101
  # Used by `#handle_logging`.
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bwrap/output"
4
+
5
+ # Path checking methods.
6
+ module Bwrap::Execution::Path
7
+ # @api internal
8
+ class Environment
9
+ def self.each_env_path command
10
+ exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [ "" ]
11
+
12
+ ENV["PATH"].split(File::PATH_SEPARATOR).each do |env_path|
13
+ exts.each do |ext|
14
+ exe = File.join(env_path, "#{command}#{ext}")
15
+ yield exe
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ # Check if requested program can be found.
22
+ #
23
+ # Should work cross-platform and in restricted environents pretty well.
24
+ private def command_available? command
25
+ # Special handling for absolute paths.
26
+ path = Pathname.new command
27
+ if path.absolute?
28
+ if path.executable? && !path.directory?
29
+ return true
30
+ end
31
+
32
+ return false
33
+ end
34
+
35
+ Bwrap::Execution::Path::Environment.each_env_path command do |exe|
36
+ return true if File.executable?(exe) && !File.directory?(exe)
37
+ end
38
+
39
+ false
40
+ end
41
+
42
+ # Returns path to given executable.
43
+ private def which command, fail: true
44
+ # Special handling for absolute paths.
45
+ path = Pathname.new command
46
+ if path.absolute?
47
+ if path.executable?
48
+ return command
49
+ end
50
+
51
+ raise CommandNotFound.new command: command if fail
52
+
53
+ return nil
54
+ end
55
+
56
+ Bwrap::Execution::Path::Environment.each_env_path command do |exe|
57
+ return exe if File.executable?(exe) && !File.directory?(exe)
58
+ end
59
+
60
+ return nil unless fail
61
+
62
+ raise CommandNotFound.new command: command
63
+ end
64
+ end
@@ -3,12 +3,14 @@
3
3
  require "bwrap/version"
4
4
  require "bwrap/output"
5
5
  require_relative "execution/execute"
6
+ require_relative "execution/path"
6
7
 
7
8
  # @abstract Module to be included in a class that needs to execute commands.
8
9
  #
9
10
  # Methods to execute processes.
10
11
  module Bwrap::Execution
11
12
  include Bwrap::Output
13
+ include Bwrap::Execution::Path
12
14
 
13
15
  # Unspecified execution related error.
14
16
  class CommandError < StandardError
@@ -18,6 +20,19 @@ module Bwrap::Execution
18
20
  class ExecutionFailed < CommandError
19
21
  end
20
22
 
23
+ # Thrown if given command was not found.
24
+ class CommandNotFound < CommandError
25
+ # Command that was looked at.
26
+ attr_reader :command
27
+
28
+ def initialize command:
29
+ @command = command
30
+ msg = "Failed to find #{command} from PATH."
31
+
32
+ super msg
33
+ end
34
+ end
35
+
21
36
  # Actual implementation of execution command. Can be used when static method is needed.
22
37
  #
23
38
  # @note When an array is given as a command, empty strings are passed as empty arguments.
@@ -83,33 +98,6 @@ module Bwrap::Execution
83
98
  @last_status
84
99
  end
85
100
 
86
- # Check if requested program can be found.
87
- #
88
- # Should work cross-platform and in restricted environents pretty well.
89
- private def command_available? command
90
- exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [ "" ]
91
- ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
92
- exts.each do |ext|
93
- exe = File.join(path, "#{command}#{ext}")
94
- return true if File.executable?(exe) && !File.directory?(exe)
95
- end
96
- end
97
- false
98
- end
99
-
100
- # Returns path to given executable.
101
- private def which command, fail: true
102
- exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [ "" ]
103
- ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
104
- exts.each do |ext|
105
- exe = File.join(path, "#{command}#{ext}")
106
- return exe if File.executable?(exe) && !File.directory?(exe)
107
- end
108
- end
109
- error "Failed to find #{command} from PATH." if fail
110
- nil
111
- end
112
-
113
101
  # Execute a command.
114
102
  #
115
103
  # This method can be used by including Execution module in a class that should be able to
@@ -1,4 +1,4 @@
1
- # frozen_string_literal: false
1
+ # frozen_string_literal: true
2
2
 
3
3
  # force_encoding modifies string, so can’t freeze strings.
4
4
 
@@ -15,12 +15,12 @@ class Bwrap::Output::Log
15
15
 
16
16
  # Writes given string to log.
17
17
  def self.write_to_log str
18
- @@log_file&.write str.force_encoding("UTF-8")
18
+ @@log_file&.write str.dup.force_encoding("UTF-8")
19
19
  end
20
20
 
21
21
  # Writes given string to log.
22
22
  def self.puts_to_log str
23
- @@log_file&.puts str.force_encoding("UTF-8")
23
+ @@log_file&.puts str.dup.force_encoding("UTF-8")
24
24
  end
25
25
 
26
26
  # Closes an open log file.
data/lib/bwrap/output.rb CHANGED
@@ -81,13 +81,16 @@ module Bwrap::Output
81
81
  # Aborts current process.
82
82
  #
83
83
  # Use this instead of Ruby’s #exit in order to filter out dummy #exit calls from the code.
84
- def self.error_output str = nil, label: :unspecified, log_callback: 1
84
+ def self.error_output str = nil, label: :unspecified, log_callback: 1, raise_exception: false
85
85
  unless str.nil?
86
86
  out = Bwrap::Output::Levels.error_print_formatted str, log_callback: (log_callback + 1)
87
87
  Bwrap::Output::Log.puts_to_log out
88
88
  end
89
89
 
90
- exit Bwrap::Execution::Labels.resolve_exit_code(label)
90
+ exit_code = Bwrap::Execution::Labels.resolve_exit_code(label)
91
+ raise str if raise_exception
92
+
93
+ exit exit_code
91
94
  end
92
95
 
93
96
  # @return true if --verbose, --debug or --trace has been passed, false if not.
@@ -147,7 +150,8 @@ module Bwrap::Output
147
150
  #
148
151
  # @param str String to be outputted
149
152
  # @param label [Symbol] Exit label accepted by {Bwrap::Execution.resolve_exit_code}
150
- private def error str = nil, label: :unspecified
151
- Bwrap::Output.error_output(str, label: label, log_callback: 2)
153
+ # @param raise [Boolean] if true, an exception is raised instead of just existing with exit code.
154
+ private def error str = nil, label: :unspecified, raise_exception: false
155
+ Bwrap::Output.error_output(str, label: label, log_callback: 2, raise_exception: raise_exception)
152
156
  end
153
157
  end
data/lib/bwrap/version.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  # bwrap command runner tools.
4
4
  module Bwrap
5
5
  # Current version of bwrap.
6
- VERSION = "1.0.0-alpha1"
6
+ VERSION = "1.0.0-alpha5"
7
7
  end
8
8
 
9
- require "deep-cover" if ENV["DEEP_COVER"]
9
+ require "deep-cover" if ENV["DEEP_COVER"]
data/lib/bwrap.rb CHANGED
@@ -15,8 +15,13 @@ class Bwrap::Bwrap
15
15
 
16
16
  def initialize config
17
17
  @config = config
18
+ end
19
+
20
+ # Parses command line arguments given to caller script.
21
+ def parse_command_line_arguments
22
+ options = parse_options
18
23
 
19
- parse_command_line_arguments
24
+ Bwrap::Output.handle_output_options options
20
25
  end
21
26
 
22
27
  # Runs given command inside bwrap.
@@ -24,29 +29,27 @@ class Bwrap::Bwrap
24
29
  # @param command [String, Array] Command, with necessary arguments, to be executed inside bwrap
25
30
  def run command
26
31
  construct = Bwrap::Args::Construct.new
32
+ construct.command = command
27
33
  construct.config = @config
28
34
  bwrap_args = construct.construct_bwrap_args
29
35
 
30
36
  exec_command = [ "bwrap" ]
31
37
  exec_command += bwrap_args
32
- exec_command += %W{ #{command} --new-instance }
33
- exec_command += ARGV unless ARGV.empty?
38
+ exec_command.append command
39
+ exec_command += @cli_args if @cli_args
34
40
 
35
41
  execute exec_command
36
42
 
37
43
  construct.cleanup
38
44
  end
39
45
 
40
- # Parses command line arguments given to caller script.
41
- private def parse_command_line_arguments
42
- options = optimist_cli_args
43
-
44
- Bwrap::Output.handle_output_options options
45
- end
46
-
47
46
  # Parses global bwrap flags using Optimist.
48
- private def optimist_cli_args
49
- Optimist.options do
47
+ #
48
+ # Sets instance variable `@cli_args`.
49
+ #
50
+ # @return [Hash] options parsed by {Optimist.options}
51
+ private def parse_options
52
+ options = Optimist.options do
50
53
  version ::Bwrap::VERSION
51
54
 
52
55
  banner "Usage:"
@@ -70,5 +73,9 @@ class Bwrap::Bwrap
70
73
 
71
74
  educate_on_error
72
75
  end
76
+
77
+ @cli_args = ARGV.dup
78
+
79
+ options
73
80
  end
74
81
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bwrap
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.alpha1
4
+ version: 1.0.0.pre.alpha5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samu Voutilainen
@@ -34,7 +34,7 @@ cert_chain:
34
34
  X4ioQwEn1/9tHs19VO1CLF58451HgEo1BXd7eWLmV1V5cqw0YWok1ly4L/Su/Phf
35
35
  MRxVMHiVAqY=
36
36
  -----END CERTIFICATE-----
37
- date: 2021-11-14 00:00:00.000000000 Z
37
+ date: 2021-11-29 00:00:00.000000000 Z
38
38
  dependencies:
39
39
  - !ruby/object:Gem::Dependency
40
40
  name: optimist
@@ -54,16 +54,16 @@ dependencies:
54
54
  name: bundler
55
55
  requirement: !ruby/object:Gem::Requirement
56
56
  requirements:
57
- - - "~>"
57
+ - - ">="
58
58
  - !ruby/object:Gem::Version
59
- version: '1.17'
59
+ version: '1.16'
60
60
  type: :development
61
61
  prerelease: false
62
62
  version_requirements: !ruby/object:Gem::Requirement
63
63
  requirements:
64
- - - "~>"
64
+ - - ">="
65
65
  - !ruby/object:Gem::Version
66
- version: '1.17'
66
+ version: '1.16'
67
67
  - !ruby/object:Gem::Dependency
68
68
  name: rake
69
69
  requirement: !ruby/object:Gem::Requirement
@@ -120,7 +120,8 @@ dependencies:
120
120
  - - "~>"
121
121
  - !ruby/object:Gem::Version
122
122
  version: '3.7'
123
- description: For now this just reserves this name. Please be back later.
123
+ description: For now this is tailored to my needs, so this may or may not be of any
124
+ use.
124
125
  email:
125
126
  - smar@smar.fi
126
127
  executables: []
@@ -135,6 +136,8 @@ files:
135
136
  - lib/bwrap/args/bind.rb
136
137
  - lib/bwrap/args/construct.rb
137
138
  - lib/bwrap/args/environment.rb
139
+ - lib/bwrap/args/features.rb
140
+ - lib/bwrap/args/library.rb
138
141
  - lib/bwrap/args/machine_id.rb
139
142
  - lib/bwrap/args/mount.rb
140
143
  - lib/bwrap/config.rb
@@ -142,6 +145,7 @@ files:
142
145
  - lib/bwrap/execution/execute.rb
143
146
  - lib/bwrap/execution/execution.rb
144
147
  - lib/bwrap/execution/labels.rb
148
+ - lib/bwrap/execution/path.rb
145
149
  - lib/bwrap/output.rb
146
150
  - lib/bwrap/output/colors.rb
147
151
  - lib/bwrap/output/levels.rb
@@ -149,11 +153,13 @@ files:
149
153
  - lib/bwrap/output/output.rb
150
154
  - lib/bwrap/version.rb
151
155
  homepage: https://git.sr.ht/~smar/ruby-bwrap
152
- licenses: []
156
+ licenses:
157
+ - ISC
153
158
  metadata:
154
159
  homepage_uri: https://git.sr.ht/~smar/ruby-bwrap
155
160
  source_code_uri: https://git.sr.ht/~smar/ruby-bwrap
156
161
  changelog_uri: https://git.sr.ht/~smar/ruby-bwrap/tree/master/item/CHANGELOG.md
162
+ rubygems_mfa_required: 'false'
157
163
  post_install_message:
158
164
  rdoc_options: []
159
165
  require_paths:
@@ -173,5 +179,5 @@ rubyforge_project:
173
179
  rubygems_version: 2.7.6.3
174
180
  signing_key:
175
181
  specification_version: 4
176
- summary: 'THIS WILL BE IN FUTURE: Framework to create commands for bwrap'
182
+ summary: Framework to create commands for bwrap
177
183
  test_files: []
metadata.gz.sig CHANGED
Binary file