# CVE-2026-69321: Windows Power Dependency Coordinator Tampering Vulnerability

> CVE-2026-69321 root cause, patch diff, and CVSS 5.5 rating: six umpo.dll RPC handlers in Windows Power Dependency Coordinator skipped the caller check.

- Published: 2026-09-08
- Updated: 2026-10-14
- Series: Windows Service Trust Boundaries
- Tags: Windows, Tampering, Authorization, RPC, Registry, Reverse Engineering
- Source: https://stevenfoerster.com/notes/cve-2026-69321/

CVE-2026-69321 is a missing-authorization flaw in `umpo.dll`, the User Mode Power Service plug-in of Windows, in the component Microsoft calls Power Dependency Coordinator. Six RPC procedures wrote to the admin-protected `HKLM\...\Power\PowerSettings\` hive under the SYSTEM token without checking the caller's identity (CWE-306), while a seventh procedure on the same interface correctly impersonated the caller and ran the access check. Microsoft rated it Tampering / Important (CVSS 5.5) and fixed it in the September 8, 2026 security update.

**In plain terms:** Windows stores power-policy definitions (which sleep and performance profiles exist, what their parameters are, how they are described) in a part of the registry that only administrators are supposed to change. Before the patch, any program running on the machine, even one with no special privileges or running inside an app sandbox, could silently delete, create, or rewrite those definitions by asking a built-in Windows service to do the work on its behalf. The service performed the operation under its own elevated credentials without ever checking who was asking. A bad actor with ordinary user access to a machine could have removed or corrupted power-policy definitions that administrators and enterprise group policy depend on, disrupted power behavior across the system, or planted misleading settings. The bug did not allow running code, reading private data, or escaping a browser sandbox.

|              |                                                                                                                                                                                                                                                                           |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **CVE**      | [CVE-2026-69321](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-69321), published September 8, 2026                                                                                                                                                       |
| **Class**    | Microsoft: Tampering / Important; CWE-306, Missing Authentication for Critical Function. The analysis below describes the observed authorization bypass.                                                                                                                  |
| **Affected** | Windows `umpo.dll` (User Mode Power Service plug-in), observed on Win11 25H2, build 26200, and on Insider Canary 29617. The Microsoft [advisory](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-69321) is the authoritative source for affected products. |
| **Patch**    | Security update released September 8, 2026. Windows 11 24H2 and 25H2 are fixed from builds 26100.9445 and 26200.9445. See the [advisory](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-69321) for the update packages that apply to each product.                                                                                  |
| **CVSS**     | Microsoft CVSS 3.1 base score: 5.5, `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N`. My pre-disclosure estimate was 4.4 (`AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L`); Microsoft rated the integrity impact High and assessed no availability impact.                            |
| **Attacker** | Any authenticated local standard user (non-admin, medium integrity, member of `BUILTIN\Users`). Also a classic AppContainer token at Low integrity, which I originally and wrongly scoped out (see [The AppContainer correction](#the-appcontainer-correction))           |
| **Reporter** | Steven Foerster, independent, via MSRC. Microsoft's public advisory acknowledges Steven Foerster, Prayas (Pro bear), and 6ucy.                                                                                                                                            |
| **Timeline** | Reported May 2026 · behavior verified by MSRC June 2026, rated Tampering / Important · supplementary evidence filed Aug 2026 · CVE-2026-69321 published September 8, 2026 · technical analysis after the 30-day patch window                                              |

## What did CVE-2026-69321 allow an attacker to do?

In testing, a raw RPC client running in a classic AppContainer at Low integrity exploited CVE-2026-69321 to delete a genuine Microsoft-shipped power setting from an administrator-protected registry hive. The same token was denied when it attempted the registry operation directly. The RPC call returned success, the key disappeared, and the documented `powercfg /Q` tool then reported that the setting did not exist. The setting was restored from a backup immediately after the test.

The User Mode Power Service plug-in (`umpo.dll`, hosted in `svchost.exe -k DcomLaunch` under the `Power` service, running as SYSTEM) exposes the local RPC interface that made this possible. One writer on that interface does exactly what a privileged service should: it impersonates the RPC caller and checks the setting's security descriptor before touching the registry.

Six sibling dispatchers reach into the same admin-protected hive, `HKLM\SYSTEM\CurrentControlSet\Control\Power\PowerSettings\`, without impersonating the caller or running the per-setting access check. Their workers perform `RegCreateKeyExW`, `RegSetValueExW`, and `RegDeleteTreeW` under the SYSTEM service token. On a later Insider build, one narrow administrator check appears inside one of those paths, which shows that caller authorization was considered but applied inconsistently.

The bug is not a parser or memory-safety failure. It is an authorization asymmetry inside one binary: the correct pattern and the missing pattern sit side by side while operating on the same registry subsystem. That comparison is strong evidence that local reachability was not intended to grant authority to modify machine-scoped power policy.

Three possible impact uplifts were tested and did not hold: resource-string loading did not execute code, the gated runtime-value writer blocked changes to the wake-password control, and AppContainer reachability did not extend to the browser renderer sandbox. The demonstrated result remains a local Tampering vulnerability with bounded integrity and availability impact.

The whole finding fits in one picture. A non-admin binds the same local port every writer sits behind; one writer checks who is calling and six do not:

```mermaid
graph TD
    U["Non-admin local user (medium IL)<br/>or classic AppContainer (low IL)"] -->|"binds the umpo ALPC port<br/>(Everyone gets GR/GW/GX; the AppContainer<br/>SID's GENERIC_READ still includes Connect)"| P["umpo.dll RPC interface<br/>Power service, running as SYSTEM"]
    P --> G{"Which registry writer?"}
    G -->|"opnum 7 (user-key writer)"| S["UmpoRpcSettingAccessCheck x2:<br/>impersonate caller + per-setting SDDL"]
    S --> OK["Non-admin write correctly refused<br/>ACCESS_DENIED"]
    G -->|"opnums 6, 27, 28, 33, 34, 35"| N["No impersonation. No AccessCheck.<br/>Caller identity never consulted."]
    N --> W["RegCreateKeyExW / RegSetValueExW / RegDeleteTreeW<br/>run under the SYSTEM token"]
    W --> H["HKLM\...\Power\PowerSettings\<br/>on-disk ACL denies Users write, but the write is SYSTEM"]
    style OK fill:#1a3d1a,stroke:#44cc44,color:#c6f5c6
    style S fill:#1a3d1a,stroke:#44cc44,color:#c6f5c6
    style N fill:#5c1a1a,stroke:#cc4444,color:#f5c6c6
    style W fill:#5c1a1a,stroke:#cc4444,color:#f5c6c6
    style H fill:#5c1a1a,stroke:#cc4444,color:#f5c6c6
```

## Background: umpo, the Power service, and the PowerSettings hive

A few pieces matter:

- **The service.** `umpo.dll` is the User Mode Power Orchestration plug-in loaded into the `Power` service. [Anatomy of a Windows Service DLL in Ghidra](https://stevenfoerster.com/tutorials/anatomy-of-a-windows-service-dll-in-ghidra/) explains the hosting and initialization landmarks behind that architecture. The plug-in registers a local RPC interface (`{6c9b7b96-45a8-4cca-9eb3-e21ccf8b5a89}`, v1.0 on 25H2 and v1.1 on later Insider builds) on the `umpo` ALPC port. For the distinction between an interface, endpoint, binding, and procedure, see [Windows Local RPC and ALPC](https://stevenfoerster.com/tutorials/windows-local-rpc-and-alpc/). The port DACL is wide open: `D:(A;;GRGWGX;;;WD)…`, so `Everyone` (`WD`) gets generic read/write/execute and any local user can bind. The AppContainer SID gets only `GENERIC_READ`, which I originally read as putting sandboxed callers out of scope. That was wrong, and the correction has its own section below.
- **The hive.** `HKLM\…\Control\Power\PowerSettings\<subgroup>\<setting>\…` stores the machine's power-policy definitions and metadata. Its on-disk ACL grants `BUILTIN\Users` read-only (`BU:ReadKey`), and administrators/SYSTEM full control. [Windows Registry Security](https://stevenfoerster.com/tutorials/windows-registry-security-access-tokens-dacls/) covers how a key DACL, the thread's effective token, and the access requested at handle-open time combine to produce that result. Individual settings can carry a per-setting SDDL under `…\Control\Power\SecurityDescriptors\<setting>` that narrows write access further.
- **The gated sibling.** `UmpoRpcWriteToUserPowerKey` (opnum 7) is the writer that honors both gates. Before its worker runs it calls `UmpoRpcSettingAccessCheck` twice: impersonate the caller, open the thread token, and `AccessCheck` against the per-setting SDDL. On a setting whose SDDL grants `BUILTIN\Users` only `KEY_READ`, opnum 7 correctly returns `ACCESS_DENIED` to a non-admin. That is the reference behavior.

Six procedures do not follow it. The missing caller check is present in static analysis across all six, while the observable effects were established to different levels:

| opnum | name                             | registry op                          | evidence                                                                                  |
| ----: | -------------------------------- | ------------------------------------ | ----------------------------------------------------------------------------------------- |
|     6 | `UmpoRpcWriteToSystemPowerKey`   | `RegCreateKeyExW` + `RegSetValueExW` | Dynamic write on released 25H2; inert on Canary 29617                                     |
|    27 | `UmpoCleanupSettingOverride`     | `RegSetValueExW`                     | Static missing-check path and non-admin reachability; no distinct side effect isolated    |
|    28 | `UmpoCleanupAllSettingOverrides` | `RegSetValueExW` xN                  | Static missing-check path and non-admin reachability; no distinct side effect isolated    |
|    33 | `UmpoRpcCreateSetting`           | `RegCreateKeyExW` xN                 | Dynamic create from standard-user and AppContainer callers                                |
|    34 | `UmpoRpcCreatePossibleSetting`   | `RegCreateKeyExW`                    | Static missing-check path and non-admin reachability; no distinct side effect isolated    |
|    35 | `UmpoRpcRemoveSetting`           | **`RegDeleteTreeW`**                 | Dynamic deletion from standard-user and AppContainer callers, including a shipped setting |

## What is the root cause of CVE-2026-69321?

The root cause of CVE-2026-69321 is a missing caller check in six `umpo.dll` RPC procedures (opnums 6, 27, 28, 33, 34, and 35). Every one of the six ungated dispatchers has the same prologue. Taking opnum 6, `UmpoRpcWriteToSystemPowerKey`:

```
call UmpoIsClientLocal          ; I_RpcBindingIsClientLocal: always TRUE on ncalrpc
test eax, eax
je   ret_5                       ; ACCESS_DENIED only if remote (impossible here)
mov  al, [UmpoPowerPolicyInitialized]
test al, al
je   ret_0x15                    ; ERROR_NOT_READY
call UmpoInternalSanitizeWrite   ; Canary 29617 only: narrow admin check on a metadata subset
call UmpoWriteToSystemPowerKey   ; <-- caller-controlled path/value reach the Reg* APIs
```

The only two things the prologue checks are "is the client local" (trivially true on a local-only ALPC port) and "is the power policy subsystem initialized." Neither is an authorization check. There is no `RpcImpersonateClient`, no `OpenThreadToken`, no `AccessCheck`, and no call to `UmpoRpcSettingAccessCheck`, the per-setting gating helper this same interface already ships and uses correctly at opnum 7. The worker then takes a caller-supplied subgroup GUID, setting GUID, value selector, registry type, and data buffer, builds a path under the PowerSettings hive, and writes it under the SYSTEM token.

The token transition is the decisive Windows behavior here. [Windows Service Impersonation](https://stevenfoerster.com/tutorials/windows-service-impersonation-and-access-tokens/) shows why an RPC worker with no thread impersonation token falls back to the service process token for downstream object access.

Compare the gated sibling at opnum 7:

```
call UmpoIsClientLocal
call UmpoRpcSettingAccessCheck   ; impersonate + per-setting SDDL AccessCheck
call UmpoRpcSettingAccessCheck   ; second-level check on the runtime key
call UmpoWriteToUserPowerKey
```

The entire difference is the two `UmpoRpcSettingAccessCheck` calls: present for the user-key path, absent on all six system-key paths. Both gates that should stop a non-admin are bypassed as a result. The registry-key ACL passes because the write runs as SYSTEM and SYSTEM is on the ACL. The per-setting SDDL is never consulted, because the code that consults it is the code that was skipped.

The flawed assumption in one sentence: **the six system-key dispatchers treat "the request arrived on the local Power ALPC port" as sufficient authority to modify machine-scoped power policy, and reuse none of the caller-identity checks that the user-key writer three functions over already performs.**

### The check they did write, inside the ungated path

On Insider Canary 29617, `UmpoInternalSanitizeWrite` sits in the opnum 6 chain between the initialization flag and the worker, and it is not a no-op. The released 25H2 build I first reported against (`umpo.dll` 10.0.26100.8328) has no such step: its opnum 6 goes straight from the initialization flag to the worker. Decompiled on 29617 at `0x18000A29C`:

```c
if (accessor < 0xe && ((0x306c >> accessor) & 1) && buf != 0 && len > 1 && *buf == 0x40) {
    if (UmpoRpcIsAdministrator() != 0) return 5;      // ERROR_ACCESS_DENIED
}
return 0;
```

`0x306c` sets bits 2, 3, 5, 6, 12 and 13. Read against the accessor table at `0x180025000` (stride `0x10`, string pointer at `+8`, valid range `0..0x18`), those are exactly the `FriendlyName` and `Description` metadata accessors and their variants. So a caller-identity check does exist here: on one operation, on a narrow subset of its accessors, conditioned on the payload's leading byte being `0x40`.

I found this after the original report went in, while chasing something else, and it is the single most useful piece of evidence in the finding. The weakest reading of any missing-authorization bug is "nobody considered access control on this surface." That reading is not available here. The authors considered it twice: once properly at opnum 7, with impersonation and a per-setting `AccessCheck`; and, on 29617, once narrowly inside opnum 6 itself, with an administrator check on a specific metadata subset. Every other accessor on opnum 6, and opnums 27, 28, 33, 34 and 35 in their entirety, still reach the registry with the caller's identity never consulted.

The enforcement pattern was in the file the whole time. It just never got applied to the neighboring paths.

The asymmetry, drawn as a call-graph: one writer routes through the access check, six route straight to the registry worker.

```mermaid
graph TD
    O7["opnum 7: UmpoRpcWriteToUserPowerKey<br/>(the one gated writer)"] --> AC["UmpoRpcSettingAccessCheck x2<br/>impersonate + per-setting SDDL"]
    AC --> UW["Write reaches the registry only after the check"]
    SIX["opnums 6, 27, 28, 33, 34, 35<br/>(six ungated writers)"] --> PR["Prologue checks only UmpoIsClientLocal<br/>and UmpoPowerPolicyInitialized"]
    PR --> RW["RegCreate / RegSetValue / RegDeleteTree<br/>under the SYSTEM token, caller never checked"]
    style AC fill:#1a3d1a,stroke:#44cc44,color:#c6f5c6
    style UW fill:#1a3d1a,stroke:#44cc44,color:#c6f5c6
    style PR fill:#5c1a1a,stroke:#cc4444,color:#f5c6c6
    style RW fill:#5c1a1a,stroke:#cc4444,color:#f5c6c6
```

## Verification

How each claim was pinned down:

1. **Confirm reachability from a non-admin seat.** A missing check in a decompile is a hypothesis, not a finding. I called each affected opnum from a non-admin standard user (`BUILTIN\Users`, medium integrity, non-elevated). None of the calls was refused at dispatch.

2. **Walk each primitive end to end from the non-admin seat.** Writing `OverrideACSettingIndex` on an admin-only setting landed a marker value under the hive while the same token could not `reg add` the parent (`ACCESS_DENIED`), the ACL bypass in one before/after pair. Overwriting `Attributes` on a setting whose per-setting SDDL grants `BUILTIN\Users` only `KEY_READ` succeeded through opnum 6, proving the bypass works even where the system explicitly marked the setting user-read-only. Opnum 33 planted an attacker-named GUID subkey under the SYSTEM token; opnum 35 deleted a subtree back out.

3. **Test the strongest plausible impact claims.** I tested three paths from the registry primitives to code execution, a security-control bypass, or an escape from a real sandbox:
   - **Resource-string to `DllMain`.** Metadata fields like `ValueUnits` accept `REG_EXPAND_SZ` resource references. A SYSTEM-side consumer resolves them with `RegLoadMUIStringW`, which does call `LoadLibraryExW` on the caller-controlled path. But it loads with `LOAD_LIBRARY_AS_IMAGE_RESOURCE`, so `DllMain` never runs. Planting the string worked; the code execution did not. **Not a code-execution primitive.**
   - **Disable "Require a password on wakeup" as a non-admin.** That requires writing the runtime AC/DC value, which goes through opnum 7, the one gated writer. Against the locked setting, opnum 7 returned `ACCESS_DENIED`, exactly as designed. **The most damaging chain runs straight into the one check that works.**
   - **Turn sandbox reachability into a sandbox escape.** Finding later that a Low-IL AppContainer can drive these writers raised the question of whether a browser renderer or similar sandbox could reach them. I measured that against real processes before making any claim. That is the next section.

Those negative controls define the finding's boundary: protected configuration can be created, changed on affected builds, or deleted, but the tests did not establish code execution, a wake-password bypass, or an escape from a sandbox that handles hostile content.

## The AppContainer correction

My original report told MSRC that sandboxed callers were out of scope, and gave the port DACL as the reason: the AppContainer SID gets only `GENERIC_READ`, so an AppContainer process cannot send the requests the write primitives need.

That was an inference from a security descriptor that I never tested, and it is wrong. `GENERIC_READ` on an ALPC port maps to `[Connect, ReadControl]`, and `Connect` is all you need to bind the interface and dispatch calls. I filed a supplementary erratum rather than leave a false statement standing on an open case.

What a classic AppContainer token at Low integrity actually does:

|                                                        | result                                                                    |
| ------------------------------------------------------ | ------------------------------------------------------------------------- |
| Token                                                  | `type=Primary ac=True il=Low`, derived from an ordinary non-admin account |
| Control: perform the registry operation directly       | **ACCESS_DENIED**                                                         |
| `Connect` to the `umpo` port from the LowBox           | **OK**                                                                    |
| opnum 33 `UmpoRpcCreateSetting`                        | **rc = 0**; key exists afterwards, owner `NT AUTHORITY\SYSTEM`            |
| opnum 35 `UmpoRpcRemoveSetting`, admin-created subtree | **rc = 0**; subtree gone                                                  |
| opnum 35 against a **shipped Windows setting**         | **rc = 0**; `powercfg /Q` then reports it does not exist                  |

The last row is the one that matters. Every earlier demonstration used a synthetic marker or an administrator-planted key, which invites the reply that nothing real was ever touched. Deleting a genuine Microsoft-shipped setting (provenance asserted from its `powrprof.dll` resource metadata before the call) and then watching the documented `powercfg` tool report it missing closes that off. It was restored from a backup taken beforehand.

Getting this right took more care than the original finding did:

> - **An ordering artifact nearly became the result.** The first sandboxed run appeared to succeed only because a medium-integrity call had already primed the client-side binding earlier in the same process. That result was discarded. Every reported run restarts the `Power` service first and puts _only_ the sandboxed leg in the process.
> - **`powrprof.dll` is not the vehicle.** It is documented desktop-apps-only and returns `RPC_S_SERVER_UNAVAILABLE` (1722) from a LowBox. The working vehicle is a raw NDR client, which is evidence that the reachable path is not the sanctioned API.
> - **The sandbox was proved behaviorally, not asserted.** From inside the calling context, a file granted to `ALL APPLICATION PACKAGES` opens while the caller's own profile directory becomes unlistable. The restriction was demonstrably in force, not merely requested.

Limits of the AppContainer evidence:

> - The harness needed an administrator to grant `ALL APPLICATION PACKAGES` read access to the staging directory, so the sandboxed thread could load a prebuilt RPC client. A real sandboxed attacker runs its own code inside its own container and needs no such grant, but I did not demonstrate that.
> - What was tested is a classic AppContainer token impersonated on a thread. Not a true `CreateProcess`-hosted container, and not an LPAC process. The port's SDDL carries no `ALL RESTRICTED APPLICATION PACKAGES` entry, but it does grant the named capability `lpacAppExperience`, so it deliberately admits LPAC callers holding that capability. Whether any particular LPAC sandbox on the system holds it, I did not test and do not claim.

### Which real sandboxes can reach the port

A test token proves the port admits AppContainers in general. The question that matters to a defender is narrower: can the sandboxes that actually process hostile content reach it? A browser renderer is the obvious case. I computed granted access against the live umpo port security descriptor using the real token of each running process, with no impersonation:

| process                       | AppContainer | granted on umpo port         |
| ----------------------------- | ------------ | ---------------------------- |
| `msedge.exe` renderer ×2      | True         | **0 (denied)**               |
| `msedgewebview2.exe` renderer | True         | **0 (denied)**               |
| `msedgewebview2.exe` renderer | False        | **0 (denied)**               |
| `msedge.exe` browser (main)   | False        | Connect, Delete, ReadControl |
| `msedge.exe` gpu-process      | False        | Connect, ReadControl         |
| `msedgewebview2.exe` utility  | True         | Connect, ReadControl         |

Every renderer is denied. The Chromium processes that _can_ reach the port are the browser, GPU and utility helpers, not the renderers that execute web content. Access tracks the capability set rather than AppContainer-ness: an AppContainer utility process is granted while a non-AppContainer renderer is denied.

Three other Windows sandbox hosts were not evaluated, and I record them as untested rather than negative. `MsMpEngCP.exe` (the Defender content process) is not present on the image, and tamper protection blocks reconfiguring Defender to deploy it. The WPAD sandboxed host only spawns when there is a real PAC script to execute, and the lab network has none. `UtcDecoderHost.exe` cannot be started standalone, because it needs its parent service to supply its arguments and its token.

The net result: the port admits AppContainers as a class, but the browser renderer, the sandbox most exposed to attacker-controlled input, is denied. That rules out the most direct browser chain, a compromised renderer calling `umpo` itself, though not a chain that first compromises a helper process the port does admit.

## How severe is CVE-2026-69321?

Microsoft rates CVE-2026-69321 Tampering / Important (CVSS 3.1 base score 5.5), and the demonstrated impact is local integrity loss only. Stated against the evidence, highest impact first:

- **Confirmed.** `RegDeleteTreeW` of arbitrary subkeys under `HKLM\…\Power\PowerSettings\` (opnum 35) from a non-admin seat. Against a subgroup populated by GPO `Computer Configuration\Administrative Templates\System\Power Management`, this is integrity loss of admin-applied policy until the next GPO refresh; on a standalone host where local policy is the only source, permanent loss.
- **Confirmed.** Attacker-named GUID subkey creation through opnum 33 (`RegCreateKeyExW`) under the same admin-protected hive, created under the SYSTEM token. Opnum 34 has the same missing-check shape statically, but its distinct side effect was not dynamically isolated and is not counted as a separately confirmed primitive.
- **Confirmed on released 25H2.** Metadata overwrite through opnum 6: `Attributes`, `ValueMin`, `ValueMax`, `ValueIncrement`, `Description`, `FriendlyName`, `ValueUnits`, `OverrideACSettingIndex`, `OverrideDCSettingIndex`, and related fields. The write reached settings whose per-setting SDDL grants `BUILTIN\Users` only `KEY_READ`. Overwriting the `Attributes` `HIDE` bit is a UI-visibility primitive, not authority over the setting's runtime value. The related opnums 27 and 28 share the static missing-check path but did not produce separately attributable side effects in the dynamic coverage test.
- **Build-dependent.** Opnum 6 is inert on Canary 29617: it returns `ERROR_SUCCESS` while performing no registry operation. Registry SACL auditing over the `Control\Power` subtree was validated with a known write, which produced three event 4657 records. Confirmed opnum 6 calls inside the measured window produced zero events. The same result held for a medium-integrity caller, so it was not a sandbox restriction. Opnums 33 and 35 remained effective on that build.
- **Not a code-execution primitive.** The `RegLoadMUIStringW` resource path loads with `LOAD_LIBRARY_AS_IMAGE_RESOURCE`; `DllMain` does not run. Ruled out by a working negative control, not left as an open question.
- **Does not defeat the wake-password control**, for two independent reasons. Modifying a setting's runtime value requires the gated opnum 7, which returns `ACCESS_DENIED` to a non-admin on a Users-read-only setting. And opnum 35's two-GUID form addresses `PowerSettings\<subgroup>\<setting>`, so settings stored directly under `PowerSettings`, including `GUID_CONSOLE_LOCK` ("Require a password on wakeup"), are not addressable through it at all. Ruled out twice over.
- **Not a renderer sandbox escape**, despite AppContainer reachability. Covered above: Edge and WebView2 renderers are denied at the port, and three other sandbox hosts remain untested.

Preconditions are a local authenticated standard user, with no special privileges or user interaction. **Remote abuse does not apply** because the interface lives on a local-only ALPC port. MSRC assessed the demonstrated terminal impact as Tampering / Important; no confidentiality impact, code execution, or change of security principal was established.

## How did Microsoft fix CVE-2026-69321?

Microsoft fixed CVE-2026-69321 by adding an administrator check to all six affected `umpo.dll` procedures. I compared the released 25H2 `umpo.dll` I reported against (10.0.26100.8328) with the version my test VM received from Windows Update on October 14, 2026 (10.0.26100.9549, OS build 26200.9711, past the advisory's fixed build of 26200.9445). This is a static comparison of decompiled code, with public symbols from Microsoft's symbol server:

- **All six dispatchers gained a caller check.** A new function, `UmpoRpcAuthorizeSystemPowerKeyMutation`, impersonates the RPC caller and checks membership in `BUILTIN\Administrators` through the existing `UmpoRpcIsAdministrator`. Opnums 6, 27, 28, 33, 34, and 35 now call it before their registry workers and return `ERROR_ACCESS_DENIED` to a non-admin.
- **The port was left alone.** The `umpo` ALPC port security descriptor is identical before and after the update. Everyone and AppContainer callers can still connect; the decision moved to the handlers, which is where it belonged.
- **AppContainer callers are covered by the same check.** A Low-IL AppContainer token is never a member of Administrators, so it fails the new check exactly as a standard user does. The AppContainer correction needed no separate fix.
- **The check is admin-only, not per-setting.** Microsoft did not reuse the per-setting `AccessCheck` that opnum 7 performs. For these six machine-scope operations, the new rule is simply that only administrators may mutate the hive.
- **The check sits behind a feature flag.** Each call is wrapped in `Feature_3971983672__private_IsEnabledDeviceUsageNoInline()`, the pattern Windows uses for staged rollout. With the flag off, the old path runs. I did not query the flag's state on the test VM, and I did not rerun the original proof of concept against the patched build, so the dynamic confirmation here is Microsoft's, not mine.

The patched build also adds `UmpoInternalValidateKeyImport`, reached from power-scheme import, which rejects `@`-prefixed resource strings in `Description`, `FriendlyName`, and `IconResource` unless they point into `powrprof.dll`. It is a related hardening of a different entry point, not part of the six-dispatcher fix, and it does not cover the `ValueUnits` field used in the Verification section.

## Lessons for researchers, service authors, and defenders

**For researchers, especially if you are getting started:**

- **Prove it with a real call.** A gap in decompiled code is a hypothesis until an unprivileged caller actually reaches the worker and changes something it should not be able to change.
- **Test your own scoping claims, too.** My original report excluded AppContainer callers based on a security descriptor I had read but never tested. Testing it widened the finding, and I corrected the case. An untested exclusion is as much a guess as an untested claim.
- **Write down what did not work.** The three failed escalation paths in this post are part of the result. They tell the vendor where the boundary is, make triage faster, and keep the impact statement defensible.
- **Work on disposable systems and restore what you change.** Every test here ran on disposable lab VMs, and the shipped setting deleted in the AppContainer test was restored from a backup taken beforehand.

The background for this kind of work is in the Windows Service Internals tutorials: [local RPC and ALPC](https://stevenfoerster.com/tutorials/windows-local-rpc-and-alpc/), [service impersonation and access tokens](https://stevenfoerster.com/tutorials/windows-service-impersonation-and-access-tokens/), [registry security](https://stevenfoerster.com/tutorials/windows-registry-security-access-tokens-dacls/), and [reading a service DLL in Ghidra](https://stevenfoerster.com/tutorials/anatomy-of-a-windows-service-dll-in-ghidra/).

**For authors of privileged RPC services:**

- **The port DACL decides who can connect, not what they can do.** Every caller that reaches `umpo` passed the port check legitimately. Authorization for a state-changing operation has to happen in the handler, against the caller's identity.
- **Decide per procedure, in one place.** An [interface security callback](https://learn.microsoft.com/en-us/windows/win32/api/rpcdce/nc-rpcdce-rpc_if_callback_fn) answers whether a client may call methods in the interface at all, which suits an interface-wide floor but not a mix of user-allowed and admin-only procedures like this one. A single table of procedure-to-policy, checked before dispatch, makes a missing entry visible in review. Microsoft's fix effectively did this for six procedures with one shared helper.
- **Share one authorization helper across every mutating procedure.** Microsoft's fix routes all six procedures through a single new check. Had that helper existed from the start, a new procedure without it would stand out in review.

**For defenders:**

- **Confirm patch level.** Windows 11 24H2 and 25H2 are fixed from builds 26100.9445 and 26200.9445; see the [advisory](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-69321) for other products.
- **Expect the wrong actor in registry audit logs.** Registry auditing on `HKLM\SYSTEM\CurrentControlSet\Control\Power` does record these writes, as the Canary test above showed. Because the write runs on the service token, the event attributes it to the Power service, not the user who made the RPC call. Unexpected deletions or new GUID subkeys under `PowerSettings` on an unpatched host are worth investigating even when the logged actor looks routine.

## Prior art

The `umpo` RPC interface is not untouched ground.

- **2010: the interface was enumerated.** redplait's [umpo.dll RPC interfaces](https://redplait.blogspot.com/2010/11/umpodll-rpc-interfaces.html) (November 6, 2010) lists the `umpoapi` interface, `6C9B7B96-45A8-4CCA-9EB3-E21CCF8B5A89` v1.0, and its 28 methods. `UmpoRpcWriteToSystemPowerKey`, `UmpoRpcCreateSetting`, and `UmpoRpcRemoveSetting` are all in that list, so the surface behind this finding has been publicly named for sixteen years. The post is a method listing, not a security analysis.
- **2019: an earlier umpo.dll CVE of a different class.** [CVE-2019-1341](https://www.tenable.com/cve/CVE-2019-1341), a Power Service elevation of privilege published October 10, 2019, concerned how umpo.dll handled a registry restore-key function. I did not establish whether it reached through this RPC interface.
- **2025: input-validation crashes on two other procedures.** Remco van der Meer's [Unplugging Power Service](https://www.incendium.rocks/posts/Unplugging-Power-Service/) (April 4, 2025) showed that a NULL GUID passed to `UmpoRpcReadProfileAlias` or `UmpoRpcReadFromUserPowerKey` crashes the Power service and the system. Microsoft rated it moderate severity, and the post names no CVE. That is a denial-of-service bug in read paths, not missing authorization on the write paths covered here.

Microsoft's advisory names the component "Power Dependency Coordinator" and does not name a binary. The behavior I reported lives in the `umpo.dll` RPC interface, not in the `pdc.sys` driver behind earlier Power Dependency Coordinator CVEs; that attribution comes from my own analysis, not from the advisory. At submission time, and again before publishing this analysis, I found no public write-up, PoC, or patch diff covering this missing-authorization path.

## Is CVE-2026-69321 the same as CVE-2026-69459?

No. CVE-2026-69459 is a separate elevation-of-privilege vulnerability that is also filed under Windows Power Dependency Coordinator and was fixed in the same September 2026 updates. According to [0patch's micropatch coverage](https://malware.news/t/micropatches-released-for-windows-power-dependency-coordinator-elevation-of-privilege-cve-2026-69459/126415), it is a heap buffer overflow in `PsmServiceExtHost.dll` that lets a non-admin local user reach code execution as Local System. CVE-2026-69321 is a missing-authorization flaw in `umpo.dll` whose demonstrated impact is registry tampering, with no code execution. The advisory component name is the same, but the binary, bug class, and impact differ. I did not analyze CVE-2026-69459.

## Which Windows versions does CVE-2026-69321 affect, and which update fixes it?

I observed the flaw on Windows 11 25H2 (build 26200) and on Insider Canary 29617. Windows 11 24H2 and 25H2 are fixed from builds 26100.9445 and 26200.9445, delivered in the September 8, 2026 security update. The Microsoft [advisory](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-69321) is the authoritative source for the full list of affected products and the update package for each.

## Timeline

- **May 2026:** Reported to MSRC. One case for the `umpo.dll` interface, with the root-cause analysis and the full PoC matrix, including the negative controls that rule out the higher-impact chains.
- **June 2026:** MSRC verified the reported behavior and rated it Tampering / Important; fix in development.
- **Aug 2026:** Supplementary evidence filed: the AppContainer correction, the `UmpoInternalSanitizeWrite` observation, and the opnum 6 inertness note. Filed to correct my own scoping statement and to help Microsoft confirm the fix covered every caller class.
- **September 8, 2026:** Microsoft released the security update and published CVE-2026-69321, acknowledging Steven Foerster and Prayas (Pro bear).
- **September 15, 2026:** Microsoft revised the advisory to update its acknowledgements, which now also credit 6ucy.
- **October 14, 2026:** Technical analysis published on this page after the 30-day patch-adoption window.

## Coordination notes

MSRC verified the reported behavior in June 2026 and told me on June 30 that the fix was scheduled for September Patch Tuesday, which is when it shipped. Microsoft rated it Tampering / Important. The rating did not change after the AppContainer supplement, which is consistent with the measured result: wider reachability, same terminal impact. The advisory credits me under my requested name, Steven Foerster, alongside Prayas (Pro bear) and 6ucy. Shared acknowledgements do not establish report order.

## About the author

I am Steven Foerster, a hands-on CTO and security architect. Alongside leading engineering teams, I research authorization failures in privileged Windows services, report them to Microsoft under coordinated disclosure, and publish the analysis once a fix has shipped and users have had time to apply it. I also created GovCon Enclave, the platform behind Rimstorm's 2026 acquisition by BOOST LLC. If a Microsoft Security Update Guide entry led you here, you can connect on [LinkedIn](https://www.linkedin.com/in/stevenfoerster/), read [more about my background](https://stevenfoerster.com/about/), browse [public security research](https://stevenfoerster.com/research/), or [get in touch](https://stevenfoerster.com/contact/).
