wgengine/magicsock: fix data race in ReceiveIPv4.

The UDP reader goroutine was clobbering `n` and `err` from the
main goroutine, whose accesses are not synchronized the way `b` is.

Signed-off-by: David Anderson <danderson@tailscale.com>
pull/159/head
David Anderson 5 years ago
parent 77354d4617
commit f265603110

@ -830,8 +830,7 @@ func (c *Conn) ReceiveIPv4(b []byte) (n int, ep conn.Endpoint, addr *net.UDPAddr
go func() { go func() {
// Read a packet, and process any STUN packets before returning. // Read a packet, and process any STUN packets before returning.
for { for {
var pAddr net.Addr n, pAddr, err := c.pconn.ReadFrom(b)
n, pAddr, err = c.pconn.ReadFrom(b)
if err != nil { if err != nil {
select { select {
case c.udpRecvCh <- udpReadResult{err: err}: case c.udpRecvCh <- udpReadResult{err: err}:
@ -854,6 +853,10 @@ func (c *Conn) ReceiveIPv4(b []byte) (n int, ep conn.Endpoint, addr *net.UDPAddr
} }
}() }()
// Once the above goroutine has started, it owns b until it writes
// to udpRecvCh. The code below must not access b until it's
// completed a successful receive on udpRecvCh.
var addrSet *AddrSet var addrSet *AddrSet
select { select {
@ -863,13 +866,18 @@ func (c *Conn) ReceiveIPv4(b []byte) (n int, ep conn.Endpoint, addr *net.UDPAddr
select { select {
case <-c.udpRecvCh: case <-c.udpRecvCh:
// It's likely an error, since we just canceled the read. // It's likely an error, since we just canceled the read.
// But there's a small window where the pconn.ReadFrom could've // But there's a small window where the pconn.ReadFrom
// succeeded but not yet sent, and we got into the derp recv path // could've succeeded but not yet sent, and we got into
// first. In that case this udpReadResult is a real non-err packet // the derp recv path first. In that case this
// and we need to choose which to use. Currently, arbitrarily, we currently // udpReadResult is a real non-err packet and we need to
// select DERP and discard this result entirely. // choose which to use. Currently, arbitrarily, we
// The main point of this receive, though, is to make sure that the goroutine // currently select DERP and discard this result entirely.
// is done with our b []byte buf. //
// TODO(danderson): don't just discard packets here, it
// makes the stack unreliable and harder to test.
//
// The main point of this receive, though, is to make sure
// that the goroutine is done with our b []byte buf.
c.pconn.SetReadDeadline(time.Time{}) c.pconn.SetReadDeadline(time.Time{})
case <-c.donec(): case <-c.donec():
return 0, nil, nil, errors.New("Conn closed") return 0, nil, nil, errors.New("Conn closed")

Loading…
Cancel
Save