From 51dadadadbf4b6d0052d85266e31cf6ffe531225 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 30 Mar 2026 09:21:32 -0700 Subject: [PATCH] Fix prev_is_cr flag handling in chunked encoding parser The prev_is_cr flag in ChunkedHandler::read_size() was being set within conditional expressions but not consistently updated, which could cause it to retain stale values across parsing iterations. Update the flag at the end of each loop iteration to ensure it accurately reflects the current character state. This improves the correctness of line break validation when parsing chunked transfer encoding with strict_chunk_parsing enabled. --- src/proxy/http/HttpTunnel.cc | 12 ++++++++--- .../bad_chunked_encoding.test.py | 2 ++ .../malformed_chunked_header.replay.yaml | 20 +++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/proxy/http/HttpTunnel.cc b/src/proxy/http/HttpTunnel.cc index 1a370241612..cb654d3f1a7 100644 --- a/src/proxy/http/HttpTunnel.cc +++ b/src/proxy/http/HttpTunnel.cc @@ -191,7 +191,7 @@ ChunkedHandler::read_size() done = true; break; } else { - if ((prev_is_cr = ParseRules::is_cr(*tmp)) == true) { + if (ParseRules::is_cr(*tmp)) { ++num_cr; } state = CHUNK_READ_SIZE_CRLF; // now look for CRLF @@ -213,7 +213,7 @@ ChunkedHandler::read_size() done = true; num_cr = 0; break; - } else if ((prev_is_cr = ParseRules::is_cr(*tmp)) == true) { + } else if (ParseRules::is_cr(*tmp)) { if (num_cr != 0) { state = CHUNK_READ_ERROR; done = true; @@ -236,7 +236,7 @@ ChunkedHandler::read_size() num_digits = 0; num_cr = 0; state = CHUNK_READ_SIZE; - } else if ((prev_is_cr = ParseRules::is_cr(*tmp)) == true) { + } else if (ParseRules::is_cr(*tmp)) { if (num_cr != 0) { Dbg(dbg_ctl_http_chunk, "Found multiple CRs before chunk size"); state = CHUNK_READ_ERROR; @@ -249,9 +249,15 @@ ChunkedHandler::read_size() done = true; } } + prev_is_cr = ParseRules::is_cr(*tmp); tmp++; data_size--; } + + if (data_size > 0) { + prev_is_cr = ParseRules::is_cr(*tmp); + } + if (drop_chunked_trailers) { chunked_buffer->write(chunked_reader, bytes_used); chunked_size += bytes_used; diff --git a/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py b/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py index 29c42214c96..707813d2f02 100644 --- a/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py +++ b/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py @@ -134,6 +134,8 @@ def setupOriginServer(self): "chunked body of 3 bytes for key 2 with chunk stream", "Verify that writing the second response failed.") self.server.Streams.stdout += Testers.ContainsExpression( "Unexpected chunked content for key 3: too small", "Verify that writing the third response failed.") + self.server.Streams.stdout += Testers.ContainsExpression( + "Unexpected chunked content for key 8: too small", "Verify that writing the sixth response failed.") # ATS should close the connection before any body gets through. "abcwxyz" # is the body sent by the client for each of these chunked cases. diff --git a/tests/gold_tests/chunked_encoding/replays/malformed_chunked_header.replay.yaml b/tests/gold_tests/chunked_encoding/replays/malformed_chunked_header.replay.yaml index 6e058a3d814..ac0c35d9c36 100644 --- a/tests/gold_tests/chunked_encoding/replays/malformed_chunked_header.replay.yaml +++ b/tests/gold_tests/chunked_encoding/replays/malformed_chunked_header.replay.yaml @@ -118,6 +118,26 @@ sessions: server-response: status: 200 +- transactions: + - client-request: + method: "POST" + version: "1.1" + url: /malformed/chunk/header3 + headers: + fields: + - [ Host, example.com ] + - [ Transfer-Encoding, chunked ] + - [ uuid, 8 ] + content: + transfer: plain + encoding: uri + # chunk-size is set to 1, but no chunk-data is present. + data: 1%0D%0A%0D%0A0%0D%0A%0D%0A + + # The connection will be dropped and this response will not go out. + server-response: + status: 200 + # # Now repeat the above two malformed chunk header tests, but on the server # side.