Skip to content

Commit

Permalink
[release-branch.go1.14-security] net/http: synchronize "100 Continue"…
Browse files Browse the repository at this point in the history
… write and Handler writes

The expectContinueReader writes to the connection on the first
Request.Body read. Since a Handler might be doing a read in parallel or
before a write, expectContinueReader needs to synchronize with the
ResponseWriter, and abort if a response already went out.

The tests will land in a separate CL.

Fixes #34902
Fixes CVE-2020-15586

Change-Id: Icdd8dd539f45e8863762bd378194bb4741e875fc
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/793350
Reviewed-by: Filippo Valsorda <[email protected]>
(cherry picked from commit b5e504f4a07c572744b228fa1b10e3989c4c44f3)
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/793500
  • Loading branch information
rsc authored and katiehockman committed Jul 13, 2020
1 parent 83b181c commit f3529ca
Showing 1 changed file with 36 additions and 7 deletions.
43 changes: 36 additions & 7 deletions src/net/http/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,16 @@ type response struct {
wants10KeepAlive bool // HTTP/1.0 w/ Connection "keep-alive"
wantsClose bool // HTTP request has Connection "close"

// canWriteContinue is a boolean value accessed as an atomic int32
// that says whether or not a 100 Continue header can be written
// to the connection.
// writeContinueMu must be held while writing the header.
// These two fields together synchronize the body reader
// (the expectContinueReader, which wants to write 100 Continue)
// against the main writer.
canWriteContinue atomicBool
writeContinueMu sync.Mutex

w *bufio.Writer // buffers output in chunks to chunkWriter
cw chunkWriter

Expand Down Expand Up @@ -515,6 +525,7 @@ type atomicBool int32

func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 }
func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) }
func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) }

// declareTrailer is called for each Trailer header when the
// response header is written. It notes that a header will need to be
Expand Down Expand Up @@ -877,21 +888,27 @@ type expectContinueReader struct {
resp *response
readCloser io.ReadCloser
closed bool
sawEOF bool
sawEOF atomicBool
}

func (ecr *expectContinueReader) Read(p []byte) (n int, err error) {
if ecr.closed {
return 0, ErrBodyReadAfterClose
}
if !ecr.resp.wroteContinue && !ecr.resp.conn.hijacked() {
ecr.resp.wroteContinue = true
ecr.resp.conn.bufw.WriteString("HTTP/1.1 100 Continue\r\n\r\n")
ecr.resp.conn.bufw.Flush()
w := ecr.resp
if !w.wroteContinue && w.canWriteContinue.isSet() && !w.conn.hijacked() {
w.wroteContinue = true
w.writeContinueMu.Lock()
if w.canWriteContinue.isSet() {
w.conn.bufw.WriteString("HTTP/1.1 100 Continue\r\n\r\n")
w.conn.bufw.Flush()
w.canWriteContinue.setFalse()
}
w.writeContinueMu.Unlock()
}
n, err = ecr.readCloser.Read(p)
if err == io.EOF {
ecr.sawEOF = true
ecr.sawEOF.setTrue()
}
return
}
Expand Down Expand Up @@ -1310,7 +1327,7 @@ func (cw *chunkWriter) writeHeader(p []byte) {
// because we don't know if the next bytes on the wire will be
// the body-following-the-timer or the subsequent request.
// See Issue 11549.
if ecr, ok := w.req.Body.(*expectContinueReader); ok && !ecr.sawEOF {
if ecr, ok := w.req.Body.(*expectContinueReader); ok && !ecr.sawEOF.isSet() {
w.closeAfterReply = true
}

Expand Down Expand Up @@ -1560,6 +1577,17 @@ func (w *response) write(lenData int, dataB []byte, dataS string) (n int, err er
}
return 0, ErrHijacked
}

if w.canWriteContinue.isSet() {
// Body reader wants to write 100 Continue but hasn't yet.
// Tell it not to. The store must be done while holding the lock
// because the lock makes sure that there is not an active write
// this very moment.
w.writeContinueMu.Lock()
w.canWriteContinue.setFalse()
w.writeContinueMu.Unlock()
}

if !w.wroteHeader {
w.WriteHeader(StatusOK)
}
Expand Down Expand Up @@ -1871,6 +1899,7 @@ func (c *conn) serve(ctx context.Context) {
if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
// Wrap the Body reader with one that replies on the connection
req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
w.canWriteContinue.setTrue()
}
} else if req.Header.get("Expect") != "" {
w.sendExpectationFailed()
Expand Down

0 comments on commit f3529ca

Please sign in to comment.