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).
This commit is contained in:
Mitchell Hashimoto
2026-03-01 15:21:56 -08:00
parent 1ead8f4275
commit f253c54fac

View File

@@ -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);
}