From f8fe50314614aae1d1540f6a5ca0505bcbf66da0 Mon Sep 17 00:00:00 2001 From: Loris Cro Date: Fri, 21 Feb 2025 18:53:21 +0100 Subject: [PATCH] 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`. --- lib/fuzzer.zig | 85 +++++++++++++++++++++++++----------- lib/std/start.zig | 3 ++ src/codegen/llvm.zig | 12 ++++- src/link/MachO.zig | 8 +++- src/link/MachO/ZigObject.zig | 1 + 5 files changed, 80 insertions(+), 29 deletions(-) diff --git a/lib/fuzzer.zig b/lib/fuzzer.zig index 0c287c6afc..309ffab8b7 100644 --- a/lib/fuzzer.zig +++ b/lib/fuzzer.zig @@ -17,12 +17,7 @@ fn logOverride( comptime format: []const u8, args: anytype, ) void { - const f = if (log_file) |f| f else f: { - const f = fuzzer.cache_dir.createFile("tmp/libfuzzer.log", .{}) catch - @panic("failed to open fuzzer log file"); - log_file = f; - break :f f; - }; + const f = if (log_file) |f| f else return; const prefix1 = comptime level.asText(); const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): "; 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 { fn handleCmp(pc: usize, arg1: u64, arg2: u64) void { fuzzer.traceValue(pc ^ arg1 ^ arg2); - //std.log.debug("0x{x}: comparison of {d} and {d}", .{ pc, arg1, arg2 }); + // std.log.debug("0x{x}: comparison of {d} and {d}", .{ pc, arg1, arg2 }); } const Fuzzer = struct { + inited: bool = false, rng: std.Random.DefaultPrng, pcs: []const usize, pc_counters: []u8, @@ -157,6 +153,9 @@ const Fuzzer = struct { f.pc_counters = pc_counters; f.pcs = pcs; + log_file = fuzzer.cache_dir.createFile("tmp/libfuzzer.log", .{}) catch + @panic("failed to open fuzzer log file"); + // Choose a file name for the coverage based on a hash of the PCs that will be stored within. const pc_digest = std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(pcs)); f.coverage_id = pc_digest; @@ -210,6 +209,8 @@ const Fuzzer = struct { f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems * @sizeOf(usize)); f.seen_pcs.appendSliceAssumeCapacity(std.mem.sliceAsBytes(pcs)); } + + f.inited = true; } fn initNextInput(f: *Fuzzer) void { @@ -296,7 +297,9 @@ const Fuzzer = struct { /// where it branches. fn traceValue(f: *Fuzzer, x: usize) void { errdefer |err| oom(err); - try f.traced_comparisons.put(gpa, x, {}); + if (f.inited) { + try f.traced_comparisons.put(gpa, x, {}); + } } const Mutation = enum { @@ -310,19 +313,21 @@ const Fuzzer = struct { f.input.clearRetainingCapacity(); const old_input = f.corpus.items[corpus_index].bytes; f.input.ensureTotalCapacity(old_input.len + 1) catch @panic("mmap file resize failed"); - switch (mutation) { + sw: switch (mutation) { .remove_byte => { + if (old_input.len == 0) continue :sw .add_byte; const omitted_index = rng.uintLessThanBiased(usize, old_input.len); f.input.appendSliceAssumeCapacity(old_input[0..omitted_index]); f.input.appendSliceAssumeCapacity(old_input[omitted_index + 1 ..]); }, .modify_byte => { + if (old_input.len == 0) continue :sw .add_byte; const modified_index = rng.uintLessThanBiased(usize, old_input.len); f.input.appendSliceAssumeCapacity(old_input); f.input.items[modified_index] = rng.int(u8); }, .add_byte => { - const modified_index = rng.uintLessThanBiased(usize, old_input.len); + const modified_index = if (old_input.len == 0) 0 else rng.uintLessThanBiased(usize, old_input.len); f.input.appendSliceAssumeCapacity(old_input[0..modified_index]); f.input.appendAssumeCapacity(rng.int(u8)); f.input.appendSliceAssumeCapacity(old_input[modified_index..]); @@ -468,27 +473,55 @@ export fn fuzzer_init(cache_dir_struct: Fuzzer.Slice) void { // Linkers are expected to automatically add `__start_
` and // `__stop_
` symbols when section names are valid C identifiers. - const pc_counters_start = @extern([*]u8, .{ - .name = "__start___sancov_cntrs", - .linkage = .weak, - }) orelse fatal("missing __start___sancov_cntrs symbol", .{}); + const pc_counters_start = switch (builtin.os.tag) { + .linux => @extern([*]u8, .{ + .name = "__start___sancov_cntrs", + .linkage = .weak, + }) orelse fatal("missing __start___sancov_cntrs symbol", .{}), + .macos => @extern([*]u8, .{ + .name = "\x01section$start$__DATA$__sancov_cntrs", + .linkage = .weak, + }) orelse fatal("missing section$start$__DATA$__sancov_cntrs symbol", .{}), + else => @compileError("TODO: implement fuzzing support for the target platform"), + }; - const pc_counters_end = @extern([*]u8, .{ - .name = "__stop___sancov_cntrs", - .linkage = .weak, - }) orelse fatal("missing __stop___sancov_cntrs symbol", .{}); + const pc_counters_end = switch (builtin.os.tag) { + .linux => @extern([*]u8, .{ + .name = "__stop___sancov_cntrs", + .linkage = .weak, + }) orelse fatal("missing __stop___sancov_cntrs symbol", .{}), + .macos => @extern([*]u8, .{ + .name = "\x01section$end$__DATA$__sancov_cntrs", + .linkage = .weak, + }) orelse fatal("missing section$end$__DATA$__sancov_cntrs symbol", .{}), + else => @compileError("TODO: implement fuzzing support for the target platform"), + }; const pc_counters = pc_counters_start[0 .. pc_counters_end - pc_counters_start]; - const pcs_start = @extern([*]usize, .{ - .name = "__start___sancov_pcs1", - .linkage = .weak, - }) orelse fatal("missing __start___sancov_pcs1 symbol", .{}); + const pcs_start = switch (builtin.os.tag) { + .linux => @extern([*]usize, .{ + .name = "__start___sancov_pcs1", + .linkage = .weak, + }) orelse fatal("missing __start___sancov_pcs1 symbol", .{}), + .macos => @extern([*]usize, .{ + .name = "\x01section$start$__DATA_CONST$__sancov_pcs1", + .linkage = .weak, + }) orelse fatal("missing section$start$__DATA_CONST$__sancov_pcs1 symbol", .{}), + else => @compileError("TODO: implement fuzzing support for the target platform"), + }; - const pcs_end = @extern([*]usize, .{ - .name = "__stop___sancov_pcs1", - .linkage = .weak, - }) orelse fatal("missing __stop___sancov_pcs1 symbol", .{}); + const pcs_end = switch (builtin.os.tag) { + .linux => @extern([*]usize, .{ + .name = "__stop___sancov_pcs1", + .linkage = .weak, + }) orelse fatal("missing __stop___sancov_pcs1 symbol", .{}), + .macos => @extern([*]usize, .{ + .name = "\x01section$end$__DATA_CONST$__sancov_pcs1", + .linkage = .weak, + }) orelse fatal("missing section$end$__DATA_CONST$__sancov_pcs1 symbol", .{}), + else => @compileError("TODO: implement fuzzing support for the target platform"), + }; const pcs = pcs_start[0 .. pcs_end - pcs_start]; diff --git a/lib/std/start.zig b/lib/std/start.zig index a91df35700..94377493db 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -617,6 +617,9 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 { } fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int { + // Code coverage instrumentation might try to use thread local variables. + @disableInstrumentation(); + var env_count: usize = 0; while (c_envp[env_count] != null) : (env_count += 1) {} const envp = @as([*][*:0]u8, @ptrCast(c_envp))[0..env_count]; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 2f58dc7897..b57a49f369 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1732,7 +1732,11 @@ pub const Object = struct { try o.used.append(gpa, counters_variable.toConst(&o.builder)); counters_variable.setLinkage(.private, &o.builder); counters_variable.setAlignment(comptime Builder.Alignment.fromByteUnits(1), &o.builder); - counters_variable.setSection(try o.builder.string("__sancov_cntrs"), &o.builder); + const name = if (target.os.tag == .macos) + "__DATA,__sancov_cntrs" + else + "__sancov_cntrs"; + counters_variable.setSection(try o.builder.string(name), &o.builder); break :f .{ .counters_variable = counters_variable, @@ -1794,7 +1798,11 @@ pub const Object = struct { pcs_variable.setLinkage(.private, &o.builder); pcs_variable.setMutability(.constant, &o.builder); pcs_variable.setAlignment(Type.usize.abiAlignment(zcu).toLlvm(), &o.builder); - pcs_variable.setSection(try o.builder.string("__sancov_pcs1"), &o.builder); + const name = if (target.os.tag == .macos) + "__DATA_CONST,__sancov_pcs1" + else + "__sancov_pcs1"; + pcs_variable.setSection(try o.builder.string(name), &o.builder); try pcs_variable.setInitializer(init_val, &o.builder); } diff --git a/src/link/MachO.zig b/src/link/MachO.zig index fb30788e69..4e47b4af06 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -416,7 +416,12 @@ pub fn flushModule( } if (comp.config.any_fuzz) { - try positionals.append(try link.openObjectInput(diags, comp.fuzzer_lib.?.full_object_path)); + try positionals.append(try link.openArchiveInput( + diags, + comp.fuzzer_lib.?.full_object_path, + true, + false, + )); } if (comp.ubsan_rt_lib) |crt_file| { @@ -1524,6 +1529,7 @@ fn scanRelocs(self: *MachO) !void { if (self.getInternalObject()) |obj| { try obj.checkUndefs(self); } + try self.reportUndefs(); if (self.getZigObject()) |zo| { diff --git a/src/link/MachO/ZigObject.zig b/src/link/MachO/ZigObject.zig index 8e186d400a..8969b83869 100644 --- a/src/link/MachO/ZigObject.zig +++ b/src/link/MachO/ZigObject.zig @@ -1313,6 +1313,7 @@ pub fn updateExports( } const exp_name = exp.opts.name.toSlice(&zcu.intern_pool); + const global_nlist_index = if (metadata.@"export"(self, exp_name)) |exp_index| exp_index.* else blk: {