wave 7: fuzz sidecar scaffold (fuzz/ workspace, shared crate, runner, dict, seeds skeleton)
This commit is contained in:
1 parent
67d1d7e34f
commit
62ff18d1ed
14 files changed
+614
-15
No files matched your search
@@ -7,6 +7,7 @@ members = [
|
||||
"alkstore-contract-suite",
|
||||
]
|
||||
resolver = "3"
|
||||
exclude = ["fuzz"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
target
|
||||
corpus/*
|
||||
artifacts
|
||||
coverage
|
||||
Generated
+238
@@ -0,0 +1,238 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "alkstore"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alkstore-fuzz"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"alkstore",
|
||||
"alkstore-fuzz-shared",
|
||||
"libfuzzer-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alkstore-fuzz-shared"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"alkstore",
|
||||
"arbitrary",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bc62ac97cc33321f50863d514c3bc38a453947a8f9e781137e47c7401020aed"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50a649af8a827553c29fb0cb4bd4a6f1a0dd695bd3232b9bc98bd9c8a3ffbb8b"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b034bd7d5f032402a2479444dcc6f74e36a03f31854d41680fb240ef682a1ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.190"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1741ab7a6cc54a03a89b5d563ed60075c277d9e3cfa73ad0c1f23f23974703c6"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d62a2e0561533f2ca2561d0cf27fd9fedb640a1bf2616ff5d5c80d99017faadc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
@@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "alkstore-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
alkstore-fuzz-shared = { path = "shared" }
|
||||
|
||||
[dependencies.alkstore]
|
||||
path = "../alkstore"
|
||||
|
||||
[[bin]]
|
||||
name = "placeholder"
|
||||
path = "fuzz_targets/placeholder.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[workspace]
|
||||
@@ -0,0 +1,57 @@
|
||||
# alkstore fuzzing
|
||||
|
||||
libFuzzer targets for alkstore's parse/codec surfaces (see
|
||||
`docs/research/fuzzing.md` for the full rationale and campaign plan).
|
||||
|
||||
## Layout
|
||||
|
||||
- `fuzz_targets/` — one binary per target; thin `fuzz_target!` wrappers
|
||||
(land with the wave-7 target tasks).
|
||||
- `shared/` — the invariant logic, as a plain library so normal
|
||||
`cargo test` (stable toolchain) can replay the committed corpora
|
||||
through the same invariants (`fuzz/shared/src/*.rs` `#[cfg(test)]`
|
||||
modules; quinn's CI pattern). The fuzz binaries are nightly-only;
|
||||
the shared crate is stable-clean. It consumes alkstore's public API
|
||||
only and must never become a dependency of any workspace crate.
|
||||
- `corpus/<target>/` — committed hand-made seeds (regenerate with
|
||||
`python3 fuzz/gen_fuzz_seeds.py` from the repo root). Grown corpora
|
||||
and artifacts are gitignored; each target task adds its `!`-negation
|
||||
line when its corpus lands.
|
||||
- `run-detached.sh` — mandatory runner for agent sessions: wraps
|
||||
`cargo fuzz run` in `setsid` + `nohup` + log redirection so an OOM
|
||||
in a target can never take down the agent host (research doc §5).
|
||||
- `json.dict` — JSON token dictionary for the structured targets.
|
||||
- `gen_fuzz_seeds.py` — deterministic seed generator (quiche pattern).
|
||||
|
||||
## Targets
|
||||
|
||||
| Target | Drives | Invariants |
|
||||
|---|---|---|
|
||||
| `payload_codec` | `encode_payload` / `payload_as::<T>` (`alkstore/src/payload.rs`) | filled by its target task |
|
||||
| `every_parse` | the three `parse_every_interval` implementations (differential) | filled by its target task |
|
||||
| `name_validation` | `validate_name` / `validate_shared_name` / `validate_local_name` (`alkstore/src/validation.rs`) | filled by its target task |
|
||||
|
||||
## Commands
|
||||
|
||||
`cargo fuzz build` and `cargo fuzz run` must be executed with `fuzz/`
|
||||
(or deeper) as the working directory so rustup selects the pinned
|
||||
nightly toolchain — the detached runner handles that itself.
|
||||
|
||||
```sh
|
||||
# build (nightly, pinned by rust-toolchain.toml inside fuzz/; run from fuzz/)
|
||||
cargo fuzz build
|
||||
|
||||
# agent sessions: detached campaign (never foreground; CWD-independent)
|
||||
FUZZ_RUNTIME_SECS=600 fuzz/run-detached.sh <target> [extra libfuzzer args...]
|
||||
|
||||
# corpus replay through the invariants (stable toolchain, no nightly)
|
||||
cargo test --manifest-path fuzz/shared/Cargo.toml
|
||||
|
||||
# coverage
|
||||
cargo fuzz coverage <target>
|
||||
```
|
||||
|
||||
The fuzz workspace is excluded from the main workspace (`exclude` in
|
||||
the root `Cargo.toml`); it pins its own nightly toolchain via
|
||||
`rust-toolchain.toml` and does not affect the root workspace's stable
|
||||
toolchain or MSRV.
|
||||
@@ -0,0 +1,11 @@
|
||||
//! Placeholder target: the scaffold carries no real targets yet. The
|
||||
//! wave-1 target tasks (payload_codec, every_parse, name_validation)
|
||||
//! replace this with their fuzz_targets entries; it exists only so the
|
||||
//! bins-only sidecar manifest parses and `cargo fuzz build` exercises
|
||||
//! the nightly + ASAN toolchain check from wave 7 onward.
|
||||
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
fuzz_target!(|_data: &[u8]| {});
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/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 main():
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,23 @@
|
||||
# JSON tokens for the envelope framing targets (ADR-014 wire format).
|
||||
"call.requested"
|
||||
"call.responded"
|
||||
"call.completed"
|
||||
"call.aborted"
|
||||
"call.error"
|
||||
"call.published"
|
||||
"type"
|
||||
"id"
|
||||
"payload"
|
||||
"operationId"
|
||||
"input"
|
||||
"output"
|
||||
"code"
|
||||
"message"
|
||||
"retryable"
|
||||
"details"
|
||||
"NOT_FOUND"
|
||||
"INVALID_INPUT"
|
||||
"INTERNAL"
|
||||
"TIMEOUT"
|
||||
"FORBIDDEN"
|
||||
"CONNECTION_CLOSED"
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
# Detached fuzzing runner for agent sessions: the fuzz campaign never
|
||||
# runs as a foreground child of the session (OOM in a target must not
|
||||
# take down the agent host), and survives the session ending.
|
||||
#
|
||||
# Usage: fuzz/run-detached.sh <target> [extra libfuzzer args...]
|
||||
# (works from the repo root or from fuzz/; CWD-independent)
|
||||
# FUZZ_RUNTIME_SECS overrides the per-campaign budget (default 600 s).
|
||||
#
|
||||
# Poll instead of waiting:
|
||||
# tail -n 50 fuzz/artifacts/<target>-*.log
|
||||
# ls fuzz/artifacts/<target>/ (crash-* / oom-* / timeout-* files)
|
||||
# pgrep -f "cargo fuzz run <target>"
|
||||
set -euo pipefail
|
||||
target="${1:?usage: run-detached.sh <target> [extra libfuzzer args...]}"
|
||||
shift
|
||||
|
||||
root="$(git rev-parse --show-toplevel)"
|
||||
fuzz_dir="$root/fuzz"
|
||||
mkdir -p "$fuzz_dir/artifacts"
|
||||
runtime="${FUZZ_RUNTIME_SECS:-600}"
|
||||
log="$fuzz_dir/artifacts/${target}-$(date -u +%Y%m%d-%H%M%S).log"
|
||||
|
||||
cd "$fuzz_dir"
|
||||
setsid nohup cargo fuzz run "$target" -- \
|
||||
-fork=1 -rss_limit_mb=2048 -malloc_limit_mb=2048 -timeout=25 \
|
||||
-max_total_time="$runtime" "$@" \
|
||||
>"$log" 2>&1 < /dev/null &
|
||||
echo "pid=$! log=$log"
|
||||
@@ -0,0 +1,3 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
components = ["llvm-tools-preview"]
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "alkstore-fuzz-shared"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
alkstore = { path = "../../alkstore" }
|
||||
serde_json = "1"
|
||||
|
||||
[dependencies.arbitrary]
|
||||
version = "1"
|
||||
features = ["derive"]
|
||||
@@ -0,0 +1,108 @@
|
||||
//! `Arbitrary` support for `serde_json::Value` — a bounded-depth
|
||||
//! recursive generator shared by the semantic targets. Depth and size
|
||||
//! bounds keep exec/s high (serde_json's own 128-depth recursion limit
|
||||
//! is the decode-side guard; this generator explores structure).
|
||||
|
||||
use arbitrary::{Arbitrary, Unstructured};
|
||||
use serde_json::{Map, Number, Value};
|
||||
|
||||
#[derive(Debug, arbitrary::Arbitrary, Clone)]
|
||||
#[allow(dead_code)]
|
||||
enum JsonShape {
|
||||
Null,
|
||||
Bool(bool),
|
||||
Number {
|
||||
mantissa: u32,
|
||||
negative: bool,
|
||||
},
|
||||
String {
|
||||
len: u8,
|
||||
byte: u8,
|
||||
},
|
||||
Array {
|
||||
len: u8,
|
||||
items: Vec<JsonShape>,
|
||||
},
|
||||
Object {
|
||||
len: u8,
|
||||
keys: Vec<(u8, u8)>,
|
||||
values: Vec<JsonShape>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Generate a `Value` with bounded depth: arrays/objects nest at most
|
||||
/// 6 levels, each holding at most 6 items. The `u8`-bounded shapes
|
||||
/// keep the generator cheap; the semantic target's value space is
|
||||
/// structure, not size.
|
||||
impl<'a> Arbitrary<'a> for JsonValue {
|
||||
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
|
||||
let shape = JsonShape::arbitrary(u)?;
|
||||
Ok(JsonValue(shape_to_value(shape, 6)))
|
||||
}
|
||||
}
|
||||
|
||||
fn shape_to_value(shape: JsonShape, depth: u32) -> Value {
|
||||
if depth == 0 {
|
||||
return Value::Null;
|
||||
}
|
||||
match shape {
|
||||
JsonShape::Null => Value::Null,
|
||||
JsonShape::Bool(b) => Value::Bool(b),
|
||||
JsonShape::Number { mantissa, negative } => {
|
||||
let n = i64::from(mantissa % 1_000_000);
|
||||
Number::from(if negative { -n } else { n }).into()
|
||||
}
|
||||
JsonShape::String { len, byte } => {
|
||||
// Repeat a printable-ish char; embedded control bytes and
|
||||
// invalid UTF-8 come from the fuzzer's byte mutations of
|
||||
// the raw input in the payload targets, not here.
|
||||
let n = (len % 64) as usize;
|
||||
std::iter::repeat_n((0x41u8 + (byte % 26)) as char, n)
|
||||
.collect::<String>()
|
||||
.into()
|
||||
}
|
||||
JsonShape::Array { len, items } => {
|
||||
let items: Vec<Value> = items
|
||||
.into_iter()
|
||||
.take(6)
|
||||
.map(|s| shape_to_value(s, depth - 1))
|
||||
.collect();
|
||||
let _ = len;
|
||||
Value::Array(items)
|
||||
}
|
||||
JsonShape::Object { keys, values, .. } => {
|
||||
let mut map = Map::new();
|
||||
for (i, (k_len, k_byte)) in keys.into_iter().take(6).enumerate() {
|
||||
let key: String = std::iter::repeat_n(
|
||||
(0x61u8 + (k_byte % 26)) as char,
|
||||
(k_len % 16) as usize + 1,
|
||||
)
|
||||
.collect();
|
||||
let value = values
|
||||
.get(i)
|
||||
.map(|s| shape_to_value(s.clone(), depth - 1))
|
||||
.unwrap_or(Value::Null);
|
||||
map.insert(key, value);
|
||||
}
|
||||
Value::Object(map)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Newtype so `impl Arbitrary` applies (serde_json re-exports `Value`
|
||||
/// as `serde_json::Value`; a foreign-type impl is not allowed).
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct JsonValue(pub Value);
|
||||
|
||||
impl std::ops::Deref for JsonValue {
|
||||
type Target = Value;
|
||||
fn deref(&self) -> &Value {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsonValue> for Value {
|
||||
fn from(value: JsonValue) -> Value {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
//! Invariant logic shared by alkstore's fuzz targets. Kept out of the
|
||||
//! fuzz-target binaries so the corpus replay unit tests can exercise the
|
||||
//! same invariant checks against every committed corpus entry (the quinn
|
||||
//! CI pattern) without a nightly toolchain.
|
||||
|
||||
pub mod arbitrary_value;
|
||||
+65
-15
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: fuzz-scaffold
|
||||
name: Fuzz sidecar scaffold — `fuzz/` workspace, runner, dict, seed generator, shared-crate skeleton
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on: []
|
||||
scope: moderate
|
||||
risk: low
|
||||
@@ -67,26 +67,31 @@ Toolchain prerequisites verified present in the dev environment
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] `fuzz/` exists with the §2 layout: sidecar `Cargo.toml` (own
|
||||
- [x] `fuzz/` exists with the §2 layout: sidecar `Cargo.toml` (own
|
||||
`[workspace]`, `publish = false`), `rust-toolchain.toml`
|
||||
(nightly + llvm-tools-preview), `shared/` crate, `run-detached.sh`
|
||||
(executable), `json.dict`, `gen_fuzz_seeds.py`, `README.md`,
|
||||
`fuzz/.gitignore`
|
||||
- [ ] `fuzz/shared` compiles and tests green on **stable** via
|
||||
- [x] `fuzz/shared` compiles and tests green on **stable** via
|
||||
`cargo test --manifest-path fuzz/shared/Cargo.toml` (the gate
|
||||
command works from the wave's first task onward)
|
||||
- [ ] `cargo fuzz build` succeeds from `fuzz/` (nightly selected by the
|
||||
pin; zero targets yet is acceptable if cargo-fuzz tolerates it —
|
||||
otherwise a placeholder target documents the check; record which)
|
||||
- [ ] Root workspace unaffected: `cargo build` / `cargo test` /
|
||||
- [x] `cargo fuzz build` succeeds from `fuzz/` (nightly selected by the
|
||||
pin; **cargo-fuzz does NOT tolerate zero targets** here — a
|
||||
bins-only package with no `[[bin]]` fails manifest parse, which
|
||||
also blocks the shared gate's workspace discovery; a placeholder
|
||||
target `fuzz_targets/placeholder.rs` documents the check and is
|
||||
replaced by the wave-1 target tasks — recorded, as this AC asked)
|
||||
- [x] Root workspace unaffected: `cargo build` / `cargo test` /
|
||||
`clippy -D warnings` / `fmt --check` green from the root, and the
|
||||
root toolchain is still stable (the pin did not leak upward)
|
||||
- [ ] `exclude = ["fuzz"]` present on the root `[workspace]` table
|
||||
- [ ] `run-detached.sh` is byte-identical to alksocks' modulo the
|
||||
usage-line naming (no behavior drift)
|
||||
- [ ] `shared` is not referenced by any workspace crate manifest
|
||||
(wasm posture intact: `cargo check --target
|
||||
wasm32-unknown-unknown -p alkstore-mem` still clean)
|
||||
- [x] `exclude = ["fuzz"]` present on the root `[workspace]` table
|
||||
- [x] `run-detached.sh` is byte-identical to alksocks' modulo the
|
||||
usage-line naming (`cmp` clean — the usage line is already
|
||||
repo-agnostic, so no diff at all)
|
||||
- [x] `shared` is not referenced by any workspace crate manifest
|
||||
(only the root's `exclude = ["fuzz"]` mentions fuzz at all;
|
||||
`cargo check --target wasm32-unknown-unknown -p alkstore-mem`
|
||||
clean)
|
||||
|
||||
## References
|
||||
|
||||
@@ -99,8 +104,53 @@ Toolchain prerequisites verified present in the dev environment
|
||||
|
||||
## Notes
|
||||
|
||||
> To be filled by implementation agent
|
||||
- **Zero targets are NOT tolerated by cargo-fuzz / cargo here** — the
|
||||
AC's "if tolerated" branch failed: a bins-only package with no
|
||||
`[[bin]]` entries cannot manifest-parse ("no targets specified"),
|
||||
and that parse failure also breaks the shared gate's workspace
|
||||
discovery (`cargo test --manifest-path fuzz/shared/Cargo.toml`
|
||||
resolves the workspace root at `fuzz/`). Placeholder route taken
|
||||
(as the AC's otherwise-branch prescribed): `fuzz_targets/placeholder.rs`
|
||||
— a minimal `fuzz_target!` stub with a doc comment saying the wave-1
|
||||
target tasks replace it — plus its `[[bin]]` entry in `fuzz/Cargo.toml`.
|
||||
- **Repo-layout substitution beyond naming:** alksocks has its package
|
||||
at the repo root (`alksocks = { path = "../.." }`); alkstore is the
|
||||
ADR-001 workspace split, so the core crate sits in the `alkstore/`
|
||||
subdir — shared's dep is `alkstore = { path = "../../alkstore" }`
|
||||
and the fuzz package's is `path = "../alkstore"`.
|
||||
- **`json.dict`** is byte-identical across alkcall / alktunnels /
|
||||
alksocks (md5-verified), so it was copied verbatim (alkcall-envelope
|
||||
tokens). Whether any alkstore-specific tokens should be added is a
|
||||
target-task concern, not scaffold scope.
|
||||
- `fuzz/Cargo.lock` is committed (alksocks pattern); generated
|
||||
fresh — alksocks' lock is not portable across dep sets.
|
||||
- `arbitrary_value.rs` copied verbatim except one intra-comment stale
|
||||
reference: alksocks' `envelope_frame` → "the payload targets".
|
||||
- `fuzz/.gitignore` has no `!corpus/<target>` negation lines yet —
|
||||
each target task adds its own when its corpus lands (task spec).
|
||||
- `gen_fuzz_seeds.py` is the plumbing-only skeleton (`seed_name` /
|
||||
`write_seed`, empty `main()` registry); runs clean as a no-op.
|
||||
|
||||
## Summary
|
||||
|
||||
> To be filled on completion
|
||||
Landed the wave-7 scaffold: `fuzz/` sidecar workspace
|
||||
(`alkstore-fuzz`, `publish = false`, own `[workspace]`, libfuzzer-sys +
|
||||
path-dep shared crate) with `rust-toolchain.toml` pinning
|
||||
nightly + llvm-tools-preview inside `fuzz/` only; stable-toolchain
|
||||
`fuzz/shared` (`alkstore-fuzz-shared`) with the quinn-CI-pattern crate
|
||||
doc and the alksocks `arbitrary_value.rs` `JsonValue` bounded-depth
|
||||
helper; `run-detached.sh` (byte-identical to alksocks', executable);
|
||||
`json.dict`; `gen_fuzz_seeds.py` skeleton; `.gitignore` (no corpus
|
||||
negations yet); `README.md` skeleton; `exclude = ["fuzz"]` on the root
|
||||
workspace; and the placeholder fuzz target so the toolchain check and
|
||||
the shared gate work before the real targets land.
|
||||
|
||||
Verified: `cargo test --manifest-path fuzz/shared/Cargo.toml` green on
|
||||
stable from the root; `cargo fuzz build` green from `fuzz/` (nightly
|
||||
pin selected; placeholder + shared + alkstore compile under ASAN);
|
||||
shared clippy `-D warnings` + `fmt --check` green; root `cargo build` /
|
||||
`cargo test` (all suites pass) / clippy `-D warnings` / `fmt --check`
|
||||
green with the stable toolchain still active at the root (rustc 1.99.0
|
||||
stable; no upward pin leak); `cargo check -p alkstore-mem --target
|
||||
wasm32-unknown-unknown` clean; no workspace manifest references
|
||||
`alkstore-fuzz-shared` (only the root `exclude` mentions `fuzz`).
|
||||
Reference in new issue
Block a user