# CVE-2026-45640 — Windows Bluetooth Port Driver `bthport.sys` Use-After-Free in `HciLELegacyAdvertisingRequestCoordinator::MarkAllAdvertisingRequestAsNew`

---

## Summary

| | |
|---|---|
| **Product** | Windows — `bthport.sys` (Bluetooth Port Driver; LE legacy advertising request coordinator) |
| **CVE ID** | CVE-2026-45640 |
| **Impact** | Elevation of Privilege |
| **MSRC severity** | Important |
| **CVSS** | 7.0 / 6.1 — `CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H/E:U/RL:O/RC:C` |
| **CWE** | CWE-416: Use After Free (double-free / UAF race) |
| **Delivery** | Local — race against the IRP cancellation callback (`AC:H` reflects the timing window) |
| **KB / Fixed build** | KB5094126 — `bthport.sys` 10.0.26100.8655 (Win11 24H2 x64) |
| **Patch Date** | June 9, 2026 (2026-Jun) |
| **Pre-patch binary** | `bthport.sys` 10.0.26100.8524 — SHA256 `107eac1b5892ded9e589ce4eed7950b51ca79299076a6bdb72a3de763d8e9810` |
| **Post-patch binary** | `bthport.sys` 10.0.26100.8655 — SHA256 `0c71fbf4b3469b3f61acdd9e4b922a7d969fa618aae82c43bbb71e348e93a797` |
| **Feature flag** | `Feature_697033018` — **the fix is CFR-gated** |
| **Exploitability** | Exploitation Less Likely; not publicly disclosed; not exploited (per MSRC) |

---

## Product Description

`bthport.sys` is the Windows Bluetooth port driver. Its
`HciLELegacyAdvertisingRequestCoordinator` manages pending Bluetooth **LE legacy
advertising** requests, each backed by an IRP with a **cancellation routine** —
the function the I/O manager invokes when the IRP is cancelled, to complete and
clean up (and free) the request. Because the driver runs in kernel and the
advertising surface is reachable by a local, low-privileged caller, a
lifetime bug in its IRP handling is a local elevation vector.

---

## Vulnerability Summary

`MarkAllAdvertisingRequestAsNew` iterates the pending advertising requests and
**re-sets** each request's IRP cancellation routine: it first calls
`ClearRequestCancellationRoutine` (to detach the current routine), then
`SetRequestCancellationRoutine` (to install the new one). `ClearRequestCancellationRoutine`
**fails** when the IRP's cancellation is *already in progress* — i.e. the
cancellation callback is already completing/freeing that same IRP.

Pre-patch, a `Clear` failure still fell through to the error path and set the
per-request **error flag** (`| 4`), marking the IRP as a target for
`MarkAllAdvertisingRequestAsNew`'s own error-handling/cleanup — even though the
cancellation callback was concurrently processing (and about to free) that IRP:

```c
// MarkAllAdvertisingRequestAsNew (10.0.26100.8524) — PRE-PATCH, from our diff
this_00 = this;
bVar2 = ClearRequestCancellationRoutine(this, irp);
if (bVar2) {                              // Clear succeeded
    bVar2 = SetRequestCancellationRoutine(this_00, irp, in_R8);
    if (!bVar2) goto LAB_0;               // Set failed -> error flag
}
else {                                    // *** Clear FAILED (cancel already in progress) ***
LAB_0:
    *(uint *)(lVar1 + 8) |= 4;            // *** error flag set anyway -> IRP double-owned ***
}
local_res8 = local_res8 & -(ulonglong)((*(byte *)(lVar1 + 8) & 4) != 0);  // mark for error handling
```

A single IRP can therefore be **completed and freed on two paths at once** — the
in-flight cancellation callback and `MarkAllAdvertisingRequestAsNew`'s error
handling — a double free / use-after-free (CWE-416).

---

## Prerequisites and Constraints

- Local, low-privileged (`PR:L`, `AV:L`). The attacker drives LE advertising
  request churn and races IRP cancellation.
- `AC:H`: the bug is a race — it triggers only in the narrow window where
  `ClearRequestCancellationRoutine` fails because the cancellation callback is
  already processing the same IRP.
- Success yields a kernel double-free / UAF and, via heap grooming, elevation.

---

## Vulnerability Details

### Root Cause

`MarkAllAdvertisingRequestAsNew` treated a `ClearRequestCancellationRoutine`
failure as an ordinary error and set the request's error flag, claiming the IRP
for its own cleanup. But a `Clear` failure specifically means the IRP is *already
owned by the cancellation callback*, which will complete and free it. Both paths
then act on the same IRP — the race that produces the double free / UAF.

### The patch (confirmed — diff, .8524 → .8655)

Gated behind `Feature_697033018`, the coordinator changes the failure handling so
that a `Clear` failure is **not** treated as an error to be handled here:

```c
// MarkAllAdvertisingRequestAsNew (10.0.26100.8655) — PATCHED, feature-enabled branch
iVar4 = Feature_697033018__private_IsEnabledDeviceUsageNoInline();
if (iVar4 == 0) {
    /* ...original (feature-disabled) behaviour still ships... */
}
else {                                    // *** patched path ***
    pHVar5 = this;
    bVar3 = ClearRequestCancellationRoutine(this, irp);
    if (bVar3) {                          // Clear succeeded
        bVar3 = SetRequestCancellationRoutine(pHVar5, irp, in_R8);
        if (bVar3) goto LAB_1;            // Set succeeded -> OK, no error flag
        *(uint *)(lVar2 + 8) |= 4;        // *** only Clear-OK + Set-FAIL sets the error flag ***
    }
    // *** Clear FAILED -> do NOTHING, no error flag -> exit; the cancel callback owns the IRP ***
}
```

Now:
- **`Clear` fails** → the function sets no error flag and exits, so the IRP that
  is already being cancelled is completed/freed **only** by the cancellation
  callback.
- **`Clear` succeeds but `Set` fails** → the IRP is genuinely orphaned (no
  cancellation routine installed), so the error flag is set and this function
  cleans it up.

This removes the race in which the cancellation callback and
`MarkAllAdvertisingRequestAsNew` could both complete and free the same IRP.

### Patch Completeness Assessment

**CFR-gated behind `Feature_697033018`.** The corrected handling runs only when
the flag is enabled; the original (racy) `iVar4 == 0` branch still ships in
.8655. Patch state is not determined by file version alone — the runtime-gated
pattern seen across this corpus. Verify `Feature_697033018` is enabled to confirm
the fixed path is live.

---

## Detection Guidance

**Behavioural.** Local processes driving rapid LE advertising register/cancel
churn to force `ClearRequestCancellationRoutine` failures during
`MarkAllAdvertisingRequestAsNew`.

**Crash signature.** Kernel double-free / pool-corruption bugchecks
(`BAD_POOL_CALLER` / UAF) in `bthport!...MarkAllAdvertisingRequestAsNew` /
`...OnPendingAdvertisementRequestCancelThunk` under advertising cancellation.

**Config.** The fix is CFR-gated — confirm `Feature_697033018` is enabled so the
non-racy handling is active.

---

## References

- MSRC advisory — CVE-2026-45640 (Windows Bluetooth Port Driver Elevation of Privilege), released 2026-06-09, KB5094126.
- Full binary diff: `/data/patch_diffs/bthport_sys-cve-2026-45640-ghidriff.md`
- Related `bthport.sys` bug: CVE-2022-35820 (BthCreateKeyEx).
