mirror of
https://github.com/bdbrown4/peltier.io.git
synced 2026-11-07 19:08:27 +00:00
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EcKPWQ3MQD4NdEHwar96u2
91 lines
3.1 KiB
Python
91 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Differential fuzz for comrak attempts: pristine vs candidate must
|
|
produce byte-identical HTML (stdout), stderr, and exit code on mutated
|
|
markdown. comrak is a single-threaded stdin->stdout filter, so raw byte
|
|
comparison needs no canonicalization.
|
|
|
|
python3 scripts/diff-fuzz-comrak.py <workdir> <iters> <candidate-bin>
|
|
|
|
Seeds: windows of the pinned Pro Git corpus. Mutations focus on inline/
|
|
block structure the parser and teardown paths exercise: emphasis, code
|
|
fences, links, HTML blocks, list markers, plus raw byte noise.
|
|
"""
|
|
|
|
import random
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path("/home/user/peltier.io")
|
|
BASE = ROOT / "targets/comrak/baseline/release/comrak"
|
|
CAND = ROOT / sys.argv[3]
|
|
CORPUS = ROOT / "corpora/comrak/progit.md"
|
|
WORK = Path(sys.argv[1])
|
|
ITERS = int(sys.argv[2])
|
|
|
|
TOKENS = [
|
|
b"**", b"*", b"__", b"_", b"`", b"```", b"~~~", b"~~", b"[", b"](", b")",
|
|
b"![", b"#", b"##", b">", b"- ", b"1. ", b"|", b"---", b"<div>", b"</div>",
|
|
b"<!--", b"-->", b"\\", b"&", b"<http://x>", b" ", b"\t",
|
|
]
|
|
|
|
|
|
def mutate(data: bytes, rng: random.Random) -> bytes:
|
|
b = bytearray(data)
|
|
for _ in range(rng.randrange(1, 8)):
|
|
if not b:
|
|
b = bytearray(rng.choice(TOKENS))
|
|
continue
|
|
op = rng.randrange(5)
|
|
pos = rng.randrange(len(b) + 1)
|
|
if op == 0:
|
|
b[pos:pos] = rng.choice(TOKENS)
|
|
elif op == 1 and len(b) > 2:
|
|
del b[pos:min(len(b), pos + rng.randrange(1, 16))]
|
|
elif op == 2 and pos < len(b):
|
|
b[pos] = rng.randrange(256)
|
|
elif op == 3:
|
|
end = min(len(b), pos + rng.randrange(1, 64))
|
|
b[end:end] = b[pos:end]
|
|
else:
|
|
b[pos:pos] = b"\n\n"
|
|
return bytes(b)
|
|
|
|
|
|
def run(binary: Path, payload: bytes) -> tuple[bytes, bytes, int]:
|
|
r = subprocess.run([str(binary)], input=payload, capture_output=True, timeout=60)
|
|
return r.stdout, r.stderr, r.returncode
|
|
|
|
|
|
def main() -> int:
|
|
rng = random.Random(0xC0FFEE)
|
|
corpus = CORPUS.read_bytes()
|
|
# Seed windows across the whole book, varied sizes.
|
|
seeds = [
|
|
corpus[o : o + n]
|
|
for o in range(0, len(corpus) - 65536, len(corpus) // 300)
|
|
for n in (512, 4096, 32768)
|
|
]
|
|
WORK.mkdir(parents=True, exist_ok=True)
|
|
divergences = 0
|
|
for i in range(ITERS):
|
|
payload = mutate(rng.choice(seeds), rng)
|
|
a = run(BASE, payload)
|
|
b = run(CAND, payload)
|
|
if a != b:
|
|
divergences += 1
|
|
keep = WORK / f"divergence-{i}"
|
|
keep.mkdir()
|
|
(keep / "input.md").write_bytes(payload)
|
|
(keep / "baseline.out").write_bytes(a[0] + b"\n--stderr--\n" + a[1] + f"\nrc={a[2]}".encode())
|
|
(keep / "candidate.out").write_bytes(b[0] + b"\n--stderr--\n" + b[1] + f"\nrc={b[2]}".encode())
|
|
print(f"DIVERGENCE at iter {i}; kept in {keep}")
|
|
if (i + 1) % 1000 == 0:
|
|
print(f"{i + 1}/{ITERS}, {divergences} divergences", flush=True)
|
|
print(f"done: {ITERS} inputs, {divergences} divergences")
|
|
return 1 if divergences else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|