Testing & CI hardening phase 2: fuzz the HTTP/1.1 parser, even out malformed-server coverage (#48) - #54
Merged
Conversation
…lformed-server coverage Continues issue #48 after #53, taking the two hostile-input items: - The hand-rolled HTTP/1.1 message reader — the one network-facing parser with no fuzz coverage — moves out of socket_transport.cc into a pure, callback-fed function (smithy/http/http1.h) behind the same transports, byte-for-byte behavior-preserving. On top of it: * //fuzz:http1_fuzz, a libFuzzer harness that replays the wire bytes in varying chunk sizes so the incremental header/body accumulation paths are exercised, wired into the CI fuzz smoke loop and `make fuzz-smoke` (and the deterministic-driver //fuzz:http1_fuzz_smoke variant that runs in every bazel matrix job). * http1_hostile_test.cc, a platform-independent hostile bank at the pure parser level: request-smuggling framing (duplicate/conflicting CL, transfer-encoding, TE+CL), hostile content-lengths, malformed header blocks, every-strict-prefix truncation, header floods, plus the accept-side (padding, case-insensitivity, body-until-EOF) and the start-line helpers. Unlike socket_transport_hostile_test.cc it needs no sockets, so it also runs on Windows. Hardening found while banking: an empty Content-Length and a "+4"-style signed one previously parsed as valid lengths (strtoull laxity); the parser now requires digits-only per RFC 9110. - Malformed-server coverage was jsonrpc2-only (its model carries httpMalformedRequestTests; the alloy and official rpcv2Cbor suites carry none). New hand-written suites — outside the golden generated/ trees — pin the generated servers' reject paths before the handler runs: * simpleRestJson (PizzaAdminService): 404/405 routing, unparseable JSON body -> 400 + x-error-type SerializationException, wrong content-type -> 415, and the suite-exact enum-violation ValidationException message. * simpleRestJson @pattern-violation wire message (issue #48's explicit gap) via the roundtrip REST fixture's pattern-constrained SinkId, plus the exact length-violation message. * rpcv2Cbor (RpcV2Protocol): missing/wrong smithy-protocol header -> 400 SerializationException, wrong content-type -> 415 UnsupportedMediaTypeException, truncated and non-map CBOR bodies, 404/405 routing — asserting the CBOR __type and protocol headers on every error response. CI's clang-format check (and make lint/format) now covers protocol-tests outside generated/. Verified locally: bazel test //... green (72 tests; Boost/benchmark targets excluded per docs/development.md's proxy note, CI covers them), plus a 15s real-libFuzzer ASan run of the new harness (160k execs, no findings). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012VFUppN1idpmU1UrbHkknZ
aaylward
pushed a commit
that referenced
this pull request
Jul 8, 2026
…overage tooling Continues issue #48 after #53 and #54: - Union x {rpcv2Cbor, jsonRpc2} conformance (protocol-tests/unions/): union round-tripping was only pinned for simpleRestJson; the other two cells relied on the seeded random integration tests, which flip a coin on whether the union appears at all and can only prove serde self-consistency — a symmetric encode/decode bug passes them. The new suites pin the wire subdocument for every SinkChoice variant deterministically, in all four directions per protocol (client encode, client decode, server decode, server echo), plus the reject cells (empty, multi-member, unknown-member, null-member, non-map unions), the exactly-one-member wire invariant, and the __type discriminator tolerance. Both suites were mutation-checked: one expectation flipped per suite fails the suite, so a first-run pass is not vacuous. - Code-coverage tooling (issue #48's "module gaps only findable by manual srcs<->tests correspondence"): a coverage CI job runs bazel coverage with a combined lcov report over the runtime, prints the per-module summary in the log, and uploads the rendered HTML as a build artifact; `make coverage` runs the same locally. Measurement only, no threshold gate yet — same posture as the benchmarks job. Verified locally: bazel test //... green (74 tests; Boost/benchmark targets excluded per docs/development.md's proxy note, CI covers them), and bazel coverage --combined_report=lcov produces a 21-file runtime report. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012VFUppN1idpmU1UrbHkknZ
Merged
4 tasks
aaylward
pushed a commit
that referenced
this pull request
Jul 8, 2026
… pinning, macOS sanitizers Continues issue #48 (after #53, #54, #55) and works through issue #56: - Union member-type gauntlet (protocol-tests/unions/): SinkChoice only covers string/int/struct members, so a generated-in-graph UnionGauntlet service (same Bazel path as codegen/compile-tests) extends the union x {rpcv2Cbor, jsonRpc2} cells to blob, timestamp, list, map, enum, intEnum, and recursive-struct members. Each member kind is pinned with a hand-built typed value on one side and a hand-built wire document on the other — never both ends produced by the code under test — through client encode, the full client<->server loopback loop, and the wire subdocument. Protocol-specific representations are pinned where they differ (blobs as base64 text and timestamps as epoch-seconds numbers in JSON; blob and tag-1 timestamp nodes in CBOR). - Byte-exact request vectors: the CBOR body pinned as hex derived by hand from RFC 8949's deterministic encoding rules, and the full JSON-RPC envelope pinned as its exact compact sorted-key text — closing the seam Document-level equality leaves between "codec is deterministic" and "serde feeds the codec what we think" (issue #56). - Error-shape union cell: a modeled error whose union member rides next to its __type discriminator, inspected on the wire (the payload keeps both) and round-tripped into the typed error detail on the client — the case the serde's exactly-one-member arithmetic exists to tolerate. That contract is now documented in docs/generated-types.md, giving the tolerance test an authority beyond the implementation (issue #56). - Reject-cell diagnosis pinning (issue #56): the phase-3 union reject cells now assert which rule was violated ("expected exactly one union member", "unknown or missing union member", "expected a map on the wire") on both client and server, not merely that a rejection happened. - Sanitizers x platforms (issue #48): the asan+ubsan CI job is now a linux/clang + macos/apple-clang matrix, putting the transport layer's Apple-specific paths (SO_NOSIGPIPE, libc++) under a sanitizer for the first time. MSVC ASan under Bazel remains future work. Also confirmed while scoping: the Gradle toolchain works in this environment (gradle build + the Java unit suite pass), so the generator-class unit-test item is unblocked for a next phase. Model note: recursion *through* a union (Node carrying a BigUnion) is rejected by the generator with a clear diagnostic (std::variant needs complete alternatives); the gauntlet documents that boundary and covers a self-recursive struct as a union member instead. Verified locally: bazel test //... green (76 tests; Boost/benchmark targets excluded per docs/development.md's proxy note, CI covers them). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012VFUppN1idpmU1UrbHkknZ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Phase 2 of #48 (after #53), taking the two hostile-input items:
HTTP-parser fuzzing. The hand-rolled HTTP/1.1 message reader was the one network-facing parser with no fuzz coverage. It moves out of
socket_transport.ccinto a pure, callback-fed function (smithy/http/http1.h) behind the same transports — byte-for-byte behavior-preserving (all existing socket/hostile-framing tests pass unchanged). On top of it://fuzz:http1_fuzz: a libFuzzer harness that replays wire bytes in varying chunk sizes so the incremental header/body accumulation paths get exercised, wired into the CI fuzz smoke loop andmake fuzz-smoke(plus the deterministic//fuzz:http1_fuzz_smokecc_test that runs in every bazel matrix job).http1_hostile_test.cc: a hostile bank at the pure-parser level — request-smuggling framing (duplicate/conflicting Content-Length, transfer-encoding, TE+CL), hostile content-lengths, malformed header blocks, an every-strict-prefix truncation property, header floods, the accept side (padding/case-insensitivity/body-until-EOF), and the start-line helpers. No sockets, so unlikesocket_transport_hostile_test.ccit also runs on Windows.+4-style signed one previously parsed as valid lengths (strtoulllaxity); the parser now requires digits-only per RFC 9110.Malformed-server evenness. Only jsonrpc2 had a malformed-server suite (its model carries
httpMalformedRequestTests; the alloy and official rpcv2Cbor suites carry none). New hand-written suites — outside the goldengenerated/trees, so the byte-identical regeneration check is untouched — pin the generated servers' reject paths before the handler runs:x-error-type: SerializationException, wrong content-type → 415, and the suite-exact enum-violation ValidationException message + fieldList path.@pattern-violation wire message Tracking: testing & CI hardening #48 explicitly called out as unasserted, via the roundtrip REST fixture's pattern-constrainedSinkId— plus the exact length-violation message.smithy-protocolheader → 400 SerializationException, wrong content-type → 415 UnsupportedMediaTypeException, truncated and non-map CBOR bodies → 400, 404/405 routing — asserting the CBOR__typeand protocol headers on every error response.Also: CI's clang-format check (and
make lint/format) now coversprotocol-testsoutsidegenerated/.Remaining on #48: golden self-ratification, generator-class unit tests, union×{cbor, jsonRpc2} conformance cells, coverage tooling, benchmark gating, cross-platform sanitizers, executing-rule Bazel tests beyond the compile harness.
Testing
bazel test //...green locally — 72 tests (up from 67: the http1 hostile bank, the http1 fuzz smoke, and the three malformed-server suites), with the Boost/Beast targets and//benchmarks/...excluded perdocs/development.md's proxy note; CI covers those.socket_transport_testandsocket_transport_hostile_test(9 raw-socket framing attacks) passing unchanged.Checklist
bazel test //...passes locally (Boost/benchmark targets excluded per docs; CI covers them); codegen untouched, gradle job unaffected🤖 Generated with Claude Code
https://claude.ai/code/session_012VFUppN1idpmU1UrbHkknZ
Generated by Claude Code