| ... | ... | @@ -57,9 +57,6 @@ fn bitsetUsizes(elems: usize) usize { |
| 57 | 57 | const Executable = struct { |
| 58 | 58 | /// Tracks the hit count for each pc as updated by the process's instrumentation. |
| 59 | 59 | pc_counters: []u8, |
| 60 | | /// Read-only memory section containing compiled-in constants found from parsing the executable |
| 61 | | rodata_addr: usize, |
| 62 | | rodata_size: usize, |
| 63 | 60 | |
| 64 | 61 | cache_f: std.fs.Dir, |
| 65 | 62 | /// Shared copy of all pcs that have been hit stored in a memory-mapped file that can viewed |
| ... | ... | @@ -72,80 +69,12 @@ const Executable = struct { |
| 72 | 69 | /// Used before this structure is initialized to avoid illegal behavior |
| 73 | 70 | /// from instrumentation functions being called and using undefined values. |
| 74 | 71 | pub const preinit: Executable = .{ |
| 75 | | .rodata_addr = 0, |
| 76 | | .rodata_size = 0, |
| 77 | 72 | .pc_counters = undefined, // instrumentation works off the __sancov_cntrs section |
| 78 | 73 | .cache_f = undefined, |
| 79 | 74 | .shared_seen_pcs = undefined, |
| 80 | 75 | .pc_digest = undefined, |
| 81 | 76 | }; |
| 82 | 77 | |
| 83 | | /// Even on error, this initializes rodata_addr and rodata_size to valid values |
| 84 | | fn initRodata(self: *Executable) !void { |
| 85 | | errdefer { |
| 86 | | self.rodata_addr = 0; |
| 87 | | self.rodata_size = 0; |
| 88 | | } |
| 89 | | |
| 90 | | const exec_path = std.fs.selfExePathAlloc(gpa) catch |e| |
| 91 | | if (e == error.OutOfMemory) @panic("OOM") else return e; |
| 92 | | defer gpa.free(exec_path); |
| 93 | | const exec_file = try std.fs.cwd().openFile(exec_path, .{}); |
| 94 | | defer exec_file.close(); |
| 95 | | |
| 96 | | switch (builtin.object_format) { |
| 97 | | .elf => { |
| 98 | | // We use two reader instances since the data they respectively read are |
| 99 | | // not next to each other in the file. |
| 100 | | // |
| 101 | | // Multiple instances is safe since Elf.SectionHeaderIterator always calls |
| 102 | | // seekTo (which we also use to arbitrarily set the index) and we always |
| 103 | | // call seekTo to arbitrarily read from the string table. |
| 104 | | var r_buf: [4096]u8 = undefined; |
| 105 | | var r = exec_file.reader(&r_buf); |
| 106 | | var str_r_buf: [4096]u8 = undefined; |
| 107 | | var str_r = exec_file.reader(&str_r_buf); |
| 108 | | |
| 109 | | const ehdr: std.elf.Header = try .read(&r.interface); |
| 110 | | if (ehdr.shstrndx == 0) return error.NoElfStringTable; |
| 111 | | var shdr_it = ehdr.iterateSectionHeaders(&r); |
| 112 | | |
| 113 | | shdr_it.index = ehdr.shstrndx; |
| 114 | | const str_tab_shdr = try shdr_it.next() orelse return error.InvalidElfSection; |
| 115 | | const str_tab_off = str_tab_shdr.sh_offset; |
| 116 | | |
| 117 | | shdr_it.index = 0; |
| 118 | | while (try shdr_it.next()) |shdr| { |
| 119 | | const flags: packed struct { |
| 120 | | write: bool, |
| 121 | | alloc: bool, |
| 122 | | execinstr: bool, |
| 123 | | } = @bitCast(@as(u3, @truncate(shdr.sh_flags))); |
| 124 | | if (shdr.sh_addr == 0 or shdr.sh_size == 0 or flags != @TypeOf(flags){ |
| 125 | | .alloc = true, |
| 126 | | .write = false, |
| 127 | | .execinstr = false, |
| 128 | | }) continue; |
| 129 | | |
| 130 | | const rodata_name = ".rodata\x00"; |
| 131 | | try str_r.seekTo(try math.add(u64, str_tab_off, shdr.sh_name)); |
| 132 | | const section_name = str_r.interface.take(rodata_name.len) catch return r.err.?; |
| 133 | | if (!std.mem.eql(u8, section_name, rodata_name)) |
| 134 | | continue; |
| 135 | | |
| 136 | | const addr = math.cast(usize, shdr.sh_addr) orelse return error.Overflow; |
| 137 | | const size = math.cast(usize, shdr.sh_size) orelse return error.Overflow; |
| 138 | | _ = try math.add(usize, addr, size); // make sure there is no overflow |
| 139 | | self.rodata_addr = addr; |
| 140 | | self.rodata_size = size; |
| 141 | | return; |
| 142 | | } |
| 143 | | return error.NoRodataSection; |
| 144 | | }, |
| 145 | | else => return error.UnsupportedObjectFormat, |
| 146 | | } |
| 147 | | } |
| 148 | | |
| 149 | 78 | fn getCoverageFile(cache_dir: std.fs.Dir, pcs: []const usize, pc_digest: u64) MemoryMappedList { |
| 150 | 79 | const pc_bitset_usizes = bitsetUsizes(pcs.len); |
| 151 | 80 | const coverage_file_name = std.fmt.hex(pc_digest); |
| ... | ... | @@ -284,11 +213,6 @@ const Executable = struct { |
| 284 | 213 | .{ self.pc_counters.len, pcs.len }, |
| 285 | 214 | ); |
| 286 | 215 | |
| 287 | | self.initRodata() catch |e| if (e != error.UnsupportedObjectFormat) std.log.err( |
| 288 | | \\failed to enumerate read-only memory: {t} |
| 289 | | \\efficiency will be severly reduced |
| 290 | | , .{e}); |
| 291 | | |
| 292 | 216 | self.pc_digest = std.hash.Wyhash.hash(0, mem.sliceAsBytes(pcs)); |
| 293 | 217 | self.shared_seen_pcs = getCoverageFile(cache_dir, pcs, self.pc_digest); |
| 294 | 218 | |
| ... | ... | @@ -322,33 +246,22 @@ const Executable = struct { |
| 322 | 246 | }; |
| 323 | 247 | }; |
| 324 | 248 | |
| 325 | | /// Data gathered from instrumentation functions |
| 326 | | /// Seperate from Executable since its state is resetable and changes |
| 327 | | /// Seperate from Fuzzer since it may be needed before fuzzing starts |
| 249 | /// Data gathered from instrumentation functions. |
| 250 | /// Seperate from Executable since its state is resetable and changes. |
| 251 | /// Seperate from Fuzzer since it may be needed before fuzzing starts. |
| 328 | 252 | const Instrumentation = struct { |
| 329 | 253 | /// Bitset of seen pcs across all runs excluding fresh pcs. |
| 330 | 254 | /// This is seperate then shared_seen_pcs because multiple fuzzing processes are likely using |
| 331 | 255 | /// it which causes contention and unrelated pcs to our campaign being set. |
| 332 | 256 | seen_pcs: []usize, |
| 333 | | /// Bitset of seen rodata bytes read across all runs |
| 334 | | seen_rodata_loads: []usize, |
| 335 | | |
| 336 | | /// Bitset of run's read bytes that weren't present in seen_loads |
| 337 | | /// Elements are always zero if !any_new_data_loads |
| 338 | | new_rodata_loads: []usize, |
| 339 | | any_new_rodata_loads: bool, |
| 340 | 257 | |
| 341 | 258 | /// Stores a fresh input's new pcs |
| 342 | 259 | fresh_pcs: []usize, |
| 343 | | /// Stores a fresh input's new reads |
| 344 | | /// Elements are always zero if !any_fresh_rodata_loads |
| 345 | | fresh_rodata_loads: []usize, |
| 346 | | any_fresh_rodata_loads: bool, |
| 347 | 260 | |
| 348 | 261 | /// Pcs which __sanitizer_cov_trace_switch and __sanitizer_cov_trace_const_cmpx |
| 349 | 262 | /// have been called from and have had their already been added to const_x_vals |
| 350 | 263 | const_pcs: std.AutoArrayHashMapUnmanaged(usize, void) = .empty, |
| 351 | | /// Values that have been constant operands in comparisons, switch cases, or memory reads |
| 264 | /// Values that have been constant operands in comparisons and switch cases. |
| 352 | 265 | /// There may be duplicates in this array if they came from different addresses, which is |
| 353 | 266 | /// fine as they are likely more important and hence more likely to be selected. |
| 354 | 267 | const_vals2: std.ArrayListUnmanaged(u16) = .empty, |
| ... | ... | @@ -361,12 +274,7 @@ const Instrumentation = struct { |
| 361 | 274 | /// from instrumentation functions being called and using undefined values. |
| 362 | 275 | pub const preinit: Instrumentation = .{ |
| 363 | 276 | .seen_pcs = undefined, // currently only updated by `Fuzzer` |
| 364 | | .seen_rodata_loads = undefined, |
| 365 | | .new_rodata_loads = undefined, |
| 366 | | .any_new_rodata_loads = undefined, |
| 367 | 277 | .fresh_pcs = undefined, |
| 368 | | .fresh_rodata_loads = undefined, |
| 369 | | .any_fresh_rodata_loads = undefined, |
| 370 | 278 | }; |
| 371 | 279 | |
| 372 | 280 | pub fn depreinit(self: *Instrumentation) void { |
| ... | ... | @@ -379,20 +287,14 @@ const Instrumentation = struct { |
| 379 | 287 | |
| 380 | 288 | pub fn init() Instrumentation { |
| 381 | 289 | const pc_bitset_usizes = bitsetUsizes(exec.pc_counters.len); |
| 382 | | const rodata_bitset_usizes = bitsetUsizes(exec.rodata_size); |
| 383 | | const alloc_usizes = pc_bitset_usizes * 2 + rodata_bitset_usizes * 3; |
| 290 | const alloc_usizes = pc_bitset_usizes * 2; |
| 384 | 291 | const buf = gpa.alloc(u8, alloc_usizes * @sizeOf(usize)) catch @panic("OOM"); |
| 385 | 292 | var fba_ctx: std.heap.FixedBufferAllocator = .init(buf); |
| 386 | 293 | const fba = fba_ctx.allocator(); |
| 387 | 294 | |
| 388 | 295 | var self: Instrumentation = .{ |
| 389 | 296 | .seen_pcs = fba.alloc(usize, pc_bitset_usizes) catch unreachable, |
| 390 | | .seen_rodata_loads = fba.alloc(usize, rodata_bitset_usizes) catch unreachable, |
| 391 | | .new_rodata_loads = fba.alloc(usize, rodata_bitset_usizes) catch unreachable, |
| 392 | | .any_new_rodata_loads = undefined, |
| 393 | 297 | .fresh_pcs = fba.alloc(usize, pc_bitset_usizes) catch unreachable, |
| 394 | | .fresh_rodata_loads = fba.alloc(usize, rodata_bitset_usizes) catch unreachable, |
| 395 | | .any_fresh_rodata_loads = undefined, |
| 396 | 298 | }; |
| 397 | 299 | self.reset(); |
| 398 | 300 | return self; |
| ... | ... | @@ -400,12 +302,7 @@ const Instrumentation = struct { |
| 400 | 302 | |
| 401 | 303 | pub fn reset(self: *Instrumentation) void { |
| 402 | 304 | @memset(self.seen_pcs, 0); |
| 403 | | @memset(self.seen_rodata_loads, 0); |
| 404 | | @memset(self.new_rodata_loads, 0); |
| 405 | | self.any_new_rodata_loads = false; |
| 406 | 305 | @memset(self.fresh_pcs, 0); |
| 407 | | @memset(self.fresh_rodata_loads, 0); |
| 408 | | self.any_fresh_rodata_loads = false; |
| 409 | 306 | self.const_pcs.clearRetainingCapacity(); |
| 410 | 307 | self.const_vals2.clearRetainingCapacity(); |
| 411 | 308 | self.const_vals4.clearRetainingCapacity(); |
| ... | ... | @@ -418,16 +315,7 @@ const Instrumentation = struct { |
| 418 | 315 | return (self.const_pcs.getOrPut(gpa, pc) catch @panic("OOM")).found_existing; |
| 419 | 316 | } |
| 420 | 317 | |
| 421 | | pub fn clearNewRodataLoads(self: *Instrumentation) void { |
| 422 | | if (self.any_new_rodata_loads) { |
| 423 | | @memset(self.new_rodata_loads, 0); |
| 424 | | self.any_new_rodata_loads = false; |
| 425 | | } |
| 426 | | } |
| 427 | | |
| 428 | 318 | pub fn isFresh(self: *Instrumentation) bool { |
| 429 | | if (self.any_new_rodata_loads) return true; |
| 430 | | |
| 431 | 319 | var hit_pcs = exec.pcBitsetIterator(); |
| 432 | 320 | for (self.seen_pcs) |seen_pcs| { |
| 433 | 321 | if (hit_pcs.next() & ~seen_pcs != 0) return true; |
| ... | ... | @@ -436,38 +324,24 @@ const Instrumentation = struct { |
| 436 | 324 | return false; |
| 437 | 325 | } |
| 438 | 326 | |
| 439 | | /// Updates fresh_pcs and fresh_rodata_loads |
| 440 | | /// any_new_rodata_loads and elements of new_rodata_loads are unspecified |
| 441 | | /// afterwards, but still valid. |
| 327 | /// Updates `fresh_pcs` |
| 442 | 328 | pub fn setFresh(self: *Instrumentation) void { |
| 443 | 329 | var hit_pcs = exec.pcBitsetIterator(); |
| 444 | 330 | for (self.seen_pcs, self.fresh_pcs) |seen_pcs, *fresh_pcs| { |
| 445 | 331 | fresh_pcs.* = hit_pcs.next() & ~seen_pcs; |
| 446 | 332 | } |
| 447 | | |
| 448 | | mem.swap([]usize, &self.fresh_rodata_loads, &self.new_rodata_loads); |
| 449 | | mem.swap(bool, &self.any_fresh_rodata_loads, &self.any_new_rodata_loads); |
| 450 | 333 | } |
| 451 | 334 | |
| 452 | | /// Returns if exec.pc_counters and new_rodata_loads are the same or a superset of fresh_pcs and |
| 453 | | /// fresh_rodata_loads respectively. |
| 335 | /// Returns if `exec.pc_counters` is a superset of `fresh_pcs`. |
| 454 | 336 | pub fn atleastFresh(self: *Instrumentation) bool { |
| 455 | 337 | var hit_pcs = exec.pcBitsetIterator(); |
| 456 | 338 | for (self.fresh_pcs) |fresh_pcs| { |
| 457 | 339 | if (fresh_pcs & hit_pcs.next() != fresh_pcs) return false; |
| 458 | 340 | } |
| 459 | | |
| 460 | | if (self.any_fresh_rodata_loads) { |
| 461 | | if (!self.any_new_rodata_loads) return false; |
| 462 | | for (self.new_rodata_loads, self.fresh_rodata_loads) |n, f| { |
| 463 | | if (n & f != f) return false; |
| 464 | | } |
| 465 | | } |
| 466 | | |
| 467 | 341 | return true; |
| 468 | 342 | } |
| 469 | 343 | |
| 470 | | /// Updates based off fresh_pcs and fresh_rodata_loads |
| 344 | /// Updates based off `fresh_pcs` |
| 471 | 345 | fn updateSeen(self: *Instrumentation) void { |
| 472 | 346 | comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize); |
| 473 | 347 | const shared_seen_pcs: [*]volatile usize = @ptrCast( |
| ... | ... | @@ -479,11 +353,6 @@ const Instrumentation = struct { |
| 479 | 353 | if (fresh != 0) |
| 480 | 354 | _ = @atomicRmw(usize, shared_seen, .Or, fresh, .monotonic); |
| 481 | 355 | } |
| 482 | | |
| 483 | | if (self.any_fresh_rodata_loads) { |
| 484 | | for (self.seen_rodata_loads, self.fresh_rodata_loads) |*s, f| |
| 485 | | s.* |= f; |
| 486 | | } |
| 487 | 356 | } |
| 488 | 357 | }; |
| 489 | 358 | |
| ... | ... | @@ -496,8 +365,8 @@ const Fuzzer = struct { |
| 496 | 365 | /// input. |
| 497 | 366 | input: MemoryMappedList, |
| 498 | 367 | |
| 499 | | /// Minimized past inputs leading to new pcs or rodata reads. These are randomly mutated in |
| 500 | | /// round-robin fashion |
| 368 | /// Minimized past inputs leading to new pc hits. |
| 369 | /// These are randomly mutated in round-robin fashion |
| 501 | 370 | /// Element zero is always an empty input. It is gauraunteed no other elements are empty. |
| 502 | 371 | corpus: std.ArrayListUnmanaged([]const u8), |
| 503 | 372 | corpus_pos: usize, |
| ... | ... | @@ -596,10 +465,9 @@ const Fuzzer = struct { |
| 596 | 465 | self.run(); |
| 597 | 466 | inst.setFresh(); |
| 598 | 467 | inst.updateSeen(); |
| 599 | | inst.clearNewRodataLoads(); |
| 600 | 468 | } |
| 601 | 469 | |
| 602 | | /// Assumes fresh_pcs and fresh_rodata_loads correspond to the input |
| 470 | /// Assumes `fresh_pcs` correspond to the input |
| 603 | 471 | fn minimizeInput(self: *Fuzzer) void { |
| 604 | 472 | // The minimization technique is kept relatively simple, we sequentially try to remove each |
| 605 | 473 | // byte and check that the new pcs and memory loads are still hit. |
| ... | ... | @@ -609,7 +477,6 @@ const Fuzzer = struct { |
| 609 | 477 | const old = self.input.orderedRemove(i); |
| 610 | 478 | |
| 611 | 479 | @memset(exec.pc_counters, 0); |
| 612 | | inst.clearNewRodataLoads(); |
| 613 | 480 | self.run(); |
| 614 | 481 | |
| 615 | 482 | if (!inst.atleastFresh()) { |
| ... | ... | @@ -623,11 +490,7 @@ const Fuzzer = struct { |
| 623 | 490 | } |
| 624 | 491 | |
| 625 | 492 | fn run(self: *Fuzzer) void { |
| 626 | | // We don't need to clear pc_counters here; all we care about is new hits and not already |
| 627 | | // seen hits. Ideally, we wouldn't even have these counters and do something similiar to |
| 628 | | // what we do for tracking memory (i.e. a __sanitizer_cov function that updates a flag on a |
| 629 | | // new hit.) |
| 630 | | assert(!inst.any_new_rodata_loads); |
| 493 | // `pc_counters` is not cleared since only new hits are relevant. |
| 631 | 494 | |
| 632 | 495 | mem.bytesAsValue(usize, self.input.items[0..8]).* = |
| 633 | 496 | mem.nativeToLittle(usize, self.input.items.len - 8); |
| ... | ... | @@ -673,7 +536,6 @@ const Fuzzer = struct { |
| 673 | 536 | inst.setFresh(); |
| 674 | 537 | self.minimizeInput(); |
| 675 | 538 | inst.updateSeen(); |
| 676 | | inst.clearNewRodataLoads(); |
| 677 | 539 | |
| 678 | 540 | // An empty-input has always been tried, so if an empty input is fresh then the |
| 679 | 541 | // test has to be non-deterministic. This has to be checked as duplicate empty |
| ... | ... | @@ -796,58 +658,6 @@ export fn __sanitizer_cov_trace_switch(val: u64, cases: [*]const u64) void { |
| 796 | 658 | } |
| 797 | 659 | } |
| 798 | 660 | |
| 799 | | fn genericLoad(T: anytype, ptr: *align(1) const T, comptime opt_const_vals_field: ?[]const u8) void { |
| 800 | | const addr = @intFromPtr(ptr); |
| 801 | | const off = addr -% exec.rodata_addr; |
| 802 | | if (off >= exec.rodata_size) { |
| 803 | | @branchHint(.likely); |
| 804 | | return; |
| 805 | | } |
| 806 | | |
| 807 | | const i = off / @bitSizeOf(usize); |
| 808 | | // Bits are intentionally truncated since the pointer will almost always be aligned |
| 809 | | const hit = (@as(usize, (1 << @sizeOf(T)) - 1)) << @intCast(off % @bitSizeOf(usize)); |
| 810 | | const new = hit & ~inst.seen_rodata_loads[i]; |
| 811 | | if (new == 0) { |
| 812 | | @branchHint(.likely); |
| 813 | | return; |
| 814 | | } |
| 815 | | |
| 816 | | inst.new_rodata_loads[i] |= new; |
| 817 | | inst.any_new_rodata_loads = true; |
| 818 | | |
| 819 | | if (opt_const_vals_field) |const_vals_field| { |
| 820 | | // This may have already been hit and this run is just being used for evaluating the |
| 821 | | // input, in which case we do not want to readd the same value. |
| 822 | | if (inst.any_fresh_rodata_loads) { |
| 823 | | @branchHint(.unlikely); |
| 824 | | if (new & ~inst.fresh_rodata_loads[i] == 0) |
| 825 | | return; |
| 826 | | } |
| 827 | | @field(inst, const_vals_field).append(gpa, ptr.*) catch @panic("OOM"); |
| 828 | | } |
| 829 | | } |
| 830 | | |
| 831 | | export fn __sanitizer_cov_load1(ptr: *align(1) const u8) void { |
| 832 | | genericLoad(u8, ptr, null); |
| 833 | | } |
| 834 | | |
| 835 | | export fn __sanitizer_cov_load2(ptr: *align(1) const u16) void { |
| 836 | | genericLoad(u16, ptr, "const_vals2"); |
| 837 | | } |
| 838 | | |
| 839 | | export fn __sanitizer_cov_load4(ptr: *align(1) const u32) void { |
| 840 | | genericLoad(u32, ptr, "const_vals4"); |
| 841 | | } |
| 842 | | |
| 843 | | export fn __sanitizer_cov_load8(ptr: *align(1) const u64) void { |
| 844 | | genericLoad(u64, ptr, "const_vals8"); |
| 845 | | } |
| 846 | | |
| 847 | | export fn __sanitizer_cov_load16(ptr: *align(1) const u128) void { |
| 848 | | genericLoad(u128, ptr, "const_vals16"); |
| 849 | | } |
| 850 | | |
| 851 | 661 | export fn __sanitizer_cov_trace_cmp1(arg1: u8, arg2: u8) void { |
| 852 | 662 | _ = arg1; |
| 853 | 663 | _ = arg2; |