- commit
- 1fc6306
- parent
- df38b13
- author
- Ian Tay
- date
- 2026-03-08 12:58:59 -0400 EDT
fix(ipc): validate wire data before arithmetic and indexing - expectedLength: header.len is u32 off the wire; adding sizeof(Header) at u32 width could wrap (panic in safe mode, UB slice bounds in release). Widen to usize first. - get_session_entries: cmd_len/cwd_len are u16 (max 65535) but index into [256]u8 arrays. Clamp before slicing.
3 files changed,
+17,
-7
+7,
-1
1@@ -15,6 +15,10 @@ pub const Tag = enum(u8) {
2 History = 8,
3 Run = 9,
4 Ack = 10,
5+ // Non-exhaustive: this enum comes off the wire via bytesToValue and
6+ // @enumFromInt, so out-of-range values (11-255) are representable
7+ // rather than UB. Switches must handle `_` (unknown tag).
8+ _,
9 };
10
11 pub const Header = packed struct {
12@@ -53,7 +57,9 @@ pub const Info = extern struct {
13 pub fn expectedLength(data: []const u8) ?usize {
14 if (data.len < @sizeOf(Header)) return null;
15 const header = std.mem.bytesToValue(Header, data[0..@sizeOf(Header)]);
16- return @sizeOf(Header) + header.len;
17+ // header.len comes off the wire; widen to usize before adding so a
18+ // near-u32-max value can't wrap (panic in safe mode, UB in release).
19+ return @as(usize, @sizeOf(Header)) + @as(usize, header.len);
20 }
21
22 pub fn send(fd: i32, tag: Tag, data: []const u8) !void {
+2,
-1
1@@ -624,7 +624,7 @@ const Daemon = struct {
2
3 pub fn handleHistory(self: *Daemon, client: *Client, term: *ghostty_vt.Terminal, payload: []const u8) !void {
4 const format: util.HistoryFormat = if (payload.len > 0)
5- @enumFromInt(payload[0])
6+ std.meta.intToEnum(util.HistoryFormat, payload[0]) catch .plain
7 else
8 .plain;
9 if (util.serializeTerminal(self.alloc, term, format)) |output| {
10@@ -1489,6 +1489,7 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
11 .History => try daemon.handleHistory(client, &term, msg.payload),
12 .Run => try daemon.handleRun(client, pty_fd, msg.payload),
13 .Output, .Ack => {},
14+ _ => std.log.warn("ignoring unknown IPC tag={d}", .{@intFromEnum(msg.header.tag)}),
15 }
16 }
17 }
+8,
-5
1@@ -64,13 +64,16 @@ pub fn get_session_entries(alloc: std.mem.Allocator, socket_dir: []const u8) !st
2 };
3 posix.close(result.fd);
4
5- // Extract cmd and cwd from the fixed-size arrays
6- const cmd: ?[]const u8 = if (result.info.cmd_len > 0)
7- alloc.dupe(u8, result.info.cmd[0..result.info.cmd_len]) catch null
8+ // Extract cmd and cwd from the fixed-size arrays. Lengths come
9+ // off the wire (u16 range), so clamp to the actual array size.
10+ const cmd_len = @min(result.info.cmd_len, ipc.MAX_CMD_LEN);
11+ const cwd_len = @min(result.info.cwd_len, ipc.MAX_CWD_LEN);
12+ const cmd: ?[]const u8 = if (cmd_len > 0)
13+ alloc.dupe(u8, result.info.cmd[0..cmd_len]) catch null
14 else
15 null;
16- const cwd: ?[]const u8 = if (result.info.cwd_len > 0)
17- alloc.dupe(u8, result.info.cwd[0..result.info.cwd_len]) catch null
18+ const cwd: ?[]const u8 = if (cwd_len > 0)
19+ alloc.dupe(u8, result.info.cwd[0..cwd_len]) catch null
20 else
21 null;
22