This is a follow-up to #67382. See that issue for some prior discussion. (That issue is also full of watchflakes reports, so I'm letting it be about the test flakiness. This issue is about the underlying problem.)

An HTTP/1 or HTTP/2 client can send an Expect: 100-continue header in a request to indicate that it might not send the request body until the server asks for it. The client is permitted to send the request body anyway, either immediately or after a delay.

A server may respond to a Expect: 100-continue request with an informational 100 status, indicating that it would like the client to send the request body. A server may also respond with a non-1xx status, without waiting for the request body.

For HTTP/1, a client must send the body for a request or close the connection. If a client sends a Expect: 100-continue request and receives a non-1xx response from the server, it still needs to finish sending the current request's body before sending any further requests on the connection.

For HTTP/2, a client can reset a request's stream and continue to use the connection without sending the body.

This issue is about how our client and server implementations handle the case of a server responding to an Expect: 100-continue request with a non-1xx response.

Client: If we receive a non-1xx response to an Expect: 100-continue request, should we send the request body on the assumption that the server might want it? Or should we assume that receiving response headers from the server indicates that the server didn't need the body? Should we modify this behavior for different response codes? A 200 OK might indicate that the server is going to use the request body, but a 403 Forbidden probably does not. Should we modify this behavior based on the presence or absence of a Connection: close header in the response? (Note that for HTTP/1 requests, if we do not send the request body, we may not reuse the connection for further requests.)

Server: When handling an Expect: 100-continue request, we automatically send a 100 Continue response if the server handler reads from the request body. What should we do if the server handler calls WriteHeader to send a non-1xx response, and then reads from the request body? Should we assume the server handler knows what it's doing, and trust that the client is going to send us a request body? Or should we return an error from the body read?

Our current behavior is inconsistent and occasionally wrong.

The HTTP/1 client does not send the request body if it receives a non-1xx response before ExpectContinueTimeout expires. This can leave the connection in an invalid state. (Server responds with a non-1xx response and attempts to read the request body; client reuses the connection for another request without sending the body.)

The HTTP/2 client always sends the request body after ExpectContinueTimeout, regardless of whether it has received a non-1xx response or not. If the server is trying to read the body, it will eventually get it, but only after a timeout.

Both HTTP/1 and HTTP/2 servers appear to permit reading from a request body even after sending a non-1xx response (and no 100 Continue response).

I'm not certain yet what we should be doing, but at a minimum:

  • The HTTP/1 client should not send any further requests on a connection if it decides not to send a request body.
  • The HTTP/2 client should disable the ExpectContinueTimeout timer after receiving a non-1xx response. It should either send or not send the request body, but it shouldn't keep delaying.
  • We should make an intentional decision on whether a server handler reading from a Expect: 100-continue request body after sending response headers gets an error or not.

Comment From: gopherbot

Change https://go.dev/cl/587155 mentions this issue: net/http: disable flaky 100-continue tests

Comment From: gopherbot

Change https://go.dev/cl/591255 mentions this issue: net/http: send body or close connection on expect-100-continue requests

Comment From: neild

@gopherbot please open backport issues. This is a significant bug with no workaround.

Comment From: gopherbot

Backport issue(s) opened: #68199 (for 1.21), #68200 (for 1.22).

Remember to create the cherry-pick CL(s) as soon as the patch is submitted to master, according to https://go.dev/wiki/MinorReleases.

Comment From: gopherbot

Change https://go.dev/cl/595096 mentions this issue: [release-branch.go1.21] net/http: send body or close connection on expect-100-continue requests

Comment From: gopherbot

Change https://go.dev/cl/595235 mentions this issue: [release-branch.go1.22] net/http: send body or close connection on expect-100-continue requests

Comment From: thanm

From the security team related to this issue:

The net/http HTTP/1.1 client mishandled the case where a server responds to a request with an "Expect: 100-continue" header with a non-informational (200 or higher) status. This mishandling could leave a client connection in an invalid state, where the next request sent on the connection will fail.

An attacker sending a request to a net/http/httputil.ReverseProxy proxy can exploit this mishandling to cause a denial of service by sending "Expect: 100-continue" requests which elicit a non-informational response from the backend. Each such request leaves the proxy with an invalid connection, and causes one subsequent request using that connection to fail.

Thanks to Geoff Franks for reporting this issue.

This is CVE-2024-24791 and Go issue https://go.dev/issue/67555.

Comment From: 864893031735973

6a7a631025117de930d2118545eb35125

Comment From: 864893031735973

https://github.com/golang/go/issues/67555#top

Comment From: cherrymui

As the CLs are in and the backports are done, is there anything else needed for this issue, or we can close this? Thanks.