1
0
mirror of https://github.com/golang/go synced 2024-11-11 23:40:22 -07:00

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>
This commit is contained in:
Brad Fitzpatrick 2019-12-10 17:40:22 +00:00
parent 41348081fa
commit ecde0bfa1f

View File

@ -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 {