Skip to content

fix(http): bounds-check proxy Content-Length and URI/string helpers#17

Open
andypost wants to merge 1 commit into
masterfrom
claude/audit-pr-f-http-uri-bounds
Open

fix(http): bounds-check proxy Content-Length and URI/string helpers#17
andypost wants to merge 1 commit into
masterfrom
claude/audit-pr-f-http-uri-bounds

Conversation

@andypost
Copy link
Copy Markdown
Owner

@andypost andypost commented May 8, 2026

Summary

Audit-driven HTTP/URI parser & string-helper bounds pass (PR-F from
security-audit.md / PR #10). Four findings; small, no protocol
or config-surface changes.

Findings addressed

  • V1 [High] Proxy response Content-Length underflow (nxt_h1proto.c)
  • V1 [Medium] Proxy CL > NXT_OFF_T_MAX handling (nxt_http_proxy.c)
  • V13 [Medium] nxt_is_complex_uri_encoded off-by-one (nxt_string.c)
  • V13 [Low] nxt_rmemstrn length underflow (nxt_string.c)

Test plan

  • ./configure --openssl && make -j unitd clean
  • pytest-3 --collect-only test/test_proxy.py test/test_proxy_chunked.py test/test_http_header.py collects 54 tests
  • Full pytest-3 test/test_proxy.py requires root; not run in worktree
  • Manual: malformed upstream Content-Length: 99999999999999999999 is logged and flags request inconsistent
  • Manual: URI ending in %X (one hex digit) is rejected without OOB read

Upstream

Same fixes apply to `freeunitorg/freeunit`; will forward after merge.

Generated by Claude Code

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request hardens the HTTP/URI parser and string helper bounds by adding checks for Content-Length underflow, handling invalid upstream Content-Length values, and fixing an off-by-one error in URI encoding detection. Feedback suggests that extra body bytes should be truncated before being passed downstream to avoid protocol violations. Additionally, reviewers recommended handling zero-length inputs in nxt_rmemstrn to prevent underflow and improving the safety of size comparisons to avoid signed integer overflow on 64-bit systems.

Comment thread src/nxt_h1proto.c Outdated
Comment on lines 2884 to 2898
if (nxt_slow_path((nxt_off_t) length > h1p->remainder)) {
/*
* Upstream sent more body bytes than its Content-Length
* declared. Avoid signed underflow of h1p->remainder and
* surface the inconsistency immediately instead of waiting
* for connection close.
*/
nxt_log(task, NXT_LOG_WARN,
"upstream sent %uz body bytes past Content-Length "
"(remainder %O)", length, h1p->remainder);

peer->request->inconsistent = 1;
h1p->remainder = 0;
nxt_buf_chain_add(&out, nxt_http_buf_last(peer->request));
peer->closed = 1;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When the upstream sends more data than specified in the Content-Length header (length > h1p->remainder), the code correctly identifies the inconsistency and flags the request. However, the buffer chain out is passed to the next stage without being truncated to the expected remainder. This results in the extra bytes being sent to the downstream client, which violates the Content-Length previously sent in the response headers. This can lead to protocol errors or client-side confusion, even if the connection is closed immediately after.

Comment thread src/nxt_string.c Outdated
Comment on lines +319 to +321
if (nxt_slow_path(length > (size_t) (end - s))) {
return NULL;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The nxt_rmemstrn function does not handle the case where length is 0. If length is 0, the subsequent length-- at line 326 (in the full file) will cause an underflow to SIZE_MAX. This leads to an out-of-bounds memory comparison in memcmp at line 332 and an out-of-bounds read at line 329 (c1 = *s1 where s1 is end). The bounds check should be updated to reject length == 0 or handle it appropriately.

    if (nxt_slow_path(length == 0 || length > (size_t) (end - s))) {

Comment thread src/nxt_h1proto.c Outdated
h1p->remainder -= length;

if (h1p->remainder == 0) {
if (nxt_slow_path((nxt_off_t) length > h1p->remainder)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The comparison (nxt_off_t) length > h1p->remainder involves casting an unsigned size_t to a signed nxt_off_t. On 64-bit systems where both types are 64-bit, if length is extremely large (specifically > 2^63 - 1), the cast will result in a negative value. Since h1p->remainder is positive, the comparison negative > positive will be false, and the overflow check will be bypassed. A safer approach is to check for (nxt_off_t) length < 0 as well to handle potential overflow.

        if (nxt_slow_path((nxt_off_t) length < 0
                         || (nxt_off_t) length > h1p->remainder))

Audit-driven HTTP/URI parser and string-helper bounds pass (PR-F from
security-audit.md / PR #10).  Four findings, no protocol or config-surface
changes.

V1 [High] Proxy response Content-Length underflow
(src/nxt_h1proto.c:2881).  When an upstream sent more body bytes than its
Content-Length declared, h1p->remainder (signed nxt_off_t) silently
underflowed and subsequent `remainder > 0` checks dropped excess data,
masking the inconsistency.  Guard the subtraction on the slow path: log
the violation, set request->inconsistent immediately, clamp remainder to
zero, and close the peer cleanly.  Happy path stays branch-predicted.

V1 [Medium] Proxy Content-Length > NXT_OFF_T_MAX
(src/nxt_http_proxy.c:417).  nxt_off_t_parse() returns -2 on overflow and
-1 on parse error; the previous `n >= 0` guard collapsed both into "leave
content_length_n at -1", indistinguishable from "header absent".  Now log
at NXT_LOG_WARN and mark r->inconsistent — symmetric with the underflow
path above and matches the upstream-side handling in nxt_http_request.c.

V13 [Medium] nxt_is_complex_uri_encoded() off-by-one
(src/nxt_string.c:718).  The bounds check `end - src < 2` is wrong: the
two pre-increments below it read src+1 and src+2, so three bytes must
remain.  A URI ending in "%X" (one hex digit) read one byte past end.
Bumped to `< 3` with a short comment explaining the pre-increment shape.

V13 [Low] nxt_rmemstrn() length underflow (src/nxt_string.c:319).
`s1 = end - length` had no defensive check; all current callers validate
upstream but a future caller could trigger pointer underflow.  Added the
audit's recommended guard at function entry.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@andypost andypost force-pushed the claude/audit-pr-f-http-uri-bounds branch from 1851081 to f61be03 Compare May 11, 2026 21:37
@andypost
Copy link
Copy Markdown
Owner Author

All three Gemini findings addressed in f61be03f:

  • [high] Out-buffer extra bytes flow downstream — the buf chain is now walked on the overrun path and trimmed to h1p->remainder bytes (mem.free adjusted on the boundary buf, ->next = NULL to drop the rest) before the connection is flagged inconsistent and closed. Excess upstream bytes no longer reach the client.
  • [security-high] nxt_rmemstrn length=0 underflow — added length == 0 to the existing guard at line 319 so the subsequent length-- can no longer underflow to SIZE_MAX.
  • [medium] (nxt_off_t)length cast wraparound — added (nxt_off_t) length < 0 to the overrun check so a >2^63 length now triggers the inconsistency path instead of bypassing.

Force-pushed; build clean.

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.

1 participant