From adbb432930ad66bc7f8d700a8701560efd038439 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 28 Feb 2026 15:17:58 -0800 Subject: [PATCH] test/fuzz-libghostty: basic afl++-based fuzzer for libghostty --- src/lib_vt.zig | 1 + test/fuzz-libghostty/AGENTS.md | 4 ++ test/fuzz-libghostty/build.zig | 62 ++++++++++++++++++++++++++++++ test/fuzz-libghostty/build.zig.zon | 14 +++++++ test/fuzz-libghostty/src/lib.zig | 11 ++++++ test/fuzz-libghostty/src/main.c | 27 +++++++++++++ 6 files changed, 119 insertions(+) create mode 100644 test/fuzz-libghostty/AGENTS.md create mode 100644 test/fuzz-libghostty/build.zig create mode 100644 test/fuzz-libghostty/build.zig.zon create mode 100644 test/fuzz-libghostty/src/lib.zig create mode 100644 test/fuzz-libghostty/src/main.c diff --git a/src/lib_vt.zig b/src/lib_vt.zig index 03a883e20..251faa0a4 100644 --- a/src/lib_vt.zig +++ b/src/lib_vt.zig @@ -32,6 +32,7 @@ pub const modes = terminal.modes; pub const page = terminal.page; pub const parse_table = terminal.parse_table; pub const search = terminal.search; +pub const sgr = terminal.sgr; pub const size = terminal.size; pub const x11_color = terminal.x11_color; diff --git a/test/fuzz-libghostty/AGENTS.md b/test/fuzz-libghostty/AGENTS.md new file mode 100644 index 000000000..bf89bd7e0 --- /dev/null +++ b/test/fuzz-libghostty/AGENTS.md @@ -0,0 +1,4 @@ +# AFL++ Fuzzer for Libghostty + +- `ghostty-fuzz` is a binary built with `afl-cc` +- Build `ghostty-fuzz` with `zig build` diff --git a/test/fuzz-libghostty/build.zig b/test/fuzz-libghostty/build.zig new file mode 100644 index 000000000..43e5d4f89 --- /dev/null +++ b/test/fuzz-libghostty/build.zig @@ -0,0 +1,62 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + // Create the C ABI library from Zig source that exports the + // API that the `afl-cc` main.c entrypoint can call into. This + // lets us just use standard `afl-cc` to fuzz test our library without + // needing to write any Zig-specific fuzzing harnesses. + const lib = lib: { + // Zig module + const lib_mod = b.createModule(.{ + .root_source_file = b.path("src/lib.zig"), + .target = target, + .optimize = optimize, + }); + if (b.lazyDependency("ghostty", .{ + .simd = false, + })) |dep| { + lib_mod.addImport( + "ghostty-vt", + dep.module("ghostty-vt"), + ); + } + + // C lib + const lib = b.addLibrary(.{ + .name = "ghostty-fuzz", + .root_module = lib_mod, + }); + + // Required to build properly with afl-cc + lib.bundle_compiler_rt = true; + lib.bundle_ubsan_rt = true; + lib.root_module.stack_check = false; + + break :lib lib; + }; + + // Build a C entrypoint with afl-cc that links against the generated + // static Zig library. afl-cc is expecte to be on the PATH. + const exe = exe: { + const cc = b.addSystemCommand(&.{"afl-cc"}); + cc.addArgs(&.{ + "-std=c11", + "-O2", + "-g", + "-o", + }); + const output = cc.addOutputFileArg("ghostty-fuzz"); + cc.addFileArg(b.path("src/main.c")); + cc.addFileArg(lib.getEmittedBin()); + + break :exe output; + }; + + // Install + b.installArtifact(lib); + const exe_install = b.addInstallBinFile(exe, "ghostty-fuzz"); + b.getInstallStep().dependOn(&exe_install.step); +} diff --git a/test/fuzz-libghostty/build.zig.zon b/test/fuzz-libghostty/build.zig.zon new file mode 100644 index 000000000..091730360 --- /dev/null +++ b/test/fuzz-libghostty/build.zig.zon @@ -0,0 +1,14 @@ +.{ + .name = .fuzz_libghostty, + .version = "0.0.0", + .fingerprint = 0x2cb2c498237c5d43, + .minimum_zig_version = "0.15.1", + .dependencies = .{ + .ghostty = .{ .path = "../../" }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/test/fuzz-libghostty/src/lib.zig b/test/fuzz-libghostty/src/lib.zig new file mode 100644 index 000000000..a4d2cb765 --- /dev/null +++ b/test/fuzz-libghostty/src/lib.zig @@ -0,0 +1,11 @@ +const std = @import("std"); +const ghostty_vt = @import("ghostty-vt"); + +pub export fn ghostty_fuzz_parser( + input_ptr: [*]const u8, + input_len: usize, +) callconv(.c) void { + var p: ghostty_vt.Parser = .init(); + defer p.deinit(); + for (input_ptr[0..input_len]) |byte| _ = p.next(byte); +} diff --git a/test/fuzz-libghostty/src/main.c b/test/fuzz-libghostty/src/main.c new file mode 100644 index 000000000..e2f6942bb --- /dev/null +++ b/test/fuzz-libghostty/src/main.c @@ -0,0 +1,27 @@ +#include +#include +#include + +void ghostty_fuzz_parser(const uint8_t *input, size_t input_len); + +int main(int argc, char **argv) { + uint8_t buf[4096]; + size_t len = 0; + FILE *f = stdin; + + if (argc > 1) { + f = fopen(argv[1], "rb"); + if (f == NULL) { + return 0; + } + } + + len = fread(buf, 1, sizeof(buf), f); + + if (argc > 1) { + fclose(f); + } + + ghostty_fuzz_parser(buf, len); + return 0; +}