Created
February 27, 2026 23:20
-
-
Save mitchellh/816a191504e1f8e5e9c206b6b787289c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| diff --git a/build.zig b/build.zig | |
| index f9d861b19..871e2fccb 100644 | |
| --- a/build.zig | |
| +++ b/build.zig | |
| @@ -50,6 +50,10 @@ pub fn build(b: *std.Build) !void { | |
| "test-valgrind", | |
| "Run tests under valgrind", | |
| ); | |
| + const fuzz_sgr_lib_step = b.step( | |
| + "fuzz-sgr-lib", | |
| + "Build static SGR fuzzing library for AFL++", | |
| + ); | |
| const translations_step = b.step( | |
| "update-translations", | |
| "Update translation files", | |
| @@ -118,6 +122,47 @@ pub fn build(b: *std.Build) !void { | |
| libghostty_vt_shared.install(libvt_step); | |
| libghostty_vt_shared.install(b.getInstallStep()); | |
| + // Static library for AFL++ harnesses that fuzz terminal/sgr.zig. | |
| + { | |
| + var vt_options = config.terminalOptions(); | |
| + vt_options.artifact = .lib; | |
| + vt_options.oniguruma = false; | |
| + | |
| + const fuzz_sgr_lib = b.addLibrary(.{ | |
| + .name = "ghostty-sgr-fuzz", | |
| + .root_module = b.createModule(.{ | |
| + .root_source_file = b.path("src/terminal/fuzz/sgr_afl_export.zig"), | |
| + .target = config.target, | |
| + .optimize = config.optimize, | |
| + }), | |
| + }); | |
| + fuzz_sgr_lib.bundle_compiler_rt = true; | |
| + fuzz_sgr_lib.bundle_ubsan_rt = true; | |
| + fuzz_sgr_lib.root_module.stack_check = false; | |
| + fuzz_sgr_lib.root_module.addImport("ghostty-vt", mod.vt); | |
| + vt_options.add(b, fuzz_sgr_lib.root_module); | |
| + | |
| + // Pull in static dependencies so we can produce a single archive | |
| + // that afl-cc can link directly. | |
| + var fuzz_sgr_lib_list = try deps.add(fuzz_sgr_lib); | |
| + try fuzz_sgr_lib_list.append(b.allocator, fuzz_sgr_lib.getEmittedBin()); | |
| + | |
| + if (config.target.result.os.tag.isDarwin()) { | |
| + const libtool = buildpkg.LibtoolStep.create(b, .{ | |
| + .name = "ghostty-sgr-fuzz", | |
| + .out_name = "libghostty-sgr-fuzz-fat.a", | |
| + .sources = fuzz_sgr_lib_list.items, | |
| + }); | |
| + libtool.step.dependOn(&fuzz_sgr_lib.step); | |
| + | |
| + const install = b.addInstallLibFile(libtool.output, "libghostty-sgr-fuzz.a"); | |
| + fuzz_sgr_lib_step.dependOn(&install.step); | |
| + } else { | |
| + const install = b.addInstallArtifact(fuzz_sgr_lib, .{}); | |
| + fuzz_sgr_lib_step.dependOn(&install.step); | |
| + } | |
| + } | |
| + | |
| // Helpgen | |
| if (config.emit_helpgen) deps.help_strings.install(); | |
| diff --git a/src/terminal/fuzz/sgr_afl_export.zig b/src/terminal/fuzz/sgr_afl_export.zig | |
| new file mode 100644 | |
| index 000000000..a7b2da44f | |
| --- /dev/null | |
| +++ b/src/terminal/fuzz/sgr_afl_export.zig | |
| @@ -0,0 +1,82 @@ | |
| +const std = @import("std"); | |
| +const ghostty_vt = @import("ghostty-vt"); | |
| + | |
| +const SepList = ghostty_vt.Parser.Action.CSI.SepList; | |
| +const max_params = ghostty_vt.Parser.MAX_PARAMS; | |
| + | |
| +pub export fn ghostty_fuzz_sgr_parse(input_ptr: [*]const u8, input_len: usize) callconv(.c) void { | |
| + var params: [max_params]u16 = undefined; | |
| + var params_len: usize = 0; | |
| + var params_sep: SepList = .initEmpty(); | |
| + var truncated = false; | |
| + | |
| + var acc: u32 = 0; | |
| + var have_digits = false; | |
| + var saw_any = false; | |
| + | |
| + for (input_ptr[0..input_len]) |byte| { | |
| + switch (byte) { | |
| + '0'...'9' => { | |
| + saw_any = true; | |
| + have_digits = true; | |
| + acc = @min(acc * 10 + byte - '0', std.math.maxInt(u16)); | |
| + }, | |
| + | |
| + ';', ':' => { | |
| + saw_any = true; | |
| + | |
| + if (params_len >= max_params) { | |
| + truncated = true; | |
| + break; | |
| + } | |
| + | |
| + params[params_len] = if (have_digits) @intCast(acc) else 0; | |
| + if (byte == ':') params_sep.set(params_len); | |
| + params_len += 1; | |
| + | |
| + acc = 0; | |
| + have_digits = false; | |
| + }, | |
| + | |
| + else => {}, | |
| + } | |
| + } | |
| + | |
| + if (truncated and params_len > 0) params_sep.unset(params_len - 1); | |
| + | |
| + if (params_len < max_params and (have_digits or saw_any)) { | |
| + params[params_len] = if (have_digits) @intCast(acc) else 0; | |
| + params_len += 1; | |
| + } | |
| + | |
| + var seq_buf: [256]u8 = undefined; | |
| + seq_buf[0] = 0x1B; | |
| + seq_buf[1] = '['; | |
| + var seq_len: usize = 2; | |
| + | |
| + for (params[0..params_len], 0..) |param, i| { | |
| + const remaining = seq_buf[seq_len..]; | |
| + const written = std.fmt.bufPrint(remaining, "{}", .{param}) catch break; | |
| + seq_len += written.len; | |
| + if (i + 1 >= params_len) break; | |
| + | |
| + if (seq_len >= seq_buf.len - 1) break; | |
| + seq_buf[seq_len] = if (params_sep.isSet(i)) ':' else ';'; | |
| + seq_len += 1; | |
| + } | |
| + | |
| + seq_buf[seq_len] = 'm'; | |
| + seq_len += 1; | |
| + const seq = seq_buf[0..seq_len]; | |
| + | |
| + var term: ghostty_vt.Terminal = ghostty_vt.Terminal.init(std.heap.page_allocator, .{ | |
| + .cols = 80, | |
| + .rows = 24, | |
| + }) catch return; | |
| + defer term.deinit(std.heap.page_allocator); | |
| + | |
| + var stream = term.vtStream(); | |
| + defer stream.deinit(); | |
| + | |
| + stream.nextSlice(seq) catch {}; | |
| +} | |
| diff --git a/src/terminal/fuzz/sgr_afl_harness.c b/src/terminal/fuzz/sgr_afl_harness.c | |
| new file mode 100644 | |
| index 000000000..728380e3b | |
| --- /dev/null | |
| +++ b/src/terminal/fuzz/sgr_afl_harness.c | |
| @@ -0,0 +1,27 @@ | |
| +#include <stdint.h> | |
| +#include <stdio.h> | |
| +#include <stdlib.h> | |
| + | |
| +void ghostty_fuzz_sgr_parse(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_sgr_parse(buf, len); | |
| + return 0; | |
| +} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment