6d6235f4eca7d4ed7672f6521dd12903b76a4c9d WebAuthn: Remove dangling discovery_factory_testing_override
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
- Discovery — MiraclePtr flagged a dangling raw_ptr in interactive_ui_tests WebAuthnDevtoolsAutofillIntegrationTest.SelectAccountWithAllowCredentials (mac-rel), tracked as FlakyDanglingUntriaged.
- Root-cause — The cached testing-override pointer could outlive the AuthenticatorEnvironment override across request teardown/reset, producing the flaky dangle.
- 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
- 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.
- 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.
- 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 —