WeaveBundle is a small C++ library for parsing a deliberately rich binary container format called WVBF (WeaveBundle Format). The format is designed to be realistic enough for parser-hardening work while staying compact enough to audit, fuzz, and integrate into OSS-Fuzz quickly.
The parser accepts untrusted binary input and exercises:
- header validation
- nested sections
- variable-length records
- optional names and footers
- dynamic allocation for strings, payloads, attributes, records, and child sections
- optional RLE-compressed payload blocks
- checksum verification at both file and section levels
- multiple malformed-input error paths
This makes it a good fuzzing target because a single input can drive deep control flow, repeated length parsing, recursive structure handling, and heap-backed decoding logic.
This repository is intended as a safe fuzzing target template. It is intentionally parser-complex and sanitizer-friendly, but it does not seed known memory corruption vulnerabilities on purpose.
The parser assumes every byte of input is attacker-controlled. A hostile input may contain malformed headers, inconsistent length fields, invalid checksums, truncated nested sections, malformed records, oversized compressed payloads, or deliberately chosen flag combinations intended to stress edge cases in allocation, bounds checking, recursion, and decompression logic.
The security goal is to handle those cases without memory corruption, out-of-bounds access, integer-overflow-driven misbehavior, or excessive parsing work that would make fuzzing less effective.
WeaveBundle is intentionally designed to exercise common parser risk patterns:
- multiple independent length fields
- recursive section structures
- variable-length metadata blocks
- optional compression
- checksummed data boundaries
These features mimic real-world container formats that historically produce memory-safety bugs when fuzzed, while still keeping the codebase small enough to audit and integrate into OSS-Fuzz quickly.
.
├── CMakeLists.txt
├── LICENSE
├── README.md
├── examples
│ ├── parse_example.cpp
│ └── sample.wvbf
├── fuzz
│ ├── fuzz_footer.cpp
│ ├── fuzz_footer.dict
│ ├── fuzz_helpers.h
│ ├── fuzz_parser.cpp
│ ├── fuzz_parser.dict
│ ├── fuzz_rle.cpp
│ ├── fuzz_rle.dict
│ ├── fuzz_section.cpp
│ └── fuzz_section.dict
├── include
│ └── weavebundle
│ └── parser.h
├── oss-fuzz
│ ├── build.sh
│ ├── Dockerfile
│ └── project.yaml
├── src
│ ├── parser.cpp
│ └── parser.h
└── tests
└── test_parser.cpp
All integers are little-endian.
| Offset | Size | Field |
|---|---|---|
| 0 | 4 | Magic = WVBF |
| 4 | 1 | Version (1 or 2) |
| 5 | 1 | Global flags |
| 6 | 2 | Top-level section count |
| 8 | 4 | Directory size in bytes |
| 12 | 4 | File ID |
| 16 | 4 | Body checksum |
| 20 | 4 | Header checksum over bytes 0..19 |
global_flags & 0x01 means the parser must verify the body checksum across the entire directory payload.
Each top-level or nested section is encoded inline as:
| Offset | Size | Field |
|---|---|---|
| 0 | 1 | Section type |
| 1 | 1 | Section flags |
| 2 | 2 | Reserved |
| 4 | 4 | Section body size |
| 8 | 4 | Section ID |
| 12 | 4 | Section checksum |
Section flags:
0x01: section has a UTF-8-ish byte name0x02: section has an optional footer blob0x04: verifysection_checksumagainst the body0x08: section contains nested child sections
The section body fields appear in this order:
name_len:u16andname:name_len bytesifflags & 0x01attr_block_len:u16- attribute TLVs within the block:
key:u8value_len:u8value:value_len bytes
record_count:u16record_countrepeated record entriespayload_mode:u8- payload data, depending on the mode
child_count:u16followed by inline child sections ifflags & 0x08footer_len:u16andfooter:footer_len bytesifflags & 0x02- any remaining bytes are preserved as opaque trailer data
Each record begins with:
| Field | Size |
|---|---|
record_type |
u8 |
record_flags |
u8 |
record_size |
u16 |
Record body layout:
timestamp:u32ifrecord_flags & 0x01metadata_count:u8- repeated metadata entries:
meta_key:u8meta_len:u8meta_value:meta_len bytes
data_len:u16data:data_len byteschild_count:u8and repeated nested values ifrecord_flags & 0x02
Nested value encoding:
| Field | Size |
|---|---|
tag |
u8 |
flags |
u8 |
length |
u16 |
value |
length bytes |
0: no payload1: raw payloadraw_len:u32raw_bytes:raw_len bytes
2: RLE-compressed payloadexpected_output_len:u32compressed_len:u32compressed_bytes:compressed_len bytes
RLE payloads are encoded as (run_len:u8, value:u8) pairs. The expanded size must exactly match expected_output_len.
WeaveBundle uses a simple 32-bit FNV-1a style checksum:
state = 2166136261
for each byte:
state ^= byte
state *= 16777619
state ^= size
state *= 16777619
The same checksum is used for the file header, file body, and sections.
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build
ctest --test-dir build --output-on-failurecmake -S . -B build-fuzz \
-DCMAKE_CXX_COMPILER=clang++ \
-DWEAVEBUNDLE_ENABLE_FUZZING=ON
cmake --build build-fuzz --target fuzz_parser fuzz_rle fuzz_section fuzz_footer
./build-fuzz/fuzz_parser -max_len=4096 ./examples/sample.wvbf
./build-fuzz/fuzz_rle -max_len=4096 ./examples/sample.wvbf
./build-fuzz/fuzz_section -max_len=4096 ./examples/sample.wvbf
./build-fuzz/fuzz_footer -max_len=4096 ./examples/sample.wvbfOn macOS, AppleClang often lacks the libFuzzer runtime. In that case, use an upstream LLVM toolchain, for example:
cmake -S . -B build-fuzz \
-DCMAKE_CXX_COMPILER="$(brew --prefix llvm)/bin/clang++" \
-DWEAVEBUNDLE_ENABLE_FUZZING=ON
cmake --build build-fuzz --target fuzz_parser fuzz_rle fuzz_section fuzz_footer
./build-fuzz/fuzz_parser -max_len=4096 ./examples/sample.wvbf
./build-fuzz/fuzz_rle -max_len=4096 ./examples/sample.wvbf
./build-fuzz/fuzz_section -max_len=4096 ./examples/sample.wvbf
./build-fuzz/fuzz_footer -max_len=4096 ./examples/sample.wvbfIf you want to force sanitizer coverage flags explicitly in a local build:
cmake -S . -B build-fuzz \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_CXX_FLAGS="-fsanitize=fuzzer,address" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=fuzzer,address" \
-DWEAVEBUNDLE_ENABLE_FUZZING=ON
cmake --build build-fuzz --target fuzz_parser fuzz_rle fuzz_section fuzz_footer./build/weavebundle_example ./examples/sample.wvbf- Multiple independent length fields influence allocation and control flow.
- Recursive child sections and nested record values create deep parser states.
- The optional compressed payload path exercises heap allocation and decompression logic.
- Checksum-gated sections give the fuzzer both reject and accept paths to explore.
fuzz_parserparses raw inputs directly and also wraps arbitrary bytes into a structurally valid container to improve coverage.fuzz_rleconcentrates on compressed payload length handling and decompression paths.fuzz_sectionconcentrates on nested section, record, and recursive parsing paths.fuzz_footerconcentrates on optional footer blobs and trailing section data.- Each target caps input processing at 4096 bytes to keep allocations and decompression work fuzz-efficient.
- Raw parsing is only exercised on roughly 30% of executions; the remaining runs bias toward structured containers.
- Raw fuzzing: each target still exercises direct
ParseContainer(data, size)on a smaller share of runs so header-validation and error-handling paths remain reachable. - Structured container fuzzing: most executions wrap arbitrary bytes into partially valid
WVBFcontainers so the fuzzer spends more time in deep parsing logic instead of failing at the magic header. - Targeted RLE fuzzing:
fuzz_rlefocuses mutation energy on compressed payload headers, run-length pairs, declared output sizes, and footer-adjacent parsing. - Recursive section fuzzing:
fuzz_sectiondrives nested sections, child counts, record payloads, and section-flag combinations that influence optional fields and recursion. - Footer and trailer fuzzing:
fuzz_footeremphasizes optional names, footer blobs, and opaque trailing bytes that are easy to under-exercise in a single generic target. - Guided mutation: target-specific dictionaries and exposed flag bits help libFuzzer discover valid structural states more quickly.
Target-specific dictionaries are included in fuzz/ and are copied by oss-fuzz/build.sh:
fuzz_parser.dictfuzz_rle.dictfuzz_section.dictfuzz_footer.dict
The oss-fuzz/Dockerfile, oss-fuzz/build.sh, and oss-fuzz/project.yaml files are ready to be copied into a new OSS-Fuzz project directory.
Typical OSS-Fuzz submission flow:
- Publish this repository on GitHub or another public host.
- Update
oss-fuzz/project.yamlwith the real repository URL and maintainer email. - Copy the
oss-fuzz/files intogoogle/oss-fuzz/projects/weavebundle/. - Open a pull request against the OSS-Fuzz repository.
An example valid container is included at examples/sample.wvbf. It exercises:
- a valid file header
- one top-level section
- a named section
- section checksum verification
- one record with nested values
- an RLE-compressed payload
- a nested child section
- a footer blob