mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 07:46:12 +03:00
Prevent fd leaks to the running shell or command (#5341)
Multiple fixes to prevent file descriptor leaks: - libxev eventfd now uses CLOEXEC - linux: cgroup clone now uses CLOEXEC for the cgroup fd - termio pipe uses pipe2 with CLOEXEC - pty master always sets CLOEXEC because the child doesn't need it - termio exec now closes pty slave fd after fork There still appear to be some fd leaks happening. They seem related to GTK, they aren't things we're accessig directly. I still want to investigate them but this at least cleans up the major sources of fd leakage.
This commit is contained in:
@ -5,8 +5,8 @@
|
|||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
// Zig libs
|
// Zig libs
|
||||||
.libxev = .{
|
.libxev = .{
|
||||||
.url = "https://github.com/mitchellh/libxev/archive/db6a52bafadf00360e675fefa7926e8e6c0e9931.tar.gz",
|
.url = "https://github.com/mitchellh/libxev/archive/aceef3d11efacd9d237c91632f930ed13a2834bf.tar.gz",
|
||||||
.hash = "12206029de146b685739f69b10a6f08baee86b3d0a5f9a659fa2b2b66c9602078bbf",
|
.hash = "12205b2b47fe61a4cde3a45ee4b9cddee75897739dbc196d6396e117cb1ce28e1ad0",
|
||||||
},
|
},
|
||||||
.mach_glfw = .{
|
.mach_glfw = .{
|
||||||
.url = "https://github.com/mitchellh/mach-glfw/archive/37c2995f31abcf7e8378fba68ddcf4a3faa02de0.tar.gz",
|
.url = "https://github.com/mitchellh/mach-glfw/archive/37c2995f31abcf7e8378fba68ddcf4a3faa02de0.tar.gz",
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
# This file is auto-generated! check build-support/check-zig-cache-hash.sh for
|
# This file is auto-generated! check build-support/check-zig-cache-hash.sh for
|
||||||
# more details.
|
# more details.
|
||||||
"sha256-H6o4Y09ATIylMUWuL9Y1fHwpuxSWyJ3Pl8fn4VeoDZo="
|
"sha256-AvfYl8vLxxsRnf/ERpw5jQIro5rVd98q63hwFsgQOvo="
|
||||||
|
@ -77,7 +77,22 @@ pub fn cloneInto(cgroup: []const u8) !posix.pid_t {
|
|||||||
// Get a file descriptor that refers to the cgroup directory in the cgroup
|
// Get a file descriptor that refers to the cgroup directory in the cgroup
|
||||||
// sysfs to pass to the kernel in clone3.
|
// sysfs to pass to the kernel in clone3.
|
||||||
const fd: linux.fd_t = fd: {
|
const fd: linux.fd_t = fd: {
|
||||||
const rc = linux.open(path, linux.O{ .PATH = true, .DIRECTORY = true }, 0);
|
const rc = linux.open(
|
||||||
|
path,
|
||||||
|
.{
|
||||||
|
// Self-explanatory: we expect to open a directory, and
|
||||||
|
// we only need the path-level permissions.
|
||||||
|
.PATH = true,
|
||||||
|
.DIRECTORY = true,
|
||||||
|
|
||||||
|
// We don't want to leak this fd to the child process
|
||||||
|
// when we clone below since we're using this fd for
|
||||||
|
// a cgroup clone.
|
||||||
|
.CLOEXEC = true,
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
switch (posix.errno(rc)) {
|
switch (posix.errno(rc)) {
|
||||||
.SUCCESS => break :fd @as(linux.fd_t, @intCast(rc)),
|
.SUCCESS => break :fd @as(linux.fd_t, @intCast(rc)),
|
||||||
else => |errno| {
|
else => |errno| {
|
||||||
|
@ -3,10 +3,11 @@ const builtin = @import("builtin");
|
|||||||
const windows = @import("windows.zig");
|
const windows = @import("windows.zig");
|
||||||
const posix = std.posix;
|
const posix = std.posix;
|
||||||
|
|
||||||
/// pipe() that works on Windows and POSIX.
|
/// pipe() that works on Windows and POSIX. For POSIX systems, this sets
|
||||||
|
/// CLOEXEC on the file descriptors.
|
||||||
pub fn pipe() ![2]posix.fd_t {
|
pub fn pipe() ![2]posix.fd_t {
|
||||||
switch (builtin.os.tag) {
|
switch (builtin.os.tag) {
|
||||||
else => return try posix.pipe(),
|
else => return try posix.pipe2(.{ .CLOEXEC = true }),
|
||||||
.windows => {
|
.windows => {
|
||||||
var read: windows.HANDLE = undefined;
|
var read: windows.HANDLE = undefined;
|
||||||
var write: windows.HANDLE = undefined;
|
var write: windows.HANDLE = undefined;
|
||||||
|
26
src/pty.zig
26
src/pty.zig
@ -94,6 +94,9 @@ const PosixPty = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// The file descriptors for the master and slave side of the pty.
|
/// The file descriptors for the master and slave side of the pty.
|
||||||
|
/// The slave side is never closed automatically by this struct
|
||||||
|
/// so the caller is responsible for closing it if things
|
||||||
|
/// go wrong.
|
||||||
master: Fd,
|
master: Fd,
|
||||||
slave: Fd,
|
slave: Fd,
|
||||||
|
|
||||||
@ -117,6 +120,24 @@ const PosixPty = struct {
|
|||||||
_ = posix.system.close(slave_fd);
|
_ = posix.system.close(slave_fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set CLOEXEC on the master fd, only the slave fd should be inherited
|
||||||
|
// by the child process (shell/command).
|
||||||
|
cloexec: {
|
||||||
|
const flags = std.posix.fcntl(master_fd, std.posix.F.GETFD, 0) catch |err| {
|
||||||
|
log.warn("error getting flags for master fd err={}", .{err});
|
||||||
|
break :cloexec;
|
||||||
|
};
|
||||||
|
|
||||||
|
_ = std.posix.fcntl(
|
||||||
|
master_fd,
|
||||||
|
std.posix.F.SETFD,
|
||||||
|
flags | std.posix.FD_CLOEXEC,
|
||||||
|
) catch |err| {
|
||||||
|
log.warn("error setting CLOEXEC on master fd err={}", .{err});
|
||||||
|
break :cloexec;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Enable UTF-8 mode. I think this is on by default on Linux but it
|
// Enable UTF-8 mode. I think this is on by default on Linux but it
|
||||||
// is NOT on by default on macOS so we ensure that it is always set.
|
// is NOT on by default on macOS so we ensure that it is always set.
|
||||||
var attrs: c.termios = undefined;
|
var attrs: c.termios = undefined;
|
||||||
@ -126,7 +147,7 @@ const PosixPty = struct {
|
|||||||
if (c.tcsetattr(master_fd, c.TCSANOW, &attrs) != 0)
|
if (c.tcsetattr(master_fd, c.TCSANOW, &attrs) != 0)
|
||||||
return error.OpenptyFailed;
|
return error.OpenptyFailed;
|
||||||
|
|
||||||
return Pty{
|
return .{
|
||||||
.master = master_fd,
|
.master = master_fd,
|
||||||
.slave = slave_fd,
|
.slave = slave_fd,
|
||||||
};
|
};
|
||||||
@ -134,7 +155,6 @@ const PosixPty = struct {
|
|||||||
|
|
||||||
pub fn deinit(self: *Pty) void {
|
pub fn deinit(self: *Pty) void {
|
||||||
_ = posix.system.close(self.master);
|
_ = posix.system.close(self.master);
|
||||||
_ = posix.system.close(self.slave);
|
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,8 +221,6 @@ const PosixPty = struct {
|
|||||||
// Can close master/slave pair now
|
// Can close master/slave pair now
|
||||||
posix.close(self.slave);
|
posix.close(self.slave);
|
||||||
posix.close(self.master);
|
posix.close(self.master);
|
||||||
|
|
||||||
// TODO: reset signals
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1098,6 +1098,10 @@ const Subprocess = struct {
|
|||||||
});
|
});
|
||||||
self.pty = pty;
|
self.pty = pty;
|
||||||
errdefer {
|
errdefer {
|
||||||
|
if (comptime builtin.os.tag != .windows) {
|
||||||
|
_ = posix.close(pty.slave);
|
||||||
|
}
|
||||||
|
|
||||||
pty.deinit();
|
pty.deinit();
|
||||||
self.pty = null;
|
self.pty = null;
|
||||||
}
|
}
|
||||||
@ -1182,6 +1186,13 @@ const Subprocess = struct {
|
|||||||
log.info("subcommand cgroup={s}", .{self.linux_cgroup orelse "-"});
|
log.info("subcommand cgroup={s}", .{self.linux_cgroup orelse "-"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (comptime builtin.os.tag != .windows) {
|
||||||
|
// Once our subcommand is started we can close the slave
|
||||||
|
// side. This prevents the slave fd from being leaked to
|
||||||
|
// future children.
|
||||||
|
_ = posix.close(pty.slave);
|
||||||
|
}
|
||||||
|
|
||||||
self.command = cmd;
|
self.command = cmd;
|
||||||
return switch (builtin.os.tag) {
|
return switch (builtin.os.tag) {
|
||||||
.windows => .{
|
.windows => .{
|
||||||
|
Reference in New Issue
Block a user