diff options
| author | 2026-10-06 18:29:47 +0200 | |
|---|---|---|
| committer | 2026-10-06 18:33:02 +0200 | |
| commit | 90433bd1aa35ff082b7b2ceb89b6451a4fadd2af (patch) | |
| tree | ae34768f98182e5a8a51de9590af7cd9ee7ef2e2 | |
| parent | driver: ioctl: reset IsDeviceRemoving on cancel-remove (diff) | |
| download | wireguard-nt-master.tar.xz wireguard-nt-master.zip | |
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, or even while
blocking on MuAcquirePushLockExclusive(), A completes and then also a
handshake initiation that's already been queued up runs in thread C,
which itself takes that same MuAcquirePushLockExclusive(). The handshake
initiation creation succeeds, and sets the state back to
waiting-for-response, and calls MuReleasePushLockExclusive(), at which
point thread B resumes, because either its finished its calculations or
was finally allowed to acquire MuAcquirePushLockExclusive(). 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.
Thread A Thread B Thread C
MuAcquirePushLockShared()
sA = Handshake->State
RtlCopyMemory(cA, Handshake->crypto)
MuReleasePushLockShared()
if (sA != 1)
goto fail
SlowCrypto(cA)
MuAcquirePushLockShared()
sB = Handshake->State
RtlCopyMemory(cB, Handshake->crypto)
MuReleasePushLockShared()
if (sB != 1)
goto fail
SlowCrypto(cB)
MuAcquirePushLockExclusive()
if (sA != Handshake->State)
goto fail
RtlCopyMemory(Handshake->crypto, cA)
Handshake->State = 2
MuReleasePushLockExclusive()
MuAcquirePushLockExclusive()
if (Handshake->State != 2)
goto fail
DeriveSession(Handshake->crypto)
MuReleasePushLockExclusive()
MuAcquirePushLockExclusive()
SlowCrypto(Handshake->crypto)
Handshake->State = 1
MuReleasePushLockExclusive()
MuAcquirePushLockExclusive()
if (sB != Handshake->State)
goto fail
RtlCopyMemory(Handshake->crypto, cB)
Handshake->State = 2
MuReleasePushLockExclusive()
MuAcquirePushLockExclusive()
if (Handshake->State != 2)
goto fail
DeriveSession(Handshake->crypto)
MuReleasePushLockExclusive()
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>
| -rw-r--r-- | driver/noise.c | 7 |
1 files changed, 4 insertions, 3 deletions
diff --git a/driver/noise.c b/driver/noise.c index e9f3c68..bef6f63 100644 --- a/driver/noise.c +++ b/driver/noise.c @@ -790,10 +790,11 @@ NoiseHandshakeConsumeResponse(CONST MESSAGE_HANDSHAKE_RESPONSE *Src, WG_DEVICE * /* Success! Copy everything to peer */ MuAcquirePushLockExclusive(&Handshake->Lock); - /* It's important to check that the state is still the same, while we - * have an exclusive lock. + /* Check that the state is the same and that this is still the + * initiation we started with, while we have an exclusive lock. */ - if (Handshake->State != State) + if (Handshake->State != State || + !CryptoEqualMemory32(Handshake->EphemeralPrivate, EphemeralPrivate, NOISE_PUBLIC_KEY_LEN)) { MuReleasePushLockExclusive(&Handshake->Lock); goto fail; |
