device: fix some lock ordering violations, add a test for a deadlock we hit

Discovered by a tool + test that will come in a future change.

Updates tailscale/tailscale#19513

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2026-04-24 20:49:35 +00:00 committed by 世界
parent f69b24781e
commit 010dd5c6f2
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
3 changed files with 63 additions and 11 deletions

View file

@ -186,14 +186,22 @@ func (device *Device) upLocked() error {
device.ipcMutex.Lock()
defer device.ipcMutex.Unlock()
// Collect peers under RLock and then release before calling into them,
// because SendKeepalive can reach CreateMessageInitiation which acquires
// staticIdentity.RLock; holding peers.RLock across that path would
// invert the staticIdentity < peers hierarchy (see lock-ordering.md).
device.peers.RLock()
peers := make([]*Peer, 0, len(device.peers.keyMap))
for _, peer := range device.peers.keyMap {
peers = append(peers, peer)
}
device.peers.RUnlock()
for _, peer := range peers {
peer.Start()
if peer.persistentKeepaliveInterval.Load() > 0 {
peer.SendKeepalive()
}
}
device.peers.RUnlock()
return nil
}
@ -540,16 +548,25 @@ func (device *Device) SendKeepalivesToPeersWithCurrentKeypair() {
return
}
// Collect the set of peers to keepalive under peers.RLock, then release
// before invoking SendKeepalive. SendKeepalive can reach
// CreateMessageInitiation which acquires staticIdentity.RLock; holding
// peers.RLock across that path would invert the
// staticIdentity < peers hierarchy (see lock-ordering.md).
var peers []*Peer
device.peers.RLock()
for _, peer := range device.peers.keyMap {
peer.keypairs.RLock()
sendKeepalive := peer.keypairs.current != nil && !peer.keypairs.current.created.Add(RejectAfterTime).Before(time.Now())
peer.keypairs.RUnlock()
if sendKeepalive {
peer.SendKeepalive()
peers = append(peers, peer)
}
}
device.peers.RUnlock()
for _, peer := range peers {
peer.SendKeepalive()
}
}
// closeBindLocked closes the device's net.bind.