High watch CVSS 8.8 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in FedCM
DescriptionUse after free in FedCM
ComponentFedCM
Sub-componentFedCM
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker491869946
Fix commit392c4d50d407 (chromium/src) +42/-3
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedShaheen Fazim
Reported2026-03-12 (per vendor advisory)
Fix landed2026-03-16 (public git)
Disclosed2026-03-23
Patch gap7 days — fix public in source before the advisory

Background

FedCM `IdentityProvider.resolve()`
a Federated Credential Management API, implemented in identity_provider.cc, that lets an identity provider page hand a token back to the browser; Blink forwards it via ResolveTokenRequest on the FederatedAuthRequest Mojo interface.
`CredentialManagerProxy::From(script_state)->FederatedAuthRequest()`
returns a raw pointer to the per-context FederatedAuthRequest interface owned by the CredentialManagerProxy attached to the frame’s context.
`WebV8ValueConverter`
converts a JavaScript value into a base::Value; converting an object walks its properties, so page-defined getters run during the conversion.
`ScriptState::ContextIsValid()`
reports whether the JavaScript context (and its frame/document) is still alive; it becomes false if the frame is detached.

Root Cause Analysis

In the IdentityProvider resolve implementation in third_party/blink/renderer/modules/credentialmanagement/identity_provider.cc (the hunks do not show the enclosing signature, but the regression test calls IdentityProvider.resolve), the code previously fetched the raw request pointer from CredentialManagerProxy::From(script_state)->FederatedAuthRequest() near the start of the function, before converting the token argument. When RuntimeEnabledFeatures::FedCmNonStringTokenEnabled() is on, the token is converted with WebV8ValueConverter, which invokes page-defined property getters; the regression test uses such a getter to call frame.remove(), detaching the frame mid-call. After the conversion, the stale request pointer was then used for request->ResolveTokenRequest(...), even though the frame’s context and its interface objects may have been torn down, giving the use-after-free (the exact freed object is inferred to be the FederatedAuthRequest interface or its owner).

The fix removes the early lookup, adds an early return promise when script_state->ContextIsValid() is false after conversion, and only then fetches request immediately before calling ResolveTokenRequest, with a comment that no JavaScript may run between the lookup and the use.

Key insight
A raw pointer to a context-owned object was cached across a call that can run arbitrary page JavaScript (the token conversion), which can destroy that context. The fix re-validates the context and obtains the pointer only after all script-executing steps are complete.

Attack Path

  1. Create a same-origin frame The page creates an iframe and obtains its contentWindow.IdentityProvider, as in the shipped WPT test.
  2. Craft a side-effecting token The page passes a token object with an enumerable getter that removes the iframe when read.
  3. Call `resolve()` in the child context IdentityProvider.resolve(token, { accountId: '123' }) fetched the FederatedAuthRequest pointer, then converted token, running the getter and detaching the frame.
  4. Use the stale pointer After conversion the code called request->ResolveTokenRequest(...) on the pointer obtained before the frame was destroyed, accessing freed renderer memory.

Impact Assessment

The vendor advisory rates this High as a use-after-free in FedCM; no in-the-wild exploitation is stated. The bug is in the renderer process and is triggered by page JavaScript calling IdentityProvider.resolve() on a frame it can then remove; the getter-based trigger shown in the test relies on the FedCmNonStringTokenEnabled conversion path, and the diff does not show whether that feature is enabled by default in stock builds (nor whether the else conversion path has its own side effects). Renderer heap corruption that could lead to code execution inside the sandboxed renderer is inferred, not demonstrated by the patch.

Files Changed

  • third_party/blink/renderer/modules/credentialmanagement/identity_provider.cc
  • third_party/blink/web_tests/external/wpt/fedcm/fedcm-authz/fedcm-resolve-side-effects.https.html

Audit Directions

  • Raw pointers cached across V8 conversions
    search Blink bindings implementations for pointers to supplements, proxies, Mojo remotes or frame-owned objects obtained before WebV8ValueConverter, NativeValueTraits, dictionary/any conversion or other user-script-invoking steps, then used afterwards.
  • Missing context revalidation after script
    after any operation that can run getters, toString/valueOf, or Proxy traps, check that code re-tests ContextIsValid() (or frame detachment) before touching per-context state.
  • Other Credential Management entry points
    review sibling FedCM and CredentialsContainer methods that accept object parameters for the same lookup-then-convert ordering.
From 392c4d50d407236a3212d654cdea1fca202fd7ad Mon Sep 17 00:00:00 2001
From: Christian Biesinger <cbiesinger@chromium.org>
Date: Mon, 16 Mar 2026 10:32:18 -0700
Subject: [PATCH] [FedCM] Get the FederatedAuthRequest right before using it

And only get it if the context is (still) valid.

R=npm@chromium.org

Fixed: 491869946
Change-Id: I0552f07f7fbffc743a85f95a7026225987a55147
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7665206
Commit-Queue: Christian Biesinger <cbiesinger@chromium.org>
Reviewed-by: Nicolás Peña <npm@chromium.org>
Auto-Submit: Christian Biesinger <cbiesinger@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1599990}
---

diff --git a/third_party/blink/renderer/modules/credentialmanagement/identity_provider.cc b/third_party/blink/renderer/modules/credentialmanagement/identity_provider.cc
index 05e254e..364f6457 100644
--- a/third_party/blink/renderer/modules/credentialmanagement/identity_provider.cc
+++ b/third_party/blink/renderer/modules/credentialmanagement/identity_provider.cc
@@ -244,9 +244,6 @@
       MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(script_state);
   auto promise = resolver->Promise();
 
-  auto* request =
-      CredentialManagerProxy::From(script_state)->FederatedAuthRequest();
-
   std::unique_ptr<base::Value> token_base_value;
   if (RuntimeEnabledFeatures::FedCmNonStringTokenEnabled()) {
     std::unique_ptr<WebV8ValueConverter> converter =
@@ -322,6 +319,17 @@
         std::move(*token_base_value));
   }
 
+  if (!script_state->ContextIsValid()) {
+    // This can happen if converting the `token` parameter had side effects
+    // that destroyed the document. With an invalid context, we also can't
+    // reject the promise.
+    return promise;
+  }
+
+  // There must not be JavaScript execution between getting the request pointer
+  // and using it.
+  auto* request =
+      CredentialManagerProxy::From(script_state)->FederatedAuthRequest();
   request->ResolveTokenRequest(
       account_id, std::move(params),
       BindOnce(&OnResolveTokenRequest, WrapPersistent(resolver)));
diff --git a/third_party/blink/web_tests/external/wpt/fedcm/fedcm-authz/fedcm-resolve-side-effects.https.html b/third_party/blink/web_tests/external/wpt/fedcm/fedcm-authz/fedcm-resolve-side-effects.https.html
new file mode 100644
index 0000000..6a464e65
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fedcm/fedcm-authz/fedcm-resolve-side-effects.https.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API network request tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+
+promise_test(async t => {
+  const frame = document.createElement('iframe');
+  document.body.appendChild(frame);
+
+  const token = {};
+  Object.defineProperty(token, 'prop', {
+      get: function() {
+          frame.remove();
+          return "value";
+      },
+      enumerable: true
+  });
+
+  try {
+    frame.contentWindow.IdentityProvider.resolve(token, { accountId: '123' });
+  } catch(e) {}
+}, "If a getter in the token in IdentityProvider.resolve removes the frame, the user agent should not crash");
+
+</script>
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/web_tests/external/wpt/fedcm/fedcm-authz/fedcm-resolve-side-effects.https.html b/third_party/blink/web_tests/external/wpt/fedcm/fedcm-authz/fedcm-resolve-side-effects.https.html
new file mode 100644
index 0000000..6a464e65
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fedcm/fedcm-authz/fedcm-resolve-side-effects.https.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API network request tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+
+promise_test(async t => {
+  const frame = document.createElement('iframe');
+  document.body.appendChild(frame);
+
+  const token = {};
+  Object.defineProperty(token, 'prop', {
+      get: function() {
+          frame.remove();
+          return "value";
+      },
+      enumerable: true
+  });
+
+  try {
+    frame.contentWindow.IdentityProvider.resolve(token, { accountId: '123' });
+  } catch(e) {}
+}, "If a getter in the token in IdentityProvider.resolve removes the frame, the user agent should not crash");
+
+</script>
Loading diff…

Original Bug Report

reported by fa...@gmail.com

heap-use-after-free in blink::IdentityProvider::resolve

=================================================================
==62372==ERROR: AddressSanitizer: heap-use-after-free on address 0x12be97ae9630 at pc 0x7ff858f0742e bp 0x00e6035fe160 sp 0x00e6035fe1a8
READ of size 8 at 0x12be97ae9630 thread T0
    #0 0x7ff858f0742d in blink::IdentityProvider::resolve(class blink::ScriptState *, class blink::ScriptValue const &, class blink::IdentityResolveOptions const *) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\modules\credentialmanagement\identity_provider.cc:316:12
    #1 0x7ff85671195b in blink::`anonymous namespace'::v8_identity_provider::ResolveStaticOperationCallback C:\b\s\w\ir\cache\builder\src\out\069a-Win_ASan_Releas\gen\third_party\blink\renderer\bindings\modules\v8\v8_identity_provider.cc:204:23
    #2 0x7ff85e5f3824 in Builtins_CallApiCallbackGeneric (C:\Users\Admin\Downloads\chrome-asan\chrome.dll+0x1ad4d3824)
    #3 0x7ff85e5f197b in Builtins_InterpreterEntryTrampoline (C:\Users\Admin\Downloads\chrome-asan\chrome.dll+0x1ad4d197b)
    ...
    #35 0x7ff845866613 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork(void) C:\b\s\w\ir\cache\builder\src\base\task\sequence_manager\thread_controller_with_message_pump_impl.cc:346:40
    #36 0x7ff8459d3607 in base::MessagePumpDefault::Run(class base::MessagePump::Delegate *) C:\b\s\w\ir\cache\builder\src\base\message_loop\message_pump_default.cc:42:55
    #37 0x7ff8458694ff in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::Run(bool, class base::TimeDelta) C:\b\s\w\ir\cache\builder\src\base\task\sequence_manager\thread_controller_with_message_pump_impl.cc:650:12
    #38 0x7ff84590ef5c in base::RunLoop::Run(class base::Location const &) C:\b\s\w\ir\cache\builder\src\base\run_loop.cc:135:14

SUMMARY: AddressSanitizer: heap-use-after-free C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\modules\credentialmanagement\identity_provider.cc:316:12 in blink::IdentityProvider::resolve(class blink::ScriptState *, class blink::ScriptValue const &, class blink::IdentityResolveOptions const *)
Shadow bytes around the buggy address:
  0x12be97ae9380: f7 fa 00 fa f7 fa 00 fa f7 fa 00 fa f7 fa 00 fa
  0x12be97ae9400: f7 fa fd fa f7 fa fd fa f7 fa fd fa f7 fa fd fa
  0x12be97ae9480: f7 fa 00 fa f7 fa fd fd f7 fa fd fa f7 fa fd fa
  0x12be97ae9500: f7 fa fd fa f7 fa 00 07 f7 fa fd fa f7 fa fd fa
  0x12be97ae9580: f7 fa fd fa f7 fa fd fa f7 fa fd fa f7 fa 00 fa
=>0x12be97ae9600: f7 fa fd fd f7 fa[fd]fd f7 fa fd fa f7 fa fd fa
  0x12be97ae9680: f7 fa fd fa f7 fa fd fd f7 fa fd fa f7 fa fd fa
  0x12be97ae9700: f7 fa fd fa f7 fa fd fa f7 fa 00 00 f7 fa fd fa
  0x12be97ae9780: f7 fa 00 00 f7 fa 00 00 f7 fa 00 00 f7 fa 00 fa
  0x12be97ae9800: f7 fa fd fd f7 fa fd fa f7 fa fd fa f7 fa fd fd
  0x12be97ae9880: f7 fa 00 fa f7 fa fd fa f7 fa fd fa f7 fa fd fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb

==62372==ADDITIONAL INFO

==62372==Note: Please include this section with the ASan report.
Task trace:
    #0 0x7ff855e7d154 in blink::DOMTimer::DOMTimer(class blink::ExecutionContext &, class blink::ScheduledAction *, class base::TimeDelta, bool) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\core\scheduler\dom_timer.cc:343:27
    #1 0x7ff849446109 in IPC::ChannelAssociatedGroupController::Accept(class mojo::Message *) C:\b\s\w\ir\cache\builder\src\ipc\ipc_mojo_bootstrap.cc:1138:13


Command line: `"C:\Users\Admin\Downloads\chrome-asan\chrome.exe" --type=renderer --no-pre-read-main-dll --start-stack-profiler --no-sandbox --file-url-path-alias="/gen=C:\Users\Admin\Downloads\chrome-asan\gen" --video-capture-use-gpu-memory-buffer --lang=en-US --device-scale-factor=1 --num-raster-threads=4 --enable-main-frame-before-activation --renderer-client-id=5 --time-ticks-at-unix-epoch=-1773079218143692 --launch-time-ticks=197939893276 --metrics-shmem-handle=3844,i,15911451909191243047,11158795503653610499,2097152 --field-trial-handle=1940,i,1091808593593248265,3138818943078118960,262144 --variations-seed-version --pseudonymization-salt-handle=2108,i,7123884904703337945,13134237462315843116,4 --trace-process-track-uuid=3190708990997080739 --mojo-platform-channel-handle=3780 /prefetch:1`


MiraclePtr Status: NOT PROTECTED
No raw_ptr<T> access to this region was detected prior to this crash.
This crash is still exploitable with MiraclePtr.
Refer to https://chromium.googlesource.com/chromium/src/+/main/base/memory/raw_ptr.md for details.

==62372==END OF ADDITIONAL INFO

==62372==ABORTING

VERSION

148.0.7730.0 (Developer Build) (64-bit)

REPRODUCTION CASE

Build: asan-win32-release_x64-1598047

Run: ./chrome.exe --no-sandbox poc.html


Reporter credit: Shaheen Fazim

View on issue tracker