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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b7494b5195627610f67bb7a41a5adcb99857f19e1639d88b1107d3d5d43a639c
4
- data.tar.gz: '065496b9a75a046e91b1d9445dae704bd4531d7de61391d4506c9f480789a32a'
3
+ metadata.gz: 06c677a70d7c9573f8900255447f3407b917a32d20b431ddf3639660ae410847
4
+ data.tar.gz: 07a99cefc054bbc33693f9d72531ef2639e8b926895de8f5972cd62a40974413
5
5
  SHA512:
6
- metadata.gz: 310b8cf56b7906297466b1273d4c5e924dc859148bc0adf143d44825b2c2b68be07d67d7058def44f9551470c48880b37963db2eb48be991b4a07829a03a3e9e
7
- data.tar.gz: 362a3ea8d6aaf5115d100adf06524a53cfd87d9a46cfc29f1677c351196fadbffee88fa80d348b433d8d518ad3e517ec3893b6b5a827f9e67b799c5954b093b7
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
@@ -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
@@ -31,6 +31,10 @@ module Landlock
31
31
  Landlock.__send__(:_close_fd, fd)
32
32
  end
33
33
 
34
+ def pidfd_open(pid)
35
+ Landlock.__send__(:_pidfd_open, pid)
36
+ end
37
+
34
38
  def seccomp_deny_network!
35
39
  Landlock.seccomp_deny_network!
36
40
  end
@@ -5,7 +5,7 @@ require_relative "result"
5
5
 
6
6
  module Landlock
7
7
  READ_CHUNK_BYTES = 16 * 1024
8
- PROCESS_POLL_SECONDS = 0.1
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? && status
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
- status ||= poll_pid(pid)
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
- status ||= wait_for_pid(pid)
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 poll_pid(pid)
153
- result = ::Process.wait2(pid, ::Process::WNOHANG)
154
- result&.last
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Landlock
4
- VERSION = "0.4"
4
+ VERSION = "0.4.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: landlock
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.4'
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron