Draft: sanitize response header CRLF values - #2
Conversation
|
I see! What happens with the current behavior? I’m not sure if it is better to mangle data or just close the connection, but arguably this is less likely to cause problems in production. In particular, breaking TLS session resumption is less than great, especially under high load. |
|
With the current reject-on-invalid response-filter behavior, the proxy construction cases are fine because they fail before bytes are sent upstream. The awkward part is In my local experiment, trying to convert those late filter errors into a clean The sanitization branch changes only that late response-filter policy: it keeps the response alive, maps NUL/CR/LF to SP in a request-pool copy, and prevents those bytes from creating new field syntax. The focused test passes for both So I agree with your production-safety concern. Closing the connection may be the purer fail-closed behavior, but at this point in the filter chain it is not a clean failure mode and can harm reuse/resumption. Sanitizing is a smaller operational blast radius, with the caveat that it intentionally mangles the field value: text like If upstream prefers rejection as the policy, I think |
|
Would you mind sending an upstream PR for the Unfortunately, I don’t have much time, so it would be better to send PRs directly to upstream if possible. |
|
Done. I split this into direct upstream drafts, as requested:
I kept the scope response-filter-only, separate from the proxy request-boundary work in nginx#590. The nginx-tests companion is standalone now rather than stacked on the proxy behavior:
I will keep this fork PR around as a local/reference sandbox, but the maintainer-facing discussion can move to the upstream drafts from here. |
Summary
This is an experimental stacked follow-up for the
add_header/add_trailerresidual behavior discussed in nginx#590.It sanitizes runtime-expanded response header/trailer values by copying only when NUL/CR/LF is present, replacing those bytes with spaces before serialization.
This intentionally treats response filters differently from the proxy request path: proxy request construction can reject before upstream bytes are emitted, while trailers may be discovered after the response status/body has already been committed.
Stacking
This draft is stacked on top of #1.
Until that PR lands, the diff includes the log-level commit as well as this sanitization commit. The new policy code in this draft is the second commit only:
Why draft
This is not intended as a final upstream policy decision by itself. It is a small reviewable sandbox for one possible response-filter policy:
add_header/add_trailerruntime values to avoid emitting new field syntax when a clean 500 is no longer reliable.The alternative policy would be deeper response-filter state-machine work to produce deterministic rejection semantics, especially for trailers.
Validation
Normal debug build:
ASAN/UBSAN debug build:
Supplementary lab notes: https://gist.github.com/louzt/7bdf370a28126718e7e7b69d53b0ae86