diff --git a/.gitignore b/.gitignore index de60b122ffad7b230001dabd0b577d932b3940d1..afc1e796b30efc5f718838d2ac1752a357408811 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ deps.zig zig-* zigmod.lock +fuzz/output diff --git a/build.zig b/build.zig index d71439d7bd232fcad07efb93f1aabd042728014c..66c73647467212abf730b3e1b5daa00f5856233a 100644 --- a/build.zig +++ b/build.zig @@ -18,4 +18,52 @@ pub fn build(b: *std.Build) void { const test_step = b.step("test", "Run the tests"); test_step.dependOn(&test_cmd.step); + + // + + const fuzz_exe = addFuzzer(b, target, "json", &.{}); + + const fuzz_run = b.addSystemCommand(&.{"afl-fuzz"}); + fuzz_run.step.dependOn(&fuzz_exe.step); + fuzz_run.addArgs(&.{ "-i", "fuzz/input" }); + fuzz_run.addArgs(&.{ "-o", "fuzz/output" }); + fuzz_run.addArgs(&.{ "-x", "fuzz/json.dict" }); + fuzz_run.addArg("--"); + fuzz_run.addFileArg(fuzz_exe.source); + + const fuzz_step = b.step("fuzz", "Run AFL++"); + fuzz_step.dependOn(&fuzz_run.step); +} + +fn addFuzzer(b: *std.Build, target: std.Build.ResolvedTarget, comptime name: []const u8, afl_clang_args: []const []const u8) *std.Build.Step.InstallFile { + // The library + const fuzz_lib = b.addStaticLibrary(.{ + .name = "fuzz-" ++ name, + .root_source_file = b.path("fuzz/main.zig"), + .target = target, + .optimize = .Debug, + }); + fuzz_lib.want_lto = true; + fuzz_lib.bundle_compiler_rt = true; + // Seems to be necessary for LLVM >= 15 + fuzz_lib.root_module.pic = true; + + deps.addAllTo(fuzz_lib); + + // Setup the output name + const fuzz_executable_name = "fuzz-" ++ name; + const fuzz_exe_path = std.fs.path.join(b.allocator, &.{ b.cache_root.path.?, fuzz_executable_name }) catch @panic("OOM"); + + // We want `afl-clang-lto -o path/to/output path/to/library` + const fuzz_compile = b.addSystemCommand(&.{ "afl-clang-lto", "-v", "-o", fuzz_exe_path }); + // Add the path to the library file to afl-clang-lto's args + fuzz_compile.addArtifactArg(fuzz_lib); + // Custom args + fuzz_compile.addArgs(afl_clang_args); + + // Install the cached output to the install 'bin' path + const fuzz_install = b.addInstallBinFile(.{ .path = fuzz_exe_path }, fuzz_executable_name); + fuzz_install.step.dependOn(&fuzz_compile.step); + + return fuzz_install; } diff --git a/fuzz/input/input.json b/fuzz/input/input.json new file mode 100644 index 0000000000000000000000000000000000000000..72ed25dd04472e8c1034f52915d582e40a292f65 --- /dev/null +++ b/fuzz/input/input.json @@ -0,0 +1,26 @@ +{ + "Image": { + "Width": 800, + "Height": 600, + "Title": "View from 15th Floor", + "Thumbnail": { + "Url": "http://www.example.com/image/481989943", + "Height": 125, + "Width": 100 + }, + "Animated": false, + "IDs": [ + 116, + 943, + 234, + 38793 + ], + "ArrayOfObject": [ + { + "n": "m" + } + ], + "double": 1.3412, + "LargeInt": 18446744073709551615 + } +} diff --git a/fuzz/json.dict b/fuzz/json.dict new file mode 100644 index 0000000000000000000000000000000000000000..b7604fb947e6b0808e24ba4ea28dcdf4a0d8c8ff --- /dev/null +++ b/fuzz/json.dict @@ -0,0 +1,61 @@ +# +# AFL dictionary for JSON +# ----------------------- +# +# Just the very basics. +# +# Inspired by a dictionary by Jakub Wilk +# + +"0" +",0" +":0" +"0:" +"-1.2e+3" + +"true" +"false" +"null" + +"\"\"" +",\"\"" +":\"\"" +"\"\":" + +"{}" +",{}" +":{}" +"{\"\":0}" +"{{}}" + +"[]" +",[]" +":[]" +"[0]" +"[[]]" + +"''" +"\\" +"\\b" +"\\f" +"\\n" +"\\r" +"\\t" +"\\u0000" +"\\x00" +"\\0" +"\\uD800\\uDC00" +"\\uDBFF\\uDFFF" + +"\"\":0" +"//" +"/**/" + +"$ref" +"type" +"coordinates" +"@context" +"@id" + +"," +":" diff --git a/fuzz/main.zig b/fuzz/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..74e7af13da8d8aa466c1e814c6ed2eef0dbc9b58 --- /dev/null +++ b/fuzz/main.zig @@ -0,0 +1,25 @@ +const std = @import("std"); +const json = @import("json"); + +comptime { + @export(cMain, .{ .name = "main", .linkage = .strong }); +} + +fn cMain() callconv(.C) void { + main(); +} + +pub fn main() void { + // Setup an allocator that will detect leaks/use-after-free/etc + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + // this will check for leaks and crash the program if it finds any + defer std.debug.assert(gpa.deinit() == .ok); + const allocator = gpa.allocator(); + + // Read the data from stdin + const stdin = std.io.getStdIn(); + + // Try to parse the data + var parsed = json.parse(allocator, "[stdin]", stdin.reader()) catch return; + defer parsed.deinit(allocator); +}