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(
1717 comptime format: []const u8,
1818 args: anytype,
1919) void {
20 const f = if (log_file) |f| f else f: {
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 };
20 const f = if (log_file) |f| f else return;
2621 const prefix1 = comptime level.asText();
2722 const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
2823 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 {
9893
9994fn handleCmp(pc: usize, arg1: u64, arg2: u64) void {
10095 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 });
10297}
10398
10499const Fuzzer = struct {
100 inited: bool = false,
105101 rng: std.Random.DefaultPrng,
106102 pcs: []const usize,
107103 pc_counters: []u8,
......@@ -157,6 +153,9 @@ const Fuzzer = struct {
157153 f.pc_counters = pc_counters;
158154 f.pcs = pcs;
159155
156 log_file = fuzzer.cache_dir.createFile("tmp/libfuzzer.log", .{}) catch
157 @panic("failed to open fuzzer log file");
158
160159 // Choose a file name for the coverage based on a hash of the PCs that will be stored within.
161160 const pc_digest = std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(pcs));
162161 f.coverage_id = pc_digest;
......@@ -210,6 +209,8 @@ const Fuzzer = struct {
210209 f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems * @sizeOf(usize));
211210 f.seen_pcs.appendSliceAssumeCapacity(std.mem.sliceAsBytes(pcs));
212211 }
212
213 f.inited = true;
213214 }
214215
215216 fn initNextInput(f: *Fuzzer) void {
......@@ -296,7 +297,9 @@ const Fuzzer = struct {
296297 /// where it branches.
297298 fn traceValue(f: *Fuzzer, x: usize) void {
298299 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 }
300303 }
301304
302305 const Mutation = enum {
......@@ -310,19 +313,21 @@ const Fuzzer = struct {
310313 f.input.clearRetainingCapacity();
311314 const old_input = f.corpus.items[corpus_index].bytes;
312315 f.input.ensureTotalCapacity(old_input.len + 1) catch @panic("mmap file resize failed");
313 switch (mutation) {
316 sw: switch (mutation) {
314317 .remove_byte => {
318 if (old_input.len == 0) continue :sw .add_byte;
315319 const omitted_index = rng.uintLessThanBiased(usize, old_input.len);
316320 f.input.appendSliceAssumeCapacity(old_input[0..omitted_index]);
317321 f.input.appendSliceAssumeCapacity(old_input[omitted_index + 1 ..]);
318322 },
319323 .modify_byte => {
324 if (old_input.len == 0) continue :sw .add_byte;
320325 const modified_index = rng.uintLessThanBiased(usize, old_input.len);
321326 f.input.appendSliceAssumeCapacity(old_input);
322327 f.input.items[modified_index] = rng.int(u8);
323328 },
324329 .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);
326331 f.input.appendSliceAssumeCapacity(old_input[0..modified_index]);
327332 f.input.appendAssumeCapacity(rng.int(u8));
328333 f.input.appendSliceAssumeCapacity(old_input[modified_index..]);
......@@ -468,27 +473,55 @@ export fn fuzzer_init(cache_dir_struct: Fuzzer.Slice) void {
468473 // Linkers are expected to automatically add `__start_<section>` and
469474 // `__stop_<section>` symbols when section names are valid C identifiers.
470475
471 const pc_counters_start = @extern([*]u8, .{
472 .name = "__start___sancov_cntrs",
473 .linkage = .weak,
474 }) orelse fatal("missing __start___sancov_cntrs symbol", .{});
476 const pc_counters_start = switch (builtin.os.tag) {
477 .linux => @extern([*]u8, .{
478 .name = "__start___sancov_cntrs",
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, .{
477 .name = "__stop___sancov_cntrs",
478 .linkage = .weak,
479 }) orelse fatal("missing __stop___sancov_cntrs symbol", .{});
488 const pc_counters_end = switch (builtin.os.tag) {
489 .linux => @extern([*]u8, .{
490 .name = "__stop___sancov_cntrs",
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
481500 const pc_counters = pc_counters_start[0 .. pc_counters_end - pc_counters_start];
482501
483 const pcs_start = @extern([*]usize, .{
484 .name = "__start___sancov_pcs1",
485 .linkage = .weak,
486 }) orelse fatal("missing __start___sancov_pcs1 symbol", .{});
502 const pcs_start = switch (builtin.os.tag) {
503 .linux => @extern([*]usize, .{
504 .name = "__start___sancov_pcs1",
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, .{
489 .name = "__stop___sancov_pcs1",
490 .linkage = .weak,
491 }) orelse fatal("missing __stop___sancov_pcs1 symbol", .{});
514 const pcs_end = switch (builtin.os.tag) {
515 .linux => @extern([*]usize, .{
516 .name = "__stop___sancov_pcs1",
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
493526 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 {
617617}
618618
619619fn 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
620623 var env_count: usize = 0;
621624 while (c_envp[env_count] != null) : (env_count += 1) {}
622625 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 {
17321732 try o.used.append(gpa, counters_variable.toConst(&o.builder));
17331733 counters_variable.setLinkage(.private, &o.builder);
17341734 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
17371741 break :f .{
17381742 .counters_variable = counters_variable,
......@@ -1794,7 +1798,11 @@ pub const Object = struct {
17941798 pcs_variable.setLinkage(.private, &o.builder);
17951799 pcs_variable.setMutability(.constant, &o.builder);
17961800 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);
17981806 try pcs_variable.setInitializer(init_val, &o.builder);
17991807 }
18001808
src/link/MachO.zig+7-1
......@@ -416,7 +416,12 @@ pub fn flushModule(
416416 }
417417
418418 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 ));
420425 }
421426
422427 if (comp.ubsan_rt_lib) |crt_file| {
......@@ -1524,6 +1529,7 @@ fn scanRelocs(self: *MachO) !void {
15241529 if (self.getInternalObject()) |obj| {
15251530 try obj.checkUndefs(self);
15261531 }
1532
15271533 try self.reportUndefs();
15281534
15291535 if (self.getZigObject()) |zo| {
src/link/MachO/ZigObject.zig+1
......@@ -1313,6 +1313,7 @@ pub fn updateExports(
13131313 }
13141314
13151315 const exp_name = exp.opts.name.toSlice(&zcu.intern_pool);
1316
13161317 const global_nlist_index = if (metadata.@"export"(self, exp_name)) |exp_index|
13171318 exp_index.*
13181319 else blk: {