
CVE-2026-42978 — Use-After-Free race condition in Windows Push Notifications (WpnService). Patch diff, root cause analysis, TOCTOU lab, Sysmon/ETW detection rules.
Race condition in Windows Push Notifications service (WpnService) that runs as NT AUTHORITY\SYSTEM. An attacker with local access can trigger a use-after-free during platform shutdown and potentially elevate privileges.
WpnService is the Windows Push Notification service. It runs in session 0 as LocalSystem inside svchost.exe -k netsvcs -p. All notification delivery — toasts, tiles, badges — goes through it.
The vulnerability sits in wpncore.dll, specifically in the PresentationEndpointFacade class. This class wraps all notification API calls (toast delivery, session management, settings queries, etc.) and delegates to PresentationEndpointImpl.
The bug: during platform shutdown, the NotificationPlatform object gets destroyed. But the Facade methods don't check if shutdown is in progress — they grab a pointer to the platform, the platform gets freed by the shutdown thread, and the Facade uses a dangling pointer. Classic use-after-free race.
// PresentationEndpointFacade::ToastUnblockAll — VULNERABLE
long ToastUnblockAll(PresentationEndpointFacade *this) {
// No lock. No shutdown check. Just grab the platform pointer and go.
NotificationPlatformHandle::Get(this + 0x50);
if (platform == NULL) Throw_Hr(...);
// If shutdown frees the platform right here — use-after-free
return PresentationEndpointImpl::UnblockToastsForEachApp(...);
}
Ghidra: vulnerable ToastUnblockAll — no synchronization before accessing the platform
// PresentationEndpointFacade::ToastUnblockAll — PATCHED
long ToastUnblockAll(PresentationEndpointFacade *this) {
if (Feature_4097557817::IsEnabled()) {
AcquireSRWLockShared(&Wns::s_platformLock); // 1. shared lock
if (Wns::s_platformShutdown) // 2. shutdown guard
Throw_Hr(E_APPLICATION_EXITING);
NotificationPlatformHandle::Get(this + 0x50);
// ... do work ...
ReleaseSRWLockShared(&Wns::s_platformLock); // 3. RAII release
} else {
// feature flag off — old behavior preserved for rollback
}
}
The fix adds three things:
AcquireSRWLockShared — shared readers-writer lock. Multiple API calls can run concurrently, but shutdown takes an exclusive lock and blocks them all.s_platformShutdown check — if shutdown already started, bail out immediately with E_APPLICATION_EXITING.wil::unique_storage wrapper, so it gets released even if an exception is thrown.The feature flag Feature_4097557817 is for staged rollout via WIL (Windows Internal Library). It lets Microsoft enable the fix gradually and disable it if something breaks.
This isn't a one-function bug. The same lock+guard pattern was added to 49 functions in wpncore.dll:
| Category | Functions |
|---|---|
| Toast operations | ToastUnblockAll, ToastCreateSession, ToastCloseSession, ToastRequestAllNotifications, ToastSuppress |
| Tile operations | TileCreateSession, TileCloseSession, TileRequestResourceForeground |
| Registration | RegisterApplication, UnregisterApplication, RegisterHandler, UpdateRegistration, RegisterSystemApplication |
| Settings | ChangeAppSetting, QueryAppSetting, QueryGlobalSetting, RegisterSettingCallback, UnregisterSettingCallback |
| Delivery | Deliver, GetPayloadForNotificationId, Submit, PostScheduledNotification |
| Queries | GetRegisteredHandler, GetRegisteredHandlersFromParent, GetSettingsFromHandler, GetAssetsFromHandler |
Every PresentationEndpointFacade::* method that touches the platform got the same fix. The PresentationEndpointImpl::* methods underneath are unchanged — the issue was only at the facade layer.
WpnService runs as SYSTEM. If the race is won:
NotificationPlatform object's memory can be reclaimed and filled with attacker-controlled data (heap spray)NT AUTHORITY\SYSTEMThe actual exploitation — heap spray, vtable hijacking, achieving code execution — is outside the scope of this research. This repo focuses on understanding the root cause and building detection.
The lab/ directory contains a standalone C demo that reproduces the class of bug without touching WpnService. It uses a named pipe + shared memory architecture similar to WpnService.
How it works:
vulnerable_service.exe reads a message length from shared memory, validates it, sleeps for 50ms, then reads the length again (double-fetch).race_attacker.exe sets a safe length, triggers the service, then immediately flips the shared memory to an overflow value.Run vulnerable_service.exe --patched to see the fix: single-fetch capture into a local variable.
Left: service detects race (5/5). Right: attacker flipping shared memory values.
Requires GCC (MinGW). From the lab/ directory:
build.bat
Terminal 1:
vulnerable_service.exe
Terminal 2:
race_attacker.exe 5
Then compare with vulnerable_service.exe --patched — same attack, zero races detected.
detection/etw_wpn_monitor.ps1)PowerShell script that checks 7 indicators:
powershell -ExecutionPolicy Bypass .\detection\etw_wpn_monitor.ps1
ETW monitor: WpnService running (PID 6432), no crashes, 100 events/hour, system patched
detection/sysmon_wpn_race_detect.xml)Six rule groups for continuous monitoring:
| Rule | What it catches |
|---|---|
WPN_EoP_ChildProcess | svchost (netsvcs) spawning cmd/powershell/wscript |
WPN_PipeAccess | Connections to WPN-related named pipes |
WPN_FileCreation | File creation in notification data directories |
WPN_RegistryTampering | Registry writes to PushNotifications keys |
WPN_ProcessAccess | PROCESS_ALL_ACCESS handles to svchost |
WPN_ThreadInjection | CreateRemoteThread into svchost |