Some checks are pending
Fuzz Smoke / libFuzzer smoke (push) Waiting to run
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>
44 lines
1.7 KiB
Python
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()
|