From f253c54facb6fc00a4f408e1ab641f616b7d9f91 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 1 Mar 2026 15:21:56 -0800 Subject: [PATCH] terminal: handle trailing colon in SGR underline parsing A trailing colon with no following sub-parameter (e.g. "ESC[58:4:m") leaves the colon separator bit set on the last param without adding another entry to the params array. When the SGR parser later iterates to that param (4 = underline) and sees the colon bit, it entered the colon path which asserted slice.len >= 2, but the slice only had one element. Replace the assert with a bounds check that treats the malformed sequence as a default single underline. Add a regression test reproducing the crash from AFL++ fuzzing (afl-out/stream/default/crashes/id:000021). --- src/terminal/sgr.zig | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/terminal/sgr.zig b/src/terminal/sgr.zig index 6fd4f1e79..314d60686 100644 --- a/src/terminal/sgr.zig +++ b/src/terminal/sgr.zig @@ -249,7 +249,16 @@ pub const Parser = struct { // Colons are fairly rare in the wild. @branchHint(.unlikely); - assert(slice.len >= 2); + // A trailing colon with no following sub-param + // (e.g. "ESC[58:4:m") leaves the colon separator + // bit set on the last param without adding another + // entry, so we can see param 4 with a colon but + // nothing after it. + if (slice.len < 2) { + @branchHint(.cold); + break :underline; + } + if (self.isColon()) { // Invalid/unknown SGRs are just not very likely. @branchHint(.cold); @@ -1068,3 +1077,30 @@ test "sgr: kakoune input issue underline, fg, and bg" { try testing.expect(p.next() == null); } + +// Fuzz crash: afl-out/stream/default/crashes/id:000021 +// Input "ESC [ 5 8 : 4 : m" produces params [58, 4] with colon +// separator bits set at indices 0 and 1. The trailing colon causes +// the second iteration to see param 4 (underline) with a colon, +// triggering assert(slice.len >= 2) with slice.len == 1. +test "sgr: underline colon with trailing separator and short slice" { + var p: Parser = .{ + .params = &[_]u16{ 58, 4 }, + .params_sep = sep: { + var list = SepList.initEmpty(); + list.set(0); + list.set(1); + break :sep list; + }, + }; + + // 58:4 is not a valid underline color (sub-param 4 is not 2 or 5), + // so it falls through as unknown. + try testing.expect(p.next().? == .unknown); + + // Param 4 with a trailing colon but no sub-param is malformed, + // so it also falls through as unknown rather than panicking. + try testing.expect(p.next().? == .unknown); + + try testing.expect(p.next() == null); +}