Skip to content

Commit 1045351

Browse files
committed
net/http: bound the number of bytes read seeking EOF in Handler's Body.Close
If a client sent a POST with a huge request body, calling req.Body.Close in the handler (which is implicit at the end of a request) would end up consuming it all. Put a cap on that, using the same threshold used elsewhere for similar cases. Fixes #9662 Change-Id: I26628413aa5f623a96ef7c2609a8d03c746669e5 Reviewed-on: https://go-review.googlesource.com/11412 Reviewed-by: Andrew Gerrand <[email protected]>
1 parent 26f12be commit 1045351

File tree

4 files changed

+231
-13
lines changed

4 files changed

+231
-13
lines changed

src/net/http/request.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -686,12 +686,13 @@ func ReadRequest(b *bufio.Reader) (req *Request, err error) {
686686

687687
fixPragmaCacheControl(req.Header)
688688

689+
req.Close = shouldClose(req.ProtoMajor, req.ProtoMinor, req.Header, false)
690+
689691
err = readTransfer(req, b)
690692
if err != nil {
691693
return nil, err
692694
}
693695

694-
req.Close = shouldClose(req.ProtoMajor, req.ProtoMinor, req.Header, false)
695696
return req, nil
696697
}
697698

src/net/http/serve_test.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
. "net/http"
2121
"net/http/httptest"
2222
"net/http/httputil"
23+
"net/http/internal"
2324
"net/url"
2425
"os"
2526
"os/exec"
@@ -1167,6 +1168,164 @@ func TestServerUnreadRequestBodyLarge(t *testing.T) {
11671168
}
11681169
}
11691170

1171+
type handlerBodyCloseTest struct {
1172+
bodySize int
1173+
bodyChunked bool
1174+
reqConnClose bool
1175+
1176+
wantEOFSearch bool // should Handler's Body.Close do Reads, looking for EOF?
1177+
wantNextReq bool // should it find the next request on the same conn?
1178+
}
1179+
1180+
func (t handlerBodyCloseTest) connectionHeader() string {
1181+
if t.reqConnClose {
1182+
return "Connection: close\r\n"
1183+
}
1184+
return ""
1185+
}
1186+
1187+
var handlerBodyCloseTests = [...]handlerBodyCloseTest{
1188+
// Small enough to slurp past to the next request +
1189+
// has Content-Length.
1190+
0: {
1191+
bodySize: 20 << 10,
1192+
bodyChunked: false,
1193+
reqConnClose: false,
1194+
wantEOFSearch: true,
1195+
wantNextReq: true,
1196+
},
1197+
1198+
// Small enough to slurp past to the next request +
1199+
// is chunked.
1200+
1: {
1201+
bodySize: 20 << 10,
1202+
bodyChunked: true,
1203+
reqConnClose: false,
1204+
wantEOFSearch: true,
1205+
wantNextReq: true,
1206+
},
1207+
1208+
// Small enough to slurp past to the next request +
1209+
// has Content-Length +
1210+
// declares Connection: close (so pointless to read more).
1211+
2: {
1212+
bodySize: 20 << 10,
1213+
bodyChunked: false,
1214+
reqConnClose: true,
1215+
wantEOFSearch: false,
1216+
wantNextReq: false,
1217+
},
1218+
1219+
// Small enough to slurp past to the next request +
1220+
// declares Connection: close,
1221+
// but chunked, so it might have trailers.
1222+
// TODO: maybe skip this search if no trailers were declared
1223+
// in the headers.
1224+
3: {
1225+
bodySize: 20 << 10,
1226+
bodyChunked: true,
1227+
reqConnClose: true,
1228+
wantEOFSearch: true,
1229+
wantNextReq: false,
1230+
},
1231+
1232+
// Big with Content-Length, so give up immediately if we know it's too big.
1233+
4: {
1234+
bodySize: 1 << 20,
1235+
bodyChunked: false, // has a Content-Length
1236+
reqConnClose: false,
1237+
wantEOFSearch: false,
1238+
wantNextReq: false,
1239+
},
1240+
1241+
// Big chunked, so read a bit before giving up.
1242+
5: {
1243+
bodySize: 1 << 20,
1244+
bodyChunked: true,
1245+
reqConnClose: false,
1246+
wantEOFSearch: true,
1247+
wantNextReq: false,
1248+
},
1249+
1250+
// Big with Connection: close, but chunked, so search for trailers.
1251+
// TODO: maybe skip this search if no trailers were declared
1252+
// in the headers.
1253+
6: {
1254+
bodySize: 1 << 20,
1255+
bodyChunked: true,
1256+
reqConnClose: true,
1257+
wantEOFSearch: true,
1258+
wantNextReq: false,
1259+
},
1260+
1261+
// Big with Connection: close, so don't do any reads on Close.
1262+
// With Content-Length.
1263+
7: {
1264+
bodySize: 1 << 20,
1265+
bodyChunked: false,
1266+
reqConnClose: true,
1267+
wantEOFSearch: false,
1268+
wantNextReq: false,
1269+
},
1270+
}
1271+
1272+
func TestHandlerBodyClose(t *testing.T) {
1273+
for i, tt := range handlerBodyCloseTests {
1274+
testHandlerBodyClose(t, i, tt)
1275+
}
1276+
}
1277+
1278+
func testHandlerBodyClose(t *testing.T, i int, tt handlerBodyCloseTest) {
1279+
conn := new(testConn)
1280+
body := strings.Repeat("x", tt.bodySize)
1281+
if tt.bodyChunked {
1282+
conn.readBuf.WriteString("POST / HTTP/1.1\r\n" +
1283+
"Host: test\r\n" +
1284+
tt.connectionHeader() +
1285+
"Transfer-Encoding: chunked\r\n" +
1286+
"\r\n")
1287+
cw := internal.NewChunkedWriter(&conn.readBuf)
1288+
io.WriteString(cw, body)
1289+
cw.Close()
1290+
conn.readBuf.WriteString("\r\n")
1291+
} else {
1292+
conn.readBuf.Write([]byte(fmt.Sprintf(
1293+
"POST / HTTP/1.1\r\n"+
1294+
"Host: test\r\n"+
1295+
tt.connectionHeader()+
1296+
"Content-Length: %d\r\n"+
1297+
"\r\n", len(body))))
1298+
conn.readBuf.Write([]byte(body))
1299+
}
1300+
if !tt.reqConnClose {
1301+
conn.readBuf.WriteString("GET / HTTP/1.1\r\nHost: test\r\n\r\n")
1302+
}
1303+
conn.closec = make(chan bool, 1)
1304+
1305+
ls := &oneConnListener{conn}
1306+
var numReqs int
1307+
var size0, size1 int
1308+
go Serve(ls, HandlerFunc(func(rw ResponseWriter, req *Request) {
1309+
numReqs++
1310+
if numReqs == 1 {
1311+
size0 = conn.readBuf.Len()
1312+
req.Body.Close()
1313+
size1 = conn.readBuf.Len()
1314+
}
1315+
}))
1316+
<-conn.closec
1317+
if numReqs < 1 || numReqs > 2 {
1318+
t.Fatalf("%d. bug in test. unexpected number of requests = %d", i, numReqs)
1319+
}
1320+
didSearch := size0 != size1
1321+
if didSearch != tt.wantEOFSearch {
1322+
t.Errorf("%d. did EOF search = %v; want %v (size went from %d to %d)", i, didSearch, !didSearch, size0, size1)
1323+
}
1324+
if tt.wantNextReq && numReqs != 2 {
1325+
t.Errorf("%d. numReq = %d; want 2", i, numReqs)
1326+
}
1327+
}
1328+
11701329
func TestTimeoutHandler(t *testing.T) {
11711330
defer afterTest(t)
11721331
sendHi := make(chan bool, 1)

src/net/http/server.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,8 @@ func newBufioReader(r io.Reader) *bufio.Reader {
502502
br.Reset(r)
503503
return br
504504
}
505+
// Note: if this reader size is every changed, update
506+
// TestHandlerBodyClose's assumptions.
505507
return bufio.NewReader(r)
506508
}
507509

@@ -627,6 +629,9 @@ func (c *conn) readRequest() (w *response, err error) {
627629

628630
req.RemoteAddr = c.remoteAddr
629631
req.TLS = c.tlsState
632+
if body, ok := req.Body.(*body); ok {
633+
body.doEarlyClose = true
634+
}
630635

631636
w = &response{
632637
conn: c,
@@ -1088,17 +1093,34 @@ func (w *response) finishRequest() {
10881093
if w.req.MultipartForm != nil {
10891094
w.req.MultipartForm.RemoveAll()
10901095
}
1096+
}
1097+
1098+
// shouldReuseConnection reports whether the underlying TCP connection can be reused.
1099+
// It must only be called after the handler is done executing.
1100+
func (w *response) shouldReuseConnection() bool {
1101+
if w.closeAfterReply {
1102+
// The request or something set while executing the
1103+
// handler indicated we shouldn't reuse this
1104+
// connection.
1105+
return false
1106+
}
10911107

10921108
if w.req.Method != "HEAD" && w.contentLength != -1 && w.bodyAllowed() && w.contentLength != w.written {
10931109
// Did not write enough. Avoid getting out of sync.
1094-
w.closeAfterReply = true
1110+
return false
10951111
}
10961112

10971113
// There was some error writing to the underlying connection
10981114
// during the request, so don't re-use this conn.
10991115
if w.conn.werr != nil {
1100-
w.closeAfterReply = true
1116+
return false
11011117
}
1118+
1119+
if body, ok := w.req.Body.(*body); ok && body.didEarlyClose() {
1120+
return false
1121+
}
1122+
1123+
return true
11021124
}
11031125

11041126
func (w *response) Flush() {
@@ -1267,7 +1289,7 @@ func (c *conn) serve() {
12671289
return
12681290
}
12691291
w.finishRequest()
1270-
if w.closeAfterReply {
1292+
if !w.shouldReuseConnection() {
12711293
if w.requestBodyLimitHit {
12721294
c.closeWriteAndWait()
12731295
}

src/net/http/transfer.go

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type errorReader struct {
2727
err error
2828
}
2929

30-
func (r *errorReader) Read(p []byte) (n int, err error) {
30+
func (r errorReader) Read(p []byte) (n int, err error) {
3131
return 0, r.err
3232
}
3333

@@ -71,7 +71,7 @@ func newTransferWriter(r interface{}) (t *transferWriter, err error) {
7171
n, rerr := io.ReadFull(t.Body, buf[:])
7272
if rerr != nil && rerr != io.EOF {
7373
t.ContentLength = -1
74-
t.Body = &errorReader{rerr}
74+
t.Body = errorReader{rerr}
7575
} else if n == 1 {
7676
// Oh, guess there is data in this Body Reader after all.
7777
// The ContentLength field just wasn't set.
@@ -322,6 +322,7 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err error) {
322322
// Transfer semantics for Requests are exactly like those for
323323
// Responses with status code 200, responding to a GET method
324324
t.StatusCode = 200
325+
t.Close = rr.Close
325326
default:
326327
panic("unexpected type")
327328
}
@@ -561,13 +562,16 @@ func fixTrailer(header Header, te []string) (Header, error) {
561562
// Close ensures that the body has been fully read
562563
// and then reads the trailer if necessary.
563564
type body struct {
564-
src io.Reader
565-
hdr interface{} // non-nil (Response or Request) value means read trailer
566-
r *bufio.Reader // underlying wire-format reader for the trailer
567-
closing bool // is the connection to be closed after reading body?
568-
569-
mu sync.Mutex // guards closed, and calls to Read and Close
570-
closed bool
565+
src io.Reader
566+
hdr interface{} // non-nil (Response or Request) value means read trailer
567+
r *bufio.Reader // underlying wire-format reader for the trailer
568+
closing bool // is the connection to be closed after reading body?
569+
doEarlyClose bool // whether Close should stop early
570+
571+
mu sync.Mutex // guards closed, and calls to Read and Close
572+
sawEOF bool
573+
closed bool
574+
earlyClose bool // Close called and we didn't read to the end of src
571575
}
572576

573577
// ErrBodyReadAfterClose is returned when reading a Request or Response
@@ -587,9 +591,13 @@ func (b *body) Read(p []byte) (n int, err error) {
587591

588592
// Must hold b.mu.
589593
func (b *body) readLocked(p []byte) (n int, err error) {
594+
if b.sawEOF {
595+
return 0, io.EOF
596+
}
590597
n, err = b.src.Read(p)
591598

592599
if err == io.EOF {
600+
b.sawEOF = true
593601
// Chunked case. Read the trailer.
594602
if b.hdr != nil {
595603
if e := b.readTrailer(); e != nil {
@@ -613,6 +621,7 @@ func (b *body) readLocked(p []byte) (n int, err error) {
613621
if err == nil && n > 0 {
614622
if lr, ok := b.src.(*io.LimitedReader); ok && lr.N == 0 {
615623
err = io.EOF
624+
b.sawEOF = true
616625
}
617626
}
618627

@@ -701,9 +710,30 @@ func (b *body) Close() error {
701710
}
702711
var err error
703712
switch {
713+
case b.sawEOF:
714+
// Already saw EOF, so no need going to look for it.
704715
case b.hdr == nil && b.closing:
705716
// no trailer and closing the connection next.
706717
// no point in reading to EOF.
718+
case b.doEarlyClose:
719+
// Read up to maxPostHandlerReadBytes bytes of the body, looking for
720+
// for EOF (and trailers), so we can re-use this connection.
721+
if lr, ok := b.src.(*io.LimitedReader); ok && lr.N > maxPostHandlerReadBytes {
722+
// There was a declared Content-Length, and we have more bytes remaining
723+
// than our maxPostHandlerReadBytes tolerance. So, give up.
724+
b.earlyClose = true
725+
} else {
726+
var n int64
727+
// Consume the body, or, which will also lead to us reading
728+
// the trailer headers after the body, if present.
729+
n, err = io.CopyN(ioutil.Discard, bodyLocked{b}, maxPostHandlerReadBytes)
730+
if err == io.EOF {
731+
err = nil
732+
}
733+
if n == maxPostHandlerReadBytes {
734+
b.earlyClose = true
735+
}
736+
}
707737
default:
708738
// Fully consume the body, which will also lead to us reading
709739
// the trailer headers after the body, if present.
@@ -713,6 +743,12 @@ func (b *body) Close() error {
713743
return err
714744
}
715745

746+
func (b *body) didEarlyClose() bool {
747+
b.mu.Lock()
748+
defer b.mu.Unlock()
749+
return b.earlyClose
750+
}
751+
716752
// bodyLocked is a io.Reader reading from a *body when its mutex is
717753
// already held.
718754
type bodyLocked struct {

0 commit comments

Comments
 (0)