Skip to content

Commit a524b87

Browse files
committed
net/http: avoid panic when writing 100-continue after handler done
When a request contains an "Expect: 100-continue" header, the first read from the request body causes the server to write a 100-continue status. This write caused a panic when performed after the server handler has exited. Disable the write when cleaning up after a handler exits. This also fixes a bug where an implicit 100-continue could be sent after a call to WriteHeader has sent a non-1xx header. This change drops tracking of whether we've written a 100-continue or not in response.wroteContinue. This tracking was used to determine whether we should consume the remaining request body in chunkWriter.writeHeader, but the discard-the-body path was only taken when the body was already consumed. (If the body is not consumed, we set closeAfterReply, and we don't consume the remaining body when closeAfterReply is set. If the body is consumed, then we may attempt to discard the remaining body, but there is obviously no body remaining.) Fixes #53808 Change-Id: I3542df26ad6cdfe93b50a45ae2d6e7ef031e46fa Reviewed-on: https://go-review.googlesource.com/c/go/+/585395 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Jonathan Amsterdam <[email protected]>
1 parent b86527e commit a524b87

File tree

2 files changed

+105
-21
lines changed

2 files changed

+105
-21
lines changed

src/net/http/serve_test.go

+76
Original file line numberDiff line numberDiff line change
@@ -7166,3 +7166,79 @@ func TestError(t *testing.T) {
71667166
t.Errorf("X-Content-Type-Options: %q, want %q", v, "nosniff")
71677167
}
71687168
}
7169+
7170+
func TestServerReadAfterWriteHeader100Continue(t *testing.T) {
7171+
run(t, testServerReadAfterWriteHeader100Continue)
7172+
}
7173+
func testServerReadAfterWriteHeader100Continue(t *testing.T, mode testMode) {
7174+
body := []byte("body")
7175+
cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) {
7176+
w.WriteHeader(200)
7177+
NewResponseController(w).Flush()
7178+
io.ReadAll(r.Body)
7179+
w.Write(body)
7180+
}))
7181+
7182+
req, _ := NewRequest("GET", cst.ts.URL, strings.NewReader("body"))
7183+
req.Header.Set("Expect", "100-continue")
7184+
res, err := cst.c.Do(req)
7185+
if err != nil {
7186+
t.Fatalf("Get(%q) = %v", cst.ts.URL, err)
7187+
}
7188+
defer res.Body.Close()
7189+
got, err := io.ReadAll(res.Body)
7190+
if err != nil {
7191+
t.Fatalf("io.ReadAll(res.Body) = %v", err)
7192+
}
7193+
if !bytes.Equal(got, body) {
7194+
t.Fatalf("response body = %q, want %q", got, body)
7195+
}
7196+
}
7197+
7198+
func TestServerReadAfterHandlerDone100Continue(t *testing.T) {
7199+
run(t, testServerReadAfterHandlerDone100Continue)
7200+
}
7201+
func testServerReadAfterHandlerDone100Continue(t *testing.T, mode testMode) {
7202+
readyc := make(chan struct{})
7203+
cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) {
7204+
go func() {
7205+
<-readyc
7206+
io.ReadAll(r.Body)
7207+
<-readyc
7208+
}()
7209+
}))
7210+
7211+
req, _ := NewRequest("GET", cst.ts.URL, strings.NewReader("body"))
7212+
req.Header.Set("Expect", "100-continue")
7213+
res, err := cst.c.Do(req)
7214+
if err != nil {
7215+
t.Fatalf("Get(%q) = %v", cst.ts.URL, err)
7216+
}
7217+
res.Body.Close()
7218+
readyc <- struct{}{} // server starts reading from the request body
7219+
readyc <- struct{}{} // server finishes reading from the request body
7220+
}
7221+
7222+
func TestServerReadAfterHandlerAbort100Continue(t *testing.T) {
7223+
run(t, testServerReadAfterHandlerAbort100Continue)
7224+
}
7225+
func testServerReadAfterHandlerAbort100Continue(t *testing.T, mode testMode) {
7226+
readyc := make(chan struct{})
7227+
cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) {
7228+
go func() {
7229+
<-readyc
7230+
io.ReadAll(r.Body)
7231+
<-readyc
7232+
}()
7233+
panic(ErrAbortHandler)
7234+
}))
7235+
7236+
req, _ := NewRequest("GET", cst.ts.URL, strings.NewReader("body"))
7237+
req.Header.Set("Expect", "100-continue")
7238+
res, err := cst.c.Do(req)
7239+
if err == nil {
7240+
res.Body.Close()
7241+
}
7242+
readyc <- struct{}{} // server starts reading from the request body
7243+
readyc <- struct{}{} // server finishes reading from the request body
7244+
}

src/net/http/server.go

+29-21
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,6 @@ type response struct {
425425
reqBody io.ReadCloser
426426
cancelCtx context.CancelFunc // when ServeHTTP exits
427427
wroteHeader bool // a non-1xx header has been (logically) written
428-
wroteContinue bool // 100 Continue response was written
429428
wants10KeepAlive bool // HTTP/1.0 w/ Connection "keep-alive"
430429
wantsClose bool // HTTP request has Connection "close"
431430

@@ -436,8 +435,8 @@ type response struct {
436435
// These two fields together synchronize the body reader (the
437436
// expectContinueReader, which wants to write 100 Continue)
438437
// against the main writer.
439-
canWriteContinue atomic.Bool
440438
writeContinueMu sync.Mutex
439+
canWriteContinue atomic.Bool
441440

442441
w *bufio.Writer // buffers output in chunks to chunkWriter
443442
cw chunkWriter
@@ -565,6 +564,14 @@ func (w *response) requestTooLarge() {
565564
}
566565
}
567566

567+
// disableWriteContinue stops Request.Body.Read from sending an automatic 100-Continue.
568+
// If a 100-Continue is being written, it waits for it to complete before continuing.
569+
func (w *response) disableWriteContinue() {
570+
w.writeContinueMu.Lock()
571+
w.canWriteContinue.Store(false)
572+
w.writeContinueMu.Unlock()
573+
}
574+
568575
// writerOnly hides an io.Writer value's optional ReadFrom method
569576
// from io.Copy.
570577
type writerOnly struct {
@@ -917,8 +924,7 @@ func (ecr *expectContinueReader) Read(p []byte) (n int, err error) {
917924
return 0, ErrBodyReadAfterClose
918925
}
919926
w := ecr.resp
920-
if !w.wroteContinue && w.canWriteContinue.Load() && !w.conn.hijacked() {
921-
w.wroteContinue = true
927+
if w.canWriteContinue.Load() {
922928
w.writeContinueMu.Lock()
923929
if w.canWriteContinue.Load() {
924930
w.conn.bufw.WriteString("HTTP/1.1 100 Continue\r\n\r\n")
@@ -1159,18 +1165,17 @@ func (w *response) WriteHeader(code int) {
11591165
}
11601166
checkWriteHeaderCode(code)
11611167

1168+
if code < 101 || code > 199 {
1169+
// Sending a 100 Continue or any non-1xx header disables the
1170+
// automatically-sent 100 Continue from Request.Body.Read.
1171+
w.disableWriteContinue()
1172+
}
1173+
11621174
// Handle informational headers.
11631175
//
11641176
// We shouldn't send any further headers after 101 Switching Protocols,
11651177
// so it takes the non-informational path.
11661178
if code >= 100 && code <= 199 && code != StatusSwitchingProtocols {
1167-
// Prevent a potential race with an automatically-sent 100 Continue triggered by Request.Body.Read()
1168-
if code == 100 && w.canWriteContinue.Load() {
1169-
w.writeContinueMu.Lock()
1170-
w.canWriteContinue.Store(false)
1171-
w.writeContinueMu.Unlock()
1172-
}
1173-
11741179
writeStatusLine(w.conn.bufw, w.req.ProtoAtLeast(1, 1), code, w.statusBuf[:])
11751180

11761181
// Per RFC 8297 we must not clear the current header map
@@ -1378,14 +1383,20 @@ func (cw *chunkWriter) writeHeader(p []byte) {
13781383
//
13791384
// If full duplex mode has been enabled with ResponseController.EnableFullDuplex,
13801385
// then leave the request body alone.
1386+
//
1387+
// We don't take this path when w.closeAfterReply is set.
1388+
// We may not need to consume the request to get ready for the next one
1389+
// (since we're closing the conn), but a client which sends a full request
1390+
// before reading a response may deadlock in this case.
1391+
// This behavior has been present since CL 5268043 (2011), however,
1392+
// so it doesn't seem to be causing problems.
13811393
if w.req.ContentLength != 0 && !w.closeAfterReply && !w.fullDuplex {
13821394
var discard, tooBig bool
13831395

13841396
switch bdy := w.req.Body.(type) {
13851397
case *expectContinueReader:
1386-
if bdy.resp.wroteContinue {
1387-
discard = true
1388-
}
1398+
// We only get here if we have already fully consumed the request body
1399+
// (see above).
13891400
case *body:
13901401
bdy.mu.Lock()
13911402
switch {
@@ -1626,13 +1637,8 @@ func (w *response) write(lenData int, dataB []byte, dataS string) (n int, err er
16261637
}
16271638

16281639
if w.canWriteContinue.Load() {
1629-
// Body reader wants to write 100 Continue but hasn't yet.
1630-
// Tell it not to. The store must be done while holding the lock
1631-
// because the lock makes sure that there is not an active write
1632-
// this very moment.
1633-
w.writeContinueMu.Lock()
1634-
w.canWriteContinue.Store(false)
1635-
w.writeContinueMu.Unlock()
1640+
// Body reader wants to write 100 Continue but hasn't yet. Tell it not to.
1641+
w.disableWriteContinue()
16361642
}
16371643

16381644
if !w.wroteHeader {
@@ -1900,6 +1906,7 @@ func (c *conn) serve(ctx context.Context) {
19001906
}
19011907
if inFlightResponse != nil {
19021908
inFlightResponse.cancelCtx()
1909+
inFlightResponse.disableWriteContinue()
19031910
}
19041911
if !c.hijacked() {
19051912
if inFlightResponse != nil {
@@ -2106,6 +2113,7 @@ func (w *response) Hijack() (rwc net.Conn, buf *bufio.ReadWriter, err error) {
21062113
if w.handlerDone.Load() {
21072114
panic("net/http: Hijack called after ServeHTTP finished")
21082115
}
2116+
w.disableWriteContinue()
21092117
if w.wroteHeader {
21102118
w.cw.flush()
21112119
}

0 commit comments

Comments
 (0)