bwrap 1.1.1 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +21 -0
- data/README.md +9 -0
- data/lib/bwrap/args/args.rb +33 -4
- data/lib/bwrap/args/bind/device.rb +48 -0
- data/lib/bwrap/args/bind/library.rb +63 -47
- data/lib/bwrap/args/bind.rb +39 -41
- data/lib/bwrap/args/construct.rb +27 -36
- data/lib/bwrap/args/environment.rb +5 -1
- data/lib/bwrap/args/features/ruby_binds.rb +3 -1
- data/lib/bwrap/args/features.rb +3 -4
- data/lib/bwrap/args/mount.rb +1 -7
- data/lib/bwrap/args/namespace.rb +25 -0
- data/lib/bwrap/args/network.rb +7 -2
- data/lib/bwrap/args/user.rb +36 -0
- data/lib/bwrap/bwrap.rb +58 -26
- data/lib/bwrap/config.rb +52 -9
- data/lib/bwrap/exceptions.rb +1 -0
- data/lib/bwrap/execution/exec.rb +78 -0
- data/lib/bwrap/execution/execute.rb +25 -43
- data/lib/bwrap/execution/execution.rb +77 -50
- data/lib/bwrap/execution/logging.rb +49 -0
- data/lib/bwrap/execution/popen2e.rb +84 -12
- data/lib/bwrap/execution.rb +1 -0
- data/lib/bwrap/resolvers/executable.rb +70 -0
- data/lib/bwrap/resolvers/library/base.rb +22 -0
- data/lib/bwrap/resolvers/library/library.rb +74 -0
- data/lib/bwrap/resolvers/library/llvm_readelf.rb +133 -0
- data/lib/bwrap/resolvers/library/musl.rb +54 -0
- data/lib/bwrap/resolvers/library.rb +12 -0
- data/lib/bwrap/resolvers/mime.rb +75 -0
- data/lib/bwrap/resolvers/resolvers.rb +7 -0
- data/lib/bwrap/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +29 -18
- metadata.gz.sig +0 -0
- data/lib/bwrap/args/bind/mime.rb +0 -65
- data/lib/bwrap/args/library.rb +0 -135
@@ -3,7 +3,9 @@
|
|
3
3
|
require "bwrap/version"
|
4
4
|
require "bwrap/output"
|
5
5
|
require_relative "exceptions"
|
6
|
+
require_relative "exec"
|
6
7
|
require_relative "execute"
|
8
|
+
require_relative "logging"
|
7
9
|
require_relative "path"
|
8
10
|
require_relative "popen2e"
|
9
11
|
|
@@ -36,68 +38,88 @@ module Bwrap::Execution
|
|
36
38
|
#
|
37
39
|
# fail == If true, an error is raised in case the command returns failure code.
|
38
40
|
#
|
39
|
-
# @
|
41
|
+
# @option kwargs [Boolean]
|
42
|
+
# clear_env (false)
|
43
|
+
# If true, executed command will run with no environment variables
|
44
|
+
# @option kwargs [Bwrap::Config|nil]
|
45
|
+
# config (nil)
|
46
|
+
# Configuration used to launch the command inside bubblewrap
|
47
|
+
# @option kwargs [Boolean]
|
48
|
+
# direct_output (false)
|
49
|
+
# Instead of buffering output, it will be directly outputted to `$stdout`
|
50
|
+
# @option kwargs [Hash<String, String>]
|
51
|
+
# env ({})
|
52
|
+
# Environment variables to add to the command. Also see `clear_env` option
|
53
|
+
# @option kwargs [:show|nil]
|
54
|
+
# error (nil)
|
55
|
+
# if :show and execution failed, prints output of the command with {#warn}
|
56
|
+
# @option kwargs [Boolean]
|
57
|
+
# fail (true)
|
58
|
+
# If true, {ExecutionFailed} is raised if process did not finish successfully
|
59
|
+
# @option kwargs [Boolean]
|
60
|
+
# log (true)
|
61
|
+
# If true, prints output if verbose flag has been set.
|
62
|
+
# And if a log file has been configured, also logs to that file
|
63
|
+
# @option kwargs [Integer]
|
64
|
+
# log_callback (1)
|
65
|
+
# Semi-internal variable used to tailor caller in debug messages
|
66
|
+
# @option kwargs [Array|Symbol|nil]
|
67
|
+
# rootcmd (nil)
|
68
|
+
# Flags used to construct bubblewrap environment
|
69
|
+
# @option kwargs [Boolean]
|
70
|
+
# wait (true)
|
71
|
+
# If true, this method blocks until the command has finished.
|
72
|
+
# Otherwise returns immediately. It is possible for user to
|
73
|
+
# call `Process.wait` to wait for result at suitable place
|
74
|
+
#
|
75
|
+
# TODO: log kwarg could take log level as argument. Like :trace.
|
40
76
|
#
|
41
77
|
# @see #execute
|
42
|
-
def self.do_execute command,
|
43
|
-
|
44
|
-
|
45
|
-
log: true,
|
46
|
-
direct_output: false,
|
47
|
-
env: {},
|
48
|
-
clear_env: false,
|
49
|
-
error: nil,
|
50
|
-
log_callback: 2,
|
51
|
-
rootcmd: nil
|
52
|
-
command = Execute.format_command command, rootcmd: rootcmd
|
53
|
-
Execute.handle_logging command, log_callback: log_callback, log: log, dry_run: @dry_run
|
54
|
-
return if @dry_run || Bwrap::Execution::Execute.dry_run
|
78
|
+
def self.do_execute command, **kwargs
|
79
|
+
executor = Exec.new command
|
80
|
+
executor.dry_run = @dry_run
|
55
81
|
|
56
|
-
|
82
|
+
executor.clear_env = kwargs.key?(:clear_env) ? kwargs.delete(:clear_env) : false
|
83
|
+
executor.config = kwargs.key?(:config) ? kwargs.delete(:config) : nil
|
84
|
+
executor.direct_output = kwargs.key?(:direct_output) ? kwargs.delete(:direct_output) : false
|
85
|
+
executor.env = kwargs.key?(:env) ? kwargs.delete(:env) : {}
|
86
|
+
executor.error = kwargs.key?(:error) ? kwargs.delete(:error) : nil
|
87
|
+
executor.fail = kwargs.key?(:fail) ? kwargs.delete(:fail) : true
|
88
|
+
executor.log = kwargs.key?(:log) ? kwargs.delete(:log) : true
|
89
|
+
executor.log_callback = kwargs.key?(:log_callback) ? kwargs.delete(:log_callback) : 1
|
90
|
+
executor.rootcmd = kwargs.key?(:rootcmd) ? kwargs.delete(:rootcmd) : nil
|
91
|
+
executor.wait = kwargs.key?(:wait) ? kwargs.delete(:wait) : true
|
57
92
|
|
58
|
-
|
59
|
-
|
60
|
-
if command.respond_to? :flatten!
|
61
|
-
command.flatten!
|
93
|
+
unless kwargs.empty?
|
94
|
+
raise ArgumentError, %{unknown keywords: #{kwargs.keys.join ","}}
|
62
95
|
end
|
63
96
|
|
64
|
-
|
65
|
-
# This causes spawning work correctly, as that’s how spawn() expects to have the arguments.
|
66
|
-
pid = spawn(env, *command, err: [ :child, :out ], out: Execute.w, unsetenv_others: clear_env)
|
67
|
-
output = Execute.finish_execution(log: log, wait: wait, direct_output: direct_output)
|
68
|
-
return pid unless wait
|
69
|
-
|
70
|
-
# This is instant return, but allows us to have $?/$CHILD_STATUS set.
|
71
|
-
Process.wait pid
|
72
|
-
@last_status = $CHILD_STATUS
|
73
|
-
|
74
|
-
output = Execute.process_output output: output
|
75
|
-
Execute.handle_execution_fail fail: fail, error: error, output: output, command: command
|
76
|
-
output
|
97
|
+
executor.execute
|
77
98
|
ensure
|
78
|
-
|
99
|
+
@last_status = executor.last_status if executor&.last_status
|
79
100
|
end
|
80
101
|
|
81
|
-
# Works similarly to
|
82
|
-
#
|
83
|
-
# TODO: If there will be any difference to input syntax, document those differences here.
|
84
|
-
# For now, rootcmd option has been implemented.
|
102
|
+
# Works similarly to Ruby’s official `Open3.popen2e`.
|
85
103
|
#
|
86
|
-
# A block is accepted, as does
|
87
|
-
#
|
88
|
-
# TODO: Implement this so that this uses same execution things as other things here.
|
89
|
-
# This way bwrap actually can be integrated to this...
|
104
|
+
# A block is accepted, as does `Open3.popen2e`. For now, at least.
|
90
105
|
#
|
91
106
|
# TODO: Verify default log_callback is correct
|
92
107
|
#
|
93
108
|
# @warning Only array style commands are accepted. For example, `ls /`
|
94
|
-
#
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
109
|
+
# is not ok, but `ls` or `%w{ ls / }` is ok.
|
110
|
+
#
|
111
|
+
# @param config [Bwrap::Config] Configuration used to launch the executable inside bubblewrap
|
112
|
+
# @param rootcmd [Array|Symbol|nil] A strange way to use bwrap. Probably no sense to use this
|
113
|
+
# @param log [Boolean]
|
114
|
+
# If true, prints output if verbose flag has been set.
|
115
|
+
# And if a log file has been configured, also logs to that file
|
116
|
+
# @option log_callback [Integer] Semi-internal variable used to tailor caller in debug messages
|
117
|
+
def popen2e *cmd, rootcmd: nil, config: nil, log_callback: 2, log: true, &block
|
118
|
+
popen = Bwrap::Execution::Popen2e.new rootcmd: rootcmd, config: config
|
119
|
+
popen.dry_run = @dry_run # TODO: Add a test that tests that this works as it works with execute().
|
120
|
+
popen.popen2e(*cmd, log_callback: log_callback, log: log, &block)
|
99
121
|
ensure
|
100
|
-
@last_status =
|
122
|
+
@last_status = popen&.child_status
|
101
123
|
end
|
102
124
|
module_function :popen2e
|
103
125
|
|
@@ -115,13 +137,18 @@ module Bwrap::Execution
|
|
115
137
|
# execute commands.
|
116
138
|
#
|
117
139
|
# @see .do_execute .do_execute for documentation of argument syntax
|
140
|
+
#
|
141
|
+
# TODO Default log_callback should be 2 or something, and callers should add one for each subsequent method.
|
118
142
|
private def execute *args, **kwargs
|
119
143
|
# Mangle proper location to error message.
|
120
144
|
if kwargs.is_a? Hash
|
121
|
-
kwargs[:log_callback]
|
145
|
+
kwargs[:log_callback] ||= 1
|
122
146
|
else
|
123
|
-
kwargs = { log_callback:
|
147
|
+
kwargs = { log_callback: 1 }
|
124
148
|
end
|
149
|
+
# Add 1 to log callback so execution is shown to be from caller.
|
150
|
+
kwargs[:log_callback] += 1
|
151
|
+
|
125
152
|
Bwrap::Execution.do_execute(*args, **kwargs)
|
126
153
|
end
|
127
154
|
|
@@ -162,7 +189,7 @@ module Bwrap::Execution
|
|
162
189
|
Bundler.with_unbundled_env do
|
163
190
|
yield 2
|
164
191
|
end
|
165
|
-
elsif Bundler&.bundler_major_version == 1
|
192
|
+
elsif (Bundler&.bundler_major_version) == 1
|
166
193
|
Bundler.with_clean_env do
|
167
194
|
yield 1
|
168
195
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
# force_encoding modifies string, so can’t freeze strings.
|
4
|
+
|
5
|
+
require "bwrap/output"
|
6
|
+
require_relative "execute"
|
7
|
+
|
8
|
+
# Logging utilities for execution.
|
9
|
+
class Bwrap::Execution::Logging
|
10
|
+
# Formats given command for logging, depending on dry-run flag.
|
11
|
+
def self.handle_logging command, log_callback:, log:, dry_run:
|
12
|
+
log_callback += 1 # This is another method in the chain, so need to add one.
|
13
|
+
|
14
|
+
# The debug message contents will always be evaluated, so can just do it like this.
|
15
|
+
log_command = calculate_log_command command
|
16
|
+
|
17
|
+
if dry_run || Bwrap::Execution::Execute.dry_run
|
18
|
+
puts "Would execute “#{log_command.force_encoding("UTF-8")}” at #{caller_locations(log_callback, 1)[0]}"
|
19
|
+
return
|
20
|
+
end
|
21
|
+
|
22
|
+
return unless log
|
23
|
+
|
24
|
+
msg = "Executing “#{log_command.force_encoding("UTF-8")}” at #{caller_locations(log_callback, 1)[0]}"
|
25
|
+
Bwrap::Output.debug_output msg
|
26
|
+
end
|
27
|
+
|
28
|
+
private_class_method def self.calculate_log_command command
|
29
|
+
return command.dup unless command.respond_to?(:join)
|
30
|
+
|
31
|
+
temp = command.dup
|
32
|
+
|
33
|
+
# NOTE: These are not exactly safe, but this is only a debugging aid anyway.
|
34
|
+
temp.map! do |argument|
|
35
|
+
if argument.empty?
|
36
|
+
# If empty value, insert it to quotes so pasting to cli is safer.
|
37
|
+
argument = %{'#{argument}'}
|
38
|
+
elsif argument.include? " "
|
39
|
+
# Wrap multi word arguments to quotes, for convenience.
|
40
|
+
escaped = argument.gsub '"', '\"'
|
41
|
+
argument = %{"#{escaped}"}
|
42
|
+
end
|
43
|
+
argument
|
44
|
+
end
|
45
|
+
|
46
|
+
temp.join(" ")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -1,26 +1,83 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require "bwrap"
|
4
|
+
require "bwrap/output"
|
5
|
+
require_relative "logging"
|
6
|
+
|
7
|
+
# @see Bwrap::Execution.popen2e
|
4
8
|
class Bwrap::Execution::Popen2e
|
9
|
+
include Bwrap::Output
|
10
|
+
|
5
11
|
attr_writer :dry_run
|
6
12
|
|
7
|
-
# @
|
8
|
-
#
|
13
|
+
# @param rootcmd [Array|Symbol] Flags used to construct bubblewrap environment
|
14
|
+
# @param config [Bwrap::Config] Configuration used to launch the command inside bubblewrap
|
15
|
+
def initialize rootcmd: nil, config: nil
|
16
|
+
self.rootcmd = rootcmd
|
17
|
+
self.config = config
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param value [Bwrap::Config] Configuration used to launch the command inside bubblewrap
|
21
|
+
def config= value
|
22
|
+
@config = value
|
23
|
+
|
24
|
+
return unless @config and @rootcmd
|
25
|
+
|
26
|
+
error "Only either rootcmd or config object can be given to popen2e. " \
|
27
|
+
"Please set rootcmd to nil if wanting to use config object."
|
28
|
+
end
|
29
|
+
|
30
|
+
# Can be used to implement kind of tag based bubblewrap command things.
|
31
|
+
# This system is not well documented, and originally done for purposes
|
32
|
+
# of the code where this gem was splitted from.
|
33
|
+
#
|
34
|
+
# Probably using {#config=} instead makes more sense.
|
35
|
+
#
|
36
|
+
# @param value [Array|Symbol] Flags used to construct bubblewrap environment
|
37
|
+
def rootcmd= value
|
38
|
+
@rootcmd = value
|
39
|
+
|
40
|
+
return unless @config and @rootcmd
|
41
|
+
|
42
|
+
error "Only either rootcmd or config object can be given to popen2e. " \
|
43
|
+
"Please set config to nil if wanting to use rootcmd flags."
|
44
|
+
end
|
45
|
+
|
46
|
+
# @see Bwrap::Execution.popen2e
|
9
47
|
#
|
10
|
-
# @note Also options accepted by
|
48
|
+
# @note Also options accepted by upstream’s `Open3.popen2e` are accepted
|
11
49
|
# here, in addition to those specified here.
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
50
|
+
#
|
51
|
+
# @option kwargs [Boolean]
|
52
|
+
# log (true)
|
53
|
+
# If true, prints output if verbose flag has been set.
|
54
|
+
# And if a log file has been configured, also logs to that file
|
55
|
+
# @option kwargs [Integer]
|
56
|
+
# log_callback (3)
|
57
|
+
# Semi-internal variable used to tailor caller in debug messages
|
58
|
+
#
|
59
|
+
# @yieldreturn In case a block is given, its return value is also returned by popen2e
|
60
|
+
# @return [Array<(IO, IO, Thread)>] Write and read `IO` and a wait thread
|
61
|
+
def popen2e *cmd, **kwargs, &block
|
62
|
+
@log = kwargs.key?(:log) ? kwargs.delete(:log) : true
|
63
|
+
@log_callback = kwargs.key?(:log_callback) ? kwargs.delete(:log_callback) : 1
|
64
|
+
|
65
|
+
@log_callback += 1 # Passing to another method, so need to add one more.
|
66
|
+
|
16
67
|
self.opts_from_input = cmd
|
68
|
+
if kwargs
|
69
|
+
@opts ||= {}
|
70
|
+
@opts.merge! kwargs
|
71
|
+
end
|
17
72
|
|
18
73
|
resolve_actual_command cmd
|
19
74
|
|
20
|
-
return if
|
75
|
+
return if dry_run?
|
21
76
|
|
22
77
|
open_pipes
|
23
|
-
|
78
|
+
child_io = [ @in_read, @out_write ]
|
79
|
+
parent_io = [ @in_write, @out_read ]
|
80
|
+
popen_run(@actual_command, child_io, parent_io, &block)
|
24
81
|
ensure
|
25
82
|
if block
|
26
83
|
@in_read.close
|
@@ -30,6 +87,13 @@ class Bwrap::Execution::Popen2e
|
|
30
87
|
end
|
31
88
|
end
|
32
89
|
|
90
|
+
# Only contains `Process::Status` if no block has been passed to {#popen2e}.
|
91
|
+
#
|
92
|
+
# @return [Process::Status|nil] Exit status of the process
|
93
|
+
def child_status
|
94
|
+
@child_status
|
95
|
+
end
|
96
|
+
|
33
97
|
# Sets @opts from `cmd` given to {#popen2e}, if any extra options have been given.
|
34
98
|
private def opts_from_input= cmd
|
35
99
|
@opts = cmd.last.is_a?(Hash) && cmd.pop.dup || {}
|
@@ -54,8 +118,11 @@ class Bwrap::Execution::Popen2e
|
|
54
118
|
# Delete option hash.
|
55
119
|
option_hash = temp_cmd.pop if temp_cmd.last.is_a? Hash
|
56
120
|
|
57
|
-
temp_cmd = Bwrap::Execution::Execute.format_command temp_cmd, rootcmd: @rootcmd
|
58
|
-
Bwrap::Execution::
|
121
|
+
temp_cmd = Bwrap::Execution::Execute.format_command temp_cmd, rootcmd: @rootcmd, config: @config
|
122
|
+
Bwrap::Execution::Logging.handle_logging temp_cmd,
|
123
|
+
log_callback: @log_callback,
|
124
|
+
log: @log,
|
125
|
+
dry_run: @dry_run
|
59
126
|
|
60
127
|
temp_cmd.unshift env_hash if env_hash
|
61
128
|
temp_cmd.push option_hash if option_hash
|
@@ -79,9 +146,14 @@ class Bwrap::Execution::Popen2e
|
|
79
146
|
ensure
|
80
147
|
parent_io.each(&:close)
|
81
148
|
wait_thr.join
|
149
|
+
@child_status = wait_thr.value # Process::Status
|
82
150
|
end
|
83
151
|
end
|
84
152
|
|
85
153
|
result
|
86
154
|
end
|
155
|
+
|
156
|
+
private def dry_run?
|
157
|
+
@dry_run || Bwrap::Execution::Execute.dry_run
|
158
|
+
end
|
87
159
|
end
|
data/lib/bwrap/execution.rb
CHANGED
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "resolvers"
|
4
|
+
|
5
|
+
# Resolves for example symlinks and such stuff.
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class Bwrap::Resolvers::Executable
|
9
|
+
# May be either relative or absolute path. If relative, the path may be invalid.
|
10
|
+
#
|
11
|
+
# @return [Pathname|nil] The executable, without arguments
|
12
|
+
attr_reader :executable
|
13
|
+
|
14
|
+
# If the executable is a symlink, contains each resolved symlink
|
15
|
+
# and the real target. Otherwise only contains the executable.
|
16
|
+
#
|
17
|
+
# @return [Hash<Pathname, :path|:symlink>|nil] Resolved paths to the command
|
18
|
+
attr_reader :executable_paths
|
19
|
+
|
20
|
+
# @param command [Array|String|nil] The command given to {Bwrap#run}.
|
21
|
+
def initialize command = nil
|
22
|
+
self.command = command if command
|
23
|
+
end
|
24
|
+
|
25
|
+
# The command given to {Bwrap#run}.
|
26
|
+
#
|
27
|
+
# @see Bwrap::Args::Construct#command=
|
28
|
+
def command= value
|
29
|
+
self.executable = executable_from_command value
|
30
|
+
@raw_command = value
|
31
|
+
end
|
32
|
+
|
33
|
+
# Resolves given executable and sets relevant data.
|
34
|
+
def executable= value
|
35
|
+
@executable = Pathname.new value
|
36
|
+
|
37
|
+
@executable_paths = { @executable => :path }
|
38
|
+
|
39
|
+
return unless @executable.symlink?
|
40
|
+
|
41
|
+
temp = @executable
|
42
|
+
while temp.symlink?
|
43
|
+
temp = temp.readlink
|
44
|
+
@executable_paths[temp] = :symlink
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns true if executable name parsed from command is absolute
|
49
|
+
# path, and not only the executable name.
|
50
|
+
#
|
51
|
+
# @return [Bool] true if path full path, false if it is only executable name
|
52
|
+
def absolute_path?
|
53
|
+
return false unless @executable
|
54
|
+
|
55
|
+
@executable.absolute?
|
56
|
+
end
|
57
|
+
|
58
|
+
private def executable_from_command command
|
59
|
+
if command.is_a? String
|
60
|
+
return command
|
61
|
+
end
|
62
|
+
|
63
|
+
# Array-like.
|
64
|
+
if command.respond_to? :at
|
65
|
+
return command.at(0)
|
66
|
+
end
|
67
|
+
|
68
|
+
raise "Can’t recognize type of given command. Type: #{command.class}"
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bwrap/execution"
|
4
|
+
require "bwrap/output"
|
5
|
+
require_relative "../library"
|
6
|
+
|
7
|
+
# Base definitions for library resolver subclasses.
|
8
|
+
class Bwrap::Resolvers::Library::Base
|
9
|
+
include Bwrap::Execution
|
10
|
+
include Bwrap::Output
|
11
|
+
|
12
|
+
private def convert_binary_paths binary_paths
|
13
|
+
case binary_paths
|
14
|
+
when String
|
15
|
+
[ binary_paths ]
|
16
|
+
when Pathname
|
17
|
+
[ binary_paths.to_s ]
|
18
|
+
else
|
19
|
+
binary_paths
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bwrap/execution"
|
4
|
+
require "bwrap/output"
|
5
|
+
require_relative "llvm_readelf"
|
6
|
+
require_relative "musl"
|
7
|
+
require_relative "../resolvers"
|
8
|
+
|
9
|
+
# See ../library.rb for class documentation.
|
10
|
+
class Bwrap::Resolvers::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
|
+
# Empties `@@needed_libraries_cache`.
|
18
|
+
def self.clear_needed_libraries_cache
|
19
|
+
@@needed_libraries_cache.clear
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def needed_libraries_cache
|
24
|
+
@@needed_libraries_cache
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Otherwise similar to {#needed_libraries}, but checks used libc to handle musl executables.
|
29
|
+
#
|
30
|
+
# @param executable [String] Path to the executable to find dependencies for
|
31
|
+
# @return [Array] Libraries the executable needs, if any
|
32
|
+
def libraries_needed_by executable
|
33
|
+
trace "Finding libraries needed by #{executable}"
|
34
|
+
|
35
|
+
# %i == interpreter, the library used to load the executable by kernel.
|
36
|
+
# %F == Path to given file.
|
37
|
+
output_format = "%i::SEPARATOR::%F"
|
38
|
+
scanelf_command = %W{ scanelf --nobanner --quiet --format #{output_format} }
|
39
|
+
scanelf_command << executable
|
40
|
+
|
41
|
+
data = execvalue scanelf_command
|
42
|
+
|
43
|
+
# If data is empty, target probably is a script of some sort.
|
44
|
+
if data.empty?
|
45
|
+
return []
|
46
|
+
end
|
47
|
+
|
48
|
+
data = data.strip
|
49
|
+
interpreter, _executable_path = data.split "::SEPARATOR::"
|
50
|
+
interpreter = Pathname.new interpreter
|
51
|
+
|
52
|
+
if interpreter.basename.to_s[0..6] == "ld-musl"
|
53
|
+
trace "Resolved to musl interpreter: #{interpreter}"
|
54
|
+
musl_needed_libraries executable
|
55
|
+
else
|
56
|
+
trace "Defaulting to glibc interpreter: #{interpreter}"
|
57
|
+
# For glibc, scanelf can return full paths for us most of time.
|
58
|
+
needed_libraries executable
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# @param binary_paths [String, Array] one or more paths to be resolved
|
63
|
+
def musl_needed_libraries binary_paths
|
64
|
+
musl = Musl.new
|
65
|
+
@needed_libraries = musl.needed_libraries binary_paths
|
66
|
+
end
|
67
|
+
|
68
|
+
# @param binary_paths [String, Array] one or more paths to be resolved
|
69
|
+
def needed_libraries binary_paths
|
70
|
+
llvm_readelf = LLVMReadelf.new
|
71
|
+
@needed_libraries = llvm_readelf.needed_libraries binary_paths
|
72
|
+
end
|
73
|
+
end
|
74
|
+
# class Library ended
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
# Used via {Bwrap::Resolvers::Library}.
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class Bwrap::Resolvers::Library::LLVMReadelf < Bwrap::Resolvers::Library::Base
|
9
|
+
# Resolve dependents using llvm-readelf.
|
10
|
+
#
|
11
|
+
# @param binary_paths [String, Array] one or more paths to be resolved
|
12
|
+
def needed_libraries binary_paths
|
13
|
+
raise ArgumentError, "binary_paths is nil, expected an Array" if binary_paths.nil?
|
14
|
+
|
15
|
+
trace "Finding libraries #{binary_paths} requires"
|
16
|
+
@needed_libraries = []
|
17
|
+
|
18
|
+
llvm_readelf_command = %w{ llvm-readelf --needed-libs }
|
19
|
+
|
20
|
+
binary_paths = convert_binary_paths binary_paths
|
21
|
+
|
22
|
+
# Check if the exe is already resolved.
|
23
|
+
binary_paths.delete_if do |binary_path|
|
24
|
+
Bwrap::Resolvers::Library.needed_libraries_cache.include? binary_path
|
25
|
+
end
|
26
|
+
|
27
|
+
return [] if binary_paths.empty?
|
28
|
+
|
29
|
+
data = execvalue(llvm_readelf_command + binary_paths)
|
30
|
+
parse_llvm_readelf_output binary_paths, data
|
31
|
+
|
32
|
+
@needed_libraries
|
33
|
+
end
|
34
|
+
|
35
|
+
# Parses output returned by llvm-readelf --needed-libraries
|
36
|
+
#
|
37
|
+
# Sets libraries to @needed_libraries variable.
|
38
|
+
private def parse_llvm_readelf_output binary_paths, data
|
39
|
+
iter = data.split("\n").each
|
40
|
+
source_binary = binary_paths.first
|
41
|
+
begin
|
42
|
+
while (line = iter.next)
|
43
|
+
source_binary = line[6..-1].strip if line[0..5] == "File: "
|
44
|
+
next unless line[0..16] == "NeededLibraries ["
|
45
|
+
|
46
|
+
while (library = iter.next)
|
47
|
+
library = library.strip
|
48
|
+
# End of library list for current NeededLibraries block.
|
49
|
+
break if library == "]"
|
50
|
+
|
51
|
+
library = convert_to_full_path source_binary, library
|
52
|
+
|
53
|
+
add_library_to_needed_libraries library
|
54
|
+
end
|
55
|
+
end
|
56
|
+
rescue StopIteration
|
57
|
+
# End of the iteration.
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# NOTE: Maybe this could be in general file here, if needed elsewhere also?
|
62
|
+
# This also applies to some other methods here.
|
63
|
+
#
|
64
|
+
# Finds where given library would be loaded from.
|
65
|
+
#
|
66
|
+
# source_binary is used to to if it has RPATH set as third possibility.
|
67
|
+
# TODO: Implement above.
|
68
|
+
#
|
69
|
+
# @param source_binary [String] Executable or library which is loading the binary
|
70
|
+
private def convert_to_full_path source_binary, library
|
71
|
+
# TODO: Somewhere put a cleanup for this variable, so this is erased
|
72
|
+
# before application is run. That will save at least 200 kB of memory.
|
73
|
+
@@ld_so_cache ||= File.read("/etc/ld.so.cache", mode: "rb")
|
74
|
+
|
75
|
+
path_regex = %r{[\w\-/. ]{3,}}
|
76
|
+
|
77
|
+
check_next = false
|
78
|
+
@@ld_so_cache.scan(path_regex).each do |match|
|
79
|
+
if check_next
|
80
|
+
# TODO: Any sense checking for executable bit?
|
81
|
+
return match if File.exist? match
|
82
|
+
|
83
|
+
check_next = false
|
84
|
+
end
|
85
|
+
|
86
|
+
check_next = true if match == library
|
87
|
+
end
|
88
|
+
|
89
|
+
warn "Failed to resolve full path of library #{library}, needed by #{source_binary}."
|
90
|
+
library
|
91
|
+
end
|
92
|
+
|
93
|
+
# @param library [Array] library to add.
|
94
|
+
private def add_library_to_needed_libraries library
|
95
|
+
raise ArgumentError, "library is nil, expected a String" if library.nil?
|
96
|
+
|
97
|
+
return if @needed_libraries.include? library
|
98
|
+
|
99
|
+
# Probably no sense to log these unless tracing,
|
100
|
+
# as dependencies should work most of time.
|
101
|
+
#
|
102
|
+
# It also outputs tons of output for more complex programs.
|
103
|
+
trace "Binding #{library}"
|
104
|
+
|
105
|
+
@needed_libraries << library
|
106
|
+
|
107
|
+
# Also check if requisite libraries needs some libraries.
|
108
|
+
inner = Bwrap::Resolvers::Library.new
|
109
|
+
@needed_libraries |= inner.needed_libraries library
|
110
|
+
|
111
|
+
Bwrap::Resolvers::Library.needed_libraries_cache << library
|
112
|
+
end
|
113
|
+
|
114
|
+
# @param libraries [Array] libraries to add.
|
115
|
+
private def add_libraries_to_needed_libraries libraries
|
116
|
+
# Add to needed libraries if not already added.
|
117
|
+
(@needed_libraries & libraries).each do |library|
|
118
|
+
# Probably no sense to log these unless tracing,
|
119
|
+
# as dependencies should work most of time.
|
120
|
+
#
|
121
|
+
# It also outputs tons of output for more complex programs.
|
122
|
+
trace "Binding #{library}"
|
123
|
+
end
|
124
|
+
|
125
|
+
@needed_libraries |= libraries
|
126
|
+
|
127
|
+
# Also check if requisite libraries needs some libraries.
|
128
|
+
inner = Bwrap::Resolvers::Library.new
|
129
|
+
@needed_libraries |= inner.needed_libraries libraries
|
130
|
+
|
131
|
+
Bwrap::Resolvers::Library.needed_libraries_cache |= libraries
|
132
|
+
end
|
133
|
+
end
|