feat(fuzz): session_opseq target - stateful op-sequence campaign against the session pump
Target 4 from the fuzzing plan (the alkcall-bug-class hunt): a #[derive(Arbitrary)] op sequence drives the producer session pump (drive_session_pre_negotiated) over a duplex pair against a fuzz-local mock backend. - fuzz/shared/src/session_opseq.rs: op vocabulary (client writes incl. invalid-type/oversize-length barrier probes, backend production/EOF, exit resolve/fail/drop, control messages, bounded reads, yields), kill-guard exit future (ADR-005 observability), delivery model, and the invariants: kill-on-Drop (kill_fired == !exit_resolved), exit chunk is last with first-issued code -1 on wait-failure (ADR-004), lossless per-stream FIFO + single stdout sentinel, stdin prefix-losslessness (post-shutdown chunks never delivered), control dispatch bounds, take-once allocation, teardown termination. - Harness-model correction caught by a 60s smoke run before the campaign (the alkcall §7.7 pattern, third time): a failed client write can mean the session completed and dropped the server duplex half while the write was in flight - the harness now drains and requires the exit chunk on any write/shutdown error (premature close without it is a finding). No adapter code changed. - 12 committed seeds via the deterministic generator, hand-encoded against the arbitrary 1.4.x derive layout; the encoding is pinned by a seed-decode test. Existing chunk_frame/negotiation_frame/ control_json seed corpora are byte-identical to before. - fuzz/.gitignore: the per-target seed negations never matched (corpus/* excludes the parent dir; git cannot re-include beneath an excluded dir). Fixed with the !corpus/*/ + corpus/*/* + !corpus/*/seed-* recipe - new seed files were silently ignored until now. - Campaign: 10 min detached via run-detached.sh, exited 0, artifact dir empty - 42.3k execs across 33 fork jobs, 0 crash/oom/timeout, cov 3934 -> 4075 edges. Grown corpus excised per the corpus policy. Verification: corpus replay green on stable (9 tests), cargo test 113 + --all-features 156, clippy stable/wasm/fuzz-shared, fmt, doc, publish dry-run, wasm check - all pass.
This commit is contained in:
1 parent
e72e1fc4c7
commit
fb7cfe3851
22 files changed
+1549
-11
No files matched your search
@@ -241,10 +241,277 @@ def control_json_seeds():
|
||||
return seeds
|
||||
|
||||
|
||||
# session_opseq seeds are arbitrary-encoded `SessionSequence` inputs, so
|
||||
# they are hand-encoded against the `arbitrary` 1.4.x derive layout the
|
||||
# same way alkcall's manager_routing seeds are: each element of a Vec is
|
||||
# gated by a keep-going byte (odd = another element follows; the final
|
||||
# gated read before data exhaustion yields true via zero-fill), enum
|
||||
# variant tags are 4-byte LE u32s with `(tag * 12) >> 32` selecting by
|
||||
# declaration order, and fields follow in declaration order
|
||||
# little-endian, zero-filled on short data. The encoding is pinned by
|
||||
# the seed-decode test in fuzz/shared/src/session_opseq.rs.
|
||||
SESSION_OP_VARIANTS = [
|
||||
"ClientWrite",
|
||||
"ClientCloseWrite",
|
||||
"Stdout",
|
||||
"StdoutEof",
|
||||
"Stderr",
|
||||
"StderrEof",
|
||||
"Exit",
|
||||
"ExitFail",
|
||||
"DropExitTx",
|
||||
"CtrlIn",
|
||||
"ClientReadAll",
|
||||
"Yield",
|
||||
]
|
||||
|
||||
|
||||
def _le32(v):
|
||||
return struct.pack("<I", v & 0xFFFFFFFF)
|
||||
|
||||
|
||||
class OpSeqEncoder:
|
||||
def __init__(self, stderr_enabled):
|
||||
self.buf = bytearray()
|
||||
self.buf.append(1 if stderr_enabled else 0)
|
||||
|
||||
def op(self, name):
|
||||
idx = SESSION_OP_VARIANTS.index(name)
|
||||
self.buf += _le32((idx << 32) // 12)
|
||||
|
||||
def u8(self, v):
|
||||
self.buf.append((v if isinstance(v, int) else v[0]) & 0xFF)
|
||||
|
||||
def u16(self, v):
|
||||
self.buf += struct.pack("<H", v & 0xFFFF)
|
||||
|
||||
def i16(self, v):
|
||||
self.buf += struct.pack("<h", v)
|
||||
|
||||
def end(self):
|
||||
self.buf.append(0)
|
||||
return bytes(self.buf)
|
||||
|
||||
|
||||
def session_opseq_seeds():
|
||||
seeds = []
|
||||
n = 0
|
||||
|
||||
def add(blob):
|
||||
nonlocal n
|
||||
seeds.append((f"seed-{n:03d}", blob))
|
||||
n += 1
|
||||
|
||||
add(
|
||||
bytes(
|
||||
[
|
||||
0x00, 0x01, 0xAB, 0xAA, 0xAA, 0x2A, 0x10, 0x00, 0x61, 0x01,
|
||||
0xAB, 0xAA, 0xAA, 0xEA, 0x04,
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
e = OpSeqEncoder(stderr_enabled=True)
|
||||
e.op("Stdout")
|
||||
e.u16(32)
|
||||
e.u8(b"a")
|
||||
e.op("Stderr")
|
||||
e.u16(8)
|
||||
e.u8(b"e")
|
||||
e.op("Yield")
|
||||
e.u8(8)
|
||||
add(e.end())
|
||||
|
||||
e = OpSeqEncoder(stderr_enabled=True)
|
||||
e.op("ClientWrite")
|
||||
e.u8(0)
|
||||
e.u16(3)
|
||||
e.u8(b"x")
|
||||
e.op("ClientWrite")
|
||||
e.u8(0)
|
||||
e.u16(0)
|
||||
e.u8(0)
|
||||
e.op("ClientWrite")
|
||||
e.u8(0)
|
||||
e.u16(1)
|
||||
e.u8(b"y")
|
||||
e.op("CtrlIn")
|
||||
e.u8(2)
|
||||
e.u16(0)
|
||||
e.u16(0)
|
||||
e.op("Stdout")
|
||||
e.u16(4)
|
||||
e.u8(b"o")
|
||||
e.op("StdoutEof")
|
||||
e.op("Exit")
|
||||
e.i16(3)
|
||||
e.op("ClientReadAll")
|
||||
e.u8(63)
|
||||
add(e.end())
|
||||
|
||||
e = OpSeqEncoder(stderr_enabled=True)
|
||||
e.op("Exit")
|
||||
e.i16(7)
|
||||
e.op("Exit")
|
||||
e.i16(43)
|
||||
e.op("Stdout")
|
||||
e.u16(4)
|
||||
e.u8(b"o")
|
||||
e.op("StdoutEof")
|
||||
e.op("Stderr")
|
||||
e.u16(4)
|
||||
e.u8(b"e")
|
||||
e.op("StderrEof")
|
||||
e.op("ClientReadAll")
|
||||
e.u8(63)
|
||||
add(e.end())
|
||||
|
||||
e = OpSeqEncoder(stderr_enabled=False)
|
||||
e.op("ExitFail")
|
||||
e.op("Stdout")
|
||||
e.u16(4)
|
||||
e.u8(b"o")
|
||||
e.op("StdoutEof")
|
||||
e.op("ClientReadAll")
|
||||
e.u8(63)
|
||||
add(e.end())
|
||||
|
||||
e = OpSeqEncoder(stderr_enabled=True)
|
||||
e.op("ClientWrite")
|
||||
e.u8(200)
|
||||
e.u16(0)
|
||||
e.u8(0)
|
||||
e.op("Stdout")
|
||||
e.u16(4)
|
||||
e.u8(b"o")
|
||||
e.op("ClientWrite")
|
||||
e.u8(5)
|
||||
e.u16(0)
|
||||
e.u8(0)
|
||||
e.op("StdoutEof")
|
||||
e.op("ClientReadAll")
|
||||
e.u8(63)
|
||||
e.op("Exit")
|
||||
e.i16(-1)
|
||||
add(e.end())
|
||||
|
||||
e = OpSeqEncoder(stderr_enabled=False)
|
||||
for cols, rows in [(80, 24), (0, 0), (65535, 65535), (120, 40)]:
|
||||
e.op("CtrlIn")
|
||||
e.u8(0)
|
||||
e.u16(cols)
|
||||
e.u16(rows)
|
||||
e.op("CtrlIn")
|
||||
e.u8(1)
|
||||
e.u16(0)
|
||||
e.u16(0)
|
||||
e.op("ClientReadAll")
|
||||
e.u8(4)
|
||||
e.op("Exit")
|
||||
e.i16(0)
|
||||
e.op("ClientReadAll")
|
||||
e.u8(63)
|
||||
add(e.end())
|
||||
|
||||
e = OpSeqEncoder(stderr_enabled=False)
|
||||
e.op("CtrlIn")
|
||||
e.u8(3)
|
||||
e.u16(99)
|
||||
e.u16(0)
|
||||
e.op("CtrlIn")
|
||||
e.u8(4)
|
||||
e.u16(0)
|
||||
e.u16(0)
|
||||
e.op("ClientWrite")
|
||||
e.u8(4)
|
||||
e.u16(2)
|
||||
e.u8(b"z")
|
||||
e.op("Stdout")
|
||||
e.u16(4)
|
||||
e.u8(b"o")
|
||||
e.op("StdoutEof")
|
||||
e.op("Exit")
|
||||
e.i16(5)
|
||||
e.op("ClientReadAll")
|
||||
e.u8(63)
|
||||
add(e.end())
|
||||
|
||||
e = OpSeqEncoder(stderr_enabled=True)
|
||||
e.op("ClientWrite")
|
||||
e.u8(0)
|
||||
e.u16(2)
|
||||
e.u8(b"w")
|
||||
e.op("ClientCloseWrite")
|
||||
e.op("Stdout")
|
||||
e.u16(4)
|
||||
e.u8(b"o")
|
||||
e.op("StdoutEof")
|
||||
e.op("Stderr")
|
||||
e.u16(4)
|
||||
e.u8(b"e")
|
||||
e.op("StderrEof")
|
||||
e.op("Exit")
|
||||
e.i16(0)
|
||||
e.op("ClientReadAll")
|
||||
e.u8(63)
|
||||
add(e.end())
|
||||
|
||||
e = OpSeqEncoder(stderr_enabled=False)
|
||||
e.op("DropExitTx")
|
||||
e.op("Stdout")
|
||||
e.u16(4)
|
||||
e.u8(b"o")
|
||||
e.op("StdoutEof")
|
||||
e.op("ClientReadAll")
|
||||
e.u8(63)
|
||||
add(e.end())
|
||||
|
||||
e = OpSeqEncoder(stderr_enabled=True)
|
||||
e.op("Stderr")
|
||||
e.u16(4)
|
||||
e.u8(b"e")
|
||||
e.op("StderrEof")
|
||||
e.op("Stdout")
|
||||
e.u16(4)
|
||||
e.u8(b"o")
|
||||
e.op("Exit")
|
||||
e.i16(9)
|
||||
e.op("StdoutEof")
|
||||
e.op("ClientReadAll")
|
||||
e.u8(63)
|
||||
add(e.end())
|
||||
|
||||
e = OpSeqEncoder(stderr_enabled=False)
|
||||
e.op("ClientWrite")
|
||||
e.u8(0)
|
||||
e.u16(2)
|
||||
e.u8(b"a")
|
||||
e.op("ClientWrite")
|
||||
e.u8(1)
|
||||
e.u16(2)
|
||||
e.u8(b"b")
|
||||
e.op("ClientWrite")
|
||||
e.u8(0)
|
||||
e.u16(2)
|
||||
e.u8(b"c")
|
||||
e.op("Exit")
|
||||
e.i16(1)
|
||||
e.op("Stdout")
|
||||
e.u16(4)
|
||||
e.u8(b"o")
|
||||
e.op("StdoutEof")
|
||||
e.op("ClientReadAll")
|
||||
e.u8(63)
|
||||
add(e.end())
|
||||
|
||||
return seeds
|
||||
|
||||
|
||||
def main():
|
||||
write_seeds("chunk_frame", chunk_frame_seeds())
|
||||
write_seeds("negotiation_frame", negotiation_frame_seeds())
|
||||
write_seeds("control_json", control_json_seeds())
|
||||
write_seeds("session_opseq", session_opseq_seeds())
|
||||
print("seeds written")
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user