← Chrome — 2026-W03

2ae8fe41b57e128b3758023f5a787e7642750c84  [M145] [soft navs] Only consider FullyTrusted events

severity low class reachable-NOTREACHED/crash CVSS 5.3 confidence 0.55 signal score 3.8 third_party/blink/renderer AI review CONFIRMED
Primitive: Same as parent: label-forwarded 'trusted' clicks create an unexpected SoftNavigationContext, tripping NOTREACHED in web scheduling task state. Fix switches to IsFullyTrusted().
Triage note: Identical M145 (7632 branch) cherry-pick of the soft-navs FullyTrusted crash fix. Renderer crash, not corruption; watch.

The bug at a glance

A pure renderer-stability bug with no memory-safety component. An untrusted script click on a is forwarded by the label to its associated form control as a click that the platform marks trusted (isTrusted==true), even though the initiating event was synthetic. SoftNavigationHeuristics filtered on isTrusted() only, so it created a SoftNavigationContext for this forwarded click; that context then reaches WebSchedulingTaskState::ForkAndSetVariable, which contains a NOTREACHED() asserting a SoftNavigationContext is never created in web-scheduling tasks/continuations. In current Chromium a plain NOTREACHED() is fatal, so this is a reliably reachable, script-triggerable renderer crash\u2014a denial of service confined to the renderer process, fully within the sandbox. No corruption, no cross-origin data exposure. Low severity; cvss 5.3 (network, low complexity, no privileges, no interaction, availability-only, low). The [M145] tag marks it as a backport to a release branch (7632).

isTrusted() and IsFullyTrusted() are not the same predicate. isTrusted() reports the flag on the event object as delivered, and label\u2192control click forwarding sets that flag on the synthetic control click regardless of the original event’s provenance. IsFullyTrusted() walks the event/dispatch chain so it stays false when the chain originated from script. Any security or heuristic gate that means ‘a real user did this’ and uses isTrusted() rather than IsFullyTrusted() can be spoofed by routing a scripted click through a label. The soft-nav crash is one symptom; the commit body itself flags EventTiming as another consumer with the same latent confusion.

Root cause

SoftNavigationHeuristics uses input-event scopes to attribute later DOM/network activity to a user-initiated soft navigation. EventScopeTypeFromInputEvent gates on event trust before opening a scope. Because it called event.isTrusted(), a scripted target.click() on a passed the gate: the label forwards the activation to as a click event that Blink marks trusted, so the heuristic opened an EventScope and created a SoftNavigationContext for it. That context propagates into task attribution. When the resulting work runs through the web-scheduling path (e.g. scheduler.postTask / continuations), WebSchedulingTaskState::ForkAndSetVariable is invoked with a non-null soft_navigation_context, violating its invariant that such contexts are ‘known not to be created in web scheduling tasks and continuations’\u2014the function was nothing but NOTREACHED(). The fix has three parts: (1) EventScopeTypeFromInputEvent switches !event.isTrusted() to !event.IsFullyTrusted(), so label-forwarded scripted clicks no longer open a scope; (2) as defense-in-depth, ForkAndSetVariable’s NOTREACHED() is downgraded to NOTREACHED(base::NotFatalUntil::M149) and given a real fallback that builds a TaskAttributionInfoImpl and returns a WebSchedulingTaskState instead of aborting, so any other undiscovered path degrades gracefully until M149; (3) a WPT regression test.

Key code

Trust gate corrected to walk the event chain; NOTREACHED made non-fatal with a real fallback

-  // `SoftNavigationContext` is known not be created in web scheduling tasks
-  // and continuations.
-  NOTREACHED();
+  // TODO(crbug.com/475261410): ... didn't hold in the case of dispatching
+  // simulated clicks to a form control's label. ... marked as non-fatal to
+  // flush out any other cases.
+  NOTREACHED(base::NotFatalUntil::M149);
+
+  scheduler::TaskAttributionInfo* current_task_attribution_info =
+      MakeGarbageCollected<TaskAttributionInfoImpl>(
+          next_task_id, soft_navigation_context,
+          GetTaskAttributionInfo()
+              ? GetTaskAttributionInfo()->GetResourceTimingContext()
+              : nullptr);
+  return MakeGarbageCollected<WebSchedulingTaskState>(
+      current_task_attribution_info, GetSchedulerTaskContext());
// --- soft_navigation_heuristics.cc ---
-  if (!event.isTrusted()) {
+  if (!event.IsFullyTrusted()) {
     return std::nullopt;
   }

Patch walkthrough

  • third_party/blink/renderer/core/scheduler/web_scheduling_task_state.cc — ForkAndSetVariable previously contained only NOTREACHED(), so any SoftNavigationContext reaching it aborted the renderer. The patch changes it to NOTREACHED(base::NotFatalUntil::M149) and adds a functioning implementation: it builds a TaskAttributionInfoImpl from next_task_id, the soft_navigation_context, and the current resource-timing context (if any), then returns a new WebSchedulingTaskState carrying it. This is defense-in-depth\u2014after the root-cause fix in soft_navigation_heuristics.cc the branch should be unreachable, but if another trust-confusion path exists it now records the crash non-fatally (via the NotFatalUntil mechanism) and continues correctly rather than killing the tab. The comment schedules removal of the fallback once no such cases are observed by M149.
  • third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc — The one-line root-cause fix in EventScopeTypeFromInputEvent: gate changed from !event.isTrusted() to !event.IsFullyTrusted(). IsFullyTrusted() accounts for the whole event chain, so a scripted click forwarded through a to its control is correctly treated as untrusted and no soft-navigation EventScope/SoftNavigationContext is created for it, eliminating the input that trips the downstream NOTREACHED.
  • third_party/blink/web_tests/wpt_internal/task-tracking/soft-navigation-heuristics/non-fully-trusted-click.html — New regression test. It places a and a , then in a requestIdleCallback calls target.click() (a scripted, non-trusted click on the label). Pre-patch this forwarded a trusted click to the select, created a SoftNavigationContext, and crashed at the NOTREACHED; the test asserts it ‘should not crash’. It doubles as the reproduction recipe.

Background

isTrusted vs IsFullyTrusted — Event.isTrusted reflects the trusted flag on the delivered event; browser-synthesized activations (like label\u2192control forwarding) set it true regardless of origin. IsFullyTrusted() (Blink-internal) considers the dispatch/event chain so it remains false when the sequence was initiated by script.

label click forwarding — Clicking a associated with a form control dispatches a synthetic click to that control. Blink marks the forwarded click trusted, which is what leaks ’trust’ from a scripted label click into the control’s event.

SoftNavigationHeuristics / SoftNavigationContext — Machinery that attributes post-interaction DOM/network changes to a user-initiated ‘soft navigation’ for performance metrics. A context is created when a qualifying trusted input event opens an EventScope.

WebSchedulingTaskState / task attribution — State threaded through scheduler.postTask and continuations for task attribution. Its invariant was that a SoftNavigationContext is never the variable being forked here\u2014violated by the spoofed trusted click, hitting the NOTREACHED.

NOTREACHED(base::NotFatalUntil::Mxxx) — Chromium mechanism to mark a would-be-fatal invariant as non-fatal (logged/reported, execution continues) until a target milestone, used to safely flush out unknown reachable cases before making the check fatal again.

Vulnerability window

  1. root cause — SoftNavigationHeuristics adopted isTrusted() as its ‘real user input’ filter, not anticipating label-forwarded synthetic clicks that carry the trusted flag.
  2. report — crbug.com/475261410: reachable crash in Task Attribution from a SoftNavigationContext created for a forwarded label click.
  3. fix (main) — 2026-01-15, main@{#1569171} (16adc8a8…): Scott Haseley switches to IsFullyTrusted(), softens the NOTREACHED to NotFatalUntil M149 with a fallback, and adds the WPT test.
  4. backport — 2026-01-15, cherry-picked to branch 7632 (M145) as 2ae8fe41b5 via the automated cherry-picker; bug 476129054 tracks the merge.
  5. follow-up noted — Commit body flags EventTiming as likely affected by the same isTrusted vs IsFullyTrusted confusion\u2014an open audit item.

Proof of concept

The commit ships a regression test that is a complete, honest reproduction of the crash (a DoS repro, not an exploit). Structure: a with an empty ; inside a requestIdleCallback, call target.click(). The scripted click on the label is not trusted, but the label forwards a trusted click to the select; pre-patch that opens a soft-navigation EventScope, creates a SoftNavigationContext, and the subsequent web-scheduling task-state fork hits NOTREACHED(), aborting the renderer. Post-patch, IsFullyTrusted() rejects the forwarded click, no context is created, and the page does not crash. This only demonstrates a renderer crash; there is no memory-corruption or information-disclosure primitive and none is implied.

<!DOCTYPE HTML>
<meta charset="utf-8">
<title>Test soft navigation heuristics with a non-fully trusted event</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<label id="target" for="foo"></label>
<select id="foo"></select>

<script>
  async_test(t => {
    requestIdleCallback(t.step_func_done(() => {
      // Regression test for crbug.com/475261410. This should not crash.
      target.click();
    }));
  }, "Test that an untrusted click causing a trusted click on a form control" +
     " doesn't crash");
</script>

Exploitation

  1. trigger — Any page can script target.click() on a label bound to a form control to synthesize the forwarded trusted click; no user interaction, permissions, or special origin needed.
  2. primitive — Reachable NOTREACHED() -> renderer process abort. This is a reliable in-sandbox DoS: a malicious or buggy page (or ad iframe) can crash its own renderer/tab on demand.
  3. bound — No escape from the primitive: the abort is a clean CHECK-style termination with no attacker-controlled memory state. Impact is availability of the affected renderer only.

Detection & hunting

For defenders and detection engineers:

  • crash signature —
  • spoofed-trust behavior —

Audit directions

  • isTrusted() consumers —
  • SoftNavigationContext invariants —
  • synthetic activation surfaces —

Before / after

Loading diff…