Skip to content

Commit aac8503

Browse files
net/http: disable 100 continue status after handler finished
When client supplies "Expect: 100-continue" header, server wraps request body into expectContinueReader that writes 100 Continue status on the first body read. When handler acts as a reverse proxy and passes incoming request (or body) to the client (or transport) it may happen that request body is read after handler exists which may cause nil pointer panic on connection write if server already closed the connection. This change disables write of 100 Continue status by expectContinueReader after handler finished and before connection is closed. It also fixes racy access to w.wroteContinue. Fixes #53808 Updates #46866
1 parent bb523c9 commit aac8503

File tree

3 files changed

+61
-35
lines changed

3 files changed

+61
-35
lines changed

src/net/http/clientserver_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1754,3 +1754,28 @@ func testEarlyHintsRequest(t *testing.T, mode testMode) {
17541754
t.Errorf("Read body %q; want Hello", body)
17551755
}
17561756
}
1757+
1758+
// Issue 53808
1759+
func TestServerReadAfterHandlerDone100Continue(t *testing.T) {
1760+
run(t, testServerReadAfterHandlerDone100Continue)
1761+
}
1762+
func testServerReadAfterHandlerDone100Continue(t *testing.T, mode testMode) {
1763+
readyc := make(chan struct{})
1764+
cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) {
1765+
go func() {
1766+
<-readyc
1767+
io.ReadAll(r.Body)
1768+
<-readyc
1769+
}()
1770+
}))
1771+
1772+
req, _ := NewRequest("GET", cst.ts.URL, strings.NewReader("body"))
1773+
req.Header.Set("Expect", "100-continue")
1774+
res, err := cst.c.Do(req)
1775+
if err != nil {
1776+
t.Fatalf("Get(%q) = %v", cst.ts.URL, err)
1777+
}
1778+
res.Body.Close()
1779+
readyc <- struct{}{} // server starts reading from the request body
1780+
readyc <- struct{}{} // server finishes reading from the request body
1781+
}

src/net/http/server.go

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -425,19 +425,14 @@ 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

432-
// canWriteContinue is an atomic boolean that says whether or
433-
// not a 100 Continue header can be written to the
434-
// connection.
435-
// writeContinueMu must be held while writing the header.
436-
// These two fields together synchronize the body reader (the
437-
// expectContinueReader, which wants to write 100 Continue)
438-
// against the main writer.
439-
canWriteContinue atomic.Bool
440-
writeContinueMu sync.Mutex
431+
// writeContinue synchronizes the body reader (the
432+
// expectContinueReader, which wants to write 100 Continue
433+
// to the connection) against the main writer.
434+
writeContinue sync.Once
435+
wroteContinue atomic.Bool // 100 Continue response was written
441436

442437
w *bufio.Writer // buffers output in chunks to chunkWriter
443438
cw chunkWriter
@@ -917,15 +912,12 @@ func (ecr *expectContinueReader) Read(p []byte) (n int, err error) {
917912
return 0, ErrBodyReadAfterClose
918913
}
919914
w := ecr.resp
920-
if !w.wroteContinue && w.canWriteContinue.Load() && !w.conn.hijacked() {
921-
w.wroteContinue = true
922-
w.writeContinueMu.Lock()
923-
if w.canWriteContinue.Load() {
915+
if !w.conn.hijacked() {
916+
w.writeContinue.Do(func() {
917+
w.wroteContinue.Store(true)
924918
w.conn.bufw.WriteString("HTTP/1.1 100 Continue\r\n\r\n")
925919
w.conn.bufw.Flush()
926-
w.canWriteContinue.Store(false)
927-
}
928-
w.writeContinueMu.Unlock()
920+
})
929921
}
930922
n, err = ecr.readCloser.Read(p)
931923
if err == io.EOF {
@@ -1165,10 +1157,8 @@ func (w *response) WriteHeader(code int) {
11651157
// so it takes the non-informational path.
11661158
if code >= 100 && code <= 199 && code != StatusSwitchingProtocols {
11671159
// 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()
1160+
if code == 100 {
1161+
w.disableContinue()
11721162
}
11731163

11741164
writeStatusLine(w.conn.bufw, w.req.ProtoAtLeast(1, 1), code, w.statusBuf[:])
@@ -1383,7 +1373,7 @@ func (cw *chunkWriter) writeHeader(p []byte) {
13831373

13841374
switch bdy := w.req.Body.(type) {
13851375
case *expectContinueReader:
1386-
if bdy.resp.wroteContinue {
1376+
if bdy.resp.wroteContinue.Load() {
13871377
discard = true
13881378
}
13891379
case *body:
@@ -1625,15 +1615,7 @@ func (w *response) write(lenData int, dataB []byte, dataS string) (n int, err er
16251615
return 0, ErrHijacked
16261616
}
16271617

1628-
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()
1636-
}
1618+
w.disableContinue()
16371619

16381620
if !w.wroteHeader {
16391621
w.WriteHeader(StatusOK)
@@ -1679,6 +1661,11 @@ func (w *response) finishRequest() {
16791661
}
16801662
}
16811663

1664+
// disableContinue tells body reader not to write 100 Continue.
1665+
func (w *response) disableContinue() {
1666+
w.writeContinue.Do(func() {})
1667+
}
1668+
16821669
// shouldReuseConnection reports whether the underlying TCP connection can be reused.
16831670
// It must only be called after the handler is done executing.
16841671
func (w *response) shouldReuseConnection() bool {
@@ -1905,6 +1892,7 @@ func (c *conn) serve(ctx context.Context) {
19051892
if inFlightResponse != nil {
19061893
inFlightResponse.conn.r.abortPendingRead()
19071894
inFlightResponse.reqBody.Close()
1895+
inFlightResponse.disableContinue()
19081896
}
19091897
c.close()
19101898
c.setState(c.rwc, StateClosed, runHooks)
@@ -2016,7 +2004,6 @@ func (c *conn) serve(ctx context.Context) {
20162004
if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
20172005
// Wrap the Body reader with one that replies on the connection
20182006
req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
2019-
w.canWriteContinue.Store(true)
20202007
}
20212008
} else if req.Header.get("Expect") != "" {
20222009
w.sendExpectationFailed()
@@ -2046,6 +2033,7 @@ func (c *conn) serve(ctx context.Context) {
20462033
return
20472034
}
20482035
w.finishRequest()
2036+
w.disableContinue()
20492037
c.rwc.SetWriteDeadline(time.Time{})
20502038
if !w.shouldReuseConnection() {
20512039
if w.requestBodyLimitHit || w.closedRequestBodyEarly() {

src/net/http/transport_test.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"log"
2727
mrand "math/rand"
2828
"net"
29+
"net/http"
2930
. "net/http"
3031
"net/http/httptest"
3132
"net/http/httptrace"
@@ -6901,19 +6902,31 @@ func testHandlerAbortRacesBodyRead(t *testing.T, mode testMode) {
69016902
panic(ErrAbortHandler)
69026903
})).ts
69036904

6905+
newRequest := func() *http.Request {
6906+
const reqLen = 6 * 1024 * 1024
6907+
req, _ := NewRequest("POST", ts.URL, &io.LimitedReader{R: neverEnding('x'), N: reqLen})
6908+
req.ContentLength = reqLen
6909+
return req
6910+
}
6911+
69046912
var wg sync.WaitGroup
69056913
for i := 0; i < 2; i++ {
69066914
wg.Add(1)
69076915
go func() {
69086916
defer wg.Done()
69096917
for j := 0; j < 10; j++ {
6910-
const reqLen = 6 * 1024 * 1024
6911-
req, _ := NewRequest("POST", ts.URL, &io.LimitedReader{R: neverEnding('x'), N: reqLen})
6912-
req.ContentLength = reqLen
6918+
req := newRequest()
69136919
resp, _ := ts.Client().Transport.RoundTrip(req)
69146920
if resp != nil {
69156921
resp.Body.Close()
69166922
}
6923+
6924+
req = newRequest()
6925+
req.Header.Set("Expect", "100-continue")
6926+
resp, _ = ts.Client().Transport.RoundTrip(req)
6927+
if resp != nil {
6928+
resp.Body.Close()
6929+
}
69176930
}
69186931
}()
69196932
}

0 commit comments

Comments
 (0)