CVE-2026-50369
Around October last year one of our production Windows Server 2025 RDSH was suddenly weirdly unresponsive. It accepted Remote Desktop sessions but was unable to produce a graphics session, just a black screen and pretty quick an error message. The network was fine, so i called a reboot via RemotePS, which went through and seemingly deconstructing the network stack. The machine was now also unresponsive via network, yet not rebooting. I went to the server physically and hard-reset. It logged a bugcheck DRIVER_POWER_STATE_FAILURE (0x9F) after a 600 second timeout (IndirectKmd.sys failed to complete its power transition), after svchost.exe_TermService crashed with a Access Violation (0xc0000005). Seemingly the Remote Desktop Service crashed and put the whole server in an inconsistent state.
I documented the failure and recorded it as a one-off.
It happened again in December. I deployed a harness to monitor the process as on the first look it pointed towards a resource exhaustion. The TermService process was leaking handles.
It happened again a day after. I deployed a harness to catch the process dump and some logs. Didnt seem to be a resource issue.
It happened again in March this year. 3 times in 2 hours. No resource issue. But the same signature: an access violation in rdpcorets.dll!CRdpPipeGfxPlugin::Enable . At the same time the server was logging high session churn.
I noticed something else, the svchost.exe_TermService crashed a lot more often then noticed before. About a crash a day. SCM seemed to be able to recover most of the crashes.
This drew a picture:
Seemingly a race could be triggered by high session connection and disconnection. The server is accessible from a globally distributed corporate intranet. Clients connect over VPN, WiFi, and variable-quality WAN links. The RdpCoreTS Operational log from March 19 contains 3,726 TCP write errors, 2,568 TCP read errors, and 3,209 state machine transition errors.
At 15:47:42 on March 19, a client at 10.178.xxx.xxx initiated an auto-reconnect to session 122 (GetSessionIdFromArcInfo). Within the same second:
- TCP connection accepted, licensing completed, session assigned
- RemoteFX guest mode selected, GFX channels opened on tunnel 1
OnConnectedandOnReadyfiredDisconnectNotifyfired in the same second, the connection was already being torn down- Despite the disconnect, channel setup continued:
CoreInput,MouseCursor,Graphics channelsall connected on tunnel 1 ConnectNotify(SessionId=122)andLogonNotify(SessionId=122)both fired after the disconnect
The PnP display device arrival path (OnRemoteDisplayDeviceArrival) then called into CRdpPipeGfxPlugin::Enable, which attempted to dereference a connection interface pointer that had already been nulled by the concurrent disconnect teardown.
This happened every time the process crashed. The escalation path though seemed to have other dependencies, which not have been clarified in detail.
There were 3:
- with ~35 active sessions, mass auto-reconnect from legitimate clients re-triggered the race on the SCM-restarted instance. This, in theory, would allow a continous crashing-loop.
- when
IDD (Indirect Display Driver)device count at crash time exceeds approximately 27 TermService restarts but cannot create new IDD devices viaSWD\RemoteDisplayEnum. Sessions complete authentication but render black screens. The service reportsState=Runningbut is non-functional. - after display degradation,
Restart-Computerhangs on a power IRP toSWD\RemoteDisplayEnum (IndirectKmd). The kernel power manager times out after 600 seconds and bugchecks withDRIVER_POWER_STATE_FAILURE (0x9F)as observed in the first crash back in October.
Root Cause
Enable (called via CUMRDPConnection::StartGfxPlugin during PnP device arrival) reads a COM interface pointer at this+0x60 twice without synchronization, a TOCTOU race-condition. Reconnect GFX init races against disconnect teardown on same session.
+0x150: cmp qword ptr [rdi+60h], 0 ; null check — field is NON-NULL
+0x155: je +0x413 ; skip if null
...
+0x2b9: mov rcx, qword ptr [rdi+60h] ; first load — SUCCEEDS
+0x2c1: mov rax, qword ptr [rcx] ; dereference — SUCCEEDS
... (conditional ETW trace writes widen the window) ...
+0x3ad: mov rcx, qword ptr [rdi+60h] ; second load — NOW NULL
+0x3b4: mov rax, qword ptr [rcx] ; ← CRASH: null dereference
CRdpPipeGfxPlugin::TerminateInstance() (called during session teardown) clears the same field outside its own critical section scope:
+0x1a: call CTSCriticalSection::Lock ; acquire lock
...
+0x53: call CTSAutoLock::~CTSAutoLock ; ← LOCK RELEASED
...
+0x99: lea rbx, [rdi+60h]
+0xa6: call TCntPtr<...>::SafeRelease
+0xab: and qword ptr [rbx], 0 ; ← CLEARS this+0x60 (OUTSIDE LOCK)
Both functions execute concurrently on the NT threadpool via PnP device arrival (CPnPOnRemoteDisplayDeviceArrivalWorkItem) and removal (CPnPOnRemoteDisplayDeviceRemovalWorkItem) work items. Enable acquires no lock. The field clear in TerminateInstance occurs after lock release. No synchronization protects the shared field on either path.
The race window between the two reads in Enable is widened by conditional ETW trace writes gated by RdpCoreTS_Context+0x48. The default ETW subscription level on Server 2025 RDS is 255 (confirmed via logman query -ets), which exceeds all comparison thresholds and causes all ETW branches to fire and maximizing the window. This level seems to be the standard default on Windows Server 2025 RDS deployments.
Reproduction
I was curious. Maybe i could trigger the null pointer dereference deliberately. A first PoC used a bash script running in WSL Debian. It uses xfreerdp and iptables to simulate random and rapid connect, disconnect and reconnect sessions. It fried my production server with a race window of 202ms. Outside of business-hours of-course 😉.
It was running rdpcorets.dll version 10.0.26100.7309. The most recent at that time was 10.0.26100.32230.
So i went home and setup a new Server 2025 with the RDSH role, patched up-to-date. Didnt crash at all. It wasnt crowded though as the prod system.
Updated my reproducer to run headless and python. I got the most recent version of rdpcorets.dll to crash, basically continuously. Cool.
It didnt escalate via any of the above paths though; just crashing and disconnecting all active session. Under zero natural, user load.

I went ahead and opened a case via MSRC on 01.04.
It was tracked as Case 111946.
On 29.05. Microsoft confirmed the behavior, but also noted that this is a duplicate and not eligible for bounty. They expect a CVE assignment though and a patch in the 7B update.
On June 24 CVE-2026-50369 was assigned and Microsoft rated this as an Elevation of Privilege vulnerability in Windows Remote Desktop Services.
2 researchers also got acknowledged. I assume they found the EoP in that code path. Very cool stuff.
A (vibecoded) reproducer for the DoS bug is live at https://github.com/syxlox/CVE-2026-50369.
