Skip to content

Hostile-input test banks: JSONTestSuite corpus + HTTP framing - #34

Merged
aaylward merged 1 commit into
mainfrom
claude/jsonrpc-task-2v9quv
Jul 7, 2026
Merged

aaylward merged 1 commit into
mainfrom
claude/jsonrpc-task-2v9quv

Conversation

@aaylward

@aaylward aaylward commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

What

Two data-driven test banks for the "does the parser crash on hostile input" class of bug — the same class as the JSON depth-bomb (#33) and SIGPIPE (#32) fixes. Building them surfaced two more real bugs, both fixed here.

(a) JSON conformance corpus. Vendors nst/JSONTestSuite's 318-file parsing corpus (MIT — the canonical "Parsing JSON is a Minefield" bank) under runtime/tests/json/jsontestsuite/ with LICENSE + PROVENANCE.md, and runs every file through smithy::json::Decode in json_conformance_test. The load-bearing invariant for all 318: Decode returns, never crashes or hangs. On top of that, y_ must be accepted, n_ rejected, i_ implementation-defined. Results on our stack (nlohmann + FromBackend + the depth guard): 95/95 valid accepted, 187/188 invalid rejected (one nlohmann-tolerated trailing-NUL case allowlisted with a comment), zero crashes. A genrule bakes the corpus filenames into the parametrized case list (runfiles directory enumeration isn't portable); the test is gated to POSIX since the JSON parser is platform-independent.

(b) HTTP framing. socket_transport_hostile_test drives raw bytes at SocketHttpServer's hand-rolled ReadMessage (the client only ever emits well-formed requests, so this is the only way to reach the attacker-controlled paths): conflicting Content-Length, Transfer-Encoding, negative/non-numeric/overflowing lengths, header floods, truncated headers. Invariant: never crash, never hang, never accept ambiguous framing as valid. POSIX-only (raw-socket client; the parser is platform-independent).

Bugs found and fixed (runtime/src/http/socket_transport.cc)

  1. Request smuggling. ReadMessage ignored Transfer-Encoding entirely and used the first of conflicting Content-Length headers — a proxy and this server could disagree on message boundaries. Now both are rejected as 400 (neither transport direction implements chunked). The RejectsTransferEncoding and RejectsConflictingContentLength cases fail without this fix and pass with it.
  2. Response double-framing. The server wrote its own content-length, then re-emitted a handler-set one — and generated payload responses (e.g. UploadAttachment) do set content-length — putting two on the wire. Harmless before only because clients silently took the first; the new strict client (fix Phased implementation plan for smithy-cpp client & server generators #1) correctly rejects it. The transport now strips framing headers a handler set before writing its own. Caught by the existing UploadAttachment roundtrip integration test once the client turned strict; ServerEmitsExactlyOneContentLength pins it directly.

Testing

  • New //runtime:json_conformance_test (318 parametrized cases) and //runtime:socket_transport_hostile_test (10 cases) pass locally.
  • Both framing bugs verified by stashing the fix: the smuggling cases and the UploadAttachment roundtrip fail without it, pass with it.
  • Full local suite green: 54/54 (Beast targets excluded locally per the usual proxy limitation).
  • clang-format, clang-tidy (clean on the touched source), and buildifier all pass.

Checklist

  • CI green
  • Regression tests added and verified against the pre-fix behavior
  • Vendored corpus carries its upstream MIT LICENSE + provenance
  • No wire-behavior change for well-formed traffic; only ambiguous/hostile framing is now rejected

🤖 Generated with Claude Code

https://claude.ai/code/session_019ytv3VMrURFYP2mdWhk3un


Generated by Claude Code

Two data-driven test banks for the "does the parser crash on hostile
input" class of bug (following the JSON depth-bomb and SIGPIPE fixes).

(a) JSON conformance: vendor nst/JSONTestSuite's 318-file parsing corpus
(MIT, see PROVENANCE.md) and run every case through smithy::json::Decode
in json_conformance_test. Invariant for all files: Decode returns, never
crashes or hangs; y_ must be accepted, n_ rejected (one documented
nlohmann-tolerated case allowlisted), i_ implementation-defined. A
genrule bakes the corpus filenames into the parametrized case list
(runfiles directory enumeration isn't portable); the test is gated to
POSIX since the parser is platform-independent.

(b) HTTP framing: socket_transport_hostile_test drives raw bytes at
SocketHttpServer's hand-rolled parser — request-smuggling framing,
malformed content-lengths, header floods, truncation — asserting the
server never crashes, hangs, or accepts ambiguous framing.

This surfaced two real bugs, both fixed here in socket_transport.cc:
- Request smuggling: ReadMessage ignored Transfer-Encoding entirely and
  used the first of conflicting Content-Length headers. Now rejects both
  as 400 (neither transport direction implements chunked).
- Response double-framing: the server emitted its own content-length and
  then re-emitted a handler-set one (generated payload responses set it),
  putting two on the wire. The transport now strips framing headers a
  handler set before writing its own.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ytv3VMrURFYP2mdWhk3un
@aaylward
aaylward merged commit e799d03 into main Jul 7, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants