[PATCH 1/1] Fix missed-wakeup race in ring buffer Alertable signaling

odedkatz <[email protected]> Tue, 24 Feb 2026 15:47:17 -0800
Newsgroups com.zx2c4.lists.wireguard
Message-ID <[email protected]>
From: Alexey Lapuka <[email protected]>

   Add MemoryBarrier() between store-load pairs in the Dekker-style
   synchronization used by the Receive ring's Alertable/Tail protocol.

   On x86-64, WriteRelease/ReadAcquire only prevent compiler reordering
   and provide acquire/release semantics, but do not emit MFENCE — the
   only instruction that prevents store-load reordering across cores.
   Without a full barrier, both the userspace producer and the kernel
   consumer can simultaneously read stale values:

     Userspace: STORE(Tail)  ...  LOAD(Alertable) -> sees FALSE (stale)
     Driver:    STORE(Alertable=TRUE) ... LOAD(Tail) -> sees old tail

   The driver then enters KeWaitForMultipleObjects with no pending
   SetEvent, sleeping until a TCP retransmission (typically 4-5s later)
   re-triggers the send path and wins the race.

   The fix adds MemoryBarrier() (MFENCE on x86) on both sides:
   - api/session.c WintunSendPacket: between WriteULongRelease(Tail) and
     ReadAcquire(Alertable)
   - driver/twintun.c TunProcessReceiveData: between
     WriteRelease(Alertable, TRUE) and ReadULongAcquire(Tail)

   This guarantees that at least one side always observes the other's
   store, preventing the missed wakeup while preserving the Alertable
   optimization that avoids unnecessary SetEvent syscalls.
---
 api/session.c   | 1 +
 driver/wintun.c | 1 +
 2 files changed, 2 insertions(+)

diff --git a/api/session.c b/api/session.c
index ab96c64..13d5bca 100644
--- a/api/session.c
+++ b/api/session.c
@@ -302,6 +302,7 @@ WintunSendPacket(TUN_SESSION *Session, const BYTE *Packet)
     if (Session->Descriptor.Receive.Ring->Tail != Session->Receive.TailRelease)
     {
         WriteULongRelease(&Session->Descriptor.Receive.Ring->Tail, Session->Receive.TailRelease);
+        MemoryBarrier();
         if (ReadAcquire(&Session->Descriptor.Receive.Ring->Alertable))
             SetEvent(Session->Descriptor.Receive.TailMoved);
     }
diff --git a/driver/wintun.c b/driver/wintun.c
index 82e346b..72ba5d3 100644
--- a/driver/wintun.c
+++ b/driver/wintun.c
@@ -481,6 +481,7 @@ TunProcessReceiveData(_Inout_ TUN_CTX *Ctx)
             if (RingHead == RingTail)
             {
                 WriteRelease(&Ring->Alertable, TRUE);
+                MemoryBarrier();
                 RingTail = ReadULongAcquire(&Ring->Tail);
                 if (RingHead == RingTail)
                 {
-- 
2.43.0