# CVE-2026-58534 — Windows IME `imjpapi.dll` Missing Blob-Size / Count Validation in `CLMParamSerializer::Deserialize` → Heap Overflow

---

## Summary

| | |
|---|---|
| **Product** | Windows — `imjpapi.dll` (Microsoft Japanese IME API / `sflmelm` MTF container converter) |
| **CVE ID** | CVE-2026-58534 |
| **Impact** | Elevation of Privilege |
| **MSRC severity** | Important |
| **CVSS** | 8.8 / 7.7 — `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H/E:U/RL:O/RC:C` |
| **CWE** | CWE-122: Heap-based Buffer Overflow |
| **Delivery** | Local — a low-privileged process supplies a crafted serialized LM-param blob via the IME property bag |
| **KB / Fixed build** | KB5101650 — `imjpapi.dll` 10.0.26100.8875 (Win11 24H2 x64) |
| **Patch Date** | July 14, 2026 (2026-Jul) |
| **Pre-patch binary** | `imjpapi.dll` 10.0.26100.8521 — SHA256 `b35e9aa7cb74c1fe6d67bd6298e9c52b8221c4c74433aee4de378cc8e3942abd` |
| **Post-patch binary** | `imjpapi.dll` 10.0.26100.8875 — SHA256 `f7f0c00341dfa851733b60ba7c21e256f098f71653e28d487e9553539865066d` |
| **Feature flag** | `Feature_801091896` — **the fix is CFR-gated** |
| **Exploitability** | Exploitation Less Likely; not publicly disclosed; not exploited (per MSRC) |

---

## Product Description

`imjpapi.dll` is the API surface of the Microsoft Japanese IME. Inside it,
`CLMParamSerializer` (in `onecoreuap\windows\feime\win8\jpn\suggestion\lib\sflmelm\mtfcontainerconverter.cpp`)
deserializes a language-model parameter object out of an **IME property bag**
(`IMtfPropertyBag`). Because the IME processes input on behalf of the interactive
user and the property-bag contents cross a trust boundary (scope-change `S:C` in
the CVSS vector), a memory-safety flaw in the deserializer is a local elevation
vector.

---

## Vulnerability Summary

`Deserialize` fetches a **blob size** from the property bag, allocates exactly
that many bytes with `operator new[]`, then has the property bag fill the buffer.
That blob is parsed into a **fixed `0x908`-byte object** (also `operator new[]`).
Pre-patch there is **no validation** of the blob size or of a count field taken
from the blob, even though the parser performs fixed reads through offset
`+0x174` and uses a blob-derived DWORD as a loop iteration count.

```c
// CLMParamSerializer::Deserialize (10.0.26100.8521) — PRE-PATCH, from our diff
iVar8 = (**(code **)(*param_1 + 0x20))(param_1,&DAT_0,local_res10);   // local_res10[0] = blob size from bag
...
puVar9 = operator_new__((ulonglong)local_res10[0]);   // *** size straight from the bag, unvalidated ***
...
puVar10 = operator_new__(0x908);                      // fixed 0x908-byte destination object
...
puVar10[0x146] = puVar3[0x4a];                        // dst+0x518  <-  blob+0x128  (unvalidated count)
...
do {                                                  // write 8-byte pointers into the fixed object
  ...
  *(ulonglong *)(puVar10 + uVar19*2 + 0x148) = ...;   // dst+0x520 + i*8
  uVar19++;
} while (uVar19 < (uint)puVar10[0x146]);              // *** count from blob+0x128, no upper bound ***
```

The pointer-write loop starts at `dst+0x520` and advances 8 bytes per iteration.
The next object field begins at `dst+0x848`, leaving room for exactly **101**
pointer entries (`(0x848 - 0x520) / 8 = 0x65`). The iteration count comes
straight from `blob+0x128` with no cap: index 101 reaches the adjacent field at
`dst+0x848`, and index 124 reaches `dst+0x900`, past the end of the `0x908`-byte
allocation — a controlled **heap-based buffer overflow** of 8-byte pointer
values (CWE-122).

---

## Prerequisites and Constraints

- Local, low-privileged (`PR:L`, `AV:L`). The attacker supplies a crafted
  serialized LM-param blob through the IME property-bag path.
- The overflow is driven by the count DWORD at `blob+0x128`; values above `0x65`
  overrun the fixed `0x908`-byte object with attacker-placed pointer values.
- Scope-change (`S:C`) reflects that the IME deserializer acts across the
  user/IME trust boundary, yielding elevation on success.

---

## Vulnerability Details

### Root Cause

`CLMParamSerializer::Deserialize` trusts two attacker-influenced quantities from
the serialized blob without validating them against the fixed layout of the
`0x908`-byte destination object:

1. the **blob size** (`local_res10[0]`), used to size the input allocation while
   the parser performs fixed reads through `+0x174`; and
2. the **entry count** at `blob+0x128` (stored to `dst+0x518`), used as the
   iteration count for a loop that writes 8-byte pointers beginning at
   `dst+0x520`.

With no upper bound on (2), a large count writes pointer entries past the
101-entry capacity and off the end of the object — a heap overflow.

### The patch (confirmed — diff, .8521 → .8875)

Gated behind `Feature_801091896`, `Deserialize` adds **two independent guards**:

```c
// CLMParamSerializer::Deserialize (10.0.26100.8875) — PATCHED, feature-enabled branch
bVar7 = wil::...FeatureImpl<Feature_801091896>::__private_IsEnabled(...);
if ( bVar7 && local_res10[0] < 0x178 )               // *** guard 1: minimum blob size ***
    _Throw_Hr(... 0x573 ... , 0x800700a9-ish);
puVar9 = operator_new__((ulonglong)local_res10[0]);
...
if ( bVar7 && 0x65 < (uint)puVar10[0x146] )          // *** guard 2: entry count <= 101 ***
    _Throw_Hr(... 0x59d ... );
```

**Guard 1** requires the blob to be at least `0x178` bytes before the fixed
parse — this fully covers the last fixed read (the DWORD at `+0x174` needs
`0x178` bytes), so on this build there is no residual out-of-bounds read.

**Guard 2** is the more significant one: it rejects an entry count greater than
`0x65` (101), exactly the capacity between `dst+0x520` and the next field at
`dst+0x848`, closing the pointer-array heap overflow.

### Patch Completeness Assessment

**CFR-gated behind `Feature_801091896`.** Both guards run only when the flag is
enabled; the original unchecked parse still ships in .8875. The parsing behaviour
is identical on both sides of the gate apart from the two throws, so patch state
is not determined by file version alone — the runtime-gated pattern seen across
this corpus. Verify `Feature_801091896` is enabled to confirm the checked path is
live.

---

## Detection Guidance

**Behavioural.** IME LM-param deserialization failing with the WIL
`_Throw_Hr` from `mtfcontainerconverter.cpp` (offsets `0x573` / `0x59d`) —
indicates a blob that violates the new bounds.

**Crash signature.** Heap-corruption crashes in
`imjpapi!CLMParamSerializer::Deserialize` writing 8-byte pointers past a
`0x908`-byte allocation, on unpatched or flag-disabled builds.

**Config.** The fix is CFR-gated — confirm `Feature_801091896` is enabled so both
bounds checks are active.

---

## References

- MSRC advisory — CVE-2026-58534 (Windows Input Method Editor (IME) Elevation of Privilege), released 2026-07-14, KB5101650.
- Full binary diff: `/data/patch_diffs/imjpapi_dll-cve-2026-58534-ghidriff.md`
