├── .gitignore ├── LICENSE ├── README.md ├── afl.c ├── build.zig ├── build.zig.zon └── example.zig /.gitignore: -------------------------------------------------------------------------------- 1 | .zig-cache 2 | zig-out 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Loris Cro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zig-afl-kit 2 | Convenience functions for easy integration with AFL++ for both Zig and C/C++ programmers! 3 | 4 | # Dependencies 5 | Thanks to the amazing work done in [allyourcodebase/AFLplusplus](https://github.com/allyourcodebase/AFLplusplus), you don't even need to build the toolchain manually anymore. *You will need LLVM though, we haven't packaged that yet sorry!* 6 | 7 | This package is AFL++ specific so if you're just looking how to fuzz your Zig executable, make sure to follow ziglang/zig#20702. 8 | 9 | # Usage 10 | 11 | ## Add as a dependency 12 | `zig fetch --save git+https://github.com/kristoff-it/zig-afl-kit` 13 | 14 | ## Use it in your build.zig 15 | 16 | Create an object file step with your test code (more on that later) and pass it to `addInstrumentedExe`. While not mandatory, you will probably want to create a dedicated named step, and you will also probably want to install the instrumented executable. 17 | 18 | ```zig 19 | // build.zig 20 | const afl = @import("zig-afl-kit"); 21 | 22 | // Define a step for generating fuzzing tooling: 23 | const fuzz = b.step("fuzz", "Generate an instrumented executable for AFL++"); 24 | 25 | // Define an oblect file that contains your test function: 26 | const afl_obj = b.addObject(.{ 27 | .name = "my_fuzz_obj", 28 | .root_source_file = b.path("src/fuzz.zig"), 29 | .target = target, 30 | .optimize = .Debug, 31 | }); 32 | 33 | // Required options: 34 | afl_obj.root_module.stack_check = false; // not linking with compiler-rt 35 | afl_obj.root_module.link_libc = true; // afl runtime depends on libc 36 | 37 | // Generate an instrumented executable: 38 | const afl_fuzz = afl.addInstrumentedExe(b, target, optimize, afl_obj); 39 | 40 | // Install it 41 | fuzz.dependOn(&b.addInstallBinFile(afl_fuzz, "myfuzz-afl").step); 42 | ``` 43 | 44 | ### Your test code 45 | To create an instrumented executable, your object file must export two C symbols: 46 | - `export fn zig_fuzz_init() void {}` invoked once to initialize resources (eg allocators) 47 | - `export fn zig_fuzz_test(buf: [*]u8, len: isize) void {}` invoked in a loop, containing the main test code, expected to not leave dirty state / leak memory across invocations. 48 | 49 | This library integrates with AFL++ using: 50 | - persistent mode (runs multiple tests on a single process, increases performance drammatically) 51 | - shared memory (a shared memory buffer is used to get input from the fuzzer instead of reading from stdin) 52 | 53 | See `afl.c` for more info. 54 | See `example.zig` for an example of how to structure your test code. 55 | 56 | 57 | 58 | ### **------> IMPORTANT <------** 59 | 60 | **UPDATE: Once ziglang/zig#20725 is merged, you will be able to avoid the next step by doing `afl_obj.root_module.fuzz = true;`.** 61 | 62 | For better fuzzing performance you will want to modify `std.mem.backend_can_use_eql_bytes` to return false, otherwise AFL++ will not be able to observe char-by-char string comparisons and its fuzzing capabilities will be greatly reduced. 63 | 64 | This means modifying your copy of the Zig stdlib. If you have ZLS you can simply write `std.mem` anywhere in your code and goto definiton, otherwise you can invoke `zig env` and modify `$std_dir/mem.zig`. 65 | 66 | **Also don't forget to revert this change after you're done!** 67 | 68 | ## CLI arguments 69 | `addInstrumentedExe` will define a `afl-path` option to allow you to point at a directory where you built AFL++, like so: 70 | 71 | `zig build fuzz -Dafl-path="../AFLPlusplus"` 72 | 73 | ## I'm a C or C++ programmer, can I use this? 74 | Of course you can, just setup your object file step to be compiled from C/C++ files! 75 | 76 | Something along these lines: 77 | 78 | ```zig 79 | const afl_obj = b.addObject(.{ 80 | .name = "my_fuzz_obj", 81 | //.root_source_file = b.path("src/fuzz.zig"), 82 | .target = target, 83 | .optimize = .Debug, 84 | }); 85 | 86 | afl_obj.addCSourceFiles(.{ 87 | .files = &.{ 88 | "foo.c", 89 | "bar.c", 90 | }, 91 | // In case you need flags: 92 | //.flags = &.{"-Wextra", "-DFOO"}, 93 | }); 94 | 95 | // Required options: 96 | afl_obj.root_module.stack_check = false; // not linking with compiler-rt 97 | afl_obj.root_module.link_libc = true; // afl runtime depends on libc 98 | 99 | ``` 100 | The Zig build system can also deal with all other kinds of C build requirements, see the official Zig standard library docs for more info. 101 | 102 | ## Fuzz your application 103 | By default your fuzz step (depending on the instrumented executable to me more precise) will also install the entire AFLplusplus toolchain. 104 | 105 | ``` 106 | zig-out 107 | ├─ bin 108 | │ └── myfuzz-afl 109 | └── AFLplusplus 110 |    ├── bin 111 |    │   ├── afl-analyze 112 |    │   ├── afl-as 113 |    │   ├── afl-cc 114 |    │   ├── afl-compiler-rt-64 115 |   │   ├── afl-compiler-rt.o 116 |    │   ├── afl-fuzz 117 |    │   ├── afl-gotcpu 118 |    │   ├── afl-llvm-rt-lto-64 119 |    │   ├── afl-llvm-rt-lto.o 120 |    │   ├── afl-showmap 121 |    │   └── afl-tmin 122 |    └── lib 123 |    └── 124 | ``` 125 | 126 | If you don't want to build and install the full toolchain, set the `tools` option to `false` (`-Dtools=false`), this way `afl-cc` will be used directly from inside Zig's cache. 127 | 128 | Create one or more example cases that execute successfully: 129 | 130 | ```bash 131 | cd zig-out/AFLPlusPlus 132 | mkdir cases 133 | echo "good case" > cases/init.txt 134 | ``` 135 | 136 | Start fuzzing: 137 | `./afl-fuzz -i cases -o output_dir ../../bin/myfuzz-afl` 138 | 139 | Crashing inputs will be placed in `output_dir/default/crashes`. 140 | 141 | Read the docs at https://aflplus.plus to learn more on how to use AFL++. 142 | -------------------------------------------------------------------------------- /afl.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /* Main entry point. */ 10 | 11 | /* To ensure checks are not optimized out it is recommended to disable 12 | code optimization for the fuzzer harness main() */ 13 | #pragma clang optimize off 14 | #pragma GCC optimize("O0") 15 | 16 | 17 | // Zig integration 18 | void zig_fuzz_init(); 19 | void zig_fuzz_test(unsigned char *, ssize_t); 20 | 21 | 22 | // bug in compilation causes init func to never get called 23 | // TODO: remove this once bug is fixed 24 | uint32_t __start___sancov_guards; 25 | uint32_t __stop___sancov_guards; 26 | void __sanitizer_cov_trace_pc_guard_init(uint32_t*, uint32_t*); 27 | 28 | 29 | 30 | // Symbols not defined by afl-compiler-rt 31 | __attribute__((visibility("default"))) __attribute__((tls_model("initial-exec"))) _Thread_local uintptr_t __sancov_lowest_stack; 32 | 33 | void __sanitizer_cov_trace_pc_indir () {} 34 | void __sanitizer_cov_8bit_counters_init () {} 35 | void __sanitizer_cov_pcs_init () {} 36 | 37 | //__AFL_FUZZ_INIT() 38 | int __afl_sharedmem_fuzzing = 1; 39 | extern __attribute__((visibility("default"))) unsigned int *__afl_fuzz_len; 40 | extern __attribute__((visibility("default"))) unsigned char *__afl_fuzz_ptr; 41 | unsigned char __afl_fuzz_alt[1048576]; 42 | unsigned char *__afl_fuzz_alt_ptr = __afl_fuzz_alt; 43 | 44 | int main(int argc, char **argv) { 45 | __sanitizer_cov_trace_pc_guard_init(&__start___sancov_guards, &__stop___sancov_guards); 46 | 47 | // __AFL_INIT(); 48 | static volatile const char *_A __attribute__((used,unused)); 49 | _A = (const char*)"##SIG_AFL_DEFER_FORKSRV##"; 50 | #ifdef __APPLE__ 51 | __attribute__((visibility("default"))) 52 | void _I(void) __asm__("___afl_manual_init"); 53 | #else 54 | __attribute__((visibility("default"))) 55 | void _I(void) __asm__("__afl_manual_init"); 56 | #endif 57 | _I(); 58 | 59 | 60 | zig_fuzz_init(); 61 | 62 | // unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF; 63 | unsigned char *buf = __afl_fuzz_ptr ? __afl_fuzz_ptr : __afl_fuzz_alt_ptr; 64 | 65 | // while (__AFL_LOOP(UINT_MAX)) { 66 | while (({ static volatile const char *_B __attribute__((used,unused)); _B = (const char*)"##SIG_AFL_PERSISTENT##"; extern __attribute__((visibility("default"))) int __afl_connected; 67 | #ifdef __APPLE__ 68 | __attribute__((visibility("default"))) int _L(unsigned int) __asm__("___afl_persistent_loop"); 69 | #else 70 | __attribute__((visibility("default"))) int _L(unsigned int) __asm__("__afl_persistent_loop"); 71 | #endif 72 | _L(__afl_connected ? UINT_MAX : 1); })) { 73 | 74 | // int len = __AFL_FUZZ_TESTCASE_LEN; 75 | int len = __afl_fuzz_ptr ? *__afl_fuzz_len : 76 | (*__afl_fuzz_len = read(0, __afl_fuzz_alt_ptr, 1048576)) == 0xffffffff ? 0 : 77 | *__afl_fuzz_len; 78 | 79 | 80 | zig_fuzz_test(buf, len); 81 | } 82 | 83 | return 0; 84 | } 85 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn addInstrumentedExe( 4 | b: *std.Build, 5 | target: std.Build.ResolvedTarget, 6 | optimize: std.builtin.OptimizeMode, 7 | /// Pass null if llvm-config is in PATH 8 | llvm_config_path: ?[]const []const u8, 9 | /// If true will search the path for afl++ instead of compiling from source. 10 | /// This is a workaround for issues with zig compiled afl++ and C++11 abi on ubuntu. 11 | use_system_afl: bool, 12 | obj: *std.Build.Step.Compile, 13 | ) ?std.Build.LazyPath { 14 | const afl_kit = b.dependencyFromBuildZig(@This(), .{}); 15 | 16 | // TODO: validate obj 17 | 18 | // std.debug.assert(obj.root_module.stack_check == false); // not linking with compiler-rt 19 | // std.debug.assert(obj.root_module.link_libc == true); // afl runtime depends on libc 20 | 21 | if (false) { 22 | const exe = b.addExecutable(.{ 23 | .name = obj.name, 24 | .target = target, 25 | .optimize = optimize, 26 | }); 27 | // exe.root_module.fuzz = false; 28 | exe.root_module.link_libc = true; 29 | exe.addCSourceFile(.{ 30 | .file = afl_kit.path("afl.c"), 31 | .flags = &.{}, 32 | }); 33 | obj.root_module.fuzz = true; 34 | obj.root_module.link_libc = true; 35 | obj.sanitize_coverage_trace_pc_guard = true; 36 | exe.addObject(obj); 37 | 38 | // exe.addObject(afl_kit.path("afl-compiler-rt.o")); 39 | exe.addCSourceFile(.{ 40 | .file = afl_kit.path("afl-compiler-rt.o"), 41 | .flags = &.{}, 42 | }); 43 | 44 | return exe; 45 | } 46 | 47 | var run_afl_cc: *std.Build.Step.Run = undefined; 48 | if (!use_system_afl) { 49 | const afl = afl_kit.builder.lazyDependency("AFLplusplus", .{ 50 | .target = target, 51 | .optimize = optimize, 52 | .@"llvm-config-path" = llvm_config_path orelse &[_][]const u8{}, 53 | }) orelse return null; 54 | 55 | const install_tools = b.addInstallDirectory(.{ 56 | .source_dir = std.Build.LazyPath{ 57 | .cwd_relative = afl.builder.install_path, 58 | }, 59 | .install_dir = .prefix, 60 | .install_subdir = "AFLplusplus", 61 | }); 62 | 63 | install_tools.step.dependOn(afl.builder.getInstallStep()); 64 | run_afl_cc = b.addSystemCommand(&.{ 65 | b.pathJoin(&.{ afl.builder.exe_dir, "afl-cc" }), 66 | "-O3", 67 | "-o", 68 | }); 69 | run_afl_cc.step.dependOn(&afl.builder.top_level_steps.get("llvm_exes").?.step); 70 | run_afl_cc.step.dependOn(&install_tools.step); 71 | } else { 72 | run_afl_cc = b.addSystemCommand(&.{ 73 | b.findProgram(&.{"afl-cc"}, &.{}) catch @panic("Could not find 'afl-cc', which is required to build"), 74 | "-O3", 75 | "-o", 76 | }); 77 | } 78 | _ = obj.getEmittedBin(); // hack around build system bug 79 | 80 | const fuzz_exe = run_afl_cc.addOutputFileArg(obj.name); 81 | run_afl_cc.addFileArg(afl_kit.path("afl.c")); 82 | run_afl_cc.addFileArg(obj.getEmittedLlvmBc()); 83 | return fuzz_exe; 84 | } 85 | 86 | pub fn build(b: *std.Build) !void { 87 | _ = b; 88 | // const target = b.standardTargetOptions(.{}); 89 | // const optimize = b.standardOptimizeOption(.{}); 90 | 91 | } 92 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .name = .zig_afl_kit, 3 | .fingerprint = 0x75e9f6827df84ede, 4 | .version = "0.1.0", 5 | .dependencies = .{ 6 | .AFLplusplus = .{ 7 | .url = "git+https://github.com/allyourcodebase/AFLplusplus#38860630c62e8c9862ed7dc32565c837f626f8b0", 8 | .hash = "AFLplusplus-4.21.0-aA1y4eVrAABlgpm6qCW6NDZgZySsCAluavc-IdgGvh4y", 9 | .lazy = true, 10 | }, 11 | }, 12 | .paths = .{ 13 | "LICENSE", 14 | "build.zig", 15 | "build.zig.zon", 16 | "afl.c", 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /example.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | // Disable chatty logging 4 | pub const std_options = .{ .log_level = .err }; 5 | 6 | // To allow AFL++ to observe branching char-by-char in 7 | // string comparisons, edit your stdlib according to the 8 | // following code (note that it's commented out because 9 | // std.mem.backend_can_use_eql_bytes is actually private). 10 | // 11 | // Ideally in the future this is going to be easier to set. 12 | // 13 | // const toggle_me = std.mem.backend_can_use_eql_bytes; 14 | // comptime { 15 | // std.debug.assert(toggle_me == false); 16 | // } 17 | 18 | // An example of how to initialize a GPA and an arena 19 | // semi-statically. 20 | var gpa_impl: std.heap.GeneralPurposeAllocator(.{}) = .{}; 21 | var arena_impl: std.heap.ArenaAllocator = .{ 22 | .child_allocator = undefined, 23 | .state = .{}, 24 | }; 25 | 26 | export fn zig_fuzz_init() void { 27 | const gpa = gpa_impl.allocator(); 28 | arena_impl.child_allocator = gpa; 29 | } 30 | 31 | export fn zig_fuzz_test(buf: [*]u8, len: isize) void { 32 | // If you want to test for leaks, you might want 33 | // to use gpa directly and deinit - init every loop. 34 | // Deiniting gpa will allow you to assert for the 35 | // absence of leaks. 36 | // 37 | // If you don't want to test for leaks, using 38 | // an arena and resetting it every loop is faster. 39 | const arena = arena_impl.allocator(); 40 | _ = arena_impl.reset(.retain_capacity); 41 | 42 | const src = buf[0..@intCast(len)]; 43 | _ = src; 44 | 45 | // Your test code goes here. 46 | } 47 | --------------------------------------------------------------------------------