Skip to content

Commit 04efbb8

Browse files
committed
refactor: migrate fuzzing container to ContFuzzer v2 interface
Overhaul the fuzzing container to comply with the ContFuzzer v2 platform contract. Key changes: - Entrypoint rewritten to honour FUZZ_MODE (fuzz | reproduce | coverage), FUZZ_TARGET, FUZZ_DURATION, FUZZ_WORKERS, FUZZ_MEMORY, FUZZ_CORPUS_DIR, FUZZ_CRASH_FILE, and FUZZ_OUTPUT_DIR environment variables. - Coverage mode exports LCOV and llvm-cov JSON directly (no Python shim). - Corpus merge hardened with single-thread defaults, logging, and restore. - Fuzzer manifest generated from CMake targets instead of a hardcoded list. - AVM-specific fuzzers scheduled only from the fuzzing-avm preset. - Dockerfiles updated for non-root execution (CRS moved to /opt/bb-crs, source tree world-readable, home dir traversable by UID 65534). - CI workflows use standard OCI labels and contfuzzer ORAS artifact type.
1 parent 09869bd commit 04efbb8

File tree

11 files changed

+631
-526
lines changed

11 files changed

+631
-526
lines changed

.github/workflows/fuzzing-docker-avm-build-private.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ jobs:
5151
secrets: |
5252
github_token=${{ secrets.GITHUB_TOKEN }}
5353
labels: |
54-
com.aztec.source-repo=${{ github.repository }}
55-
com.aztec.source-branch=${{ github.ref_name }}
56-
com.aztec.commit=${{ github.sha }}
54+
org.opencontainers.image.source=https://github.com/${{ github.repository }}
55+
org.opencontainers.image.revision=${{ github.sha }}
56+
org.opencontainers.image.revision.branch=${{ github.ref_name }}
5757
com.aztec.visibility=private
5858
5959
- name: Install cosign

.github/workflows/fuzzing-docker-avm-build.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ jobs:
4545
build-args: |
4646
COMMIT=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.commit || github.sha }}
4747
labels: |
48-
com.aztec.source-repo=${{ github.repository }}
49-
com.aztec.source-branch=${{ github.ref_name }}
50-
com.aztec.commit=${{ github.sha }}
48+
org.opencontainers.image.source=https://github.com/${{ github.repository }}
49+
org.opencontainers.image.revision=${{ github.sha }}
50+
org.opencontainers.image.revision.branch=${{ github.ref_name }}
5151
com.aztec.visibility=public
5252
5353
- name: Install cosign

.github/workflows/fuzzing-docker-build-private.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ jobs:
5151
secrets: |
5252
github_token=${{ secrets.GITHUB_TOKEN }}
5353
labels: |
54-
com.aztec.source-repo=${{ github.repository }}
55-
com.aztec.source-branch=${{ github.ref_name }}
56-
com.aztec.commit=${{ github.sha }}
54+
org.opencontainers.image.source=https://github.com/${{ github.repository }}
55+
org.opencontainers.image.revision=${{ github.sha }}
56+
org.opencontainers.image.revision.branch=${{ github.ref_name }}
5757
com.aztec.visibility=private
5858
5959
- name: Install oras
@@ -65,7 +65,7 @@ jobs:
6565
docker cp manifest-extract:/home/fuzzer/aztec-packages/barretenberg/cpp/fuzzer_manifest.json ./fuzzer_manifest.json
6666
docker rm manifest-extract
6767
oras attach ghcr.io/aztecprotocol/fuzzing-container-private@${{ steps.build.outputs.digest }} \
68-
--artifact-type application/vnd.aztec.fuzzer-manifest+json \
68+
--artifact-type application/vnd.contfuzzer.manifest+json \
6969
fuzzer_manifest.json
7070
7171
- name: Install cosign

.github/workflows/fuzzing-docker-build.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ jobs:
4545
build-args: |
4646
COMMIT=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.commit || github.sha }}
4747
labels: |
48-
com.aztec.source-repo=${{ github.repository }}
49-
com.aztec.source-branch=${{ github.ref_name }}
50-
com.aztec.commit=${{ github.sha }}
48+
org.opencontainers.image.source=https://github.com/${{ github.repository }}
49+
org.opencontainers.image.revision=${{ github.sha }}
50+
org.opencontainers.image.revision.branch=${{ github.ref_name }}
5151
com.aztec.visibility=public
5252
5353
- name: Install oras
@@ -59,7 +59,7 @@ jobs:
5959
docker cp manifest-extract:/home/fuzzer/aztec-packages/barretenberg/cpp/fuzzer_manifest.json ./fuzzer_manifest.json
6060
docker rm manifest-extract
6161
oras attach ghcr.io/aztecprotocol/fuzzing-container@${{ steps.build.outputs.digest }} \
62-
--artifact-type application/vnd.aztec.fuzzer-manifest+json \
62+
--artifact-type application/vnd.contfuzzer.manifest+json \
6363
fuzzer_manifest.json
6464
6565
- name: Install cosign
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
#!/usr/bin/env python3
2+
"""Flatten variant binaries into /targets/ and generate a unified v2 manifest.
3+
4+
Reads per-preset manifests (produced by generate_fuzzer_manifest.py via CMake)
5+
to discover binaries, suffixes, and source paths. No variant knowledge is
6+
hardcoded here — it all comes from the manifests.
7+
8+
Usage (from barretenberg/cpp/ during Docker build):
9+
python3 scripts/flatten_and_manifest.py /targets /tmp/fuzzer_manifest.json \
10+
bin-fuzzing/fuzzer_manifest.json \
11+
bin-fuzzing-noasm/fuzzer_manifest.json \
12+
bin-fuzzing-avm/fuzzer_manifest.json \
13+
--copy-only bin-fuzzing-asan/fuzzer_manifest.json \
14+
--copy-only build-fuzzing-cov/bin/fuzzer_manifest.json
15+
16+
Positional manifests: binaries are copied AND added to the unified manifest.
17+
--copy-only manifests: binaries are copied but NOT added (companion builds
18+
like asan/coverage that the entrypoint discovers by suffix convention).
19+
"""
20+
21+
import argparse
22+
import json
23+
import os
24+
import shutil
25+
import sys
26+
27+
28+
def _load_manifest(path: str) -> dict:
29+
with open(path) as f:
30+
return json.load(f)
31+
32+
33+
def _copy_executables(bin_dir: str, dest_dir: str, suffix: str) -> list[str]:
34+
"""Copy executable files from bin_dir to dest_dir/<name><suffix>. Returns names."""
35+
copied = []
36+
if not os.path.isdir(bin_dir):
37+
print(f"Warning: {bin_dir} not found, skipping", file=sys.stderr)
38+
return copied
39+
for name in sorted(os.listdir(bin_dir)):
40+
src = os.path.join(bin_dir, name)
41+
if os.path.isfile(src) and os.access(src, os.X_OK):
42+
shutil.copy2(src, os.path.join(dest_dir, f"{name}{suffix}"))
43+
copied.append(name)
44+
return copied
45+
46+
47+
def main():
48+
parser = argparse.ArgumentParser(description="Flatten binaries and generate v2 manifest")
49+
parser.add_argument("targets_dir", help="Destination for flattened binaries")
50+
parser.add_argument("manifest_out", help="Output path for unified v2 manifest")
51+
parser.add_argument("manifests", nargs="*", help="Per-preset manifests (schedulable)")
52+
parser.add_argument("--copy-only", action="append", default=[], dest="companions",
53+
help="Per-preset manifests for companion builds (copy only, not scheduled)")
54+
parser.add_argument("--cpus", type=int, default=4, help="Default CPUs per target")
55+
parser.add_argument("--memory-gb", type=int, default=8, help="Default memory GB per target")
56+
args = parser.parse_args()
57+
58+
os.makedirs(args.targets_dir, exist_ok=True)
59+
60+
# Collect coverage fuzzer names (from whichever manifest has preset fuzzing-coverage)
61+
cov_names: set[str] = set()
62+
for path in args.manifests + args.companions:
63+
data = _load_manifest(path)
64+
if data.get("preset") == "fuzzing-coverage":
65+
cov_names = {fz["name"] for fz in data.get("fuzzers", [])}
66+
67+
targets = []
68+
total_copied = 0
69+
70+
# The fuzzing-avm preset enables FUZZING_AVM in cmake, which adds the
71+
# AVM-specific fuzzers (avm_fuzzer_*, harness_*). However cmake also
72+
# rebuilds every base fuzzer target (stdlib_*, ecc_*, translator_*, etc.)
73+
# as a side effect. Those collateral rebuilds are identical in behaviour
74+
# to the base preset and should NOT be scheduled as separate jobs.
75+
#
76+
# We collect fuzzer names from the first (base) manifest, then for the
77+
# AVM preset we only schedule names that are new (the actual AVM fuzzers).
78+
# All binaries are still copied to /targets/ — the entrypoint may need them.
79+
#
80+
# TODO: if cmake learns to build only AVM-specific targets in the
81+
# fuzzing-avm preset, this filter can be removed.
82+
base_names: set[str] = set()
83+
84+
for i, path in enumerate(args.manifests):
85+
data = _load_manifest(path)
86+
suffix = data.get("suffix", "")
87+
preset = data.get("preset", "")
88+
label = preset.removeprefix("fuzzing-") or "asm"
89+
bin_dir = os.path.dirname(path)
90+
91+
copied = _copy_executables(bin_dir, args.targets_dir, suffix)
92+
total_copied += len(copied)
93+
94+
fuzzers_meta = {fz["name"]: fz["source_path"] for fz in data.get("fuzzers", [])}
95+
96+
if i == 0:
97+
base_names = set(copied)
98+
99+
# See docstring above — only the AVM preset needs dedup.
100+
dedup = preset == "fuzzing-avm" and bool(base_names)
101+
102+
scheduled = 0
103+
for name in copied:
104+
if dedup and name in base_names:
105+
continue
106+
scheduled += 1
107+
source_path = fuzzers_meta.get(name, "")
108+
modes = ["fuzz", "minimize", "reproduce", "regress"]
109+
if name in cov_names:
110+
modes.append("coverage")
111+
targets.append({
112+
"name": f"{name}{suffix}",
113+
"display_name": f"{name} ({label})",
114+
"language": "c++",
115+
"fuzzer_engine": "libfuzzer",
116+
"source_path": f"barretenberg/cpp/src/barretenberg/{source_path}" if source_path else "",
117+
"modes": modes,
118+
"resources": {"cpus": args.cpus, "memory_gb": args.memory_gb},
119+
})
120+
if scheduled < len(copied):
121+
print(f" {preset}: {scheduled} scheduled, {len(copied) - scheduled} skipped (in base preset)")
122+
123+
# Process companion presets: copy binaries only
124+
for path in args.companions:
125+
data = _load_manifest(path)
126+
suffix = data.get("suffix", "")
127+
bin_dir = os.path.dirname(path)
128+
copied = _copy_executables(bin_dir, args.targets_dir, suffix)
129+
total_copied += len(copied)
130+
131+
manifest = {"schema": "2", "targets": targets}
132+
os.makedirs(os.path.dirname(args.manifest_out) or ".", exist_ok=True)
133+
with open(args.manifest_out, "w") as f:
134+
json.dump(manifest, f, indent=2)
135+
136+
print(f"Copied {total_copied} binaries to {args.targets_dir}, "
137+
f"wrote {len(targets)} targets to {args.manifest_out}")
138+
139+
140+
if __name__ == "__main__":
141+
main()

barretenberg/cpp/scripts/generate_fuzzer_manifest.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,15 @@ def main():
2121
name, source_path = line.split("|", 1)
2222
fuzzers.append({"name": name, "source_path": source_path})
2323

24+
# Derive binary suffix from preset name (e.g. fuzzing-noasm -> _noasm)
25+
suffix = ""
26+
if preset and preset != "fuzzing":
27+
tag = preset.removeprefix("fuzzing-").replace("coverage", "cov")
28+
suffix = f"_{tag}"
29+
2430
os.makedirs(os.path.dirname(output_file), exist_ok=True)
2531
with open(output_file, "w") as f:
26-
json.dump({"version": 1, "preset": preset, "fuzzers": fuzzers}, f, indent=2)
32+
json.dump({"version": 1, "preset": preset, "suffix": suffix, "fuzzers": fuzzers}, f, indent=2)
2733

2834
print(f"Wrote {len(fuzzers)} fuzzers to {output_file} (preset: {preset})")
2935

barretenberg/cpp/scripts/merge_fuzzer_manifests.py

Lines changed: 0 additions & 62 deletions
This file was deleted.

0 commit comments

Comments
 (0)