Skip to content

Commit accf363

Browse files
neildFiloSottile
authored andcommitted
[release-branch.go1.16] net/http/httputil: close incoming ReverseProxy request body
Reading from an incoming request body after the request handler aborts with a panic can cause a panic, becuse http.Server does not (contrary to its documentation) close the request body in this case. Always close the incoming request body in ReverseProxy.ServeHTTP to ensure that any in-flight outgoing requests using the body do not read from it. Fixes #47474 Updates #46866 Fixes CVE-2021-36221 Change-Id: I310df269200ad8732c5d9f1a2b00de68725831df Reviewed-on: https://go-review.googlesource.com/c/go/+/333191 Trust: Damien Neil <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]> Reviewed-by: Filippo Valsorda <[email protected]> (cherry picked from commit b7a85e0) Reviewed-on: https://go-review.googlesource.com/c/go/+/338551 Trust: Filippo Valsorda <[email protected]> Run-TryBot: Filippo Valsorda <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Damien Neil <[email protected]>
1 parent ae7943e commit accf363

File tree

2 files changed

+48
-0
lines changed

2 files changed

+48
-0
lines changed

src/net/http/httputil/reverseproxy.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,15 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
234234
if req.ContentLength == 0 {
235235
outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
236236
}
237+
if outreq.Body != nil {
238+
// Reading from the request body after returning from a handler is not
239+
// allowed, and the RoundTrip goroutine that reads the Body can outlive
240+
// this handler. This can lead to a crash if the handler panics (see
241+
// Issue 46866). Although calling Close doesn't guarantee there isn't
242+
// any Read in flight after the handle returns, in practice it's safe to
243+
// read after closing it.
244+
defer outreq.Body.Close()
245+
}
237246
if outreq.Header == nil {
238247
outreq.Header = make(http.Header) // Issue 33142: historical behavior was to always allocate
239248
}

src/net/http/httputil/reverseproxy_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,6 +1121,45 @@ func TestReverseProxy_PanicBodyError(t *testing.T) {
11211121
rproxy.ServeHTTP(httptest.NewRecorder(), req)
11221122
}
11231123

1124+
// Issue #46866: panic without closing incoming request body causes a panic
1125+
func TestReverseProxy_PanicClosesIncomingBody(t *testing.T) {
1126+
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1127+
out := "this call was relayed by the reverse proxy"
1128+
// Coerce a wrong content length to induce io.ErrUnexpectedEOF
1129+
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(out)*2))
1130+
fmt.Fprintln(w, out)
1131+
}))
1132+
defer backend.Close()
1133+
backendURL, err := url.Parse(backend.URL)
1134+
if err != nil {
1135+
t.Fatal(err)
1136+
}
1137+
proxyHandler := NewSingleHostReverseProxy(backendURL)
1138+
proxyHandler.ErrorLog = log.New(io.Discard, "", 0) // quiet for tests
1139+
frontend := httptest.NewServer(proxyHandler)
1140+
defer frontend.Close()
1141+
frontendClient := frontend.Client()
1142+
1143+
var wg sync.WaitGroup
1144+
for i := 0; i < 2; i++ {
1145+
wg.Add(1)
1146+
go func() {
1147+
defer wg.Done()
1148+
for j := 0; j < 10; j++ {
1149+
const reqLen = 6 * 1024 * 1024
1150+
req, _ := http.NewRequest("POST", frontend.URL, &io.LimitedReader{R: neverEnding('x'), N: reqLen})
1151+
req.ContentLength = reqLen
1152+
resp, _ := frontendClient.Transport.RoundTrip(req)
1153+
if resp != nil {
1154+
io.Copy(io.Discard, resp.Body)
1155+
resp.Body.Close()
1156+
}
1157+
}
1158+
}()
1159+
}
1160+
wg.Wait()
1161+
}
1162+
11241163
func TestSelectFlushInterval(t *testing.T) {
11251164
tests := []struct {
11261165
name string

0 commit comments

Comments
 (0)