High watch CVSS 8.1 chrome Input Validation 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.1
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper state validation in Performance
DescriptionImproper state validation in Performance
ComponentPerformance
Sub-componentProfiler
Bug ClassInput Validation
Action tierwatch — dos-class only — no demonstrated corruption path
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.
Tracker528397177
Fix commit6081a378d25f (v8/v8) +34/-16
CWECWE-754
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-06-27 (per vendor advisory)
Fix landed2026-07-02 (public git)
Disclosed2026-08-25
Patch gap54 days — fix public in source before the advisory

Background

Heap snapshot verification
debug-only code in heap-snapshot-generator.cc, compiled under #ifdef V8_ENABLE_HEAP_SNAPSHOT_VERIFY, that cross-checks the edges a heap snapshot records against the references a GC marking visitor reports for the same object.
`HeapEntryVerifier`
the verifier class. It collects a reference_summary_ of strong and weak references from the marking visitor and a checked_objects_ set of targets the snapshot verified, then uses CHECK_NE to require that every summarized reference was checked.
Shared space / client isolate
with shared heaps enabled (for example via --shared-string-table), objects such as shared strings live in a writable shared space owned by a shared-space isolate. A client isolate’s marking visitor does not follow references into that space.
Ephemeron (`WeakMap`) visiting
according to the commit message, MarkingVisitorBase::VisitEphemeronHashTable calls AddWeakReferenceForReferenceSummarizer() on an entry’s value before it bails out for shared objects on client isolates, so shared values still end up in the weak-reference summary.

Root Cause Analysis

The reference-verification routine, which the commit message calls VerifyReference(), already skipped targets in read-only space and, on client isolates, targets in writable shared space. Its enclosing function name is not visible in the hunk. Because of that skip, references into the shared heap were never added to checked_objects_. HeapEntryVerifier::CheckAllReferencesWereChecked applied only the read-only exclusion before its CHECK_NE, not the shared-space one. A WeakMap whose value is a shared object reproduces this: the regression test uses a string under --shared-string-table. The marking visitor places the shared value in weak_references(), the snapshot side skips it, and the final consistency CHECK fails, so the process aborts during a major-snapshot GC.

The fix moves both exclusions into one helper, ShouldVerifyReferenceTo, and uses it in both places, so the two sides now agree on which references can be verified.

The patch loosens the verifier’s final check: it skips more objects. It does not change any runtime security check.

Key insight
Two halves of one debug verifier used different skip rules: one skipped shared-heap targets and the other did not. The resulting mismatch triggered a spurious CHECK failure. The fix puts the skip rule in one place, ShouldVerifyReferenceTo, and both halves now use it.

Attack Path

  1. Shared heap active V8 runs with a shared space and a client isolate, which the test sets up with the non-default --shared-string-table flag.
  2. WeakMap with shared value script stores a value that lives in the shared heap, such as a shared string, in a WeakMap keyed by an ordinary heap object.
  3. Heap snapshot taken a heap snapshot is generated in a build with V8_ENABLE_HEAP_SNAPSHOT_VERIFY enabled. The test does this with gc({type: 'major-snapshot'}) under --expose-gc; in a browser it would correspond to a DevTools memory snapshot (inferred).
  4. Verifier mismatch aborts the ephemeron visitor records the shared value as a weak reference, the snapshot side skips it, and the CHECK_NE in CheckAllReferencesWereChecked fails. The process crashes with a controlled CHECK abort.

Impact Assessment

The patch shows a reachable CHECK failure that crashes the process. That is a denial of service in the process taking the snapshot, for example a renderer being profiled (inferred). No memory corruption is involved. The vendor advisory rates this high severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.1. Several preconditions limit exposure: the code exists only in builds compiled with V8_ENABLE_HEAP_SNAPSHOT_VERIFY (normally debug/verification builds, not release; inferred), shared-heap features must be enabled, and a heap snapshot must be requested, which ordinary web content cannot do on its own.

Changed Functions

FunctionChangeNotes
HeapEntryVerifier
src/profiler/heap-snapshot-generator.cc
modified

Files Changed

  • test/mjsunit/shared-memory/regress/regress-528397177.js
  • src/profiler/heap-snapshot-generator.cc

Audit Directions

  • Paired skip rules in verifiers
    wherever a producer and a consumer must agree on which objects to ignore (snapshot vs. marking visitor, serializer vs. deserializer), check that the filters are shared code and not duplicated conditions that can drift apart.
  • Visitor side effects before bail-outs
    look for marking or visiting code such as VisitEphemeronHashTable that records, summarizes or publishes an object before an early return for shared or read-only objects. Downstream consumers may assume the bail-out happened first.
  • Shared-heap special cases in V8 tooling
    audit profiler, heap-statistics and debug-verification paths for client-isolate handling of InWritableSharedSpace() objects. These paths are tested less often with --shared-string-table and similar flags.
From 6081a378d25f70dc1ca154d833955b24c1962377 Mon Sep 17 00:00:00 2001
From: Dominik Inführ <dinfuehr@chromium.org>
Date: Thu, 02 Jul 2026 12:35:32 +0200
Subject: [PATCH] [profiler] Skip shared objects in client isolates during verification

On client isolates in VerifyReference() we skip references into the
shared heap. However, we didn't then skip them in
CheckAllReferencesWereChecked(). In
MarkingVisitorBase::VisitEphemeronHashTable we invoke
AddWeakReferenceForReferenceSummarizer() for the value unconditionally
before bailing out for shared objects on client isolates.

Bug: 528397177
Change-Id: I6593c2d3dfe146e2241d03ee04ec7cb957880d15
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8025611
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Auto-Submit: Dominik Inführ <dinfuehr@chromium.org>
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/main@{#108392}
---
diff --git a/src/profiler/heap-snapshot-generator.cc b/src/profiler/heap-snapshot-generator.cc
index d4a5161..cd9a885 100644
--- a/src/profiler/heap-snapshot-generator.cc
+++ b/src/profiler/heap-snapshot-generator.cc
@@ -68,6 +68,20 @@
 namespace v8::internal {
 
 #ifdef V8_ENABLE_HEAP_SNAPSHOT_VERIFY
+bool ShouldVerifyReferenceTo(Isolate* isolate, Tagged<HeapObject> obj) {
+  // We can't verify pointers into read-only space, because marking visitors
+  // might not mark those. For example, every Map has a pointer to the MetaMap,
+  // but marking visitors don't bother with following that link.
+  if (MemoryChunk::FromHeapObject(obj)->InReadOnlySpace()) return false;
+  // For client isolates we cannot verify references into the shared heap
+  // because the marking visitor doesn't follow such references.
+  if (isolate->has_shared_space() && !isolate->is_shared_space_isolate() &&
+      MemoryChunk::FromHeapObject(obj)->InWritableSharedSpace()) {
+    return false;
+  }
+  return true;
+}
+
 class HeapEntryVerifier {
  public:
   HeapEntryVerifier(HeapSnapshotGenerator* generator, Tagged<HeapObject> obj)
@@ -142,17 +156,15 @@
   // This ensures that there aren't retaining relationships found by the marking
   // visitor which were omitted from the heap snapshot.
   void CheckAllReferencesWereChecked() {
-    // Both loops below skip pointers to read-only objects, because the heap
-    // snapshot deliberately omits many of those (see IsEssentialObject).
-    // Read-only objects can't ever retain normal read-write objects, so these
-    // are fine to skip.
+    Isolate* isolate = generator_->heap()->isolate();
+
     for (Tagged<HeapObject> obj : reference_summary_.strong_references()) {
-      if (!MemoryChunk::FromHeapObject(obj)->InReadOnlySpace()) {
+      if (ShouldVerifyReferenceTo(isolate, obj)) {
         CHECK_NE(checked_objects_.find(obj), checked_objects_.end());
       }
     }
     for (Tagged<HeapObject> obj : reference_summary_.weak_references()) {
-      if (!MemoryChunk::FromHeapObject(obj)->InReadOnlySpace()) {
+      if (ShouldVerifyReferenceTo(isolate, obj)) {
         CHECK_NE(checked_objects_.find(obj), checked_objects_.end());
       }
     }
@@ -329,17 +341,10 @@
   }
   Tagged<HeapObject> from_obj = Cast<HeapObject>(Tagged<Object>(from_address));
   Tagged<HeapObject> to_obj = Cast<HeapObject>(Tagged<Object>(to_address));
-  if (MemoryChunk::FromHeapObject(to_obj)->InReadOnlySpace() ||
-      (!isolate()->is_shared_space_isolate() &&
-       MemoryChunk::FromHeapObject(to_obj)->InWritableSharedSpace())) {
+  if (!ShouldVerifyReferenceTo(isolate(), to_obj)) {
     // We can't verify pointers into read-only space or shared space (for client
-    // isolates), because marking visitors might not mark those. For example,
-    // every Map has a pointer to the MetaMap, but marking visitors don't bother
-    // with following that link. Read-only and shared objects are immortal (or
-    // managed externally) and can never point to things outside of their
-    // respective spaces, so ignoring these objects is safe from the perspective
-    // of ensuring accurate retaining paths for normal read-write objects.
-    // Therefore, do nothing.
+    // isolates), because marking visitors might not mark those. See
+    // ShouldVerifyReferenceTo for more details. Therefore, do nothing.
   } else if (verification == kEphemeron) {
     // Ephemerons can't be verified because they aren't marked directly by the
     // marking visitor.
diff --git a/test/mjsunit/shared-memory/regress/regress-528397177.js b/test/mjsunit/shared-memory/regress/regress-528397177.js
new file mode 100644
index 0000000..e0f2c0d
--- /dev/null
+++ b/test/mjsunit/shared-memory/regress/regress-528397177.js
@@ -0,0 +1,13 @@
+// Copyright 2026 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --expose-gc --shared-string-table --cache
+
+const value = "some value";
+const map = new WeakMap();
+const key = () => {};
+map.set(key, value);
+gc({
+  type: 'major-snapshot',
+});
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/test/mjsunit/shared-memory/regress/regress-528397177.js b/test/mjsunit/shared-memory/regress/regress-528397177.js
new file mode 100644
index 0000000..e0f2c0d
--- /dev/null
+++ b/test/mjsunit/shared-memory/regress/regress-528397177.js
@@ -0,0 +1,13 @@
+// Copyright 2026 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --expose-gc --shared-string-table --cache
+
+const value = "some value";
+const map = new WeakMap();
+const key = () => {};
+map.set(key, value);
+gc({
+  type: 'major-snapshot',
+});
Loading diff…

Original Bug Report

reported by 24...@project.gserviceaccount.com

CHECK failure: checked_objects_.find(obj) != checked_objects_.end() in heap-snapshot-generator.

Detailed Report: https://clusterfuzz.com/testcase?key=4967680113147904

Fuzzer: ochang_js_fuzzer_test Job Type: linux32_asan_d8_dbg Platform Id: linux

Crash Type: CHECK failure Crash Address: Crash State: checked_objects_.find(obj) != checked_objects_.end() in heap-snapshot-generator. v8::internal::HeapEntryVerifier::CheckAllReferencesWereChecked v8::internal::HeapEntryVerifier::~HeapEntryVerifier

Sanitizer: address (ASAN)

Regressed: https://clusterfuzz.com/revisions?job=linux32_asan_d8_dbg&range=107629:107630

Reproducer Testcase: https://clusterfuzz.com/download?testcase_id=4967680113147904

Issue filed automatically.

To reproduce this, please build the target in this report and run it against the reproducer testcase. Please use the GN arguments provided at bottom of this report when building the binary.

If you have trouble reproducing, please also export the environment variables listed under “[Environment]” in the crash stacktrace.

If you have any feedback on reproducing test cases, let us know at https://forms.gle/Yh3qCYFveHj6E5jz5 so we can improve.

View on issue tracker