mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-13 19:15:48 +00:00
terminal: fix integrity violation printing wide char with hyperlink at right edge
Printing a wide character at the right edge of the screen with an active hyperlink triggered a page integrity violation (UnwrappedSpacerHead). printCell wrote the spacer_head to the cell and then called cursorSetHyperlink, whose internal integrity check observed the spacer_head before printWrap had a chance to set the row wrap flag. Fix by setting row.wrap = true before calling printCell for the spacer_head case, so all integrity checks see a consistent state. printWrap sets wrap again afterward, which is harmless. Found by AFL++ stream fuzzer.
This commit is contained in:
@@ -629,7 +629,16 @@ pub fn print(self: *Terminal, c: u21) !void {
|
||||
// We only create a spacer head if we're at the real edge
|
||||
// of the screen. Otherwise, we clear the space with a narrow.
|
||||
// This allows soft wrapping to work correctly.
|
||||
self.printCell(0, if (right_limit == self.cols) .spacer_head else .narrow);
|
||||
if (right_limit == self.cols) {
|
||||
// Special-case: we need to set wrap to true even
|
||||
// though we call printWrap below because if there is
|
||||
// a page resize during printCell then it'll fail
|
||||
// integrity checks.
|
||||
self.screens.active.cursor.page_row.wrap = true;
|
||||
self.printCell(0, .spacer_head);
|
||||
} else {
|
||||
self.printCell(0, .narrow);
|
||||
}
|
||||
try self.printWrap();
|
||||
}
|
||||
|
||||
@@ -5112,6 +5121,50 @@ test "Terminal: overwrite hyperlink" {
|
||||
try testing.expect(t.isDirty(.{ .screen = .{ .x = 0, .y = 0 } }));
|
||||
}
|
||||
|
||||
// Printing a wide char at the right edge with an active hyperlink causes
|
||||
// printCell to write a spacer_head before printWrap sets the row wrap
|
||||
// flag. The integrity check inside setHyperlink (or increaseCapacity)
|
||||
// sees the unwrapped spacer head and panics. Found via fuzzing.
|
||||
test "Terminal: print wide char at right edge with hyperlink" {
|
||||
var t = try init(testing.allocator, .{ .cols = 10, .rows = 5 });
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
try t.screens.active.startHyperlink("http://example.com", null);
|
||||
|
||||
// Move cursor to the last column (1-indexed)
|
||||
t.setCursorPos(1, 10);
|
||||
|
||||
// Print a wide character; this will call printCell(0, .spacer_head)
|
||||
// at the right edge before calling printWrap, triggering the
|
||||
// integrity violation.
|
||||
try t.print(0x4E2D); // U+4E2D '中'
|
||||
|
||||
// Cursor wraps to row 2, after the wide char + spacer tail
|
||||
try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.y);
|
||||
try testing.expectEqual(@as(usize, 2), t.screens.active.cursor.x);
|
||||
|
||||
// Row 0, col 9: spacer head with hyperlink
|
||||
{
|
||||
const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 9, .y = 0 } }).?;
|
||||
try testing.expectEqual(Cell.Wide.spacer_head, list_cell.cell.wide);
|
||||
try testing.expect(list_cell.cell.hyperlink);
|
||||
try testing.expect(list_cell.row.wrap);
|
||||
}
|
||||
// Row 1, col 0: the wide char with hyperlink
|
||||
{
|
||||
const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 0, .y = 1 } }).?;
|
||||
try testing.expectEqual(@as(u21, 0x4E2D), list_cell.cell.content.codepoint);
|
||||
try testing.expectEqual(Cell.Wide.wide, list_cell.cell.wide);
|
||||
try testing.expect(list_cell.cell.hyperlink);
|
||||
}
|
||||
// Row 1, col 1: spacer tail with hyperlink
|
||||
{
|
||||
const list_cell = t.screens.active.pages.getCell(.{ .screen = .{ .x = 1, .y = 1 } }).?;
|
||||
try testing.expectEqual(Cell.Wide.spacer_tail, list_cell.cell.wide);
|
||||
try testing.expect(list_cell.cell.hyperlink);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: linefeed and carriage return" {
|
||||
var t = try init(testing.allocator, .{ .cols = 80, .rows = 80 });
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
Reference in New Issue
Block a user