yams/tools/fuzzing/startup_seeds.py
Trevon 6248c87bcc
Some checks are pending
Fuzz Smoke / libFuzzer smoke (push) Waiting to run
test(daemon): replay startup schedules under a fuzzer (#335)
Startup ordering defects were only observable by waiting minutes on a live
daemon, and the one that aborted it took a live restart loop to find. Give the
startup contracts a deterministic harness that runs in milliseconds.

+ startup_fuzz_support.h drives the real ServiceManagerFsm and the real
  DeferralGate with virtual time: legal phase advances, injected failure,
  shutdown and terminal reset, idle/busy, pressure/calm, starvation deadlines
  and cancellation
+ oracles fail closed on shutdown resurrection without reset, failure-to-ready,
  eligibility disagreeing with fsm.isReady(), admission outside the virtual
  deadline, and ineligible work touching the admission gate; fuzzed IEEE-754
  components must never yield non-finite or out-of-range scores
+ catch2_startup_replay runs named regressions plus 1024 seeded traces per run
  and reports the root seed and action offset for replay
+ fuzz_startup builds the same harness for libFuzzer/AFL, with a seed generator
  that refuses to overwrite different existing files
- the fuzzing lane no longer defaults to TSan: harnesses link
  -fsanitize=fuzzer,address,undefined and the two runtimes cannot share a
  process; combining them now fails with an explanation instead of producing a
  binary that cannot start
- documented the lane invocation, target naming and the coverage limits,
  including that governor call ordering inside the poller is not covered

Scope: contract-valid single-owner schedules. Executor interleavings, late
callbacks, concurrent FSM instances and filesystem/model faults are not modeled.

Signed-off-by: trvon <git@trevon.dev>
2026-10-22 16:48:50 -06:00

44 lines
1.7 KiB
Python

#!/usr/bin/env python3
"""Create deterministic startup-fuzzer seeds, preserving any existing different files."""
import argparse
from pathlib import Path
def seeds():
# Stable StartupAction vocabulary in startup_fuzz_support.h; high bit selects vectors.
advance, fail, shutdown, finish, idle, busy, pressure, calm, tick, deadline, probe, cancel, reset = range(13)
yield "ready", bytes([advance] * 7)
yield "vectors", bytes([advance | 0x80] * 7)
yield "pressure-before-ready", bytes(
[pressure] + [advance] * 4 + [deadline, probe] + [advance] * 2
+ [deadline, cancel, deadline, shutdown, finish]
)
yield "idle-pressure", bytes([advance] * 7 + [busy, pressure, deadline, idle, tick, calm, probe])
for phase in range(6):
yield f"failure-{phase}", bytes(
[advance] * phase + [fail, deadline, advance, shutdown, finish, advance, reset]
+ [advance] * 7
)
# IEEE-754 bits, little-endian, interpreted as (left,right) component pairs.
bits = [0, 0x80000000, 0x7F800000, 0xFF800000, 0x7FC00000, 1, 0x7F7FFFFF, 0x3F800000]
yield "float-boundaries", b"".join(x.to_bytes(4, "little") * 2 for x in bits)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("directory", type=Path)
args = parser.parse_args()
args.directory.mkdir(parents=True, exist_ok=True)
for name, data in seeds():
path = args.directory / name
if path.exists():
if path.read_bytes() != data:
raise ValueError(f"Refusing to replace existing different seed: {path}")
else:
with path.open("xb") as output:
output.write(data)
print(path)
if __name__ == "__main__":
main()