← Chrome — 2026-W04

6d6235f4eca7d4ed7672f6521dd12903b76a4c9d  WebAuthn: Remove dangling discovery_factory_testing_override

severity low class UAF CVSS 2.0 confidence 0.5 signal score 4.3 content/browser/webauth AI review CONFIRMED
Primitive: Drops a cached FlakyDanglingUntriaged raw_ptr to the test discovery factory and queries AuthenticatorEnvironment on demand instead.
Triage note: Eliminates a real dangling raw_ptr pattern, but the override is only populated in test/DevTools virtual-authenticator environments, so it is not attacker-reachable in default config. Lifetime hardening.

The bug at a glance

Watch-tier, not patch-now, because the dangling pointer lives on a code path that is only populated when a testing/virtual-authenticator override is installed into AuthenticatorEnvironment. In shipping Chrome with no WebDriver/DevTools virtual authenticator active, MaybeGetDiscoveryFactoryTestOverride() returns null, discovery_factory_testing_override stays nullptr, and no stale object is ever dereferenced. The dangling was observed only in interactive_ui_tests (WebAuthnDevtoolsAutofillIntegrationTest) and was carried as FlakyDanglingUntriaged | AcrossTasksDanglingUntriaged, i.e. a raw_ptr that MiraclePtr flagged but had not been triaged into a real UAF. It rates low/CVSS 2.0 because a genuine object-lifetime bug exists (a request could outlive a reset/destroyed test environment while holding the cached pointer), but the trigger requires the virtual-authenticator surface, which is not attacker-controllable from ordinary web content, and no exploitation is demonstrated.

Read this as a lifetime-correctness cleanup on WebAuthn’s virtual/testing authenticator plumbing, motivated by a MiraclePtr dangling annotation rather than a reported exploit. RequestState cached a raw_ptr to the FidoDiscoveryFactory test override at request start (InitDiscoveryFactory) and used it for the life of the request. If the testing environment was reset or torn down while a request was still in flight, that cached pointer dangled. The fix stops caching: discovery_factory() now asks AuthenticatorEnvironment for the override on demand every call, so the pointer is always fetched from the authoritative owner and can never outlive it. For defenders the value is a pattern lesson - a cached raw_ptr to an externally-owned, resettable singleton override is a classic dangling shape - more than an immediately weaponizable bug.

Root cause

AuthenticatorCommonImpl::RequestState held a member: raw_ptr<device::FidoDiscoveryFactory, FlakyDanglingUntriaged | AcrossTasksDanglingUntriaged> discovery_factory_testing_override. It was populated once, in InitDiscoveryFactory(), by copying the pointer returned from AuthenticatorEnvironment::GetInstance()->MaybeGetDiscoveryFactoryTestOverride(). discovery_factory() then returned this cached override if set, else the per-request owned discovery_factory.

The root cause is an ownership/lifetime mismatch: the override object (a long-lived VirtualFidoDeviceDiscovery-derived factory) is owned by AuthenticatorEnvironment, not by RequestState. Its lifetime is controlled by the testing environment - which can be reset or destroyed independently of, and potentially during, an active authenticator request or during teardown. The cached raw_ptr therefore captured a snapshot of a pointer whose backing object could be freed while the request still referenced it. Because it was a raw_ptr (MiraclePtr/BackupRefPtr), the dangle surfaced as a FlakyDanglingUntriaged report in interactive_ui_tests rather than as an unmediated UAF, but the underlying pattern is a use-after-free waiting on the right ordering.

The fix removes the cached member entirely and rewrites discovery_factory() to query AuthenticatorEnvironment::GetInstance()->MaybeGetDiscoveryFactoryTestOverride() on every call. Now the override is always retrieved live from its owner, so if the environment reset it, the next call sees null (or the new value) instead of a stale pointer. InitDiscoveryFactory() loses the caching write and its stale TODO about extracting a longer-lived configuration object.

Key code

authenticator_common_impl.cc: replace cached dangling raw_ptr with an on-demand query to AuthenticatorEnvironment

// Removed member from RequestState:
//   raw_ptr<device::FidoDiscoveryFactory,
//           FlakyDanglingUntriaged | AcrossTasksDanglingUntriaged>
//       discovery_factory_testing_override = nullptr;

device::FidoDiscoveryFactory* AuthenticatorCommonImpl::discovery_factory() {
  DCHECK(req_state_->discovery_factory);
  device::FidoDiscoveryFactory* override_factory =
      AuthenticatorEnvironment::GetInstance()
          ->MaybeGetDiscoveryFactoryTestOverride();
  return override_factory ? override_factory
                          : req_state_->discovery_factory.get();
}

void AuthenticatorCommonImpl::InitDiscoveryFactory() {
  req_state_->discovery_factory = MakeDiscoveryFactory(GetRenderFrameHost());
  // (no longer caches the testing override)
}

Patch walkthrough

  • content/browser/webauth/authenticator_common_impl.cc — Three edits. (1) Deletes the discovery_factory_testing_override raw_ptr member from RequestState, including its FlakyDanglingUntriaged|AcrossTasksDanglingUntriaged annotation and the comment linking the interactive_ui_tests failure. (2) Rewrites discovery_factory() to call AuthenticatorEnvironment::GetInstance()->MaybeGetDiscoveryFactoryTestOverride() at use-time and prefer that live override, falling back to req_state_->discovery_factory. (3) Removes the write in InitDiscoveryFactory() that cached the override and deletes the stale martinkr TODO. Net: the pointer is never stored across the request, eliminating the dangle by construction.

Background

raw_ptr / MiraclePtr (BackupRefPtr) — Chrome’s raw_ptr is a smart-pointer that detects use-after-free at dereference under BackupRefPtr. Annotations like FlakyDanglingUntriaged and AcrossTasksDanglingUntriaged mark pointers known to dangle in some runs but not yet converted into confirmed UAF triage - a suppression, not a guarantee of safety.

AuthenticatorEnvironment test override — MaybeGetDiscoveryFactoryTestOverride() returns a virtual FidoDiscoveryFactory installed for WebDriver virtual authenticators or DevTools autofill/WebAuthn testing. In normal browsing it is null, so production requests never touched the removed cached pointer.

RequestState lifetime — RequestState aggregates per-request objects (delegate, request_handler, owned discovery_factory). Its members outlive individual callbacks; caching an externally-owned pointer here means it can survive the owner being reset mid-request.

No CVE / no backport — No CVE and no [M###] merge/backport marker; landed to main only (refs/heads/main@{#1571200}), consistent with a lifetime cleanup rather than a stable-channel security merge.

Vulnerability window

  1. Discovery — MiraclePtr flagged a dangling raw_ptr in interactive_ui_tests WebAuthnDevtoolsAutofillIntegrationTest.SelectAccountWithAllowCredentials (mac-rel), tracked as FlakyDanglingUntriaged.
  2. Root-cause — The cached testing-override pointer could outlive the AuthenticatorEnvironment override across request teardown/reset, producing the flaky dangle.
  3. Fix — Committed 2026-01-19 (Adem Derinel), reviewed by Ken Buchanan, at refs/heads/main@{#1571200}: cache removed, override queried on demand, annotations deleted.

Proof of concept

No exploit is demonstrated and the commit ships no test. A defender wanting to observe the pre-fix dangle would run a BackupRefPtr-enabled build of the interactive_ui_tests virtual-authenticator suite and race a testing-environment reset/destroy against an in-flight WebAuthn request that has already called InitDiscoveryFactory() but not yet finished, so discovery_factory() dereferences the cached override after its owner freed it. This is reachable only through the WebDriver/DevTools virtual-authenticator surface, not from ordinary untrusted web content, so no web-facing PoC exists and none is invented here.

Exploitation

  1. Reach — The vulnerable pointer is populated only when a virtual-authenticator/test override is installed via AuthenticatorEnvironment. Ordinary web pages cannot install that override, so the dangling path is not directly reachable from remote attacker content.
  2. Trigger — Requires an active override being reset/destroyed while a request holds the cached pointer - a timing/ordering condition seen in tests. Outside a testing environment the field stays nullptr and nothing dereferences a stale object.
  3. Impact ceiling — Even in the vulnerable configuration, exploitation would need control over the freed FidoDiscoveryFactory allocation and its vtable/fields; no such control is shown. Realistic ceiling is a UAF crash within a testing/automation context, not a demonstrated RCE against users.

Detection & hunting

For defenders and detection engineers:

  • raw_ptr dangling reports — In BackupRefPtr/ASAN fleets, dangling-pointer reports naming FidoDiscoveryFactory or the WebAuthn discovery_factory path indicate the pre-fix pattern; their disappearance confirms the fix. A regression re-introducing a cached override would resurface these.
  • WebAuthn crash clustering — Crashes in AuthenticatorCommonImpl::discovery_factory() or during request teardown correlated with virtual-authenticator/DevTools automation sessions are the observable footprint; correlate with WebDriver virtual-authenticator usage.

Audit directions

  • Other cached pointers to environment-owned overrides —
  • Remaining FlakyDanglingUntriaged annotations —

Before / after

Loading diff…