test/fuzz-libghostty: basic afl++-based fuzzer for libghostty

This commit is contained in:
Mitchell Hashimoto
2026-02-28 15:17:58 -08:00
parent 25f12080cb
commit adbb432930
6 changed files with 119 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,4 @@
# AFL++ Fuzzer for Libghostty
- `ghostty-fuzz` is a binary built with `afl-cc`
- Build `ghostty-fuzz` with `zig build`

View File

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

View File

@@ -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",
},
}

View File

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

View File

@@ -0,0 +1,27 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
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;
}