322 lines
9.4 KiB
Python
322 lines
9.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Regenerate the committed seed corpora for alkstore's fuzz targets.
|
|
|
|
Writes into fuzz/corpus/<target>/. Deterministic: fixed inputs only, no
|
|
randomness. Run from the repo root:
|
|
|
|
python3 fuzz/gen_fuzz_seeds.py
|
|
|
|
Target seed functions and the main() registry land here with the
|
|
target tasks; the shared seed_name()/write_seed() plumbing is stable.
|
|
"""
|
|
|
|
import os
|
|
|
|
|
|
def seed_name(target, i):
|
|
return os.path.join("fuzz", "corpus", target, f"seed-{i:03d}")
|
|
|
|
|
|
def write_seed(target, i, data):
|
|
path = seed_name(target, i)
|
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
with open(path, "wb") as f:
|
|
f.write(data)
|
|
|
|
|
|
def payload_codec_seeds():
|
|
i = 0
|
|
|
|
# Valid JSON of every Value variant (numbers incl. bounds/floats,
|
|
# strings incl. escapes/unicode, arrays, objects), plus whitespace
|
|
# padding.
|
|
valid_docs = [
|
|
b"null",
|
|
b"true",
|
|
b"false",
|
|
b"0",
|
|
b"-1",
|
|
b"-9223372036854775808",
|
|
b"9223372036854775807",
|
|
b"18446744073709551615",
|
|
b"18446744073709551616",
|
|
b"3.5",
|
|
b"-2.25",
|
|
b"1e10",
|
|
b"1e309",
|
|
b"-0",
|
|
b'""',
|
|
b'"hello world"',
|
|
b'"unicode \\u00e9"',
|
|
b'"emoji \\ud83d\\ude00"',
|
|
b'"tab\\tnewline\\nquote\\"slash"',
|
|
b'"null"',
|
|
b"[]",
|
|
b'[1, "two", null, true, {"three": 3}, []]',
|
|
b"{}",
|
|
b'{"a": 1}',
|
|
b'{"a": 1, "nested": {"deep": [true, null, 3.5]}}',
|
|
b'{"\\u00e9": "value"}',
|
|
b' \t\r\n{"spaced": true}\t\n ',
|
|
]
|
|
for doc in valid_docs:
|
|
write_seed("payload_codec", i, doc)
|
|
i += 1
|
|
|
|
# Truncations at every prefix length of two representative docs.
|
|
for doc in [b'{"a": 1, "b": [2, 3]}', b"[true, null, 1.5]"]:
|
|
for n in range(1, len(doc)):
|
|
write_seed("payload_codec", i, doc[:n])
|
|
i += 1
|
|
|
|
# Deep nesting: shallow, at/near serde_json's 128 recursion limit,
|
|
# and well past it. Arrays and objects both.
|
|
for depth in [8, 127, 128, 129, 256, 1024]:
|
|
write_seed("payload_codec", i, ("[" * depth + "0" + "]" * depth).encode())
|
|
i += 1
|
|
for depth in [8, 200]:
|
|
write_seed("payload_codec", i, ('{"a":' * depth + "1" + "}" * depth).encode())
|
|
i += 1
|
|
|
|
# Non-UTF-8 bytes inside and outside string positions.
|
|
for raw in [b"\xff\xfe\xfd", b'{"a": "\xff"}', b"[1, 2\xff]"]:
|
|
write_seed("payload_codec", i, raw)
|
|
i += 1
|
|
|
|
# Trailing garbage after a valid document.
|
|
for raw in [
|
|
b"{} trailing",
|
|
b"[1]x",
|
|
b"1 2",
|
|
b"null null",
|
|
b'"first""second"',
|
|
]:
|
|
write_seed("payload_codec", i, raw)
|
|
i += 1
|
|
|
|
# Duplicate keys (canonicalization is serde_json's).
|
|
for raw in [b'{"a": 1, "a": 2}', b'{"o": {"k": 1, "k": 2}, "o": 3}']:
|
|
write_seed("payload_codec", i, raw)
|
|
i += 1
|
|
|
|
# The Null-decode shape: an empty byte store must never decode to
|
|
# a silent Null (ADR-023 §1).
|
|
for raw in [b"", b" ", b"null", b"nullx"]:
|
|
write_seed("payload_codec", i, raw)
|
|
i += 1
|
|
|
|
# Other loud-fail shapes (typed `Codec` partition coverage).
|
|
for raw in [
|
|
b"NaN",
|
|
b"Infinity",
|
|
b"+1",
|
|
b"01",
|
|
b".5",
|
|
b"1.",
|
|
b"--1",
|
|
b"{,}",
|
|
b"[1,]",
|
|
b'{"a"}',
|
|
b'{"missing": }',
|
|
b"\x80\x81\x82",
|
|
]:
|
|
write_seed("payload_codec", i, raw)
|
|
i += 1
|
|
|
|
|
|
def every_parse_seeds():
|
|
i = 0
|
|
|
|
# Every grammar-valid unit form (s|m|h|d), single and multi-digit.
|
|
for spec in [
|
|
"@every 1s",
|
|
"@every 5s",
|
|
"@every 9s",
|
|
"@every 17s",
|
|
"@every 42m",
|
|
"@every 1m",
|
|
"@every 90m",
|
|
"@every 2h",
|
|
"@every 24h",
|
|
"@every 1d",
|
|
"@every 7d",
|
|
"@every 365d",
|
|
]:
|
|
write_seed("every_parse", i, spec.encode())
|
|
i += 1
|
|
|
|
# Leading-zero forms (parsed through, value = the digits).
|
|
for spec in ["@every 007s", "@every 007m", "@every 000000042h", "@every 007d"]:
|
|
write_seed("every_parse", i, spec.encode())
|
|
i += 1
|
|
|
|
# The n <= 0 arms: positive zero (with and without leading zeros)
|
|
# and the negative shapes (digits == 0 inside the grammar).
|
|
for spec in ["@every 0s", "@every 00m", "@every 0000d", "@every -5s", "@every -0s"]:
|
|
write_seed("every_parse", i, spec.encode())
|
|
i += 1
|
|
|
|
# The i64::MAX neighborhood: giant-n accept at scale-1, digit
|
|
# overflow past i64, and unit-multiplier overflow above it.
|
|
for spec in [
|
|
"@every 9223372036854775807s",
|
|
"@every 9223372036854775806s",
|
|
"@every 9223372036854775808s",
|
|
"@every 9223372036854775807m",
|
|
"@every 9223372036854775807h",
|
|
"@every 9223372036854775807d",
|
|
"@every 18446744073709551616s",
|
|
"@every 18446744073709551616000000m",
|
|
]:
|
|
write_seed("every_parse", i, spec.encode())
|
|
i += 1
|
|
|
|
# The digit-overflow boundary at exactly i64::MAX/86400 (and ±1).
|
|
for spec in [
|
|
"@every 106751991167300d",
|
|
"@every 106751991167301d",
|
|
"@every 106751991167299d",
|
|
]:
|
|
write_seed("every_parse", i, spec.encode())
|
|
i += 1
|
|
|
|
# d-unit multiplier blowups.
|
|
for spec in ["@every 2147483647d", "@every 4294967295d", "@every 100000000000d"]:
|
|
write_seed("every_parse", i, spec.encode())
|
|
i += 1
|
|
|
|
# Whitespace / garbage / truncation shapes.
|
|
for raw in [
|
|
b"@every",
|
|
b"@every ",
|
|
b"@every 5s",
|
|
b"@every 5s ",
|
|
b"@every\t5s",
|
|
b"@every 5 s",
|
|
b"@every 5sx",
|
|
b"@every 5S",
|
|
b"@every +5s",
|
|
b"@every .5s",
|
|
b"@every 3.5s",
|
|
b"@every 5.0s",
|
|
b"@every 5\ts",
|
|
b"@every \xc3\xa9s",
|
|
b"\xff @every \xff 5s \xfe",
|
|
b"@every 5s\xff",
|
|
]:
|
|
write_seed("every_parse", i, raw)
|
|
i += 1
|
|
|
|
# Non-grammar prefixes and shapes (cron / @once adjacency).
|
|
for raw in [
|
|
b"@once",
|
|
b"@hourly",
|
|
b"@daily",
|
|
b"@weekly",
|
|
b"*/5 * * * *",
|
|
b"0 0 * * *",
|
|
b"every 5s",
|
|
b"5s",
|
|
b"@every 5",
|
|
b"@every s",
|
|
]:
|
|
write_seed("every_parse", i, raw)
|
|
i += 1
|
|
|
|
# Byte payloads ≥ 24 bytes: the boundary-arithmetic leg folds
|
|
# adversarial (next_fire_at, now, interval) triples straight out of
|
|
# the bytes — the i64::MAX neighborhood, negative shapes, d-blowups.
|
|
for raw in [
|
|
b"@every 9223372036854775807s skip-forward-triple payload XXXXX",
|
|
b"\x00" * 24,
|
|
b"\xff" * 24,
|
|
bytes([
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, # next = i64::MAX
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, # now = 2^62
|
|
0x80, 0xC8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, # interval = 250000
|
|
]),
|
|
bytes([
|
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, # next = i64::MIN + 1
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, # now = i64::MAX
|
|
0x80, 0x92, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, # interval = negative
|
|
]),
|
|
]:
|
|
write_seed("every_parse", i, raw)
|
|
i += 1
|
|
|
|
|
|
def name_validation_seeds():
|
|
i = 0
|
|
|
|
# Plain-content names at the probed lengths: the empty↔one-char
|
|
# boundary, the pg-identifier (±1) and target-max_len (±1/±2)
|
|
# neighborhoods. No length cap is pinned; the seeds pin
|
|
# length-independence of the verdict class.
|
|
for length in [1, 2, 4, 62, 63, 64, 65, 4095, 4096]:
|
|
write_seed("name_validation", i, b"a" * length)
|
|
i += 1
|
|
|
|
# Empty and whitespace-only shapes (spaces, tab, newline, mixed,
|
|
# unicode whitespace) — InvalidName on both kinds.
|
|
for raw in [b"", b" ", b" ", b"\t", b"\n", b" \t\n ", b"\xc2\xa0", b"\xe2\x80\x83"]:
|
|
write_seed("name_validation", i, raw)
|
|
i += 1
|
|
|
|
# The reserved prefix: the boundary form (alone), suffixed, at a
|
|
# boundary length, and the one v1-reserved string.
|
|
for raw in [
|
|
b"__alkstore_",
|
|
b"__alkstore_x",
|
|
b"__alkstore_" + b"a" * 63,
|
|
b"__alkstore_listener_reconnected__",
|
|
]:
|
|
write_seed("name_validation", i, raw)
|
|
i += 1
|
|
|
|
# Prefix near-misses: casing deviations, the missing trailing
|
|
# underscore, embedded and whitespace-led forms — the pinned rule
|
|
# is byte-exact and leading-only, so these must accept.
|
|
for raw in [
|
|
b"__ALKSTORE_x",
|
|
b"__Alkstore_x",
|
|
b"__alkStore_x",
|
|
b"__alkstore",
|
|
b"x__alkstore_y",
|
|
b" __alkstore_x",
|
|
b"__alkstore_ ",
|
|
]:
|
|
write_seed("name_validation", i, raw)
|
|
i += 1
|
|
|
|
# Whitespace-only and reserved-prefixed at the pg-identifier
|
|
# neighborhood lengths (±1, ±2).
|
|
for length in [62, 63, 64, 65]:
|
|
write_seed("name_validation", i, b" " * length)
|
|
i += 1
|
|
write_seed("name_validation", i, b"__alkstore_" + b"a" * (length - len(b"__alkstore_")))
|
|
i += 1
|
|
|
|
# Unicode and control-char shapes, including invalid UTF-8 (lossy
|
|
# decode) and embedded NUL.
|
|
for raw in [
|
|
"é-😀-名前".encode(),
|
|
"\u00e9\u0301".encode(),
|
|
b"\x00\x01\x02",
|
|
b"a\x00b",
|
|
b"tab\tname",
|
|
b"\x7f",
|
|
b"\xc3\x28",
|
|
b"\xff\xfe",
|
|
]:
|
|
write_seed("name_validation", i, raw)
|
|
i += 1
|
|
|
|
|
|
def main():
|
|
payload_codec_seeds()
|
|
every_parse_seeds()
|
|
name_validation_seeds()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |