authorgravatar for kappaloris@gmail.comLoris Cro <kappaloris@gmail.com> 2025-02-21 18:53:21+01:00
committergravatar for kappaloris@gmail.comLoris Cro <kappaloris@gmail.com> 2025-02-27 16:32:35+01:00
logf8fe50314614aae1d1540f6a5ca0505bcbf66da0
tree1fec56762b867017cb8c8f77933fe2ae42bb8893
parentc45dcd013bfe9de1c739a88203349603c0682fd9

fuzz testing: implement initial macos support

This commit implements the linker-related code required to have the `zig init` canyoufindme test succeed on macos. It fixes usage of the linker in order to account for macos specific symbol mangling and introduces some checks in the fuzzer code to prevent crashes in case that instrumented code is invoked before `fuzz_init` runs. `@disableInstrumentation` has been added to the start code to help reduce the amount of (needlessly) instrumented code that runs, but the builtin is active only in the scope where it's used, meaning that any non-inlined function call that happens in that same scope will still have instrumentation enabled unless it too gets its own `@disableInstrumentation` call. Removing temporarily the code that bails out from instrumentation callbacks when the fuzzer has not been inited can be used to turn early (and wasteful) execution of instrumented code into a crash, helping finding places where to put more calls to `@disableInstrumentation`.

5 files changed, 80 insertions(+), 29 deletions(-)

lib/fuzzer.zig+59-26
...@@ -17,12 +17,7 @@ fn logOverride(...@@ -17,12 +17,7 @@ fn logOverride(
17 comptime format: []const u8,17 comptime format: []const u8,
18 args: anytype,18 args: anytype,
19) void {19) void {
20 const f = if (log_file) |f| f else f: {20 const f = if (log_file) |f| f else return;
21 const f = fuzzer.cache_dir.createFile("tmp/libfuzzer.log", .{}) catch
22 @panic("failed to open fuzzer log file");
23 log_file = f;
24 break :f f;
25 };
26 const prefix1 = comptime level.asText();21 const prefix1 = comptime level.asText();
27 const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";22 const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
28 f.writer().print(prefix1 ++ prefix2 ++ format ++ "\n", args) catch @panic("failed to write to fuzzer log");23 f.writer().print(prefix1 ++ prefix2 ++ format ++ "\n", args) catch @panic("failed to write to fuzzer log");
...@@ -98,10 +93,11 @@ export fn __sanitizer_cov_pcs_init(start: usize, end: usize) void {...@@ -98,10 +93,11 @@ export fn __sanitizer_cov_pcs_init(start: usize, end: usize) void {
9893
99fn handleCmp(pc: usize, arg1: u64, arg2: u64) void {94fn handleCmp(pc: usize, arg1: u64, arg2: u64) void {
100 fuzzer.traceValue(pc ^ arg1 ^ arg2);95 fuzzer.traceValue(pc ^ arg1 ^ arg2);
101 //std.log.debug("0x{x}: comparison of {d} and {d}", .{ pc, arg1, arg2 });96 // std.log.debug("0x{x}: comparison of {d} and {d}", .{ pc, arg1, arg2 });
102}97}
10398
104const Fuzzer = struct {99const Fuzzer = struct {
100 inited: bool = false,
105 rng: std.Random.DefaultPrng,101 rng: std.Random.DefaultPrng,
106 pcs: []const usize,102 pcs: []const usize,
107 pc_counters: []u8,103 pc_counters: []u8,
...@@ -157,6 +153,9 @@ const Fuzzer = struct {...@@ -157,6 +153,9 @@ const Fuzzer = struct {
157 f.pc_counters = pc_counters;153 f.pc_counters = pc_counters;
158 f.pcs = pcs;154 f.pcs = pcs;
159155
156 log_file = fuzzer.cache_dir.createFile("tmp/libfuzzer.log", .{}) catch
157 @panic("failed to open fuzzer log file");
158
160 // Choose a file name for the coverage based on a hash of the PCs that will be stored within.159 // Choose a file name for the coverage based on a hash of the PCs that will be stored within.
161 const pc_digest = std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(pcs));160 const pc_digest = std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(pcs));
162 f.coverage_id = pc_digest;161 f.coverage_id = pc_digest;
...@@ -210,6 +209,8 @@ const Fuzzer = struct {...@@ -210,6 +209,8 @@ const Fuzzer = struct {
210 f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems * @sizeOf(usize));209 f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems * @sizeOf(usize));
211 f.seen_pcs.appendSliceAssumeCapacity(std.mem.sliceAsBytes(pcs));210 f.seen_pcs.appendSliceAssumeCapacity(std.mem.sliceAsBytes(pcs));
212 }211 }
212
213 f.inited = true;
213 }214 }
214215
215 fn initNextInput(f: *Fuzzer) void {216 fn initNextInput(f: *Fuzzer) void {
...@@ -296,7 +297,9 @@ const Fuzzer = struct {...@@ -296,7 +297,9 @@ const Fuzzer = struct {
296 /// where it branches.297 /// where it branches.
297 fn traceValue(f: *Fuzzer, x: usize) void {298 fn traceValue(f: *Fuzzer, x: usize) void {
298 errdefer |err| oom(err);299 errdefer |err| oom(err);
299 try f.traced_comparisons.put(gpa, x, {});300 if (f.inited) {
301 try f.traced_comparisons.put(gpa, x, {});
302 }
300 }303 }
301304
302 const Mutation = enum {305 const Mutation = enum {
...@@ -310,19 +313,21 @@ const Fuzzer = struct {...@@ -310,19 +313,21 @@ const Fuzzer = struct {
310 f.input.clearRetainingCapacity();313 f.input.clearRetainingCapacity();
311 const old_input = f.corpus.items[corpus_index].bytes;314 const old_input = f.corpus.items[corpus_index].bytes;
312 f.input.ensureTotalCapacity(old_input.len + 1) catch @panic("mmap file resize failed");315 f.input.ensureTotalCapacity(old_input.len + 1) catch @panic("mmap file resize failed");
313 switch (mutation) {316 sw: switch (mutation) {
314 .remove_byte => {317 .remove_byte => {
318 if (old_input.len == 0) continue :sw .add_byte;
315 const omitted_index = rng.uintLessThanBiased(usize, old_input.len);319 const omitted_index = rng.uintLessThanBiased(usize, old_input.len);
316 f.input.appendSliceAssumeCapacity(old_input[0..omitted_index]);320 f.input.appendSliceAssumeCapacity(old_input[0..omitted_index]);
317 f.input.appendSliceAssumeCapacity(old_input[omitted_index + 1 ..]);321 f.input.appendSliceAssumeCapacity(old_input[omitted_index + 1 ..]);
318 },322 },
319 .modify_byte => {323 .modify_byte => {
324 if (old_input.len == 0) continue :sw .add_byte;
320 const modified_index = rng.uintLessThanBiased(usize, old_input.len);325 const modified_index = rng.uintLessThanBiased(usize, old_input.len);
321 f.input.appendSliceAssumeCapacity(old_input);326 f.input.appendSliceAssumeCapacity(old_input);
322 f.input.items[modified_index] = rng.int(u8);327 f.input.items[modified_index] = rng.int(u8);
323 },328 },
324 .add_byte => {329 .add_byte => {
325 const modified_index = rng.uintLessThanBiased(usize, old_input.len);330 const modified_index = if (old_input.len == 0) 0 else rng.uintLessThanBiased(usize, old_input.len);
326 f.input.appendSliceAssumeCapacity(old_input[0..modified_index]);331 f.input.appendSliceAssumeCapacity(old_input[0..modified_index]);
327 f.input.appendAssumeCapacity(rng.int(u8));332 f.input.appendAssumeCapacity(rng.int(u8));
328 f.input.appendSliceAssumeCapacity(old_input[modified_index..]);333 f.input.appendSliceAssumeCapacity(old_input[modified_index..]);
...@@ -468,27 +473,55 @@ export fn fuzzer_init(cache_dir_struct: Fuzzer.Slice) void {...@@ -468,27 +473,55 @@ export fn fuzzer_init(cache_dir_struct: Fuzzer.Slice) void {
468 // Linkers are expected to automatically add `__start_<section>` and473 // Linkers are expected to automatically add `__start_<section>` and
469 // `__stop_<section>` symbols when section names are valid C identifiers.474 // `__stop_<section>` symbols when section names are valid C identifiers.
470475
471 const pc_counters_start = @extern([*]u8, .{476 const pc_counters_start = switch (builtin.os.tag) {
472 .name = "__start___sancov_cntrs",477 .linux => @extern([*]u8, .{
473 .linkage = .weak,478 .name = "__start___sancov_cntrs",
474 }) orelse fatal("missing __start___sancov_cntrs symbol", .{});479 .linkage = .weak,
480 }) orelse fatal("missing __start___sancov_cntrs symbol", .{}),
481 .macos => @extern([*]u8, .{
482 .name = "\x01section$start$__DATA$__sancov_cntrs",
483 .linkage = .weak,
484 }) orelse fatal("missing section$start$__DATA$__sancov_cntrs symbol", .{}),
485 else => @compileError("TODO: implement fuzzing support for the target platform"),
486 };
475487
476 const pc_counters_end = @extern([*]u8, .{488 const pc_counters_end = switch (builtin.os.tag) {
477 .name = "__stop___sancov_cntrs",489 .linux => @extern([*]u8, .{
478 .linkage = .weak,490 .name = "__stop___sancov_cntrs",
479 }) orelse fatal("missing __stop___sancov_cntrs symbol", .{});491 .linkage = .weak,
492 }) orelse fatal("missing __stop___sancov_cntrs symbol", .{}),
493 .macos => @extern([*]u8, .{
494 .name = "\x01section$end$__DATA$__sancov_cntrs",
495 .linkage = .weak,
496 }) orelse fatal("missing section$end$__DATA$__sancov_cntrs symbol", .{}),
497 else => @compileError("TODO: implement fuzzing support for the target platform"),
498 };
480499
481 const pc_counters = pc_counters_start[0 .. pc_counters_end - pc_counters_start];500 const pc_counters = pc_counters_start[0 .. pc_counters_end - pc_counters_start];
482501
483 const pcs_start = @extern([*]usize, .{502 const pcs_start = switch (builtin.os.tag) {
484 .name = "__start___sancov_pcs1",503 .linux => @extern([*]usize, .{
485 .linkage = .weak,504 .name = "__start___sancov_pcs1",
486 }) orelse fatal("missing __start___sancov_pcs1 symbol", .{});505 .linkage = .weak,
506 }) orelse fatal("missing __start___sancov_pcs1 symbol", .{}),
507 .macos => @extern([*]usize, .{
508 .name = "\x01section$start$__DATA_CONST$__sancov_pcs1",
509 .linkage = .weak,
510 }) orelse fatal("missing section$start$__DATA_CONST$__sancov_pcs1 symbol", .{}),
511 else => @compileError("TODO: implement fuzzing support for the target platform"),
512 };
487513
488 const pcs_end = @extern([*]usize, .{514 const pcs_end = switch (builtin.os.tag) {
489 .name = "__stop___sancov_pcs1",515 .linux => @extern([*]usize, .{
490 .linkage = .weak,516 .name = "__stop___sancov_pcs1",
491 }) orelse fatal("missing __stop___sancov_pcs1 symbol", .{});517 .linkage = .weak,
518 }) orelse fatal("missing __stop___sancov_pcs1 symbol", .{}),
519 .macos => @extern([*]usize, .{
520 .name = "\x01section$end$__DATA_CONST$__sancov_pcs1",
521 .linkage = .weak,
522 }) orelse fatal("missing section$end$__DATA_CONST$__sancov_pcs1 symbol", .{}),
523 else => @compileError("TODO: implement fuzzing support for the target platform"),
524 };
492525
493 const pcs = pcs_start[0 .. pcs_end - pcs_start];526 const pcs = pcs_start[0 .. pcs_end - pcs_start];
494527
lib/std/start.zig+3
...@@ -617,6 +617,9 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {...@@ -617,6 +617,9 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {
617}617}
618618
619fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int {619fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int {
620 // Code coverage instrumentation might try to use thread local variables.
621 @disableInstrumentation();
622
620 var env_count: usize = 0;623 var env_count: usize = 0;
621 while (c_envp[env_count] != null) : (env_count += 1) {}624 while (c_envp[env_count] != null) : (env_count += 1) {}
622 const envp = @as([*][*:0]u8, @ptrCast(c_envp))[0..env_count];625 const envp = @as([*][*:0]u8, @ptrCast(c_envp))[0..env_count];
src/codegen/llvm.zig+10-2
...@@ -1732,7 +1732,11 @@ pub const Object = struct {...@@ -1732,7 +1732,11 @@ pub const Object = struct {
1732 try o.used.append(gpa, counters_variable.toConst(&o.builder));1732 try o.used.append(gpa, counters_variable.toConst(&o.builder));
1733 counters_variable.setLinkage(.private, &o.builder);1733 counters_variable.setLinkage(.private, &o.builder);
1734 counters_variable.setAlignment(comptime Builder.Alignment.fromByteUnits(1), &o.builder);1734 counters_variable.setAlignment(comptime Builder.Alignment.fromByteUnits(1), &o.builder);
1735 counters_variable.setSection(try o.builder.string("__sancov_cntrs"), &o.builder);1735 const name = if (target.os.tag == .macos)
1736 "__DATA,__sancov_cntrs"
1737 else
1738 "__sancov_cntrs";
1739 counters_variable.setSection(try o.builder.string(name), &o.builder);
17361740
1737 break :f .{1741 break :f .{
1738 .counters_variable = counters_variable,1742 .counters_variable = counters_variable,
...@@ -1794,7 +1798,11 @@ pub const Object = struct {...@@ -1794,7 +1798,11 @@ pub const Object = struct {
1794 pcs_variable.setLinkage(.private, &o.builder);1798 pcs_variable.setLinkage(.private, &o.builder);
1795 pcs_variable.setMutability(.constant, &o.builder);1799 pcs_variable.setMutability(.constant, &o.builder);
1796 pcs_variable.setAlignment(Type.usize.abiAlignment(zcu).toLlvm(), &o.builder);1800 pcs_variable.setAlignment(Type.usize.abiAlignment(zcu).toLlvm(), &o.builder);
1797 pcs_variable.setSection(try o.builder.string("__sancov_pcs1"), &o.builder);1801 const name = if (target.os.tag == .macos)
1802 "__DATA_CONST,__sancov_pcs1"
1803 else
1804 "__sancov_pcs1";
1805 pcs_variable.setSection(try o.builder.string(name), &o.builder);
1798 try pcs_variable.setInitializer(init_val, &o.builder);1806 try pcs_variable.setInitializer(init_val, &o.builder);
1799 }1807 }
18001808
src/link/MachO.zig+7-1
...@@ -416,7 +416,12 @@ pub fn flushModule(...@@ -416,7 +416,12 @@ pub fn flushModule(
416 }416 }
417417
418 if (comp.config.any_fuzz) {418 if (comp.config.any_fuzz) {
419 try positionals.append(try link.openObjectInput(diags, comp.fuzzer_lib.?.full_object_path));419 try positionals.append(try link.openArchiveInput(
420 diags,
421 comp.fuzzer_lib.?.full_object_path,
422 true,
423 false,
424 ));
420 }425 }
421426
422 if (comp.ubsan_rt_lib) |crt_file| {427 if (comp.ubsan_rt_lib) |crt_file| {
...@@ -1524,6 +1529,7 @@ fn scanRelocs(self: *MachO) !void {...@@ -1524,6 +1529,7 @@ fn scanRelocs(self: *MachO) !void {
1524 if (self.getInternalObject()) |obj| {1529 if (self.getInternalObject()) |obj| {
1525 try obj.checkUndefs(self);1530 try obj.checkUndefs(self);
1526 }1531 }
1532
1527 try self.reportUndefs();1533 try self.reportUndefs();
15281534
1529 if (self.getZigObject()) |zo| {1535 if (self.getZigObject()) |zo| {
src/link/MachO/ZigObject.zig+1
...@@ -1313,6 +1313,7 @@ pub fn updateExports(...@@ -1313,6 +1313,7 @@ pub fn updateExports(
1313 }1313 }
13141314
1315 const exp_name = exp.opts.name.toSlice(&zcu.intern_pool);1315 const exp_name = exp.opts.name.toSlice(&zcu.intern_pool);
1316
1316 const global_nlist_index = if (metadata.@"export"(self, exp_name)) |exp_index|1317 const global_nlist_index = if (metadata.@"export"(self, exp_name)) |exp_index|
1317 exp_index.*1318 exp_index.*
1318 else blk: {1319 else blk: {