Fuzzing (TZ 3.5 / 5.2)
libFuzzer harnesses for the code that parses bytes we do not control live in tests/fuzz/. They run with
ASan + UBSan + LeakSanitizer.
| Target | What is fuzzed | Properties checked |
|---|---|---|
fuzz_ser | std.ser decoders (runtime/src/ser_bin.cpp, ser_json.cpp, ser_doc.cpp): protobuf wire format, canonical JSON, MessagePack and CBOR ([С §9.3]) against a hand-built schema covering all node kinds (scalars, str/bytes, enum, oneof, optional, list, map, set, array, nested message, Duration/Timestamp/Date); byte 0 = root << 2 | format (0 binary, 1 JSON, 2 MessagePack, 3 CBOR) | no crash/UB/leak; decode -> encode -> decode -> encode is canonical |
fuzz_grpc_wire | gRPC helpers parsing peer data (runtime/src/grpc/wire.cpp): grpc-timeout, grpc-status-details-bin, base64, percent-decoding, metadata validation, framing | round-trips |
fuzz_h2 | a real HTTP/2 server connection (our nghttp2 glue, runtime/src/http2) plus gRPC message framing (readMessage) fed with hostile frames over a socketpair on the OxL scheduler | no crash/UB/leak/hang; every handler task ends with its connection |
fuzz_http1 | HTTP/1.1 client response parser (runtime/src/http1/http1.cpp: status line, headers, chunked / length / close-delimited bodies, redirects) against a hostile loopback server thread, through the oxl_http_* C ABI | no crash/UB/leak/hang |
fuzz_jwt | the authentication parsers (runtime/oxlsec: strict JSON, JWKS documents, compact JWTs against a generated Ed25519 key set, join tokens, signed deny-lists, principals); byte 0 selects the target | no crash/UB/leak/hang; a token is accepted only when it is the one valid token the harness signed (no forged or bit-flipped token ever verifies) |
fuzz_config | static configuration: TOML subset parser, duration/size texts, and the loader driven with fuzzed flags / environment / config/<profile>.toml | no crash/UB/leak |
fuzz_frontend | OxL lexer, parser and oxl fmt | no crash/UB; the formatter's AST-equivalence + idempotency check never falls back |
Running#
scripts/fuzz.sh # all targets, 300 s each, build-fuzz/
scripts/fuzz.sh --time 1800 fuzz_ser # one target for 30 minutes
scripts/fuzz.sh --jobs 2 fuzz_h2 # libFuzzer fork mode with 2 workersscripts/fuzz.sh configures build-fuzz/ with -DOXL_SANITIZE=address,undefined -DOXL_FUZZ=ON and builds only the
fuzz targets. The working corpus is build-fuzz/corpus/<target>/ (seeded from tests/fuzz/corpus/<target>/, kept
between runs); crashes land in build-fuzz/artifacts/. OXL_FUZZ=ON builds the whole tree with
-fsanitize=fuzzer-no-link, so the runtime is coverage-instrumented; it requires OXL_SANITIZE.
Smoke test in ctest. With OXL_FUZZ=ON every target also registers fuzz_*_smoke (label fuzz):
tests/fuzz/smoke.sh replays the seed corpus and fuzzes for OXL_FUZZ_SMOKE_SECONDS (default 3) in a scratch copy
(the checked-in seeds are not modified). In a normal build these tests do not exist.
cmake -S . -B build-fuzz -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DOXL_SANITIZE=address,undefined -DOXL_FUZZ=ON
ninja -C build-fuzz && ctest --test-dir build-fuzz -L fuzzWhen a fuzzer finds a crash#
- Reproduce:
build-fuzz/tests/fuzz/fuzz_ser build-fuzz/artifacts/fuzz_ser-crash-...; minimise with-minimize_crash=1. - Fix the bug in
runtime/orcompiler/. - Add a regression test with the input embedded to
tests/unit/runtime/fuzz_regress_test.cpp(or next to the component's tests), and, if the input exercises new paths, copy it totests/fuzz/corpus/<target>/.
Findings#
See "Bugs found and fixed" in docs/sanitizers.md; fuzzers found the parseRfc3339 signed overflow (fuzz_ser).
The seed corpus is hand-written (tests/fuzz/corpus/); the schema of fuzz_ser and the framing in fuzz_h2 are
documented in the harness sources.
Not covered#
TLS record layer (OpenSSL), PostgreSQL wire protocol client, and the oxl driver's manifest/lock-file readers have
no harness yet.
Исходник: docs/fuzzing.md