diff options
| author | Brad Fitzpatrick <bradfitz@golang.org> | 2019-12-10 17:40:22 +0000 |
|---|---|---|
| committer | Brad Fitzpatrick <bradfitz@golang.org> | 2019-12-10 18:30:47 +0000 |
| commit | ecde0bfa1fb11328133bb335af80fc2a48a8f82a (patch) | |
| tree | 50e69e6c01965ea0d9e1008f5dd75e7990647fe2 | |
| parent | 41348081fa0511c8f442d236d32ff8d589c8c557 (diff) | |
| download | go-ecde0bfa1fb11328133bb335af80fc2a48a8f82a.tar.xz | |
net/http: fix timeout race in Transport proxy CONNECT
Fixes #36070
Change-Id: I99742aa153202436d802634c9e019a14b9ef9185
Reviewed-on: https://go-review.googlesource.com/c/go/+/210738
Reviewed-by: Bryan C. Mills <bcmills@google.com>
| -rw-r--r-- | src/net/http/transport.go | 50 |
1 files changed, 29 insertions, 21 deletions
diff --git a/src/net/http/transport.go b/src/net/http/transport.go index 7cf4615586..64d8510b95 100644 --- a/src/net/http/transport.go +++ b/src/net/http/transport.go @@ -1568,38 +1568,46 @@ func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (pconn *pers if pa := cm.proxyAuth(); pa != "" { connectReq.Header.Set("Proxy-Authorization", pa) } - didReadResponse := make(chan struct{}) // closed after reading CONNECT response - // If there's no deadline, at least set some (long) timeout here. - // This will make sure we don't block here forever and leak a goroutine - // if the connection stops replying after the TCP connect. + // If there's no done channel (no deadline or cancellation + // from the caller possible), at least set some (long) + // timeout here. This will make sure we don't block forever + // and leak a goroutine if the connection stops replying + // after the TCP connect. connectCtx := ctx - if _, ok := ctx.Deadline(); !ok { + if ctx.Done() == nil { newCtx, cancel := context.WithTimeout(ctx, 1*time.Minute) defer cancel() connectCtx = newCtx } + + didReadResponse := make(chan struct{}) // closed after CONNECT write+read is done or fails + var ( + resp *Response + err error // write or read error + ) + // Write the CONNECT request & read the response. go func() { - select { - case <-connectCtx.Done(): - conn.Close() - case <-didReadResponse: + defer close(didReadResponse) + err = connectReq.Write(conn) + if err != nil { + return } + // Okay to use and discard buffered reader here, because + // TLS server will not speak until spoken to. + br := bufio.NewReader(conn) + resp, err = ReadResponse(br, connectReq) }() - - connectReq.Write(conn) - - // Read response. - // Okay to use and discard buffered reader here, because - // TLS server will not speak until spoken to. - br := bufio.NewReader(conn) - resp, err := ReadResponse(br, connectReq) - close(didReadResponse) + select { + case <-connectCtx.Done(): + conn.Close() + <-didReadResponse + return nil, connectCtx.Err() + case <-didReadResponse: + // resp or err now set + } if err != nil { conn.Close() - if err := connectCtx.Err(); err != nil { - return nil, err - } return nil, err } if resp.StatusCode != 200 { |
