landlock 0.4 → 0.4.1
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 +4 -4
- data/CHANGELOG.md +6 -0
- data/ext/landlock/landlock.c +15 -0
- data/ext/landlock/landlock_native.h +4 -0
- data/lib/landlock/native.rb +4 -0
- data/lib/landlock/process_io.rb +79 -37
- data/lib/landlock/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 06c677a70d7c9573f8900255447f3407b917a32d20b431ddf3639660ae410847
|
|
4
|
+
data.tar.gz: 07a99cefc054bbc33693f9d72531ef2639e8b926895de8f5972cd62a40974413
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 44ff2dd41500ebfb8751aca02d09582a92a9cbcc5abce973f53ce1981fe579afde5bfe63b739c032404e874654de414ca13c17581a2ec51a34fe9e20c708105f
|
|
7
|
+
data.tar.gz: d98885b52ebe803ee29d3d5db0ee83ba44417377ddd7f9f6ce47b41576be326b5246b62ebfbd47723f79ae3d3be7c5608916816c86264e6832e7f1bfb515d793
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## [0.4.1] - 2026-08-20
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Remove the 100 ms child-exit polling interval from subprocess capture. Capture now drains stdout and stderr, then monitors the child via pidfd until exit or the wall-clock deadline, retaining polling only as a compatibility fallback when pidfds are unavailable.
|
|
12
|
+
|
|
7
13
|
## [0.4] - 2026-08-10
|
|
8
14
|
|
|
9
15
|
### Changed
|
data/ext/landlock/landlock.c
CHANGED
|
@@ -121,6 +121,20 @@ static VALUE rb_ll_close_fd(VALUE self, VALUE fd_value) {
|
|
|
121
121
|
return Qnil;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
static VALUE rb_ll_pidfd_open(VALUE self, VALUE pid_value) {
|
|
125
|
+
#ifdef SYS_pidfd_open
|
|
126
|
+
int fd = syscall(SYS_pidfd_open, NUM2PIDT(pid_value), 0);
|
|
127
|
+
if (fd < 0) {
|
|
128
|
+
raise_syscall_error("pidfd_open");
|
|
129
|
+
}
|
|
130
|
+
return INT2NUM(fd);
|
|
131
|
+
#else
|
|
132
|
+
errno = ENOSYS;
|
|
133
|
+
raise_syscall_error("pidfd_open");
|
|
134
|
+
return Qnil;
|
|
135
|
+
#endif
|
|
136
|
+
}
|
|
137
|
+
|
|
124
138
|
static VALUE rb_ll_seccomp_deny_network(VALUE self) {
|
|
125
139
|
const char *error_message = "seccomp(SECCOMP_SET_MODE_FILTER)";
|
|
126
140
|
if (rb_landlock_seccomp_deny_network(&error_message) != 0) {
|
|
@@ -150,6 +164,7 @@ void Init_landlock(void) {
|
|
|
150
164
|
rb_define_singleton_method(mLandlock, "_add_net_rule", rb_ll_add_net_rule, 3);
|
|
151
165
|
rb_define_singleton_method(mLandlock, "_restrict_self", rb_ll_restrict_self, 1);
|
|
152
166
|
rb_define_singleton_method(mLandlock, "_close_fd", rb_ll_close_fd, 1);
|
|
167
|
+
rb_define_singleton_method(mLandlock, "_pidfd_open", rb_ll_pidfd_open, 1);
|
|
153
168
|
rb_define_singleton_method(mLandlock, "seccomp_deny_network!", rb_ll_seccomp_deny_network, 0);
|
|
154
169
|
|
|
155
170
|
rb_define_const(mLandlock, "ACCESS_FS_EXECUTE", ULL2NUM(LANDLOCK_ACCESS_FS_EXECUTE));
|
|
@@ -35,6 +35,10 @@
|
|
|
35
35
|
#endif
|
|
36
36
|
#endif
|
|
37
37
|
|
|
38
|
+
#if defined(__linux__) && !defined(SYS_pidfd_open) && defined(__NR_pidfd_open)
|
|
39
|
+
#define SYS_pidfd_open __NR_pidfd_open
|
|
40
|
+
#endif
|
|
41
|
+
|
|
38
42
|
#ifndef LANDLOCK_CREATE_RULESET_VERSION
|
|
39
43
|
#define LANDLOCK_CREATE_RULESET_VERSION (1U << 0)
|
|
40
44
|
#endif
|
data/lib/landlock/native.rb
CHANGED
data/lib/landlock/process_io.rb
CHANGED
|
@@ -5,7 +5,7 @@ require_relative "result"
|
|
|
5
5
|
|
|
6
6
|
module Landlock
|
|
7
7
|
READ_CHUNK_BYTES = 16 * 1024
|
|
8
|
-
|
|
8
|
+
PID_WAIT_FALLBACK_INTERVAL_SECONDS = 0.1
|
|
9
9
|
STDIN_THREAD_JOIN_SECONDS = 0.1
|
|
10
10
|
POST_TIMEOUT_DRAIN_SECONDS = 0.05
|
|
11
11
|
|
|
@@ -91,45 +91,16 @@ module Landlock
|
|
|
91
91
|
timed_out = false
|
|
92
92
|
status = nil
|
|
93
93
|
|
|
94
|
-
until streams.empty?
|
|
94
|
+
until streams.empty?
|
|
95
95
|
if deadline
|
|
96
96
|
remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
97
97
|
if remaining <= 0
|
|
98
98
|
timed_out = true
|
|
99
|
-
terminate_process(pid)
|
|
100
|
-
status = wait_for_pid(pid)
|
|
101
|
-
drain_streams_until(
|
|
102
|
-
streams,
|
|
103
|
-
::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + POST_TIMEOUT_DRAIN_SECONDS,
|
|
104
|
-
max_output_bytes,
|
|
105
|
-
truncate_output,
|
|
106
|
-
state,
|
|
107
|
-
pid
|
|
108
|
-
)
|
|
109
|
-
close_streams(streams)
|
|
110
99
|
break
|
|
111
100
|
end
|
|
112
101
|
end
|
|
113
102
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
break if streams.empty? && status
|
|
117
|
-
|
|
118
|
-
wait =
|
|
119
|
-
(
|
|
120
|
-
if deadline
|
|
121
|
-
[deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC), PROCESS_POLL_SECONDS].min
|
|
122
|
-
else
|
|
123
|
-
PROCESS_POLL_SECONDS
|
|
124
|
-
end
|
|
125
|
-
)
|
|
126
|
-
wait = 0 if wait.negative?
|
|
127
|
-
if streams.empty?
|
|
128
|
-
sleep wait
|
|
129
|
-
next
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
readable, = IO.select(streams.keys, nil, nil, wait)
|
|
103
|
+
readable, = IO.select(streams.keys, nil, nil, remaining)
|
|
133
104
|
next unless readable
|
|
134
105
|
|
|
135
106
|
readable.each do |io|
|
|
@@ -145,16 +116,81 @@ module Landlock
|
|
|
145
116
|
end
|
|
146
117
|
end
|
|
147
118
|
|
|
148
|
-
|
|
119
|
+
if deadline
|
|
120
|
+
status, timed_out = wait_for_pid_until(pid, deadline:)
|
|
121
|
+
else
|
|
122
|
+
status = wait_for_pid(pid)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
if timed_out
|
|
126
|
+
drain_streams_until(
|
|
127
|
+
streams,
|
|
128
|
+
::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + POST_TIMEOUT_DRAIN_SECONDS,
|
|
129
|
+
max_output_bytes,
|
|
130
|
+
truncate_output,
|
|
131
|
+
state,
|
|
132
|
+
pid
|
|
133
|
+
)
|
|
134
|
+
close_streams(streams)
|
|
135
|
+
end
|
|
136
|
+
|
|
149
137
|
[status, timed_out]
|
|
150
138
|
end
|
|
151
139
|
|
|
152
|
-
def
|
|
153
|
-
|
|
154
|
-
|
|
140
|
+
def wait_for_pid_until(pid, deadline:)
|
|
141
|
+
remaining = deadline - monotonic_time
|
|
142
|
+
if remaining <= 0
|
|
143
|
+
terminate_process(pid)
|
|
144
|
+
return wait_for_pid(pid), true
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
pidfd = Native.pidfd_open(pid)
|
|
148
|
+
pid_monitor = IO.for_fd(pidfd, autoclose: false)
|
|
149
|
+
readable, = IO.select([pid_monitor], nil, nil, remaining)
|
|
150
|
+
if !readable || monotonic_time >= deadline
|
|
151
|
+
terminate_process(pid)
|
|
152
|
+
return wait_for_pid(pid), true
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
[wait_for_pid(pid), false]
|
|
156
|
+
rescue Landlock::SyscallError
|
|
157
|
+
wait_for_pid_until_by_polling(pid, deadline:)
|
|
158
|
+
ensure
|
|
159
|
+
close_stream(pid_monitor) if pid_monitor
|
|
160
|
+
Native.close_fd(pidfd) if pidfd
|
|
161
|
+
end
|
|
162
|
+
private_class_method :wait_for_pid_until
|
|
163
|
+
|
|
164
|
+
def wait_for_pid_until_by_polling(pid, deadline:)
|
|
165
|
+
loop do
|
|
166
|
+
remaining = deadline - monotonic_time
|
|
167
|
+
if remaining <= 0
|
|
168
|
+
terminate_process(pid)
|
|
169
|
+
return wait_for_pid(pid), true
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
result = ::Process.wait2(pid, ::Process::WNOHANG)
|
|
173
|
+
if result
|
|
174
|
+
status = result.last
|
|
175
|
+
if monotonic_time >= deadline
|
|
176
|
+
terminate_process_group(pid)
|
|
177
|
+
return status, true
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
return status, false
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
IO.select(nil, nil, nil, [remaining, PID_WAIT_FALLBACK_INTERVAL_SECONDS].min)
|
|
184
|
+
end
|
|
155
185
|
rescue Errno::ECHILD
|
|
156
|
-
nil
|
|
186
|
+
[nil, false]
|
|
187
|
+
end
|
|
188
|
+
private_class_method :wait_for_pid_until_by_polling
|
|
189
|
+
|
|
190
|
+
def monotonic_time
|
|
191
|
+
::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
157
192
|
end
|
|
193
|
+
private_class_method :monotonic_time
|
|
158
194
|
|
|
159
195
|
def wait_for_pid(pid)
|
|
160
196
|
::Process.wait2(pid).last
|
|
@@ -237,6 +273,12 @@ module Landlock
|
|
|
237
273
|
signal_process("KILL", pid)
|
|
238
274
|
end
|
|
239
275
|
|
|
276
|
+
def terminate_process_group(pid)
|
|
277
|
+
::Process.kill("KILL", -pid)
|
|
278
|
+
rescue Errno::ESRCH, Errno::EPERM
|
|
279
|
+
end
|
|
280
|
+
private_class_method :terminate_process_group
|
|
281
|
+
|
|
240
282
|
def signal_process(signal, pid)
|
|
241
283
|
::Process.kill(signal, -pid)
|
|
242
284
|
rescue Errno::ESRCH, Errno::EPERM
|
data/lib/landlock/version.rb
CHANGED