From ee0bd6c69ee01caf076e5c9f7461fe09fa8cf9ba Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Fri, 25 Sep 2026 10:00:39 -0400 Subject: [PATCH] Io: avoid leaking inherited descriptors --- lib/std/Io.zig | 17 +++++++++++++++++ lib/std/Io/Threaded.zig | 40 ++++++++++++++++++++++++++++++++++++++++ test/src/Runner.zig | 24 +++++++++++++++++++----- 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index c7f94cbb9652fd9e98f6b419d629c6fc55b292e2..759855179a6e99e770afc2be497774b9e5b21cd2 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -218,6 +218,8 @@ pub const VTable = struct { childKill: *const fn (?*anyopaque, *std.process.Child) void, progressParentFile: *const fn (?*anyopaque) std.Progress.ParentFileError!File, + inheritParentDir: *const fn (?*anyopaque, handle: Dir.Handle) (Cancelable || UnexpectedError)!Dir, + inheritParentFile: *const fn (?*anyopaque, handle: File.Handle, flags: File.Flags) (Cancelable || UnexpectedError)!File, now: *const fn (?*anyopaque, Clock) Timestamp, clockResolution: *const fn (?*anyopaque, Clock) Clock.ResolutionError!Duration, @@ -2800,6 +2802,8 @@ pub const failing: std.Io = .{ .childKill = unreachableChildKill, .progressParentFile = failingProgressParentFile, + .inheritParentDir = unreachableInheritParentDir, + .inheritParentFile = unreachableInheritParentFile, .random = noRandom, .randomSecure = failingRandomSecure, @@ -3470,6 +3474,19 @@ pub fn failingProgressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileE return error.UnsupportedOperation; } +pub fn unreachableInheritParentDir(userdata: ?*anyopaque, handle: Dir.Handle) (Cancelable || UnexpectedError)!Dir { + _ = userdata; + _ = handle; + unreachable; +} + +pub fn unreachableInheritParentFile(userdata: ?*anyopaque, handle: File.Handle, flags: File.Flags) (Cancelable || UnexpectedError)!File { + _ = userdata; + _ = handle; + _ = flags; + unreachable; +} + pub fn noRandom(userdata: ?*anyopaque, buffer: []u8) void { _ = userdata; @memset(buffer, 0); diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 2223d3e0659e50bea6a75595bb781596721c37c4..00f29608098c8942e621ba96d6e81e906b463d0f 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1906,6 +1906,8 @@ pub fn io(t: *Threaded) Io { .childKill = childKill, .progressParentFile = progressParentFile, + .inheritParentDir = inheritParentDir, + .inheritParentFile = inheritParentFile, .now = now, .clockResolution = clockResolution, @@ -17694,6 +17696,44 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File { return t.environ.zig_progress_file; } +fn inheritParentHandle(handle: posix.fd_t) Io.InheritParentHandleError!void { + switch (native_os) { + .linux, + .dragonfly, + .freebsd, + .netbsd, + .openbsd, + .illumos, + .driverkit, + .ios, + .maccatalyst, + .macos, + .tvos, + .visionos, + .watchos, + => return setCloexec(handle), + else => return error.UnsupportedOperation, + } +} + +fn inheritParentDir(userdata: ?*anyopaque, handle: Dir.Handle) Io.InheritParentHandleError!Dir { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; + try inheritParentHandle(handle); + return .{ .handle = handle }; +} + +fn inheritParentFile( + userdata: ?*anyopaque, + handle: File.Handle, + flags: File.Flags, +) Io.InheritParentHandleError!File { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; + try inheritParentHandle(handle); + return .{ .handle = handle, .flags = flags }; +} + pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 { t.scanEnviron(); return @field(t.environ.string, name); diff --git a/test/src/Runner.zig b/test/src/Runner.zig index c985ff8410fea3e0181a74d41d19be62d36767d0..8a7b3832a41b014de1d86626fe9b8bdd686ec15e 100644 --- a/test/src/Runner.zig +++ b/test/src/Runner.zig @@ -132,8 +132,15 @@ pub fn runServer(runner: *Runner) ProtocolError { args_body[args_body_offset..][0..@sizeOf(std.Io.Dir.Handle)], ); args_body_offset += @sizeOf(std.Io.Dir.Handle); - const dir: std.Io.Dir = .{ - .handle = dir_handle.*, + const dir = runner.io.vtable.inheritParentDir( + runner.io.userdata, + dir_handle.*, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + error.Unexpected => |e| return runner.fail( + "unable to inherit parent dir: {t}", + .{e}, + ), }; switch (state) { .positional => { @@ -171,9 +178,16 @@ pub fn runServer(runner: *Runner) ProtocolError { args_body[args_body_offset..][0..@sizeOf(std.Io.File.Handle)], ); args_body_offset += @sizeOf(std.Io.File.Handle); - const file: std.Io.File = .{ - .handle = file_handle.*, - .flags = .{ .nonblocking = false }, + const file = runner.io.vtable.inheritParentFile( + runner.io.userdata, + file_handle.*, + .{ .nonblocking = false }, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + error.Unexpected => |e| return runner.fail( + "unable to inherit parent file: {t}", + .{e}, + ), }; switch (state) { .positional => { -- 2.54.0