Medium watch CVSS 5.9 chrome OOB 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
5.9
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds read in V8
DescriptionOut of bounds read in V8
ComponentV8
Sub-componentProfiler
Bug ClassOOB
Action tierwatch — information disclosure — security-relevant, below the RCE-class bar
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.
Tracker513435594
Fix commit6d33b8b329ef (v8/v8) +10/-1
CWECWE-125 (Out-of-bounds read)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-05-15 (per vendor advisory)
Fix landed2026-05-18 (public git)
Disclosed2026-06-30
Patch gap43 days — fix public in source before the advisory

Background

`CodeEntry`
a V8 CPU-profiler object describing a piece of code (name, resource, line info) that is read from multiple threads while the sampling profiler runs.
`RareData`
a lazily-allocated sub-object of CodeEntry holding infrequently-used fields, pointed to by the rare_data_ std::unique_ptr.
Store-store reordering
a weak-memory-model behavior (e.g. on arm64) where two stores by one thread can become visible to other threads in a different order than the program writes them.
`std::atomic_thread_fence(std::memory_order_release)`
a memory barrier that prevents prior memory writes from being reordered after it, so they are all visible before any subsequent store the reader observes.

Root Cause Analysis

CodeEntry::EnsureRareData() lazily created the sub-object with rare_data_.reset(new RareData()), a single statement that both constructs/initializes the RareData fields and publishes the pointer into the rare_data_ std::unique_ptr. On a platform with store-store reordering, the store that makes the rare_data_ pointer non-null could become visible to other threads before the writes that initialize the RareData members completed. Because CodeEntry is accessed concurrently by the profiler, another thread checking if (!rare_data_) could then observe a non-null pointer and dereference a RareData whose fields are still uninitialized.

The fix separates the two steps — construct into a local RareData* rare_data = new RareData(), issue std::atomic_thread_fence(std::memory_order_release), then rare_data_.reset(rare_data) — so the barrier guarantees the field initializations are globally visible before the pointer publication, restoring the invariant that a visible rare_data_ always points to a fully-initialized object.

Key insight
The mistake was publishing the rare_data_ pointer in the same unordered step as initializing the object it points to; the release fence added between construction and reset ensures the initialization is visible before the pointer is, closing the data race without penalizing readers (as an std::atomic field would).

Attack Path

  1. Start profiling Cause the CPU profiler to run so CodeEntry objects are created and accessed concurrently by the profiler thread and other threads. (inferred)
  2. Trigger lazy allocation Reach a code path that calls CodeEntry::EnsureRareData() on a CodeEntry for the first time, publishing a fresh RareData via the unique_ptr.
  3. Race the publication On an arm64-class device, a second thread observes the reordered non-null rare_data_ before the RareData field initializations are visible, per the commit description.
  4. Read uninitialized RareData That thread dereferences the partially-constructed RareData, reading uninitialized/garbage field values, which manifests as the advisory’s out-of-bounds read in V8. (inferred)

Impact Assessment

An attacker able to drive concurrent profiler activity on a weak-memory (e.g. arm64) device can win the race and cause a thread to read a not-yet-initialized RareData, producing an out-of-bounds read within the V8/renderer process as the advisory states (“Out of bounds read in V8”). Preconditions are the profiler being active and a store-store-reordering platform; the advisory does not report in-the-wild exploitation, and any escalation beyond the OOB read is (inferred).

Changed Functions

FunctionChangeNotes
if
src/profiler/profile-generator.cc
modified

Files Changed

  • src/profiler/profile-generator.cc

Audit Directions

  • Publish-before-init pointers
    Search for lazy-init patterns (ptr.reset(new T()), raw pointer assignment, or unique_ptr/plain-pointer publication) on objects read by multiple threads without an intervening release barrier or atomic store.
  • Non-atomic shared fields on weak memory
    Review profiler and other concurrently-read structures for member pointers checked with a plain null test and dereferenced, on platforms with store-store reordering.
  • `EnsureX`/lazy accessors
    Audit similar Ensure*/GetOrCreate* helpers that construct-and-publish in one step, confirming initialization ordering is enforced for any cross-thread reader.
From 6d33b8b329ef903dde6a296ebd973a41ea298de6 Mon Sep 17 00:00:00 2001
From: Benedikt Meurer <bmeurer@chromium.org>
Date: Fri, 15 May 2026 13:06:18 +0200
Subject: [PATCH] [profiler] Prevent data race on `CodeEntry::rare_data_`.

This adds an explicit memory barrier to `CodeEntry::EnsureRareData()`
for platforms with store-store reordering (e.g. arm64), where it's
otherwise possible to that the store to `rare_data_` becomes visible to
other threads before the field initialization completes.

Fixed: 513435594
Change-Id: I78a45c35bed6a3a2459c1ec994393709027c5f22
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7849376
Reviewed-by: Yang Guo <yangguo@chromium.org>
Commit-Queue: Yang Guo <yangguo@chromium.org>
Auto-Submit: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/main@{#107368}
---

diff --git a/src/profiler/profile-generator.cc b/src/profiler/profile-generator.cc
index b62c067..bb7aea1 100644
--- a/src/profiler/profile-generator.cc
+++ b/src/profiler/profile-generator.cc
@@ -5,6 +5,7 @@
 #include "src/profiler/profile-generator.h"
 
 #include <algorithm>
+#include <atomic>
 #include <vector>
 
 #include "include/v8-profiler.h"
@@ -270,7 +271,15 @@
 
 CodeEntry::RareData* CodeEntry::EnsureRareData() {
   if (!rare_data_) {
-    rare_data_.reset(new RareData());
+    // On platforms with store-store reordering, we need to ensure that the
+    // field initializations in RareData complete before the pointer (in the
+    // std::unique_ptr) becomes visible. Using an std::atomic for rare_data_
+    // would also work, but would penalize every read, also on platforms with
+    // no store-store reordering (like x64).
+    // See http://crbug.com/513435594 for additional details.
+    RareData* rare_data = new RareData();
+    std::atomic_thread_fence(std::memory_order_release);
+    rare_data_.reset(rare_data);
   }
   return rare_data_.get();
 }
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential data race in V8 CPU Profiler allows arbitrary memory read via CDP

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A data race in V8’s CPU profiler occurs when CodeEntry::rare_data_ is accessed concurrently by the profiler and main threads without synchronization. On weakly-ordered architectures like ARM64, this can lead to an arbitrary native memory read primitive accessible via the Chrome DevTools Protocol (CDP).

Affected files:

  • v8/src/profiler/profile-generator.h
  • v8/src/profiler/profile-generator.cc
  • v8/src/profiler/cpu-profiler.cc
  • v8/src/inspector/v8-profiler-agent-impl.cc
  • v8/src/api/api.cc
  • v8/src/profiler/cpu-profiler-inl.h

Estimated timestamp from git blame: 2018-05-18

Summary

A potential data race in the V8 CPU profiler allows for a wild pointer read, which could be exploited to leak renderer process memory via the Chrome DevTools Protocol (CDP). The issue stems from unsynchronized access to CodeEntry::rare_data_ when a profile is being serialized on the main thread while the profiler background thread is concurrently updating deoptimization or bailout metadata.

Technical Details

The CodeEntry class (defined in v8/src/profiler/profile-generator.h) uses a std::unique_ptr<RareData> rare_data_ to store optional metadata such as bailout reasons and deoptimization information. This pointer is accessed across threads without atomic protections or mutexes.

  1. Concurrent Writer: The SamplingEventsProcessor thread processes code events (e.g., CodeDisableOptEvent). It calls CodeEntry::set_bailout_reason(), which invokes EnsureRareData(). This method allocates a new RareData object and assigns it to the rare_data_ unique pointer:

    CodeEntry::RareData* CodeEntry::EnsureRareData() {
      if (!rare_data_) {
        rare_data_.reset(new RareData());
      }
      return rare_data_.get();
    }
    
  2. Concurrent Reader: When multiple profiling sessions are active on a single v8::CpuProfiler, calling StopProfiling on one session does not stop the background profiler thread if other sessions remain active. While the background thread continues processing events, the main thread begins serializing the stopped profile. This eventually calls CodeEntry::bailout_reason() via the V8 API (v8::CpuProfileNode::GetBailoutReason):

    const char* bailout_reason() const {
      return rare_data_ ? rare_data_->bailout_reason_ : kEmptyBailoutReason;
    }
    
  3. The Race: On weakly-ordered architectures (such as ARM64), the memory store to rare_data_ and the initialization of its fields (like bailout_reason_) can be reordered. Consequently, the main thread may observe a non-null rare_data_ pointer before its internal bailout_reason_ pointer has been correctly initialized. If the underlying memory was previously controlled by an attacker (e.g., via heap spraying), the reader may dereference a stale or malicious pointer.

Potential Impact

An attacker with access to the Profiler CDP domain (e.g., via a chrome.debugger extension or a compromised renderer) could potentially perform an arbitrary-address null-terminated read of the renderer process memory. The leaked data is returned to the CDP client as a string in the consoleProfileFinished event or the Profiler.stop response. This could be used to leak sensitive information or bypass ASLR.

Suggested Potential Exploitation Steps

  1. Attach a CDP client to the target renderer.
  2. Spray the native heap with objects of the same size as RareData containing a target memory address at the offset of bailout_reason_.
  3. Start multiple concurrent CPU profiling sessions.
  4. Trigger a code event that causes an optimization bailout (e.g., executing code that violates a previous optimization assumption).
  5. Immediately stop one of the profiling sessions.
  6. The race condition may cause the serialization process to read the attacker-controlled pointer as a const char*, leaking memory from that address into the CDP response.

Suggested Fix

Access to CodeEntry::rare_data_ should be synchronized. This can be achieved by making rare_data_ an std::atomic<RareData*> with proper memory ordering (Acquire/Release) or by protecting the CodeEntry metadata with a mutex. Given that CodeEntry objects are heavily used during profiling, a lock-free approach with atomics is likely preferable for performance.

Evaluated with Chrome root at commit: b3153093eb3c78c3e88ccf562bcbc20437a04b0e


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker