Files
netbird/client/internal/pqkem_adapter_test.go
T
riccardom 1182239faa [client] pqkem: carry a generation on PSK callbacks to drop stale applies
OnNewPSKReady could be applied out of order: two exchanges for a peer can derive
concurrently (one over signal, one over the data path), and the callbacks run
outside the manager lock, so an older exchange's apply could land after a newer
one and restore a stale WireGuard PSK, splitting the tunnel.

Give each exchange a per-peer monotonic generation, assigned under the lock at
creation so a later exchange always carries a higher one, and pass it to
OnNewPSKReady. The host adapter records the newest generation applied per peer
and drops any callback that is not newer, with the check-and-record atomic so the
slow SetPresharedKey call stays off that lock.

Found in cubic review on #7098 (client/internal/pqkem/callbacks.go:12).
2026-10-07 13:30:52 +02:00

32 lines
969 B
Go

package internal
import "testing"
// TestAppliedGenerations_DropsStale verifies the out-of-order guard: a generation is
// accepted only when it is strictly newer than the last one applied for that peer, so a
// reordered PSK callback cannot restore an older key over a newer one.
func TestAppliedGenerations_DropsStale(t *testing.T) {
a := newAppliedGenerations()
if !a.claim("peerA", 1) {
t.Fatal("first generation must be accepted")
}
if !a.claim("peerA", 2) {
t.Fatal("a newer generation must be accepted")
}
if a.claim("peerA", 2) {
t.Fatal("re-applying the same generation must be dropped")
}
if a.claim("peerA", 1) {
t.Fatal("an older generation arriving late must be dropped")
}
if !a.claim("peerA", 3) {
t.Fatal("a newer generation after a dropped stale one must still be accepted")
}
// Generations are tracked independently per peer.
if !a.claim("peerB", 1) {
t.Fatal("a different peer's first generation must be accepted")
}
}