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

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