fuzzing: fix off-by-one in limit count

This commit is contained in:
Andrew Kelley 2025-09-25 17:16:10 -07:00
parent 98253bc0ee
commit 2da8ec9865

View File

@ -512,7 +512,7 @@ const Fuzzer = struct {
self.corpus_pos = 0; self.corpus_pos = 0;
const rng = self.rng.random(); const rng = self.rng.random();
while (true) { const m = while (true) {
const m = self.mutations.items[rng.uintLessThanBiased(usize, self.mutations.items.len)]; const m = self.mutations.items[rng.uintLessThanBiased(usize, self.mutations.items.len)];
if (!m.mutate( if (!m.mutate(
rng, rng,
@ -524,53 +524,53 @@ const Fuzzer = struct {
inst.const_vals8.items, inst.const_vals8.items,
inst.const_vals16.items, inst.const_vals16.items,
)) continue; )) continue;
break m;
};
self.run(); self.run();
if (inst.isFresh()) {
@branchHint(.unlikely);
const header = mem.bytesAsValue( if (inst.isFresh()) {
abi.SeenPcsHeader, @branchHint(.unlikely);
exec.shared_seen_pcs.items[0..@sizeOf(abi.SeenPcsHeader)],
);
_ = @atomicRmw(usize, &header.unique_runs, .Add, 1, .monotonic);
inst.setFresh(); const header = mem.bytesAsValue(
self.minimizeInput(); abi.SeenPcsHeader,
inst.updateSeen(); exec.shared_seen_pcs.items[0..@sizeOf(abi.SeenPcsHeader)],
);
_ = @atomicRmw(usize, &header.unique_runs, .Add, 1, .monotonic);
// An empty-input has always been tried, so if an empty input is fresh then the inst.setFresh();
// test has to be non-deterministic. This has to be checked as duplicate empty self.minimizeInput();
// entries are not allowed. inst.updateSeen();
if (self.input.items.len - 8 == 0) {
std.log.warn("non-deterministic test (empty input produces different hits)", .{});
_ = @atomicRmw(usize, &header.unique_runs, .Sub, 1, .monotonic);
return;
}
const arena = self.arena_ctx.allocator(); // An empty-input has always been tried, so if an empty input is fresh then the
const bytes = arena.dupe(u8, @volatileCast(self.input.items[8..])) catch @panic("OOM"); // test has to be non-deterministic. This has to be checked as duplicate empty
// entries are not allowed.
self.corpus.append(gpa, bytes) catch @panic("OOM"); if (self.input.items.len - 8 == 0) {
self.mutations.appendNTimes(gpa, m, 6) catch @panic("OOM"); std.log.warn("non-deterministic test (empty input produces different hits)", .{});
_ = @atomicRmw(usize, &header.unique_runs, .Sub, 1, .monotonic);
// Write new corpus to cache return;
var name_buf: [@sizeOf(usize) * 2]u8 = undefined;
self.corpus_dir.writeFile(.{
.sub_path = std.fmt.bufPrint(
&name_buf,
"{x}",
.{self.corpus_dir_idx},
) catch unreachable,
.data = bytes,
}) catch |e| panic(
"failed to write corpus file '{x}': {t}",
.{ self.corpus_dir_idx, e },
);
self.corpus_dir_idx += 1;
} }
break; const arena = self.arena_ctx.allocator();
const bytes = arena.dupe(u8, @volatileCast(self.input.items[8..])) catch @panic("OOM");
self.corpus.append(gpa, bytes) catch @panic("OOM");
self.mutations.appendNTimes(gpa, m, 6) catch @panic("OOM");
// Write new corpus to cache
var name_buf: [@sizeOf(usize) * 2]u8 = undefined;
self.corpus_dir.writeFile(.{
.sub_path = std.fmt.bufPrint(
&name_buf,
"{x}",
.{self.corpus_dir_idx},
) catch unreachable,
.data = bytes,
}) catch |e| panic(
"failed to write corpus file '{x}': {t}",
.{ self.corpus_dir_idx, e },
);
self.corpus_dir_idx += 1;
} }
} }
}; };
@ -618,7 +618,7 @@ export fn fuzzer_new_input(bytes: abi.Slice) void {
export fn fuzzer_main(limit_kind: abi.LimitKind, amount: u64) void { export fn fuzzer_main(limit_kind: abi.LimitKind, amount: u64) void {
switch (limit_kind) { switch (limit_kind) {
.forever => while (true) fuzzer.cycle(), .forever => while (true) fuzzer.cycle(),
.iterations => for (0..amount -| 1) |_| fuzzer.cycle(), .iterations => for (0..amount) |_| fuzzer.cycle(),
} }
} }