Repository navigation
HTTP/1.1 connection reuse accepts a buffered surplus response for the next request #1131
Unanswered
t4lesss
asked this question in
Potential Issue
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Hey,
I found a response-association problem in HTTP/1.1 connection reuse. If a network read contains a complete final response followed by another complete response, httpcore can return the connection to the pool. The next request then receives that surplus response, even though its bytes arrived before the request was sent. Both requests appear successful.
I reproduced this with CPython 3.14.8, httpcore 1.0.9 and h11 0.16.0. Separate loopback tests also reproduced it through HTTPX 0.28.1 with sync and async clients over HTTP, and an async client over HTTPS. I inspected master at
10a658221deb38a4c5b16db55ab554b0bf731707; it still has the same reuse condition. The runtime tests used the released version.Here is a deterministic reproducer. Only the network reads are scripted; parsing, connection state and pooling use the actual httpcore/h11 implementations. The first read contains the response to
/oneplus the surplus, before/twohas been requested.This prints
(b'one', b'extra')and raises the assertion.I would expect the contaminated connection to be retired, allowing
/twoto receiveb'two'from a new connection. Rejecting the malformed exchange with a protocol error would also prevent the incorrect association.The relevant code is the DONE/DONE branch of
_response_closed()in both_sync/http11.pyand_async/http11.py. It callsstart_next_cycle()without checkingself._h11_state.trailing_data. h11 retains its receive buffer across that call. The socket-readability check inhas_expired()cannot detect bytes already held by h11.The peer is violating HTTP/1.1, but the client has enough information to contain the violation at this point. RFC 9112 §6.3 prohibits processing surplus after a complete final response as a separate response.
A process-local guard that closes the connection when DONE/DONE leaves nonempty
trailing_data[0]made the reproducer return(b'one', b'two'). Local controls preserved ordinary reuse and103 Early Hints; sync controls also covered HEAD, chunked responses and a101upgrade. A production fix would need to preserve the distinct trailing-data handling for successful CONNECT and protocol upgrades.This is related to encode/httpx#1474 and msqd/harp#1150, where surplus bytes cause a later parse error. The additional case here is a well-formed surplus response being accepted without an error.
In local tests with multiple callers sharing a pool for the same origin, the shift also caused one caller to receive another caller's actual reply. Security impact in a deployment would depend on an additional way to make a legitimate server or intermediary emit the surplus. I have not demonstrated that chain against a real service or assigned a severity.
Would closing the connection on buffered surplus in the DONE/DONE branch be the right fix here?
// Tales S.
All reactions