Skip to content

fix: reject invalid Content-Length framing in requests - #2652

Closed
zyuhao7 wants to merge 4 commits into
drogonframework:masterfrom
zyuhao7:bugfix/2648-content-length-framing
Closed

zyuhao7 wants to merge 4 commits into
drogonframework:masterfrom
zyuhao7:bugfix/2648-content-length-framing

Conversation

@zyuhao7

@zyuhao7 zyuhao7 commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #2648. Follow-up to #2646, which made the Content-Length digit scan strict but left three ways for request framing to stay ambiguous.

The Content-Length value is now validated inside HttpRequestImpl::addHeader, the single place byte-level request headers enter the message:

  • Empty value — Content-Length: trimmed to an empty string and was indistinguishable from an absent header, so everything after the header section was parsed as the next request. It is now rejected (RFC 9110 §8.6, Content-Length = 1*DIGIT).
  • Repeated field with a different value — headers_.emplace silently kept the first one, so Content-Length: 0 followed by Content-Length: 100 framed the body as zero bytes (CL.CL desync). A differing repeat is now rejected; identical repeats are still accepted (RFC 9112 §6.3).
  • Whitespace between the field name and the colon — Content-Length : 10 produced the field name "content-length ", which matched nothing and was dropped entirely. The parser now rejects such a line with 400, as RFC 9112 §5.1 requires.

Because addHeader already returns the validated value, it is cached in contentLengthHeaderValue_ and the parser's duplicate digit scan is removed.

Comma-separated lists (Content-Length: 42, 42) are rejected. RFC 9112 §6.3 permits folding identical members but does not require it; rejecting every list is simpler while remaining compliant.

Test plan

  • New lib/tests/integration_test/client/ContentLengthFramingTest.cc:
    • differing repeated value → 400
    • empty value → 400
    • whitespace before the colon → 400
    • comma list → 400
    • value beyond size_t → 400
    • identical repeated value → 200
  • Full integration client suite: all 29 cases pass.
  • Unit tests (IsValidHeader, IsValidCookie): pass.
  • Existing RejectInvalidContentLength (10x → 400) from fix: validate Content-Length as complete decimal #2646 still passes.

The response side still uses std::stoull in HttpResponseParser and accepts numeric prefixes; that is out of scope here and noted in #2648.

Validate Content-Length as 1*DIGIT inside HttpRequestImpl::addHeader, the
single place byte-level request headers enter the message:

* An empty value, a numeric prefix such as "10x", or a value beyond the
  size_t range is invalid framing (RFC 9110 section 8.6). Previously an
  empty value was indistinguishable from an absent header, so the bytes
  after the header section were parsed as the next request.
* A repeated Content-Length whose value differs from the one already
  received is rejected instead of silently keeping the first one
  (RFC 9112 section 6.3), closing a CL.CL request smuggling vector.
* A header line with whitespace between the field name and the colon is
  rejected with 400, as required by RFC 9112 section 5.1.

The parsed value is cached in contentLengthHeaderValue_, so the parser no
longer carries its own copy of the digit scan.

Fixes drogonframework#2648
@zyuhao7

zyuhao7 commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

The macos (15) job fails while building the trantor submodule, before any Drogon source in this PR is compiled:

trantor/net/inner/tlsprovider/OpenSSLProvider.cc:100:18: error: 'X509_check_ip_asc' is deprecated: Since OpenSSL 4.1 [-Werror,-Wdeprecated-declarations]
trantor/net/inner/tlsprovider/OpenSSLProvider.cc:103:18: error: 'X509_check_host' is deprecated: Since OpenSSL 4.1 [-Werror,-Wdeprecated-declarations]
trantor/net/inner/tlsprovider/OpenSSLProvider.cc:122:27: error: 'X509_cmp_current_time' is deprecated: Since OpenSSL 4.0 [-Werror,-Wdeprecated-declarations]

This is an environment change on the runner image, not a regression from this PR:

  • macos (14) in this run finds openssl@3 3.6.4 and passes.
  • macos (15) now resolves to the Homebrew openssl@4 4.1.0 keg, and trantor builds with -Werror.
  • The same image resolved to openssl@3 3.6.4 on master yesterday (run 40916428459), where macos (15) passed. The unmodified master would hit the same failure on the current image.
  • trantor master already contains an OpenSSL 4.0 fix (an-tao/trantor@420d5a6) but not the APIs deprecated in 4.1 (X509_check_host, X509_check_ip_asc).

No changes in this PR touch trantor, CMake configuration, or the dependency setup. It should go green once the trantor submodule is updated for OpenSSL 4.1, or once the macOS CI job pins openssl@3. Happy to prepare a follow-up PR for either if that is preferred.

The request parser needs the same 1*DIGIT validation, so share one
implementation instead of keeping separate copies that can diverge.
Picks up the OpenSSL 4.x build fixes (an-tao/trantor#423, drogonframework#422), which
the macOS 15 CI needs because Homebrew now ships OpenSSL 4.1.
The first field already caches its parsed length in
contentLengthHeaderValue_, so compare against that instead of scanning
the stored string again for every repeated field.
@zyuhao7

zyuhao7 commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #2665, which combines the request and response framing fixes on top of current master.

@zyuhao7 zyuhao7 closed this Nov 4, 2026
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.

HTTP request parsing: remaining Content-Length framing gaps after #2646

1 participant