CmBatt.sys CmBattAddAcAdapterV1 global pointer UAF
CmBatt.sys, the ACPI control-method battery and AC-adapter miniport, keeps
a global AcAdapter that names the current AC-adapter FDO. ACPI
battery/AC-adapter notifications, IOCTLs, and WMI requests all snapshot it
under DeviceObjectMutex:
KeAcquireGuardedMutex(&DeviceObjectMutex);
void* ac = AcAdapter; // snapshot the global under the mutex
KeReleaseGuardedMutex(&DeviceObjectMutex);
if (ac != NULL) {
/* dereference ac to service the request */
}
That is the same shape as the battc.sys bug shipped this same month — the
class driver and its miniport each keep a “current adapter” global, and in
both the add path publishes before init is known to succeed.
CmBattAddAcAdapterV1 allocates and sets up the device extension,
publishes AcAdapter = DeviceExtension under DeviceObjectMutex, then
calls up into the class driver through AdapterClassInitializeDevice(...).
If that call fails the failure label detaches and destroys the FDO under
the mutex — but never retracts the publish:
// CmBattAddAcAdapterV1 failure path — vulnerable build
KeAcquireGuardedMutex(&DeviceObjectMutex);
IoDetachDevice(DeviceExtension->LowerDeviceObject); /* +0x20: detach from the stack */
CmBattDestroyFdo(DeviceExtension->Self); /* +0x10: free the FDO and its extension */
KeReleaseGuardedMutex(&DeviceObjectMutex);
/* AcAdapter still names the freed DeviceExtension; WMI still dispatches into it */
After CmBattDestroyFdo, the extension and its device object are gone, but
AcAdapter still names them. The next ACPI notification or IOCTL that
snapshots AcAdapter under the mutex gets the stale pointer and
dereferences freed memory. As in battc.sys, the mutex only serialises
readers against each other; it cannot save them when the teardown never
publishes the “gone” state.
There is a second, related omission in the same path. WMI is not told the device is going away, so even after the FDO is destroyed WMI may keep issuing requests against it — a separate dispatch surface into the same freed object, with WMI as the dispatcher.
The patch
The fix adds two steps to the failure path: deregister WMI before the
teardown begins, and clear the global AcAdapter under the mutex after
the FDO is destroyed:
// CmBattAddAcAdapterV1 failure path — patched
IoWMIRegistrationControl(DeviceExtension->Self, 2); /* +0x10: WMIREG_ACTION_DEREGISTER, before teardown */
KeAcquireGuardedMutex(&DeviceObjectMutex);
IoDetachDevice(DeviceExtension->LowerDeviceObject); /* +0x20 */
CmBattDestroyFdo(DeviceExtension->Self); /* +0x10 */
AcAdapter = NULL; /* retract the publish, still under the mutex */
KeReleaseGuardedMutex(&DeviceObjectMutex);
Ordering matters: deregister WMI first so no new WMI requests arrive, then
detach and destroy, then clear the global under the mutex so any ACPI/IOCTL
reader that arrives after sees NULL. The two fixes address different
dispatch surfaces into the same freed object.
Attack path
sequenceDiagram
participant Add as CmBattAddAcAdapterV1
participant G as AcAdapter under DeviceObjectMutex
participant N as ACPI notify or IOCTL thread
Add->>G: AcAdapter = DeviceExtension, publish under mutex
Add->>G: AdapterClassInitializeDevice fails, IoDetachDevice and CmBattDestroyFdo, AcAdapter not cleared
N->>G: take mutex, ac = AcAdapter = destroyed FDO
N->>G: release mutex, dereference ac
Note over G: use-after-free on the destroyed FDO
Triggering it needs an AC-adapter add that fails after the publish — driven by a malicious or broken ACPI battery or AC-adapter device, or resource exhaustion — plus an ACPI notification, IOCTL, or WMI request that races into the destroyed FDO. Physical or passed-through battery devices give the attacker the leverage to induce the failure.
When a class driver and its miniport ship the same fix in the same month,
the bad idiom was copied between them, and battc.sys is exactly that
companion here. Audit sibling ACPI/battery miniports for the same
publish-without-retract lifecycle, and note that WMI registration outlives
the device unless explicitly revoked — freeing an object WMI still knows
about turns later WMI requests into use-after-free on their own.