device: noise: reject response consumption after intermediate initiation

Two threads begin processing the identical response message, received
twice. The first thread, A, runs. While it's running, the second one, B,
gets partway through, and during that slow calculation, A completes and
then also a handshake initiation that's already been queued up runs in
thread C. The handshake initiation creation succeeds, and sets the state
back to waiting-for-response, at which point thread B resumes. Thread B
then copies the state back to the peer, and begins a new session, using
that state, which is the same session as the one made in thread A.

This seems basically impossible to hit in a meaningful way in practice,
but ensure that it absolutely cannot happen by comparing the ephemeral
private key that's on the stack with the latest one that the peer's
handshake state has.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Jason A. Donenfeld
2026-10-06 18:45:05 +02:00
parent ecfc5a8d54
commit 2631ce99a0
+18 -9
View File
@@ -509,7 +509,7 @@ func (device *Device) CreateMessageResponse(peer *Peer) (*MessageResponse, error
return &msg, nil
}
func (device *Device) ConsumeMessageResponse(msg *MessageResponse) *Peer {
func (device *Device) ConsumeMessageResponse(msg *MessageResponse) (peer *Peer) {
if msg.Type != MessageResponseType {
return nil
}
@@ -523,8 +523,9 @@ func (device *Device) ConsumeMessageResponse(msg *MessageResponse) *Peer {
}
var (
hash [blake2s.Size]byte
chainKey [blake2s.Size]byte
hash [blake2s.Size]byte
chainKey [blake2s.Size]byte
localEphemeral NoisePrivateKey
)
ok := func() bool {
@@ -582,6 +583,10 @@ func (device *Device) ConsumeMessageResponse(msg *MessageResponse) *Peer {
return false
}
mixHash(&hash, &hash, msg.Empty[:])
// allow unlocking temporarily
copy(localEphemeral[:], handshake.localEphemeral[:])
return true
}()
@@ -592,18 +597,22 @@ func (device *Device) ConsumeMessageResponse(msg *MessageResponse) *Peer {
// update handshake state
handshake.mutex.Lock()
handshake.hash = hash
handshake.chainKey = chainKey
handshake.remoteIndex = msg.Sender
handshake.state = handshakeResponseConsumed
if handshake.state == handshakeInitiationCreated &&
handshake.localEphemeral.Equals(localEphemeral) {
handshake.hash = hash
handshake.chainKey = chainKey
handshake.remoteIndex = msg.Sender
handshake.state = handshakeResponseConsumed
peer = lookup.peer
}
handshake.mutex.Unlock()
setZero(hash[:])
setZero(chainKey[:])
setZero(localEphemeral[:])
return lookup.peer
return
}
/* Derives a new keypair from the current handshake state