Skip to content

fix: header registration visibility and body-loop edge case - #386

Open
fzipi wants to merge 1 commit into
owasp-modsecurity:masterfrom
fzipi:fix/header-registration-and-body-loop-edges
Open

fix: header registration visibility and body-loop edge case#386
fzipi wants to merge 1 commit into
owasp-modsecurity:masterfrom
fzipi:fix/header-registration-and-body-loop-edges

Conversation

@fzipi

@fzipi fzipi commented Jul 27, 2026

Copy link
Copy Markdown

Summary

  • msc_add_n_request_header() and msc_add_n_response_header() signal failure via their return value, but every call site discarded it — the resolver dispatch loop in ngx_http_modsecurity_header_filter(), the raw headers_out/headers_in list loops, and the nested Keep-Alive add inside ngx_http_modsecurity_resolv_header_connection(). A failure (OOM inside libmodsecurity) meant that header simply never reached the WAF with no visibility into it. These now log a warning on failure. Unlike the request/response body phase (handled in a separate PR), dropping a single header isn't equivalent to skipping inspection outright, so this logs rather than fails the request closed.
  • ngx_str_to_char() returns NULL for a zero-length ngx_str_t (not an error, just "empty"), and most callers already check for it (e.g. n_uri, host_name), but client_addr and server_addr in the connection-info block didn't. An empty addr_text (e.g. some unix-socket configurations) would pass NULL straight into msc_process_connection(), which expects a C string.
  • The response-body loop in ngx_http_modsecurity_body_filter() never broke out after running msc_process_response_body() on the buffer carrying last_buf. A filter upstream of us (sub_filter, gzip, ...) can append a trailing buffer after the last_buf link in the same chain, which would loop back around and call msc_append_response_body() again on an already-finalized transaction. The full chain is still forwarded to the next filter regardless of where this loop stops inspecting, so nothing is dropped from the response.

Test plan

  • Build against libmodsecurity v3 and run the existing tests/*.t suite (prove modsecurity*.t) to confirm no regression.

msc_add_n_request_header() and msc_add_n_response_header() signal
failure via their return value, but every call site discarded it --
both the resolver dispatch loop and the raw header_out/header_in list
loops. A failure (OOM inside libmodsecurity) meant that header simply
never reached the WAF, with no visibility into it. Log a warning on
failure. Unlike the request/response body phase, dropping a single
header is not equivalent to skipping inspection outright, so this
logs rather than fails the request closed.

ngx_str_to_char() returns NULL for a zero-length ngx_str_t (not an
error, just "empty"), and most callers check for it, but client_addr
and server_addr in the connection-info block didn't. An empty
addr_text (e.g. some unix-socket configurations) would pass NULL
straight into msc_process_connection(), which expects a C string.

The response-body loop in the body filter never broke out after
running msc_process_response_body() on the buffer carrying last_buf.
A filter upstream of us (sub_filter, gzip, ...) can append a trailing
buffer after the last_buf link in the same chain, which would loop
back around and call msc_append_response_body() again on an
already-finalized transaction. The full chain is still forwarded to
the next filter regardless of where this loop stops inspecting.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

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