# CVE-2026-40377 — Microsoft Cryptographic Services `cryptxml.dll` ECDSA Size-Budget Mismatch in `I_XmlUnmarshallKeyInfo` → Heap Overflow

---

## Summary

| | |
|---|---|
| **Product** | Windows — `cryptxml.dll` (Microsoft Cryptographic Services; XML-DSig KeyInfo unmarshal) |
| **CVE ID** | CVE-2026-40377 |
| **Impact** | Elevation of Privilege |
| **MSRC severity** | Important |
| **CVSS** | 7.8 / 6.8 — `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H/E:U/RL:O/RC:C` |
| **CWE** | CWE-122: Heap-based Buffer Overflow (alignment/size-budget mismatch) |
| **Delivery** | Local — a crafted XML signature document processed via `CryptXmlOpenToDecode` |
| **KB / Fixed build** | KB5089549 — `cryptxml.dll` 10.0.26100.8457 (Win11 24H2 x64) |
| **Patch Date** | May 12, 2026 (2026-May) |
| **Pre-patch binary** | `cryptxml.dll` 10.0.26100.8115 — SHA256 `3ab1410c0a9f419821978db0a2fc5626e9d3d794f4fe7cb82c24709e3334806f` |
| **Post-patch binary** | `cryptxml.dll` 10.0.26100.8457 — SHA256 `0cb1f25a5ab9a542a182654340895b65c00777c11e5c29e6d12526e13b7d9617` |
| **Feature flag** | `Feature_1881917752` — **the fix is CFR-gated** |
| **Exploitability** | Exploitation Less Likely; not publicly disclosed; not exploited (per MSRC) |

---

## Product Description

`cryptxml.dll` implements Microsoft's XML digital-signature (XML-DSig) support.
`I_XmlUnmarshallKeyInfo` converts the `<KeyInfo>` element from the WS internal
structure into a CryptoAPI structure using a **two-pass** approach: pass 1 walks
all key entries to compute the total buffer size and does a single `HeapAlloc` /
`AiAlloc`; pass 2 walks the entries again and copies the data into that buffer.
It is reachable via `CryptXmlOpenToDecode`, which processes externally supplied
XML signature documents.

---

## Vulnerability Summary

The flaw is in the **ECDSA `KeyValue`** path (subtype 5). For the uncompressed EC
point `0x04 || X || Y`, each coordinate length is `Size = (point_length - 1) / 2`.

- **Pass 1 (size budget):** counted only `2 * Size` for the two coordinates.
- **Pass 2 (copy):** after copying each coordinate it advanced the write pointer by
  **`ALIGN8(Size) = (Size + 7) & ~7`**, i.e. it actually consumes `2 * ALIGN8(Size)`.

So pass 1 under-counts whenever `Size` is not a multiple of 8: actual usage exceeds
the budget by `2 * (8 - (Size % 8))` bytes. P-256 (`Size=32`) and P-384 (`Size=48`)
are 8-aligned and unaffected, but **P-521 (`Size=66`, `66 mod 8 = 2`)** consumes
**12 bytes** more than allocated → a **12-byte heap overflow** per entry.

A second bug: the pass-1-cached `Size` and data pointer were **overwritten by the
last ECDSA entry**, so with multiple ECDSA `KeyValue` entries of different
coordinate sizes, pass 2 copied earlier entries using the last entry's values —
additional corruption. The same defect exists in **two loops**: the macro-prefix
array loop and the global linked-list (`::P`) loop.

---

## Prerequisites and Constraints

- Local, low-privileged (`PR:L`, `AV:L`, `AC:L`): supply a crafted XML signature
  document to a component that calls `CryptXmlOpenToDecode`.
- Trigger: an ECDSA `KeyValue` with a non-8-aligned coordinate size — notably
  **P-521** — and/or multiple ECDSA entries of differing sizes.
- Result: heap/pool overflow of the under-sized `AiAlloc` buffer with
  attacker-controlled key bytes.

---

## Vulnerability Details

### Root Cause

Pass 1's size budget did not apply the 8-byte alignment that pass 2's copy uses,
so the allocation was smaller than the bytes actually written; and per-entry
`Size`/pointer state was clobbered by the last entry, mis-sizing/mis-copying
earlier entries.

### The patch (confirmed — diff, .8115 → .8457)

Gated behind `Feature_1881917752`, pass 1's size computation now **applies
`ALIGN8` to the ECDSA coordinates**, matching pass 2. Our diff shows the size
accumulator (`dwBytes`) built from `((value + 7) & 0xfffffffffffffff8)` terms per
field (and `((coord_len + 1) * 2 + 7) & ~7` for the coordinate pair):

```c
// I_XmlUnmarshallKeyInfo (10.0.26100.8457) — PATCHED, feature-enabled branch (from our diff)
dwBytes = dwBytes
        + (((coord_len + 1) * 2 + 7) & 0xfffffffffffffff8)          // *** 2*ALIGN8 for X||Y ***
        + ((*(uint*)(entry+0x70) + 7) & ~7) + ((*(uint*)(entry+0x60) + 7) & ~7)
        + ... ;                                                     // each field ALIGN8'd
v23 = AiAlloc(dwBytes, ...);                                        // now sized to actual consumption
```

Additionally, pass 2 was changed to **re-read each ECDSA entry's size and data
pointer from the source structure** (instead of the stale pass-1 cache), fixing
the multi-entry corruption; the subtype→type mapping was factored into a
`_GetKeyValueType` helper and the cleanup loop restructured for safer error
handling. Both the macro-prefix loop and the global-list loop are covered.

### Patch Completeness Assessment

**CFR-gated behind `Feature_1881917752`.** The aligned size budget and per-entry
re-read run only when the flag is enabled; the original under-counting path still
ships when disabled. Verify `Feature_1881917752` is enabled to confirm the fix is
live.

---

## Detection Guidance

**Behavioural.** Processing of XML signature documents with ECDSA `KeyValue`
elements using P-521 (or other non-8-aligned coordinate sizes), or multiple ECDSA
`KeyValue` entries; heap-corruption crashes in `cryptxml!I_XmlUnmarshallKeyInfo`
after `CryptXmlOpenToDecode` on unpatched/flag-disabled builds.

**Config.** The fix is CFR-gated — confirm `Feature_1881917752` is enabled.

---

## References

- MSRC advisory — CVE-2026-40377 (Microsoft Cryptographic Services Elevation of Privilege), released 2026-05-12, KB5089549.
- Full binary diff: `/data/patch_diffs/cryptxml_dll-cve-2026-40377-ghidriff.md`
