# Root Cause Analysis — CVE-2026-21519

## Overview

| | |
|---|---|
| CVE | CVE-2026-21519 |
| Binary | dwmcore.dll (Desktop Window Manager Composition Core) |
| Vulnerability class | CWE-843: Access of Resource Using Incompatible Type (Type Confusion) |
| Impact | Elevation of Privilege |
| CVSS 3.1 | 7.8 (High) — AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| Pre-patch version | 10.0.26100.7705 (January 2026) |
| Post-patch version | 10.0.26100.7840 (February 2026, KB5077181) |
| Feature flag | Feature_3909349690 |
| Exploited in the wild | Yes (per MSRC advisory) |

## Executive Summary

A type confusion vulnerability in DWM's expression evaluation engine allows a local attacker to achieve code execution as SYSTEM. `CBaseExpression::SetOutputValue` accepts a `CExpressionValue` (a tagged union carrying composition/animation values such as floats, colors, matrices, etc.) without validating that the incoming value's type discriminant matches the expression node's expected output type. An attacker can supply a value tagged as one type (e.g., a matrix) to an expression expecting another type (e.g., a float), causing the downstream property-resolution switch to type-pun the underlying storage — interpreting attacker-controlled matrix data as a float pointer, or vice versa.

## Root Cause

### CExpressionValue: The Tagged Union

DirectComposition's expression engine uses `CExpressionValue` as a universal value container. It consists of:

- **Type discriminant** at offset `+0x48`: a `DCOMPOSITION_EXPRESSION_TYPE` enum (int32) indicating what the value represents (float, color, 2D/3D transform, matrix, reference, etc.)
- **Data payload** at offset `+0x00` to `+0x40`: the actual value storage, interpreted according to the type tag

Expression nodes (`CBaseExpression` subclasses) each have an **expected output type** stored at `this+0xa0`.

### The Bug: No Type Validation

```c
// PRE-PATCH — dwmcore.dll 10.0.26100.7705
void CBaseExpression::SetOutputValue(CExpressionValue *param_1)
{
    // No check: param_1->type (at +0x48) vs this->expectedType (at +0xa0)

    switch (*(int *)(param_1 + 0x48)) {   // trust the incoming type tag
        case EXPR_TYPE_FLOAT:
            CVisual::GetOpacityInternal(...);
            break;
        case EXPR_TYPE_COLOR:
            CResource::GetPropertyImpl(...);
            break;
        case EXPR_TYPE_MATRIX:
            CPropertySet::GetProperty(...);
            break;
        // ... many more cases, each interpreting payload differently ...
    }
}
```

The function trusts the type tag without comparing it against the expression node's expected type. If an attacker creates an expression node that expects type A, then feeds it a `CExpressionValue` tagged as type B, the switch interprets the B-format payload using A-format semantics — classic type confusion.

### Exploitation Path

1. **Create expression nodes** via DirectComposition batch buffer (`NtDCompositionProcessChannelBatchBuffer`). Expression nodes are DWM-internal objects with a known expected output type.
2. **Supply mismatched value**: Send a batch buffer command that causes `CalculateValue` → `SetOutputValue` to be called with a `CExpressionValue` whose type discriminant differs from the expression node's `expectedType`.
3. **Type-punning**: The property-resolution switch interprets the payload bytes according to the wrong type. For example, a matrix payload (16 floats = 64 bytes) interpreted as a pointer-based type gives the attacker a controlled pointer value.
4. **Memory corruption**: The controlled pointer is used in a property set/get operation, giving read/write primitives within DWM's address space.
5. **Code execution**: DWM runs at System integrity → arbitrary code execution as SYSTEM.

### Why CalculateValue Was Also Refactored

Both `SetOutputValue` (2466 → 512 bytes) and `CalculateValue` (4791 → 2371 bytes) shrank dramatically because the large inline property-resolution switch was extracted into a new function `CBaseExpression::SetOutputValueOnTarget`. This is a code hygiene improvement that accompanied the security fix — the switch logic itself wasn't the bug, the missing type check was.

## The Fix

```c
// POST-PATCH — dwmcore.dll 10.0.26100.7840
void CBaseExpression::SetOutputValue(CExpressionValue *param_1)
{
    bVar2 = Feature_3909349690::__private_IsEnabled();
    if ((bVar2) && (*(int *)(param_1 + 0x48) != *(int *)(this + 0xa0))) {
        // TYPE MISMATCH → reject with error HRESULT
        MilInstrumentationCheckHR_MaybeFailFast(
            0x14, NULL, 0, -0x7ff8ffa9, 0x22f, NULL);
        return -0x7ff8ffa9;
    }

    // Type tag matches (or flag is off) — proceed
    pCVar1 = param_1 + 0x48;
    if (*(DCOMPOSITION_EXPRESSION_TYPE *)pCVar1 == 0xb) {
        param_1 = *(CExpressionValue **)(param_1 + 0x40);  // dereference indirection
    }
    CExpressionValue::SetValue(
        (CExpressionValue *)(this + 0x50),
        *(DCOMPOSITION_EXPRESSION_TYPE *)pCVar1,
        param_1);
}
```

The fix adds a type-tag comparison at the function entry, gated by `Feature_3909349690`:

- `*(int*)(param_1+0x48)` = incoming value's `DCOMPOSITION_EXPRESSION_TYPE`
- `*(int*)(this+0xa0)` = expression node's expected output type
- On mismatch: `MilInstrumentationCheckHR_MaybeFailFast` logs the error and returns `HRESULT 0x80070057` (E_INVALIDARG)
- When the flag is disabled: old behavior (kill-switch fallback)

The rest of `SetOutputValue` was refactored: the large inline switch was replaced with a call to `CExpressionValue::SetValue`, with the property resolution delegated to the new `SetOutputValueOnTarget` helper. This eliminates the duplicated switch logic that both `SetOutputValue` and `CalculateValue` previously maintained.

## CVSS 4.0 Vector

```
CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
```

| Metric | Value | Rationale |
|--------|-------|-----------|
| AV:L | Local | Requires local code execution |
| AC:L | Low | Deterministic type confusion — no race |
| AT:N | None | DWM always running, DComp always available |
| PR:L | Low | Standard user can create DComp expressions |
| UI:N | None | No user interaction required |
| VC:H/VI:H/VA:H | High | Type confusion → controlled pointer → SYSTEM |

## Detection

### YARA Rule

```yara
rule CVE_2026_21519_DWM_Expression_TypeConfusion
{
    meta:
        description = "Detects tools exploiting CVE-2026-21519 (DWM expression type confusion)"
        author      = "OnlyFm252 / STAR Labs SG"
        date        = "2026-07-22"
        cve         = "CVE-2026-21519"
        severity    = "critical"

    strings:
        $dcomp_create = "DCompositionCreateDevice" ascii wide
        $nt_batch     = "NtDCompositionProcessChannelBatchBuffer" ascii wide
        $nt_commit    = "NtDCompositionCommitChannel" ascii wide
        $nt_create    = "NtDCompositionCreateChannel" ascii wide
        $win32u       = "win32u" ascii wide nocase
        $d3d11        = "D3D11CreateDevice" ascii wide
        $expression   = "Expression" ascii wide nocase
        $set_property = { 0B 00 00 00 }   /* CMD_SET_PROPERTY command */
        $type_tag_a   = { 00 00 00 00 [0-4] 0A 00 00 00 }  /* type float + propId */
        $type_tag_b   = { 03 00 00 00 [0-4] 0A 00 00 00 }  /* type matrix + propId */

    condition:
        uint16(0) == 0x5A4D and
        filesize < 2MB and
        ($nt_batch or $nt_commit or $nt_create) and
        ($win32u or $dcomp_create) and
        ($d3d11 or $expression) and
        ($set_property or $type_tag_a or $type_tag_b)
}

rule CVE_2026_21519_DWM_TypeConfusion_Generic
{
    meta:
        description = "Generic: DComp batch buffer manipulation targeting expression engine"
        author      = "OnlyFm252 / STAR Labs SG"
        date        = "2026-07-22"
        cve         = "CVE-2026-21519"

    strings:
        $batch    = "ProcessChannelBatchBuffer" ascii wide
        $channel  = "CreateChannel" ascii wide
        $commit   = "CommitChannel" ascii wide
        $dcomp    = "dcomp" ascii wide nocase
        $win32u   = "win32u" ascii wide nocase
        $expr1    = "SetOutputValue" ascii
        $expr2    = "ExpressionType" ascii
        $expr3    = "CalculateValue" ascii

    condition:
        uint16(0) == 0x5A4D and
        ($batch or $channel or $commit) and
        ($dcomp or $win32u) and
        1 of ($expr1, $expr2, $expr3)
}
```

### Sigma Rule

```yaml
title: CVE-2026-21519 DWM Expression Type Confusion Exploitation
id: c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f
status: experimental
description: >
    Detects suspicious DirectComposition channel activity characteristic of
    CVE-2026-21519 exploitation — process creating DComp channels and
    manipulating expression objects via batch buffers.
author: OnlyFm252 / STAR Labs SG
date: 2026/07/22
references:
    - https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-21519
tags:
    - attack.privilege_escalation
    - attack.t1068
    - cve.2026.21519
logsource:
    product: windows
    category: process_creation
detection:
    selection:
        ParentImage|endswith: '\dwm.exe'
        IntegrityLevel: System
    filter_legitimate:
        Image|endswith:
            - '\LogonUI.exe'
            - '\consent.exe'
            - '\WerFault.exe'
            - '\fontdrvhost.exe'
    condition: selection and not filter_legitimate
level: critical
falsepositives:
    - Legitimate DWM child processes (very rare)

---
title: CVE-2026-21519 DWM Crash After Expression Manipulation
id: d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f80
status: experimental
description: >
    Detects DWM crash or unexpected restart, which may indicate exploitation
    of CVE-2026-21519 (type confusion causing memory corruption).
author: OnlyFm252 / STAR Labs SG
date: 2026/07/22
tags:
    - attack.privilege_escalation
    - attack.t1068
logsource:
    product: windows
    service: system
detection:
    selection_crash:
        EventID:
            - 7031
            - 7034
        Provider_Name: 'Service Control Manager'
        param1|contains: 'Desktop Window Manager'
    selection_appcrash:
        EventID: 1000
        Provider_Name: 'Application Error'
        param1: 'dwm.exe'
    condition: 1 of selection_*
level: high
falsepositives:
    - GPU driver crashes
    - Windows Update DWM restart
```

### Sysmon Configuration

```xml
<!-- CVE-2026-21519: DWM expression type confusion detection -->

<!-- Event 1: Suspicious child process from dwm.exe -->
<ProcessCreate onmatch="include">
    <ParentImage condition="end with">dwm.exe</ParentImage>
</ProcessCreate>

<!-- Event 7: DComp-related DLL loads in suspicious processes -->
<ImageLoad onmatch="include">
    <ImageLoaded condition="end with">dcomp.dll</ImageLoaded>
</ImageLoad>
<ImageLoad onmatch="include">
    <ImageLoaded condition="end with">win32u.dll</ImageLoaded>
</ImageLoad>

<!-- Event 10: Process access to dwm.exe (for exploit setup) -->
<ProcessAccess onmatch="include">
    <TargetImage condition="end with">dwm.exe</TargetImage>
    <GrantedAccess condition="is">0x1F0FFF</GrantedAccess>
</ProcessAccess>

<!-- Event 11: File creation by dwm.exe (post-exploit payload drop) -->
<FileCreate onmatch="include">
    <Image condition="end with">dwm.exe</Image>
    <TargetFilename condition="contains">\Temp\</TargetFilename>
</FileCreate>
```

## Version Matrix

| Version | File Version | Status |
|---------|-------------|--------|
| Pre-patch (Jan 2026) | 10.0.26100.7705 | Vulnerable (exploited ITW) |
| Post-patch (Feb 2026, KB5077181) | 10.0.26100.7840 | Fixed (Feature_3909349690) |

## References

- [MSRC Advisory: CVE-2026-21519](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-21519)
- [MITRE CWE-843: Access of Resource Using Incompatible Type](https://cwe.mitre.org/data/definitions/843.html)

---
*Analysis by OnlyFm252 / STAR Labs SG — 2026-07-22*
