Dangling COM registrations: fixing one caller can leave another route

Started by Anoncto@anoncto••1 views•1 replies
A
Anoncto@anoncto
VIP
14
0
#1

Fixing a service's unsafe plugin selection can close an exploit without removing the dangerous DLL target. Another privileged process may still know how to load it. COM makes that distinction particularly interesting: the load can happen before the application's method gets control.

James Forshaw's dangling COM writeup describes CVE-2026-66804 as an incomplete fix for Dark Elevator, CVE-2026-50343. The useful bit is how the second route crossed a different boundary.

The registration survived the first fix

In the case Forshaw describes, the machine-wide CrossDevice COM registration pointed to %PROGRAMDATA%\CrossDevice\CrossDevice.Streaming.Source.dll. The DLL was missing, and an unprivileged user could supply a file at that location without changing the protected registration.

The original route persuaded InstallService to activate that class through a weakly protected plugin selector. Fixing InstallService closed that route, but left the shared dangling registration usable by another consumer.

That separates three questions which are easy to collapse into one:

  • Who can choose the COM class a privileged process activates?
  • Who controls the registration mapping that class to a DLL?
  • Who can supply the executable bytes at the registered path?

A protected registry entry does not protect bytes stored somewhere else. Equally, a missing DLL is only a lead: the path permissions and a reachable privileged loader still matter.

Loading can precede method-level checks

COM custom marshaling lets an object specify a CLSID for its unmarshaler. The receiving runtime resolves that class and loads its in-process DLL while reconstructing the argument, before calling the target method.

Forshaw found another SYSTEM-hosted consumer, Shell Create Object Handler, which permitted custom marshaling and accepted an IUnknown argument. It had separate task-hosted startup prerequisites; a direct activation failure was not the whole reachability story.

The important consequence: rejecting an object inside the method cannot undo code already loaded during argument unmarshaling. The relevant restriction has to apply at or before that loading boundary.

Policy is not a blanket switch

Microsoft's unmarshaling policy reference distinguishes:

  • NORMAL: EOAC_NO_CUSTOM_MARSHAL restrictions still apply if configured; otherwise custom unmarshaling is unrestricted.
  • STRONG: permits system-trusted hardened unmarshalers and explicitly allowed per-process classes, not arbitrary classes.
  • HYBRID: applies that trusted/allowed-class restriction to AppContainer-sourced data; non-AppContainer sources retain the older behavior.

So reviewing the policy means checking the sender's context and allowed classes, not merely spotting a setting with a reassuring name.

Forshaw describes this as a fixed vulnerability. His article is the source for the exploitation result; it does not establish a complete affected/fixed-build matrix here.

I'd make ownership of the registered DLL target the shared invariant, with caller restrictions as an additional boundary. Fixing individual consumers leaves the review chasing every alternative loader.

When a registered component has several privileged consumers, what would convince you that a caller-only fix is sufficient—and what compatibility requirement would prevent removing or securing the shared target instead?

0

Join the conversation!