From 2dd5d82f56ed31b35c2bc26b8a3e5846268986c1 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 20 Jul 2026 14:33:03 +0000 Subject: [PATCH] tsnet: fix data race in chanTUN test device The wireguard-go receive path could be in chanTUN.Write, selecting to send on the Inbound channel, while test cleanup called chanTUN.Close, which closed that same channel. The select on the closed channel in Write did not synchronize with Close, so the race detector flagged the send racing with the close. It could also have panicked with a send on a closed channel. Add a mutex serializing Write and Close. Write now checks for closed under the lock before doing a non-blocking send, so Close can't close Inbound mid-send. Fixes #20541 Signed-off-by: Brad Fitzpatrick Change-Id: I8d8d10250cef0c1931753c78eebff6e8286f7201 --- tsnet/tsnet_test.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tsnet/tsnet_test.go b/tsnet/tsnet_test.go index fc7b11691..93f63a2e9 100644 --- a/tsnet/tsnet_test.go +++ b/tsnet/tsnet_test.go @@ -2560,6 +2560,10 @@ type chanTUN struct { Outbound chan []byte // packets to read from TUN closed chan struct{} events chan tun.Event + + // wmu serializes Write and Close so a Write can't send on + // Inbound while Close is closing it. + wmu sync.Mutex } func newChanTUN() *chanTUN { @@ -2576,6 +2580,8 @@ func newChanTUN() *chanTUN { func (t *chanTUN) File() *os.File { panic("not implemented") } func (t *chanTUN) Close() error { + t.wmu.Lock() + defer t.wmu.Unlock() select { case <-t.closed: default: @@ -2596,14 +2602,19 @@ func (t *chanTUN) Read(bufs [][]byte, sizes []int, offset int) (int, error) { } func (t *chanTUN) Write(bufs [][]byte, offset int) (int, error) { + t.wmu.Lock() + defer t.wmu.Unlock() + select { + case <-t.closed: + return 0, errors.New("closed") + default: + } for _, buf := range bufs { pkt := buf[offset:] if len(pkt) == 0 { continue } select { - case <-t.closed: - return 0, errors.New("closed") case t.Inbound <- slices.Clone(pkt): default: // Drop the packet if the channel is full, like a real