dxgkrnl.sys — Use-After-Free (CWE-416) — missing reference count on DXGSHAREDVMOBJECT in DXG_HOST_REMOTEOBJECTCHANNEL::ProcessChannelMessage fixed
KB5073724
1. Overview
- Unpatched Binary:
dxgkrnl_unpatched.sys - Patched Binary:
dxgkrnl_patched.sys - Overall Similarity: 0.99 (99%)
- Diff Statistics:
- Matched Functions: 6897
- Changed Functions: 38
- Identical Functions: 6859
- Unmatched Functions: 0 (Unpatched), 0 (Patched)
Verdict: The patch remediates multiple critical vulnerabilities in the GPU paravirtualization (VMBus) interfaces of dxgkrnl.sys, most notably a Use-After-Free (UAF) in the Remote Object Channel and a cross-device memory access bypass, both enabled by security checks being improperly gated behind disabled feature flags.
2. Vulnerability Summary
The patch addresses several critical flaws stemming from missing locks, missing reference counting, and bypassed authorization checks—most of which were erroneously gated behind internal feature-staging flags in the unpatched build.
Finding 1: Use-After-Free in ProcessChannelMessage
- Severity: Critical
- Vulnerability Class: Use-After-Free (CWE-416)
- Affected Function:
DXG_HOST_REMOTEOBJECTCHANNEL::ProcessChannelMessage - Root Cause: When processing VMBus message type 1 (CreateBundle, size 0x58), the handler iterates over guest-supplied shared VM object handles. The unpatched code only calls
DXGSHAREDVMOBJECT::AddReference()ifFeature_1259646266__private_IsEnabledDeviceUsage()returns true. Because this flag is disabled by default, the object pointer is used to read data (+0x8and+0x0) without holding a reference. A concurrent thread or VMBus message (e.g., type 3 DestroyBundle) can free the object, causing the kernel to read from reclaimed pool memory. - Entry Point & Data Flow:
- Malicious guest VM sends VMBus packet to
DXG_HOST_REMOTEOBJECTCHANNEL. VmBusProcessPacketdispatches the packet toProcessChannelMessage.- Handler matches message type 1, size 0x58.
- Iterates array of handles at
arg2+0x18(count atarg2+0x10). - Looks up
DXGSHAREDVMOBJECTfrom handle table. - Skips
AddReferencedue to disabled feature flag, leaving a dangling pointer.
Finding 2: Cross-Device Memory Access in VmBusBlt
- Severity: High
- Vulnerability Class: Missing Authorization / Confused Deputy (CWE-863)
- Affected Function:
DXG_HOST_VIRTUALGPU_VMBUS::VmBusBlt - Root Cause: While handling
DXGKVMB_COMMAND_BLTfrom a guest VM, the unpatched code validates that the provided DXGCONTEXT belongs to the provided DXGDEVICE by comparingcontext->devicepointers. However, this critical isolation check is bypassed ifFeature_611095865returns false. A malicious guest can intentionally mismatch device and context handles to execute a BLT (memory copy) operation across device boundaries, breaking VM-to-VM or process-to-process isolation on the GPU. - Entry Point & Data Flow:
- Guest VM sends
DXGKVMB_COMMAND_BLTover the virtual GPU VMBus. VmBusBltextracts the target context and device handles.- The function skips the ownership validation check due to the feature flag.
- The BLT command proceeds with unauthorized memory access.
Finding 3: Missing Resource Reference in VmBusCreateNtSharedObject
- Severity: High
- Vulnerability Class: Missing Reference Count (CWE-911)
- Affected Function:
DXG_HOST_GLOBAL_VMBUS::VmBusCreateNtSharedObject - Root Cause: If the handle table entry type is
0x4(DXGRESOURCE), the code is supposed to create aDXGRESOURCEREFERENCEfor safe tracking. The unpatched code skips this reference creation ifFeature_309893433is disabled, while still sharing the object. This leads to a potential UAF when the underlying resource is destroyed.
Finding 4: Race Conditions in Pin/Unpin Operations
- Severity: Medium to High
- Vulnerability Class: Race Condition (CWE-362)
- Affected Functions:
DXGDEVICE::PinResources,DXGDEVICE::UnpinResource,DXGCONTEXT::RenderKmLda,DXG_HOST_VIRTUALGPU_VMBUS::VmBusSignalSyncObjectCblt,SignalSynchronizationObjectFromCpu - Root Cause: The unpatched code performs these operations without the protecting locks. The patch adds/restores the locking in two distinct directions.
DXGSYNCOBJECTLOCK::AcquireSharedalready exists in the unpatched build but is gated behindFeature_2505606457; the patch removes that gate and makes the acquire unconditional. Per-resourceDXGAUTOMUTEX::Acquirelocking is entirely NEW in the patch (0 occurrences in the unpatched build) and is itself gated behind the newly-introducedFeature_253710648staging flag, i.e. a staged rollout where the lock is only taken once the flag is enabled. With the locks absent, the code modifies allocation flags, triggers GPU paging callbacks, or signals sync objects without holding the required locks, allowing concurrent modification. This locking-based hardening matches the KB's assigned CWE-362 race-condition CVEs (CVE-2026-20814, CVE-2026-20836).
3. Pseudocode Diff
DXG_HOST_REMOTEOBJECTCHANNEL::ProcessChannelMessage (UAF)
// UNPATCHED (decompilation of 0x1C028C3A0, per-handle loop body)
// v35 is the DXGSHAREDVMOBJECT looked up from the guest's handle table.
v35 = *((DXGSHAREDVMOBJECT **)v30 + 2 * (unsigned int)v34);
if ( v35 == nullptr )
break;
if ( (unsigned int)Feature_1259646266__private_IsEnabledDeviceUsage() != 0 )
{
DXGSHAREDVMOBJECT::AddReference(v35, v36); // reference taken only when flag is enabled
v66[v32] = v35; // and only then recorded for later release
}
v67[v32] = *((void **)v35 + 1); // read object+0x8 (runs even when no reference was taken)
a2[v32 + 6] = *(_DWORD *)v35; // read object+0x0 (runs even when no reference was taken)
// PATCHED (decompilation of 0x1C028C490, same loop body)
v35 = *((DXGSHAREDVMOBJECT **)v30 + 2 * (unsigned int)v34);
if ( v35 == nullptr )
break;
DXGSHAREDVMOBJECT::AddReference(v35, v33); // reference taken unconditionally
v64[v32] = *((void **)v35 + 1); // read object+0x8, reference now held
a2[v32 + 6] = *(_DWORD *)v35; // read object+0x0, reference now held
v63[v32] = v35; // recorded for later release, unconditionally
4. Assembly Analysis
DXG_HOST_REMOTEOBJECTCHANNEL::ProcessChannelMessage
Unpatched Assembly (Vulnerable Sequence, 0x1C028C3A0)
00000001C028C73C mov r12, [r8+r12*8] ; look up DXGSHAREDVMOBJECT from handle table
00000001C028C740 test r12, r12
00000001C028C743 jz loc_1C028C7CC
00000001C028C749 call Feature_1259646266__private_IsEnabledDeviceUsage ; 0x1C00281DC
00000001C028C74E test eax, eax
00000001C028C750 jz short loc_1C028C75F ; flag disabled: skip AddReference and the ref-array store
00000001C028C752 mov rcx, r12
00000001C028C755 call ?AddReference@DXGSHAREDVMOBJECT@@QEAAJXZ ; 0x1C0238ED8
00000001C028C75A mov [rbp+r13*8+110h+var_150], r12 ; record object for later release
00000001C028C75F mov rax, [r12+8] ; read object+0x8 without a reference held
00000001C028C764 mov [rbp+r13*8+110h+var_D0], rax
00000001C028C769 mov eax, [r12] ; read object+0x0 without a reference held
00000001C028C76D mov [rsi+r13*4+18h], eax
00000001C028C772 mov eax, [rsp+210h+var_1E0]
00000001C028C776 inc eax
00000001C028C778 mov [rsp+210h+var_1E0], eax
00000001C028C77C cmp eax, [rsi+10h]
00000001C028C77F jnb loc_1C028C805
00000001C028C785 mov r12, [rsp+210h+var_1D8]
00000001C028C78A jmp loc_1C028C6D4 ; next handle
Patched Assembly (Fixed Sequence, 0x1C028C490)
00000001C028C82C mov r12, [r8+r12*8] ; look up DXGSHAREDVMOBJECT from handle table
00000001C028C830 test r12, r12
00000001C028C833 jz short loc_1C028C8AF
00000001C028C835 mov rcx, r12
00000001C028C838 call ?AddReference@DXGSHAREDVMOBJECT@@QEAAJXZ ; 0x1C0238A98, no feature-flag gate
00000001C028C83D mov rax, [r12+8] ; read object+0x8, reference now held
00000001C028C842 mov [rbp+r13*8+110h+var_D0], rax
00000001C028C847 mov eax, [r12] ; read object+0x0, reference now held
00000001C028C84B mov [rsi+r13*4+18h], eax
00000001C028C850 mov eax, [rsp+210h+var_1E0]
00000001C028C854 inc eax
00000001C028C856 mov [rbp+r13*8+110h+var_150], r12 ; record object for later release, unconditionally
00000001C028C85B mov [rsp+210h+var_1E0], eax
00000001C028C85F cmp eax, [rsi+10h]
00000001C028C862 jnb loc_1C028C8E8
00000001C028C868 mov r12, [rsp+210h+var_1D8]
00000001C028C86D jmp loc_1C028C7C4 ; next handle
DXG_HOST_VIRTUALGPU_VMBUS::VmBusBlt
Unpatched Assembly (Vulnerable Sequence, 0x1C023D020)
00000001C023D1E0 call Feature_611095865__private_IsEnabledDeviceUsage ; 0x1C0026B38
00000001C023D1E5 mov rcx, [rsp+1A0h+var_148] ; context object
00000001C023D1EA test eax, eax
00000001C023D1EC jz short loc_1C023D20E ; flag disabled: skip the ownership check entirely
00000001C023D1EE cmp [rcx+10h], r14 ; context->device vs supplied device
00000001C023D1F2 jz short loc_1C023D20E ; match: proceed
00000001C023D1F4 call cs:__imp_WdLogNewEntry5_WdError ; mismatch: reject
00000001C023D1FB nop dword ptr [rax+rax+00h]
00000001C023D200 mov rcx, rax
00000001C023D203 mov rax, [rsp+1A0h+var_148]
00000001C023D208 mov [rcx+20h], r14
00000001C023D20C jmp short loc_1C023D1B0
00000001C023D20E add rcx, 1D0h ; proceed: acquire context push lock and run the BLT
Patched Assembly (Fixed Sequence, 0x1C023CBE0)
00000001C023CD57 mov rcx, [rsp+1A0h+var_148] ; context object
00000001C023CD5C test rcx, rcx
00000001C023CD5F jnz short loc_1C023CDA3
; ... null-context path logs an error ...
00000001C023CDA3 cmp [rcx+10h], r14 ; context->device vs supplied device, no feature-flag gate
00000001C023CDA7 jz short loc_1C023CDC3 ; match: proceed
00000001C023CDA9 call cs:__imp_WdLogNewEntry5_WdError ; mismatch: reject
00000001C023CDB0 nop dword ptr [rax+rax+00h]
00000001C023CDB5 mov rcx, rax
00000001C023CDB8 mov rax, [rsp+1A0h+var_148]
00000001C023CDBD mov [rcx+20h], r14
00000001C023CDC1 jmp short loc_1C023CD73
00000001C023CDC3 add rcx, 1D0h ; proceed: acquire context push lock and run the BLT
5. Trigger Conditions
To trigger the critical UAF (ProcessChannelMessage):
- Obtain access to the Hyper-V GPU Paravirtualization VMBus interface from a malicious guest VM.
- Ensure the target host has internal feature flag
Feature_1259646266disabled (default state). - Send a message of type 1 (CreateBundle) to the Remote Object Channel with a size of exactly 0x58.
- The message structure must be formatted as follows:
+0x10: Handle count (> 0and<= 0x10)+0x14: Flags (Bit 0 must be set to indicate shared VM objects are present)+0x18: Array of handles resolving to validDXGSHAREDVMOBJECT(type0xd) in the host handle table.- From a secondary thread or virtual CPU in the guest, immediately send a type 3 message (DestroyBundle) targeting one of the shared VM object handles provided in step 4.
- Race Constraint: Because no
AddReferenceis taken (feature flag disabled), the looked-up object is not kept alive by this code path. The concurrent teardown must drop the object's last reference and free it between the handle-table lookup (mov r12, [r8+r12*8]at0x1c028c73c) and the two reads (mov rax, [r12+8]at0x1c028c75fandmov eax, [r12]at0x1c028c769), so those reads land on freed pool memory. - Observable Effect: Kernel crash (BSOD) due to paged out or corrupted pool memory being accessed, or silent kernel memory corruption if the pool has been sprayed with attacker-controlled data.
6. Exploit Primitive & Development Notes
- Provided Primitive: The bug offers a Kernel Use-After-Free (UAF). The attacker frees a
DXGSHAREDVMOBJECTkernel pool allocation while the host kernel holds a stale pointer to it. The kernel subsequently reads from offsets+0x0and+0x8of the freed allocation. - Turning it into a Full Exploit:
- Heap Spray: Reclaim the freed pool allocation (typically Nonpaged Pool) using a kernel object of identical allocation size that allows user-mode data injection. Good candidates involve
NtCreateWorkerFactoryor specific GDI objects depending on the exact size ofDXGSHAREDVMOBJECT. - Info Leak: Combine with a separate information leak vulnerability (or use the stale read itself if the reclaimed data contains pointers) to defeat KASLR.
- Control Flow Hijack: Although the visible unpatched sequence only shows reads, modifying the object's internal state via reclaimed memory could corrupt subsequent kernel operations relying on that object's lifetime or vtable.
- Mitigations Impact:
- KASLR: Must be bypassed to utilize kernel function pointers.
- Pool Integrity/SEGMENTHEAP: Modern Windows pool segregation may require careful spray sizing to ensure the reclaimed object lands in the exact freed slot.
- VBS/HVCI: If active, arbitrary write primitive development must rely on data-only attacks (corrupting privilege bits or linked lists) rather than write-what-where shellcode execution.
7. Debugger PoC Playbook
To validate and trace this vulnerability on an unpatched host with a kernel debugger (KD/WinDbg) attached:
- Breakpoints:
bp dxgkrnl!DXG_HOST_REMOTEOBJECTCHANNEL::ProcessChannelMessage(orbp 0x1c028c3a0if symbols are unavailable). This is the entry point to catch message type 1.bp 0x1c00281dc(TargetFeature_1259646266__private_IsEnabledDeviceUsage). Inspect the return value to confirm the vulnerable path is taken.-
bp 0x1c028c73c(mov r12, [r8+r12*8], the handle-table lookup that loads theDXGSHAREDVMOBJECTpointer; the feature-flag check follows at0x1c028c749). -
What to inspect at each breakpoint:
- At
ProcessChannelMessage:rcxholdsarg2(the message packet). Readpoi(rcx+0x10)for handle count, andpoi(rcx+0x14)to verify bit 0 is set. - At the handle iteration loop:
r12holds the raw pointer to the looked-upDXGSHAREDVMOBJECT. Monitorr12closely. -
At
0x1c028c74e(test eax, eax, right after the feature call): verifyeax(return value of the feature check). Ifeax == 0, thejzat0x1c028c750jumps overAddReferenceand the ref-array store, straight to the reads at0x1c028c75f. -
Key instructions/offsets:
0x1c0238ed8: TheDXGSHAREDVMOBJECT::AddReferencecall that is skipped when the flag is disabled.0x1c028c805:Release@DXGAUTOPUSHLOCK, where the handle-table shared push lock is released after the loop completes.-
mov rax, [r12+0x8]at0x1c028c75fandmov eax, [r12]at0x1c028c769: the exact instructions performing the reference-less dereference. -
Trigger Setup:
- This requires a Hyper-V environment. The host runs the unpatched
dxgkrnl.sys. - Spin up a guest VM with GPU-PV (GPU Paravirtualization) enabled.
-
From the guest, use custom code to interact with the virtual GPU device interface, sending raw VMBus packets. Specifically, issue a CreateBundle (type 1) with a shared VM object handle, while concurrently issuing a DestroyBundle (type 3) for the same handle.
-
Expected observation:
- If the race is won, the kernel debugger will halt with a Bugcheck (e.g.,
PAGE_FAULT_IN_NONPAGED_AREA). - The faulting instruction will be the memory read from
r12. - Inspecting
!pool r12will show that the memory isFree, confirming the UAF.
8. Changed Functions — Full Triage
Security-Critical Changes:
- DXG_HOST_REMOTEOBJECTCHANNEL::ProcessChannelMessage: Removed feature flag gate for AddReference (Critical UAF fix).
- DXG_HOST_VIRTUALGPU_VMBUS::VmBusBlt: Removed feature flag gate for context/device authorization check (Confused Deputy fix).
- DXG_HOST_GLOBAL_VMBUS::VmBusCreateNtSharedObject: Enforced DXGRESOURCEREFERENCE for type 0x4 objects.
- SignalSynchronizationObjectFromCpu, DXG_HOST_VIRTUALGPU_VMBUS::VmBusSignalSyncObjectCblt, DXG_HOST_VIRTUALGPU_VMBUS::VmBusSignalSyncObject: Hardened sync-object locking by REMOVING the Feature_2505606457 gate around DXGSYNCOBJECTLOCK::AcquireShared (unpatched gated behind the flag, patched unconditional).
- DXGDEVICE::PinResources, DXGDEVICE::UnpinResource, DXGCONTEXT::RenderKmLda: ADDED per-resource DXGAUTOMUTEX::Acquire / DXGPUSHLOCK::AcquireShared locking that is absent from the unpatched build; the new locking is itself gated behind the newly-introduced Feature_253710648 staging flag (present only in the patched build).
Behavioral / Cosmetic Changes (Bulk Triage):
The remaining functions (DXGDEVICE::EvictAllResources, DxgEnumHandleChildrenCB, DxgkGetDeviceStateInternal, DXGCHANNELENDPOINTPROXY::FreeHandle, DXGDEVICE::DestroyDeferredAllocations, ADAPTER_RENDER::FreeResourceHandleAndWaitForZeroReferences, DXGDEVICE::PinDirectFlipResources, DXGDEVICE::DisablePinnedHardware, DXGDEVICE::AppendAllocationListToResourceOrDevice, DXGDEVICE::DestroyClientResource, DXGDEVICE::GetDeviceExecutionState, DxgkCddEvict, DxgkCddMakeResident, DXGDEVICE::OpenCddPrimaryHandle, MapGpuVirtualAddressToAllocation, DXGDEVICE::CheckMultiPlaneOverlaySupport3, DXGDEVICE::ReportDeviceResources, DxgEscapeEvictWorker, DxgkOpenSyncObjectFromNtHandle2Impl, DXGDXGIKEYEDMUTEX::OpenSharedSurfForDevice, REMOTEVSYNCMAPPING::AddMapping, DxgkCreateSynchronizationObjectImpl, DXGDEVICE::TerminateAllocations, DxgkDestroyAllocationInternal, DXGDEVICE::CreateAllocation) primarily underwent structural restructuring, register reallocation by the compiler, and the addition of the same feature flag gates for various mutex operations that were subsequently cleaned up in the security-critical fixes.
9. Unmatched Functions
There were no added or removed functions between the unpatched and patched binaries. The patch relies entirely on modifying control flow and removing conditional feature flag branches inside existing functions.
10. Confidence & Caveats
- Confidence Level: High. The patch differences clearly identify the removal of
Feature_*_IsEnabled()wrappers around critical security boundaries (locking, reference counting, and authorization). - Assumptions Made: The analysis assumes the default state of the mentioned internal feature flags (
Feature_1259646266,Feature_611095865, etc.) isFALSEin the unpatched production binary, given that their purpose in patching is to enable security mechanisms unconditionally. - Verification Required: A researcher developing a PoC must confirm the size of the
DXGSHAREDVMOBJECTto prepare the kernel heap spray. Additionally, developing a reliable VMBus packet injector from a guest VM is required to actually trigger the race condition, which requires interacting with the Hyper-V integration components.