Skip to content

Commit d4febb4

Browse files
neildgopherbot
authored andcommitted
crypto/tls: avoid data race when canceling a QUICConn's Context
Methods on QUICConn are synchronous: The connection state is expected to change only in reaction to a user calling a QUICConn method, and the state change should finish completely before the method returns. The connection context provided to QUICConn.Start violates this model, because canceling the context causes an asynchronous state change. Prior to CL 719040, this caused no problems because canceling the context did not cause any user-visible state changes. In particular, canceling the context did not cause any new events to be immediately returned by QUICConn.NextEvent. CL 719040 introduced a new error event. Now, canceling a QUICConn's context causes a new connection event to be generated. Receiving this event causes a data race visible to the race detector, but the core problem is not the data race itself: It's that an asynchronous event (canceling the connection context) causes an change to the connection events. Fix this race by reworking the handling of QUICConn context cancellation a bit. We no longer react to cancellation while control of the connection lies with the user. We only process cancellation as part of a user call, such as QUICConn.Close or QUICConn.HandleData. Fixes golang#77274 Change-Id: If2e0f73618c4852114e0931b6bd0cb0b6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/742561 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Damien Neil <dneil@google.com> Reviewed-by: Roland Shoemaker <roland@golang.org>
1 parent e2a34c7 commit d4febb4

File tree

3 files changed

+29
-13
lines changed

3 files changed

+29
-13
lines changed

src/crypto/tls/conn.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1531,7 +1531,7 @@ func (c *Conn) handshakeContext(ctx context.Context) (ret error) {
15311531
defer cancel()
15321532

15331533
if c.quic != nil {
1534-
c.quic.cancelc = handshakeCtx.Done()
1534+
c.quic.ctx = handshakeCtx
15351535
c.quic.cancel = cancel
15361536
} else if ctx.Done() != nil {
15371537
// Close the connection if ctx is canceled before the function returns.

src/crypto/tls/quic.go

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ type quicState struct {
162162
started bool
163163
signalc chan struct{} // handshake data is available to be read
164164
blockedc chan struct{} // handshake is waiting for data, closed when done
165-
cancelc <-chan struct{} // handshake has been canceled
165+
ctx context.Context // handshake context
166166
cancel context.CancelFunc
167167

168168
waitingForDrain bool
@@ -261,10 +261,11 @@ func (q *QUICConn) NextEvent() QUICEvent {
261261

262262
// Close closes the connection and stops any in-progress handshake.
263263
func (q *QUICConn) Close() error {
264-
if q.conn.quic.cancel == nil {
264+
if q.conn.quic.ctx == nil {
265265
return nil // never started
266266
}
267267
q.conn.quic.cancel()
268+
<-q.conn.quic.signalc
268269
for range q.conn.quic.blockedc {
269270
// Wait for the handshake goroutine to return.
270271
}
@@ -511,20 +512,16 @@ func (c *Conn) quicWaitForSignal() error {
511512
// Send on blockedc to notify the QUICConn that the handshake is blocked.
512513
// Exported methods of QUICConn wait for the handshake to become blocked
513514
// before returning to the user.
514-
select {
515-
case c.quic.blockedc <- struct{}{}:
516-
case <-c.quic.cancelc:
517-
return c.sendAlertLocked(alertCloseNotify)
518-
}
515+
c.quic.blockedc <- struct{}{}
519516
// The QUICConn reads from signalc to notify us that the handshake may
520517
// be able to proceed. (The QUICConn reads, because we close signalc to
521518
// indicate that the handshake has completed.)
522-
select {
523-
case c.quic.signalc <- struct{}{}:
524-
c.hand.Write(c.quic.readbuf)
525-
c.quic.readbuf = nil
526-
case <-c.quic.cancelc:
519+
c.quic.signalc <- struct{}{}
520+
if c.quic.ctx.Err() != nil {
521+
// The connection has been canceled.
527522
return c.sendAlertLocked(alertCloseNotify)
528523
}
524+
c.hand.Write(c.quic.readbuf)
525+
c.quic.readbuf = nil
529526
return nil
530527
}

src/crypto/tls/quic_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"fmt"
1212
"reflect"
1313
"strings"
14+
"sync"
1415
"testing"
1516
)
1617

@@ -480,6 +481,24 @@ func TestQUICStartContextPropagation(t *testing.T) {
480481
}
481482
}
482483

484+
func TestQUICContextCancelation(t *testing.T) {
485+
ctx, cancel := context.WithCancel(context.Background())
486+
config := &QUICConfig{TLSConfig: testConfig.Clone()}
487+
config.TLSConfig.MinVersion = VersionTLS13
488+
cli := newTestQUICClient(t, config)
489+
cli.conn.SetTransportParameters(nil)
490+
srv := newTestQUICServer(t, config)
491+
srv.conn.SetTransportParameters(nil)
492+
// Verify that canceling the connection context concurrently does not cause any races.
493+
// See https://go.dev/issue/77274.
494+
var wg sync.WaitGroup
495+
wg.Go(func() {
496+
_ = runTestQUICConnection(ctx, cli, srv, nil)
497+
})
498+
wg.Go(cancel)
499+
wg.Wait()
500+
}
501+
483502
func TestQUICDelayedTransportParameters(t *testing.T) {
484503
clientConfig := &QUICConfig{TLSConfig: testConfig.Clone()}
485504
clientConfig.TLSConfig.MinVersion = VersionTLS13

0 commit comments

Comments
 (0)