Jens Geyer created THRIFT-6192:
----------------------------------

             Summary: THttpServer matches a header name by prefix, and 
Content-Length goes through atoi
                 Key: THRIFT-6192
                 URL: https://issues.apache.org/jira/browse/THRIFT-6192
             Project: Thrift
          Issue Type: Bug
          Components: C++ - Library
            Reporter: Jens Geyer


h2. Header names

{{THttpServer::parseHeader}} compares a header name for as many characters as 
the peer sent
before the colon:

{code:cpp}
size_t sz = colon - header;
if (THRIFT_strncasecmp(header, "Transfer-Encoding", sz) == 0) { ... }
else if (THRIFT_strncasecmp(header, "Content-length", sz) == 0) { ... }
else if (strncmp(header, "X-Forwarded-For", sz) == 0) { ... }
{code}

Every name that is a _prefix_ of one the transport knows is therefore accepted 
as that name.
Measured against the unmodified library:

||Header sent||{{contentLength_}}||{{chunked_}}||
|{{Content-length: 5}}|5|0|
|{{C: 5}}|5|0|
|{{co: 7}}|7|0|
|{{Content-len: 9}}|9|0|
|{{Transfer-Encoding: chunked}}|0|1|
|{{T: chunked}}|0|1|
|{{Cookie: 5}}|0|0|

{{Cookie}} is the control: it is not a prefix of either name and is correctly 
ignored.

A header name is the whole token before the colon (RFC 9110 5.1). Reading it 
otherwise means
this transport and every other party on the connection disagree about where a 
message ends --
{{C: 5}} gives Thrift a five byte body where a parser following the grammar 
sees none.

{{lib/cpp/src/thrift/transport/TWebSocketServer.h}} overrides {{parseHeader}} 
with the same
idiom, so {{U: websocket}}, {{C: Upgrade}} and {{S: <key>}} satisfy the 
handshake. Note that
{{S:}} reaches the {{Sec-WebSocket-Key}} arm, which is tested before the 
version one. The same
function has {{std::string toHash = value + 1;}}, which steps over the space 
after the colon by
assuming one is there, and reads one past the terminator for a header with an 
empty value.

{{THttpClient::parseHeader}} is not affected -- it builds {{const string 
name(header, colon)}}
and uses {{boost::iequals}}, a full equality.

h2. Content-Length

{{contentLength_ = atoi(value)}}, in both the server and the client. {{atoi}} 
cannot report a
negative number, a value too large for the {{uint32_t}} member, or text that is 
not a number:

||Header sent||{{contentLength_}}||
|{{Content-length: -1}}|4294967295|
|{{Content-length: 99999999999}}|1215752191|
|{{Content-length: abc}}|0|
|{{Content-length: 5x}}|5|

RFC 9110 8.6 has {{Content-Length}} as {{1\*DIGIT}}, which leaves room for none 
of these.

h2. Behaviour change

Refusing an abbreviated header name, and refusing a malformed 
{{Content-Length}}, can turn away
traffic that works today. Both belong in the release notes, and a 
{{lib/cpp/README.md}} entry
comes with the patch.

_Filed with AI assistance (Claude Opus 5); reviewed and filed by Jens Geyer._



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to