mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-06 07:38:21 +00:00
fuzz: add OSC parser fuzzer
This commit is contained in:
@@ -7,9 +7,15 @@ libghostty-vt (Zig module).
|
||||
|
||||
| Target | Binary | Description |
|
||||
| -------- | ------------- | ------------------------------------------------------- |
|
||||
| `osc` | `fuzz-osc` | OSC parser with allocator (`osc.Parser.next` + `end`) |
|
||||
| `parser` | `fuzz-parser` | VT parser only (`Parser.next` byte-at-a-time) |
|
||||
| `stream` | `fuzz-stream` | Full terminal stream (`nextSlice` + `next` via handler) |
|
||||
|
||||
The osc target directly fuzzes the `osc.Parser` with an allocator enabled,
|
||||
exercising the allocating writer code paths for large payloads. The first
|
||||
byte selects the terminator variant (BEL, ST, or missing). Seeds cover OSC
|
||||
52, 66, 133, 3008, 1337, and 5522.
|
||||
|
||||
The stream target creates a small `Terminal` and exercises the readonly
|
||||
`Stream` handler, covering printing, CSI dispatch, OSC, DCS, SGR, cursor
|
||||
movement, scrolling regions, and more. The first byte of each input selects
|
||||
@@ -33,13 +39,14 @@ zig build
|
||||
|
||||
This compiles Zig static libraries for each fuzz target, emits LLVM bitcode,
|
||||
then links each with `afl.c` using `afl-cc` to produce instrumented binaries
|
||||
at `zig-out/bin/fuzz-parser` and `zig-out/bin/fuzz-stream`.
|
||||
at `zig-out/bin/fuzz-osc`, `zig-out/bin/fuzz-parser`, and `zig-out/bin/fuzz-stream`.
|
||||
|
||||
## Running the Fuzzer
|
||||
|
||||
Each target has its own run step:
|
||||
|
||||
```sh
|
||||
zig build run-osc # Run the OSC parser fuzzer
|
||||
zig build run-parser # Run the VT parser fuzzer
|
||||
zig build run-stream # Run the VT stream fuzzer
|
||||
```
|
||||
@@ -118,6 +125,8 @@ rename the output files to replace colons with underscores before committing:
|
||||
|
||||
| Directory | Contents |
|
||||
| ------------------------ | ----------------------------------------------- |
|
||||
| `corpus/osc-initial/` | Hand-written seed inputs for osc-parser |
|
||||
| `corpus/osc-cmin/` | Output of `afl-cmin` (edge-deduplicated corpus) |
|
||||
| `corpus/parser-initial/` | Hand-written seed inputs for vt-parser |
|
||||
| `corpus/parser-cmin/` | Output of `afl-cmin` (edge-deduplicated corpus) |
|
||||
| `corpus/stream-initial/` | Hand-written seed inputs for vt-stream |
|
||||
|
||||
@@ -17,6 +17,7 @@ const Fuzzer = struct {
|
||||
};
|
||||
|
||||
const fuzzers: []const Fuzzer = &.{
|
||||
.{ .name = "osc" },
|
||||
.{ .name = "parser" },
|
||||
.{ .name = "stream" },
|
||||
};
|
||||
|
||||
BIN
test/fuzz-libghostty/corpus/osc-cmin/01-osc52-clip-set-bel
Normal file
BIN
test/fuzz-libghostty/corpus/osc-cmin/01-osc52-clip-set-bel
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
||||
52;c;?
|
||||
@@ -0,0 +1 @@
|
||||
52;c;
|
||||
BIN
test/fuzz-libghostty/corpus/osc-cmin/04-osc52-large-payload
Normal file
BIN
test/fuzz-libghostty/corpus/osc-cmin/04-osc52-large-payload
Normal file
Binary file not shown.
BIN
test/fuzz-libghostty/corpus/osc-cmin/05-osc52-invalid-b64
Normal file
BIN
test/fuzz-libghostty/corpus/osc-cmin/05-osc52-invalid-b64
Normal file
Binary file not shown.
BIN
test/fuzz-libghostty/corpus/osc-cmin/06-osc66-text-sizing
Normal file
BIN
test/fuzz-libghostty/corpus/osc-cmin/06-osc66-text-sizing
Normal file
Binary file not shown.
1
test/fuzz-libghostty/corpus/osc-cmin/07-osc66-empty
Normal file
1
test/fuzz-libghostty/corpus/osc-cmin/07-osc66-empty
Normal file
@@ -0,0 +1 @@
|
||||
66;
|
||||
BIN
test/fuzz-libghostty/corpus/osc-cmin/08-osc133-prompt-start
Normal file
BIN
test/fuzz-libghostty/corpus/osc-cmin/08-osc133-prompt-start
Normal file
Binary file not shown.
1
test/fuzz-libghostty/corpus/osc-cmin/09-osc133-cmd-start
Normal file
1
test/fuzz-libghostty/corpus/osc-cmin/09-osc133-cmd-start
Normal file
@@ -0,0 +1 @@
|
||||
133;C
|
||||
1
test/fuzz-libghostty/corpus/osc-cmin/10-osc133-cmd-end
Normal file
1
test/fuzz-libghostty/corpus/osc-cmin/10-osc133-cmd-end
Normal file
@@ -0,0 +1 @@
|
||||
133;D;0
|
||||
BIN
test/fuzz-libghostty/corpus/osc-cmin/11-osc133-invalid
Normal file
BIN
test/fuzz-libghostty/corpus/osc-cmin/11-osc133-invalid
Normal file
Binary file not shown.
BIN
test/fuzz-libghostty/corpus/osc-cmin/12-osc3008-context
Normal file
BIN
test/fuzz-libghostty/corpus/osc-cmin/12-osc3008-context
Normal file
Binary file not shown.
1
test/fuzz-libghostty/corpus/osc-cmin/13-osc3008-empty
Normal file
1
test/fuzz-libghostty/corpus/osc-cmin/13-osc3008-empty
Normal file
@@ -0,0 +1 @@
|
||||
3008;
|
||||
BIN
test/fuzz-libghostty/corpus/osc-cmin/14-osc1337-file-inline
Normal file
BIN
test/fuzz-libghostty/corpus/osc-cmin/14-osc1337-file-inline
Normal file
Binary file not shown.
1
test/fuzz-libghostty/corpus/osc-cmin/15-osc1337-invalid
Normal file
1
test/fuzz-libghostty/corpus/osc-cmin/15-osc1337-invalid
Normal file
@@ -0,0 +1 @@
|
||||
1337;badcmd
|
||||
BIN
test/fuzz-libghostty/corpus/osc-cmin/16-osc5522-clip
Normal file
BIN
test/fuzz-libghostty/corpus/osc-cmin/16-osc5522-clip
Normal file
Binary file not shown.
1
test/fuzz-libghostty/corpus/osc-cmin/17-osc5522-empty
Normal file
1
test/fuzz-libghostty/corpus/osc-cmin/17-osc5522-empty
Normal file
@@ -0,0 +1 @@
|
||||
5522;
|
||||
BIN
test/fuzz-libghostty/corpus/osc-cmin/18-osc52-truncated
Normal file
BIN
test/fuzz-libghostty/corpus/osc-cmin/18-osc52-truncated
Normal file
Binary file not shown.
BIN
test/fuzz-libghostty/corpus/osc-cmin/19-single-byte
Normal file
BIN
test/fuzz-libghostty/corpus/osc-cmin/19-single-byte
Normal file
Binary file not shown.
BIN
test/fuzz-libghostty/corpus/osc-cmin/20-invalid-osc-num
Normal file
BIN
test/fuzz-libghostty/corpus/osc-cmin/20-invalid-osc-num
Normal file
Binary file not shown.
BIN
test/fuzz-libghostty/corpus/osc-initial/01-osc52-clip-set-bel
Normal file
BIN
test/fuzz-libghostty/corpus/osc-initial/01-osc52-clip-set-bel
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
||||
52;c;?
|
||||
@@ -0,0 +1 @@
|
||||
52;c;
|
||||
BIN
test/fuzz-libghostty/corpus/osc-initial/04-osc52-large-payload
Normal file
BIN
test/fuzz-libghostty/corpus/osc-initial/04-osc52-large-payload
Normal file
Binary file not shown.
BIN
test/fuzz-libghostty/corpus/osc-initial/05-osc52-invalid-b64
Normal file
BIN
test/fuzz-libghostty/corpus/osc-initial/05-osc52-invalid-b64
Normal file
Binary file not shown.
BIN
test/fuzz-libghostty/corpus/osc-initial/06-osc66-text-sizing
Normal file
BIN
test/fuzz-libghostty/corpus/osc-initial/06-osc66-text-sizing
Normal file
Binary file not shown.
1
test/fuzz-libghostty/corpus/osc-initial/07-osc66-empty
Normal file
1
test/fuzz-libghostty/corpus/osc-initial/07-osc66-empty
Normal file
@@ -0,0 +1 @@
|
||||
66;
|
||||
BIN
test/fuzz-libghostty/corpus/osc-initial/08-osc133-prompt-start
Normal file
BIN
test/fuzz-libghostty/corpus/osc-initial/08-osc133-prompt-start
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
||||
133;C
|
||||
@@ -0,0 +1 @@
|
||||
133;D;0
|
||||
BIN
test/fuzz-libghostty/corpus/osc-initial/11-osc133-invalid
Normal file
BIN
test/fuzz-libghostty/corpus/osc-initial/11-osc133-invalid
Normal file
Binary file not shown.
BIN
test/fuzz-libghostty/corpus/osc-initial/12-osc3008-context
Normal file
BIN
test/fuzz-libghostty/corpus/osc-initial/12-osc3008-context
Normal file
Binary file not shown.
1
test/fuzz-libghostty/corpus/osc-initial/13-osc3008-empty
Normal file
1
test/fuzz-libghostty/corpus/osc-initial/13-osc3008-empty
Normal file
@@ -0,0 +1 @@
|
||||
3008;
|
||||
BIN
test/fuzz-libghostty/corpus/osc-initial/14-osc1337-file-inline
Normal file
BIN
test/fuzz-libghostty/corpus/osc-initial/14-osc1337-file-inline
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
||||
1337;badcmd
|
||||
BIN
test/fuzz-libghostty/corpus/osc-initial/16-osc5522-clip
Normal file
BIN
test/fuzz-libghostty/corpus/osc-initial/16-osc5522-clip
Normal file
Binary file not shown.
1
test/fuzz-libghostty/corpus/osc-initial/17-osc5522-empty
Normal file
1
test/fuzz-libghostty/corpus/osc-initial/17-osc5522-empty
Normal file
@@ -0,0 +1 @@
|
||||
5522;
|
||||
BIN
test/fuzz-libghostty/corpus/osc-initial/18-osc52-truncated
Normal file
BIN
test/fuzz-libghostty/corpus/osc-initial/18-osc52-truncated
Normal file
Binary file not shown.
BIN
test/fuzz-libghostty/corpus/osc-initial/19-single-byte
Normal file
BIN
test/fuzz-libghostty/corpus/osc-initial/19-single-byte
Normal file
Binary file not shown.
BIN
test/fuzz-libghostty/corpus/osc-initial/20-invalid-osc-num
Normal file
BIN
test/fuzz-libghostty/corpus/osc-initial/20-invalid-osc-num
Normal file
Binary file not shown.
45
test/fuzz-libghostty/src/fuzz_osc.zig
Normal file
45
test/fuzz-libghostty/src/fuzz_osc.zig
Normal file
@@ -0,0 +1,45 @@
|
||||
const std = @import("std");
|
||||
const ghostty_vt = @import("ghostty-vt");
|
||||
const mem = @import("mem.zig");
|
||||
const osc = ghostty_vt.osc;
|
||||
|
||||
/// Use a single global allocator for simplicity and to avoid heap
|
||||
/// allocation overhead in the fuzzer. The allocator is backed by a fixed
|
||||
/// buffer, and every fuzz input resets the bump pointer to the start.
|
||||
var fuzz_alloc: mem.FuzzAllocator(8 * 1024 * 1024) = .{};
|
||||
|
||||
pub export fn zig_fuzz_init() callconv(.c) void {
|
||||
fuzz_alloc.init();
|
||||
}
|
||||
|
||||
pub export fn zig_fuzz_test(
|
||||
buf: [*]const u8,
|
||||
len: usize,
|
||||
) callconv(.c) void {
|
||||
// Need at least one byte for the terminator selector.
|
||||
if (len == 0) return;
|
||||
|
||||
fuzz_alloc.reset();
|
||||
const alloc = fuzz_alloc.allocator();
|
||||
const input = buf[0..len];
|
||||
|
||||
// Use the first byte to select the terminator variant.
|
||||
const selector = input[0];
|
||||
const payload = input[1..];
|
||||
|
||||
var p = osc.Parser.init(alloc);
|
||||
defer p.deinit();
|
||||
for (payload) |byte| p.next(byte);
|
||||
|
||||
// Exercise all three terminator paths:
|
||||
// 0 -> BEL (0x07)
|
||||
// 1 -> ST (0x9c)
|
||||
// 2 -> missing terminator (null)
|
||||
const terminator: ?u8 = switch (selector % 3) {
|
||||
0 => 0x07,
|
||||
1 => 0x9c,
|
||||
else => null,
|
||||
};
|
||||
|
||||
_ = p.end(terminator);
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
const std = @import("std");
|
||||
const ghostty_vt = @import("ghostty-vt");
|
||||
const mem = @import("mem.zig");
|
||||
const Terminal = ghostty_vt.Terminal;
|
||||
const ReadonlyStream = ghostty_vt.ReadonlyStream;
|
||||
|
||||
/// Use a single global allocator for simplicity and to avoid heap
|
||||
/// allocation overhead in the fuzzer. The allocator is backed by a fixed
|
||||
/// buffer, and every fuzz input resets the bump pointer to the start.
|
||||
var fuzz_alloc: FuzzAllocator = .{};
|
||||
var fuzz_alloc: mem.FuzzAllocator(64 * 1024 * 1024) = .{};
|
||||
|
||||
pub export fn zig_fuzz_init() callconv(.c) void {
|
||||
fuzz_alloc.init();
|
||||
@@ -50,29 +51,3 @@ pub export fn zig_fuzz_test(
|
||||
std.debug.panic("next: {}", .{err});
|
||||
}
|
||||
}
|
||||
|
||||
/// Fixed-capacity allocator that avoids heap allocation and gives the
|
||||
/// fuzzer deterministic, bounded memory behaviour. Backed by a single
|
||||
/// fixed buffer; every `reset()` returns the bump pointer to the start
|
||||
/// so the same memory is reused across iterations.
|
||||
const FuzzAllocator = struct {
|
||||
buf: [mem_size]u8 = undefined,
|
||||
state: std.heap.FixedBufferAllocator = undefined,
|
||||
|
||||
/// 64 MiB gives the fuzzer enough headroom to exercise terminal
|
||||
/// resizes, large scrollback, and other allocation-heavy paths
|
||||
/// without running into out-of-memory on every other input.
|
||||
const mem_size = 64 * 1024 * 1024;
|
||||
|
||||
fn init(self: *FuzzAllocator) void {
|
||||
self.state = .init(&self.buf);
|
||||
}
|
||||
|
||||
fn allocator(self: *FuzzAllocator) std.mem.Allocator {
|
||||
return self.state.allocator();
|
||||
}
|
||||
|
||||
fn reset(self: *FuzzAllocator) void {
|
||||
self.state.reset();
|
||||
}
|
||||
};
|
||||
|
||||
26
test/fuzz-libghostty/src/mem.zig
Normal file
26
test/fuzz-libghostty/src/mem.zig
Normal file
@@ -0,0 +1,26 @@
|
||||
const std = @import("std");
|
||||
|
||||
/// Fixed-capacity allocator that avoids heap allocation and gives the
|
||||
/// fuzzer deterministic, bounded memory behaviour. Backed by a single
|
||||
/// fixed buffer; every `reset()` returns the bump pointer to the start
|
||||
/// so the same memory is reused across iterations.
|
||||
pub fn FuzzAllocator(comptime mem_size: usize) type {
|
||||
return struct {
|
||||
buf: [mem_size]u8 = undefined,
|
||||
state: std.heap.FixedBufferAllocator = undefined,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(self: *Self) void {
|
||||
self.state = .init(&self.buf);
|
||||
}
|
||||
|
||||
pub fn allocator(self: *Self) std.mem.Allocator {
|
||||
return self.state.allocator();
|
||||
}
|
||||
|
||||
pub fn reset(self: *Self) void {
|
||||
self.state.reset();
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user