Go 1.22.5 repairs connection reuse after early HTTP responses
A final response to an Expect request could leave an HTTP/1.1 connection unusable for its next request. The July fix sends the outstanding body or closes the connection.
A reverse proxy can return one response successfully while leaving its backend connection in a state that breaks the next request. That is the failure described by CVE-2024-24791. It involves a legitimate optimization, Expect: 100-continue, and an HTTP/1.1 client that stopped sending a request body after an early final response but retained the connection for reuse. Go 1.22.5, released on July 2, repairs the client's handling of that combination.
The security team's disclosure in issue 67555 explains the reverse-proxy consequence. An attacker can send an expectation-bearing request that elicits a final backend response. The mishandled exchange leaves an invalid connection, and a subsequent request using that connection fails. This is a denial-of-service issue with a concrete relationship between the triggering request and the next failure.
I like Go's standard HTTP abstractions because a service can often express its networking behavior with relatively little machinery. Connection pooling is still stateful protocol work. This fix addresses a point where a successful response did not mean that the underlying connection was ready for another exchange.
The expectation lets a server answer before the body
RFC 9110's Expect section describes why a client uses 100-continue. A client intending to send content can first send the headers and wait for an indication that sending the content is worthwhile. A server may be able to reject the request from the method, target, or headers alone.
For example, a server that can reject an unauthenticated upload need not wait for the upload body before returning its final response. The expectation avoids transferring a potentially large body only to discover that rejection. A 100 Continue interim response instead encourages the client to send the content.
The protocol does not require the client to wait indefinitely. It can send the body without receiving that interim response. The RFC also permits the server to omit 100 Continue if it has already received some or all of the content. These possibilities mean the implementation must handle several valid orderings of headers, content, and responses.
An early final response is consequently an expected protocol path. It does not by itself establish that the connection is broken or that the server is behaving maliciously. The client still has to decide what happens to the outstanding body and whether the connection can carry another request.
HTTP/1.1 still needs the request boundary completed
The original issue explains the crucial HTTP/1 distinction. A client must send the request body or close the connection. Receiving a final response does not permit the client to begin another request on that same connection while leaving the current body outstanding.
The vulnerable HTTP/1 client did not send the body when a final response arrived before ExpectContinueTimeout expired. That choice could leave the connection in an invalid state. Reusing it for the next request exposed the unfinished exchange to another operation.
HTTP/2 has a different mechanism. The issue notes that a client can reset a stream and continue using the connection without sending that stream's body. The same shortcut is unavailable for a sequential HTTP/1 request whose body has not been completed. Applying one protocol's intuition to the other would lose this distinction.
The bug is therefore more specific than a generic problem with keep-alive. Connection reuse depends on the preceding exchange having reached a valid boundary. An application can receive the final response headers before that condition is satisfied on the write side.
The repair connects response handling to body transmission
The released transport implementation contains the decision in readResponse. If a continuation channel remains after the client reads a terminal response, the transport knows that it sent an expectation but has not received 100 Continue.
The fixed code examines whether either the response or request requires closing the connection. If so, it closes the continuation channel and the write loop skips the body. If the connection will remain open, it signals the write loop to send the body. This makes body transmission agree with the decision to retain the connection.
The associated wait function interprets the channel signal. Receiving a value permits body transmission, while observing a closed channel prevents it. Timeout permits transmission, while connection closure prevents it. These branches coordinate the reader's knowledge of the response with the writer that may still be waiting to send content.
This is why merely changing the timeout would be an incomplete explanation of the fix. The missing rule concerns a terminal response arriving while the writer is waiting. The released code makes an explicit decision at that event instead of relying on the timer to determine whether the request becomes complete.
The source also comments on 101 Switching Protocols, where the future protocol's body expectations can be ambiguous. Its policy is consistent with the timeout behavior. If the connection is not closing, it sends the body. That detail shows that the repair belongs in transport state management, where all terminal response paths meet.
A proxy exposes the effect to other requests
The vulnerability matters to reverse proxies because their backend client handles requests arriving from outside. A specially chosen request can influence the state of a connection that the proxy later selects for another request. The security disclosure states that each triggering exchange can cause one subsequent request on that invalid connection to fail.
That does not establish that every backend connection becomes invalid or that every request after an attack fails. The failure follows reuse of the particular connection left in the wrong state. Keeping that scope explicit explains both the attack and the role of pooling without inventing an effect on unrelated connections.
The Go 1.22 backport record links the change that sends the body or closes the connection for expectation-bearing requests. This issue began as a broader investigation of client and server handling, including HTTP/2 behavior. The security disclosure identifies the HTTP/1.1 client problem and its reverse-proxy denial of service. Those scopes should not be conflated.
I consider the implementation's final rule easy to justify operationally. Reusing a connection means finishing the outstanding request framing. Closing it permits abandoning the body. Go 1.22.5 makes those two decisions agree when a final response arrives before the expected interim response.