#!/usr/bin/env python3 """Regenerate the committed seed corpora for alktty's fuzz targets. Writes into fuzz/corpus//. Deterministic: fixed inputs only, no randomness. Run from the repo root: python3 fuzz/gen_fuzz_seeds.py """ import json import os import struct MAX_CHUNK_LEN = 16 * 1024 * 1024 STREAM_TYPES = [0, 1, 2, 3, 4, 5, 255] CHUNK_PAYLOADS = [ b"", b"x", b"hello chunk", b"\x00\x01\x02\x03", b'{"type":"eof"}', b'{"type":"resize","cols":80,"rows":24}', b'{"type":"signal","name":"INT"}', b'{"type":"exit","code":0}', b'{"type":"exit","code":-1}', b'{"type":"unknown-control"}', b'{"type":', b"\xff\xfe invalid utf8 \xb0", b"y" * 4096, b'{"type":"resize","cols":65535,"rows":0,"pixel_width":1,"pixel_height":2}', b'{"type":"signal","name":"NOT_A_SIGNAL"}', b"{}}", b"[]", b"null", b"\x00" * 64, ] CONTROL_SAMPLES = [ b'{"type":"resize","cols":80,"rows":24}', b'{"type":"resize","cols":0,"rows":65535,"pixel_width":800,"pixel_height":600}', b'{"type":"resize"}', b'{"type":"resize","cols":-1,"rows":24}', b'{"type":"resize","cols":70000,"rows":24}', b'{"type":"resize","cols":"80","rows":24}', b'{"type":"signal","name":"TERM"}', b'{"type":"signal","name":"HUP"}', b'{"type":"signal","name":"KILL"}', b'{"type":"signal","name":"USR1"}', b'{"type":"signal","name":"USR2"}', b'{"type":"signal","name":"TSTP"}', b'{"type":"signal","name":"CONT"}', b'{"type":"signal","name":"QUIT"}', b'{"type":"signal","name":"int"}', b'{"type":"signal","name":""}', b'{"type":"signal"}', b'{"type":"signal","name":123}', b'{"type":"eof"}', b'{"type":"EOF"}', b'{"type":"exit","code":0}', b'{"type":"exit","code":-9}', b'{"type":"exit"}', b'{"type":"exit","code":null}', b'{"type":"exit","code":"0"}', b'{"type":"bogus"}', b'{"type":123}', b'{}', b'{"type":"eof","extra":[1,2,3]}', b'{"type":"resize","cols":80,"rows":24,"type":"signal","name":"KILL"}', b'{"type":"resize","cols":80,"rows":24', b'', b'{', b'null', b'"resize"', b'[]', b'{"type":"resize","cols":80,"rows":24}}', b'\xff\xfe', b'{"type":"\xc3\xa9of"}', ] NEGOTIATION_SAMPLES = [ b'{"carriage":"raw","backend":"local","cmd":["/bin/bash","-l"]}', b'{"carriage":"raw","backend":"local","cmd":["/bin/bash","-l"],' b'"tty":{"term":"xterm-256color","cols":80,"rows":24,"pixel_width":0,' b'"pixel_height":0,"modes":{}}}', b'{"carriage":"raw","backend":"docker","cmd":["sh"],"container":"abc123",' b'"image":"ubuntu:22.04"}', b'{"carriage":"raw","backend":"local","cmd":["sh"],"cwd":"/tmp",' b'"env":{"FOO":"bar","BAZ":"qux"}}', b'{"carriage":"pipe","backend":"local","cmd":["cat"]}', b'{"carriage":"","backend":"","cmd":[]}', b'{"carriage":"raw","backend":"docker","cmd":["bash"],"container":123}', b'{"carriage":"raw","backend":"local","cmd":["/bin/sh"],' b'"tty":{"cols":-1,"rows":-1}}', b'{"carriage":"raw","backend":"local","cmd":["/bin/sh"],' b'"tty":{"cols":99999,"rows":0}}', b'{"carriage":"raw","backend":"local","cmd":["/bin/sh"],' b'"tty":null,"cwd":null,"env":null}', b'{"carriage":"raw","backend":"local","cmd":["/bin/sh"],' b'"tty":{"modes":{"deep":{"nested":[1,2,3,{"x":None}]}}}}'.replace(b"None", b"null"), b'{"carriage":"raw"}', b'{"backend":"local"}', b'{"cmd":["sh"]}', b'{"carriage":"raw","backend":"local","cmd":["/bin/sh"],' b'"env":{"KEY":"\\u00e9value"},"cwd":"/w\\u00f6rld"}', b'{"error":"malformed_negotiation"}', b'{"error":"unknown_backend","backend":"docker"}', b'{"error":"allocate_failed","reason":"spawn failed"}', b'{"error":"spoofed","error2":"real"}', b'{"error":"malformed_negotiation","detail":"carriage must be raw"}', b'{"error":123}', b'{"error":null}', b'{"error":"x","nest":{"a":{"b":{"c":[1,{"d":None}]}}}}'.replace(b"None", b"null"), b'{"error":"' + b"z" * 4096 + b'"}', b'{"error":"\\ud83d\\ude00"}', b'\xff\xfe{"error":"bad"}', b'{"error":"trailing"} trailing', b'{"error":"unicode \\u0000 control"}', b'{"type":"resize"}', b'{"carriage":"raw","backend":"local","cmd":["/bin/sh"],' b'"tty":{"cols":80,"rows":24},"cmd_extra":["ignored"]}', ] ERROR_RESPONSE_CASES = [ ("malformed_negotiation", []), ("unknown_backend", [("backend", "docker")]), ("allocate_failed", [("backend", "local"), ("reason", "spawn failed")]), ("spoof", [("error", "ignored"), ("detail", "x")]), ] def chunk_header(stream_type, length): return bytes([stream_type]) + struct.pack(">I", length) def write_seeds(target, seeds): corpus_dir = os.path.join(os.path.dirname(__file__), "corpus", target) os.makedirs(corpus_dir, exist_ok=True) for name, blob in seeds: with open(os.path.join(corpus_dir, name), "wb") as f: f.write(blob) def chunk_frame_seeds(): seeds = [] n = 0 def add(blob): nonlocal n seeds.append((f"seed-{n:03d}", blob)) n += 1 add(b"") for st in STREAM_TYPES: add(bytes([st])) add(bytes([st]) + b"\x00") for payload in CHUNK_PAYLOADS: add(chunk_header(st, len(payload)) + payload) add(chunk_header(0, 1)) add(chunk_header(1, 1) + b"ab") add(chunk_header(4, 16) + b"short") add(chunk_header(1, 0) + b"trailing-after-sentinel") add(chunk_header(2, 0) + chunk_header(1, 3) + b"abc") add(chunk_header(0, MAX_CHUNK_LEN + 1)) add(chunk_header(1, MAX_CHUNK_LEN + 1)) add(chunk_header(5, MAX_CHUNK_LEN + 1)) add(chunk_header(255, MAX_CHUNK_LEN + 1)) add(chunk_header(0, 0xFFFFFFFF)) add(chunk_header(1, 0xFFFFFFFF)) add(chunk_header(4, 0xFFFFFFFF)) add(chunk_header(3, MAX_CHUNK_LEN)) add(chunk_header(1, MAX_CHUNK_LEN)) add(b"\x01\x01\x00\x00\x00") add(b"\x02\x00\x00\x00\x00" + b"") add(b"\x04" + b'{"type":"exit","code":0}' + b"") truncated = chunk_header(1, 64) + b"partial" for i in range(1, len(truncated)): add(truncated[:i]) valid = chunk_header(1, 5) + b"hello" + chunk_header(2, 3) + b"err" for i in range(1, min(len(valid), 12)): add(valid[:i]) peekable = chunk_header(1, 4) + b"data" for lead in (b"", b"\x01", b"\x00"): add(lead + peekable) add(chunk_header(1, 0) + chunk_header(1, 0) + chunk_header(4, 2) + b"{}") return seeds def negotiation_frame_seeds(): seeds = [] n = 0 def add(blob): nonlocal n seeds.append((f"seed-{n:03d}", blob)) n += 1 add(b"") add(b"\x00") add(b"\x00\x00") add(b"\x00\x00\x00") for body in NEGOTIATION_SAMPLES: add(struct.pack(">I", len(body)) + body) add(struct.pack(">I", len(body)) + body[: len(body) // 2]) add(struct.pack(">I", 0)) add(struct.pack(">I", 1) + b"{}") add(struct.pack(">I", MAX_CHUNK_LEN + 1)[:4]) add(struct.pack(">I", 0xFFFFFFFF)) add(struct.pack(">I", 0xFFFFFFFF) + b"x" * 4) add(struct.pack(">I", MAX_CHUNK_LEN)) add(struct.pack(">I", MAX_CHUNK_LEN - 1) + b"x" * 16) valid = struct.pack(">I", len(b'{"error":"x"}')) + b'{"error":"x"}' for i in range(1, len(valid)): add(valid[:i]) return seeds def control_json_seeds(): seeds = [] n = 0 def add(blob): nonlocal n seeds.append((f"seed-{n:03d}", blob)) n += 1 for blob in CONTROL_SAMPLES: add(blob) add(b'{"type":"resize","cols":80,"rows":24' + b" " * 8) add(b' {"type":"eof"} ') add(b'{"type":"resize","cols":1e2,"rows":24}') add(b'{"type":"exit","code":1e400}') add(b'{"type":"exit","code":2147483648}') add(b'{"type":"exit","code":-2147483649}') add(b'{"type":"signal","name":"SIG' + b"T" * 64 + b'"}') add(b'{"type":"resize","cols":80,"rows":24,"pixel_width":-5}') add(b'{"type":["resize"],"cols":80}') add(b'{"TYPE":"eof"}') add(b'{"type":"resize","cols":80,"rows":24,"type":"resize"}') 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("