mirror of
https://github.com/golang/go.git
synced 2026-04-02 09:20:29 +09:00
net/http: make ResponseWriter.ReadFrom respect declared Content-Length
Unlike ResponseWriter.Write, ResponseWriter.ReadFrom does not currently respect declared Content-Length header. As a result, it is possible for a server handler to inadvertently write more bytes for their response body than has been declared via Content-Length. These excess bytes are written to the wire, and could potentially be misinterpreted by a client as a separate response. That said, this is not a concern security-wise. This edge case can only be exercised by someone who has a relatively complete control of a server handler—by which point, worse things can be done with less effort. Regardless, this is still a bug. Therefore, make sure that ResponseWriter.ReadFrom respects declared Content-Length too for consistency. Fixes #78179 Change-Id: I469b064e43e49e467b907d23fc1ee879066569f0 Reviewed-on: https://go-review.googlesource.com/c/go/+/755701 Reviewed-by: Nicholas Husin <husin@google.com> Auto-Submit: Nicholas Husin <nsh@golang.org> Reviewed-by: Damien Neil <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
committed by
Gopher Robot
parent
f26befb380
commit
9d5d6af2d5
@@ -1612,6 +1612,65 @@ func testHeadReaderFrom(t *testing.T, mode testMode) {
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure ResponseWriter.ReadFrom respects declared Content-Length header.
|
||||
// https://go.dev/issue/78179.
|
||||
func TestReaderFromTooLong(t *testing.T) { run(t, testReaderFromTooLong, []testMode{http1Mode}) }
|
||||
func testReaderFromTooLong(t *testing.T, mode testMode) {
|
||||
contentLen := 600 // Longer than content-sniffing length.
|
||||
tests := []struct {
|
||||
name string
|
||||
reader io.Reader
|
||||
wantHandlerErr error
|
||||
}{
|
||||
{
|
||||
name: "reader of correct length",
|
||||
reader: strings.NewReader(strings.Repeat("a", contentLen)),
|
||||
},
|
||||
{
|
||||
name: "wrapped reader of correct outer length",
|
||||
reader: io.LimitReader(strings.NewReader(strings.Repeat("a", 2*contentLen)), int64(contentLen)),
|
||||
},
|
||||
{
|
||||
name: "wrapped reader of correct inner length",
|
||||
reader: io.LimitReader(io.LimitReader(strings.NewReader(strings.Repeat("a", 2*contentLen)), int64(contentLen)), int64(2*contentLen)),
|
||||
},
|
||||
{
|
||||
name: "reader that is too long",
|
||||
reader: strings.NewReader(strings.Repeat("a", 2*contentLen)),
|
||||
wantHandlerErr: ErrContentLength,
|
||||
},
|
||||
{
|
||||
name: "wrapped reader that is too long",
|
||||
reader: io.LimitReader(strings.NewReader(strings.Repeat("a", 2*contentLen)), int64(2*contentLen)),
|
||||
wantHandlerErr: ErrContentLength,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) {
|
||||
w.Header().Set("Content-Length", strconv.Itoa(contentLen))
|
||||
n, err := w.(io.ReaderFrom).ReadFrom(tc.reader)
|
||||
if int(n) != contentLen || !errors.Is(err, tc.wantHandlerErr) {
|
||||
t.Errorf("got %v, %v from w.ReadFrom; want %v, %v", n, err, contentLen, tc.wantHandlerErr)
|
||||
}
|
||||
}))
|
||||
res, err := cst.c.Get(cst.ts.URL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
gotBody, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(gotBody) != contentLen {
|
||||
t.Errorf("got unexpected body len=%v, want %v", len(gotBody), contentLen)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSHandshakeTimeout(t *testing.T) {
|
||||
run(t, testTLSHandshakeTimeout, []testMode{https1Mode, http2Mode})
|
||||
}
|
||||
|
||||
@@ -616,6 +616,30 @@ func (w *response) ReadFrom(src io.Reader) (n int64, err error) {
|
||||
|
||||
// Now that cw has been flushed, its chunking field is guaranteed initialized.
|
||||
if !w.cw.chunking && w.bodyAllowed() && w.req.Method != "HEAD" {
|
||||
// When a content length is declared, but exceeded; any excess bytes
|
||||
// from src should be ignored, and ErrContentLength should be returned.
|
||||
// This mirrors the behavior of response.Write.
|
||||
if w.contentLength != -1 {
|
||||
defer func(originalReader io.Reader) {
|
||||
if w.written != w.contentLength {
|
||||
return
|
||||
}
|
||||
if n, _ := originalReader.Read([]byte{0}); err == nil && n != 0 {
|
||||
err = ErrContentLength
|
||||
}
|
||||
}(src)
|
||||
// src can be an io.LimitedReader already. To avoid unnecessary
|
||||
// alloc and having to unnest readers repeatedly in net.sendFile,
|
||||
// just adjust the existing LimitedReader N when this is the case.
|
||||
if lr, ok := src.(*io.LimitedReader); ok {
|
||||
if lenDiff := lr.N - (w.contentLength - w.written); lenDiff > 0 {
|
||||
defer func() { lr.N += lenDiff }()
|
||||
lr.N -= lenDiff
|
||||
}
|
||||
} else {
|
||||
src = io.LimitReader(src, w.contentLength-w.written)
|
||||
}
|
||||
}
|
||||
n0, err := rf.ReadFrom(src)
|
||||
n += n0
|
||||
w.written += n0
|
||||
|
||||
Reference in New Issue
Block a user