├── .gitignore ├── LICENSE ├── example.zig ├── afl.c └── README.md /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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("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 object file that contains your test function: 26 | const afl_obj = b.addObject(.{ 27 | .name = "my_fuzz_obj", 28 | .root_module = b.createModule(.{ 29 | .root_source_file = b.path("src/fuzz.zig"), 30 | .target = target, 31 | .optimize = .Debug, 32 | }), 33 | }); 34 | 35 | // Required options: 36 | afl_obj.root_module.stack_check = false; // not linking with compiler-rt 37 | afl_obj.root_module.link_libc = true; // afl runtime depends on libc 38 | 39 | // Generate an instrumented executable: 40 | const afl_fuzz = afl.addInstrumentedExe(b, target, optimize, null, false,afl_obj, &.{}); 41 | // Install it 42 | fuzz.dependOn(&b.addInstallBinFile(afl_fuzz, "myfuzz-afl").step); 43 | ``` 44 | 45 | ### Your test code 46 | To create an instrumented executable, your object file must export two C symbols: 47 | - `export fn zig_fuzz_init() void {}` invoked once to initialize resources (eg allocators) 48 | - `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. 49 | 50 | This library integrates with AFL++ using: 51 | - persistent mode (runs multiple tests on a single process, increases performance drammatically) 52 | - shared memory (a shared memory buffer is used to get input from the fuzzer instead of reading from stdin) 53 | 54 | See `afl.c` for more info. 55 | See `example.zig` for an example of how to structure your test code. 56 | 57 | 58 | 59 | ### **------> IMPORTANT <------** 60 | 61 | **UPDATE: Once ziglang/zig#20725 is merged, you will be able to avoid the next step by doing `afl_obj.root_module.fuzz = true;`.** 62 | 63 | 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. 64 | 65 | 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`. 66 | 67 | **Also don't forget to revert this change after you're done!** 68 | 69 | ## CLI arguments 70 | `addInstrumentedExe` will define a `afl-path` option to allow you to point at a directory where you built AFL++, like so: 71 | 72 | `zig build fuzz -Dafl-path="../AFLPlusplus"` 73 | 74 | ## I'm a C or C++ programmer, can I use this? 75 | Of course, you can just set up your object file step to be compiled from C/C++ files! 76 | 77 | Something along these lines: 78 | 79 | ```zig 80 | const afl_obj = b.addObject(.{ 81 | .name = "my_fuzz_obj", 82 | //.root_source_file = b.path("src/fuzz.zig"), 83 | .target = target, 84 | .optimize = .Debug, 85 | }); 86 | 87 | afl_obj.addCSourceFiles(.{ 88 | .files = &.{ 89 | "foo.c", 90 | "bar.c", 91 | }, 92 | // In case you need flags: 93 | //.flags = &.{"-Wextra", "-DFOO"}, 94 | }); 95 | 96 | // Required options: 97 | afl_obj.root_module.stack_check = false; // not linking with compiler-rt 98 | afl_obj.root_module.link_libc = true; // afl runtime depends on libc 99 | 100 | ``` 101 | 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. 102 | 103 | ## Fuzz your application 104 | By default, your fuzz step (depending on the instrumented executable to me more precise) will also install the entire AFLplusplus toolchain. 105 | 106 | ``` 107 | zig-out 108 | ├─ bin 109 | │ └── myfuzz-afl 110 | └── AFLplusplus 111 |    ├── bin 112 |    │   ├── afl-analyze 113 |    │   ├── afl-as 114 |    │   ├── afl-cc 115 |    │   ├── afl-compiler-rt-64 116 |   │   ├── afl-compiler-rt.o 117 |    │   ├── afl-fuzz 118 |    │   ├── afl-gotcpu 119 |    │   ├── afl-llvm-rt-lto-64 120 |    │   ├── afl-llvm-rt-lto.o 121 |    │   ├── afl-showmap 122 |    │   └── afl-tmin 123 |    └── lib 124 |    └── 125 | ``` 126 | 127 | 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. 128 | 129 | Create one or more example cases that execute successfully: 130 | 131 | ```bash 132 | cd zig-out/AFLPlusPlus 133 | mkdir cases 134 | echo "good case" > cases/init.txt 135 | ``` 136 | 137 | Start fuzzing: 138 | `./afl-fuzz -i cases -o output_dir ../../bin/myfuzz-afl` 139 | 140 | Crashing inputs will be placed in `output_dir/default/crashes`. 141 | 142 | Read the docs at https://aflplus.plus to learn more on how to use AFL++. 143 | --------------------------------------------------------------------------------