device: fix TOCTOU race during session state update (#77)
API introduced in a927a66e has two cases of state determination happening out of critical section for the state value: (1) expiredSession loads sessionExpiresNano, then releases all locks and calls noteSessionState(Expired). So a concurrent refresh that lands in that gap gets clobbered by a stale Expired -- and sticks until the next re-key. (2) Likewise in noteSessionHandshakeStopped, hasKeyMaterial check happens out of the session state lock and races with ZeroAndFlushAll. Both lead to a wrong state emitted via the device.sessionState.fn, but are otherwise benign. This moves the expiry timestamp under a lock to address the former, and provides a noteSessionStateLocked helper for the latter. Also changes API semantics to serialize events per-peer, to avoid sharing a single lock for all timestamps. Updates tailscale/corp#42874 Signed-off-by: Alex Valiushko <alexvaliushko@tailscale.com> Change-Id: Iee2cdf135375519e58a8e84362349d966a6a6964
This commit is contained in:
parent
35a60acb84
commit
15b912c1c0
3 changed files with 61 additions and 47 deletions
|
|
@ -141,12 +141,13 @@ func expiredZeroKeyMaterial(peer *Peer) {
|
|||
}
|
||||
|
||||
func expiredSession(peer *Peer) {
|
||||
expires := peer.sessionExpiresNano.Load()
|
||||
if expires == 0 || time.Now().UnixNano() < expires {
|
||||
peer.sessionState.Lock()
|
||||
defer peer.sessionState.Unlock()
|
||||
if peer.sessionState.sessionExpires.IsZero() || time.Now().Before(peer.sessionState.sessionExpires) {
|
||||
return
|
||||
}
|
||||
peer.device.log.Verbosef("%s - Session expired after %d seconds", peer, int(RejectAfterTime.Seconds()))
|
||||
peer.noteSessionState(PeerSessionExpired)
|
||||
peer.noteSessionStateLocked(PeerSessionExpired)
|
||||
}
|
||||
|
||||
func expiredPersistentKeepalive(peer *Peer) {
|
||||
|
|
@ -208,11 +209,15 @@ func (peer *Peer) timersHandshakeComplete() {
|
|||
/* Should be called after an ephemeral key is created, which is before sending a handshake response or after receiving a handshake response. */
|
||||
func (peer *Peer) timersSessionDerived() {
|
||||
if peer.timersActive() {
|
||||
peer.sessionExpiresNano.Store(time.Now().Add(RejectAfterTime).UnixNano())
|
||||
peer.sessionState.Lock()
|
||||
peer.sessionState.sessionExpires = time.Now().Add(RejectAfterTime)
|
||||
peer.noteSessionStateLocked(PeerSessionEstablished)
|
||||
peer.sessionState.Unlock()
|
||||
peer.timers.sessionExpired.Mod(RejectAfterTime)
|
||||
peer.timers.zeroKeyMaterial.Mod(RejectAfterTime * 3)
|
||||
} else {
|
||||
peer.noteSessionState(PeerSessionEstablished)
|
||||
}
|
||||
peer.noteSessionState(PeerSessionEstablished)
|
||||
}
|
||||
|
||||
/* Should be called before a packet with authentication -- keepalive, data, or handshake -- is sent, or after one is received. */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue