High chrome Integer Overflow 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInteger overflow in DevTools
DescriptionInteger overflow in DevTools
ComponentDevTools
Bug ClassInteger Overflow
Tracker511290038
Fix commit5472defe56a3 (v8/v8) +2/-4
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Files Changed

  • src/profiler/profile-generator.h
From 5472defe56a3100383935e051f87cf2eb338169d Mon Sep 17 00:00:00 2001
From: Benedikt Meurer <[email protected]>
Date: Tue, 12 May 2026 13:23:36 +0200
Subject: [PATCH] [profiler] Fix race condition in code entry refcounting.

Fixed: 511290038
Change-Id: I16f1d52578fa8d85d2552ee130f468efbae44e0e
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7837982
Reviewed-by: Philip Pfaffe <[email protected]>
Auto-Submit: Benedikt Meurer <[email protected]>
Commit-Queue: Benedikt Meurer <[email protected]>
Cr-Commit-Position: refs/heads/main@{#107260}
---

diff --git a/src/profiler/profile-generator.h b/src/profiler/profile-generator.h
index 38b3c71..5834520 100644
--- a/src/profiler/profile-generator.h
+++ b/src/profiler/profile-generator.h
@@ -230,15 +230,13 @@
   size_t AddRef() {
     DCHECK(is_ref_counted());
     DCHECK_LT(ref_count_, std::numeric_limits<size_t>::max());
-    ref_count_++;
-    return ref_count_;
+    return ++ref_count_;
   }
 
   size_t DecRef() {
     DCHECK(is_ref_counted());
     DCHECK_GT(ref_count_, 0UL);
-    ref_count_--;
-    return ref_count_;
+    return --ref_count_;
   }
 
   using EventField = base::BitField<LogEventListener::Event, 0, 4>;
Loading diff…

Original Bug Report

reported by [email protected]

Double-Free in V8 CPU Profiler via CodeEntry::DecRef Race Condition

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 without the Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A race condition exists in V8’s CPU Profiler where CodeEntry::DecRef implements reference counting unsafely using separate atomic decrement and load operations. This allows two concurrent threads to both observe a reference count of 0, bypassing single-deletion checks and leading to a potential Double-Free and Use-After-Free. The issue is reachable from web content via the JS Self-Profiling API.

Affected files:

  • v8/src/profiler/profile-generator.h
  • v8/src/profiler/profile-generator.cc
  • v8/src/profiler/cpu-profiler.cc
  • third_party/blink/renderer/core/timing/profiler_group.cc

Estimated timestamp from git blame: 2021-07-02

Summary

A race condition in the V8 CPU Profiler’s CodeEntry reference counting logic can potentially lead to a Use-After-Free (UAF) and Double-Free. The CodeEntry::DecRef function uses a non-atomic sequence of operations to decrement and evaluate the reference count. When two threads concurrently decrement a CodeEntry with a reference count of 2, both can observe the resulting count as 0, causing both threads to attempt to free the object.

Technical Details

In v8/src/profiler/profile-generator.h, the CodeEntry::DecRef function is implemented as follows:

  size_t DecRef() {
    DCHECK(is_ref_counted());
    DCHECK_GT(ref_count_, 0UL);
    ref_count_--;
    return ref_count_;
  }

Although ref_count_ is an std::atomic<std::size_t>, the postfix decrement (ref_count_--) and the subsequent load (return ref_count_;) are two distinct atomic operations. This introduces a race condition. If a CodeEntry has a reference count of 2, and two threads invoke DecRef concurrently, the following interleaving can occur:

  1. Thread A executes ref_count_-- (count becomes 1).
  2. Thread B executes ref_count_-- (count becomes 0).
  3. Thread A executes return ref_count_; (returns 0).
  4. Thread B executes return ref_count_; (returns 0).

When CodeEntryStorage::DecRef (in v8/src/profiler/profile-generator.cc) receives the return value, it checks if (entry->is_ref_counted() && entry->DecRef() == 0). Because both threads received 0, both will enter the block and execute delete entry; (and potentially iterate over entry->rare_data_), leading to a double-free and UAF.

Suggested Steps to Trigger (Theoretical)

Note: These are potential steps as our tooling agent cannot execute code to verify a working exploit.

  1. Enable API: The attacker serves a page with the Document-Policy: js-profiling HTTP header to enable the JS Self-Profiling API.
  2. Create Profilers: The attacker’s JavaScript creates multiple concurrent profilers (e.g., p1 and p2). This starts V8’s background SamplingEventsProcessor thread.
  3. Generate Events: The JS triggers heavy JIT compilation and subsequent Garbage Collection to generate kCodeDelete events, which are queued to the background thread.
  4. Stop One Profiler: The attacker stops p1 (p1.stop()) and drops references to it. Because p2 is still running, the SamplingEventsProcessor background thread continues executing.
  5. Trigger Cleanup: Blink’s garbage collection cleans up p1, posting an asynchronous task to the main thread to delete the CpuProfile.
  6. The Race: On the main thread, the destruction of ProfileNodes calls DecRef on CodeEntry objects. Concurrently, the background thread dequeues kCodeDelete events and calls InstructionStreamMap::RemoveCode, which also calls DecRef on the same CodeEntry objects. If timed correctly, the race condition occurs.

Exploitability

CodeEntry is a standard C++ object allocated on the heap. Pointers to it (such as those in ProfileNode and InstructionStreamMap) are held as raw C++ pointers (CodeEntry*), rather than being protected by base::raw_ptr. Therefore, this vulnerability is not mitigated by MiraclePtr (BackupRefPtr) and can potentially be leveraged for Remote Code Execution (RCE) in the Renderer process.

Suggested Fix

Refactor CodeEntry::DecRef (and similarly AddRef) to use a single atomic operation that evaluates and returns the new value. For example:

  size_t DecRef() {
    DCHECK(is_ref_counted());
    DCHECK_GT(ref_count_, 0UL);
    return --ref_count_; // Prefix decrement ensures a single atomic instruction
  }

Alternatively, return ref_count_.fetch_sub(1) - 1; can be used to ensure the returned value precisely reflects the decrement operation performed by the calling thread.

Evaluated with Chrome root at commit: eca8648a4e1cdfdda68c495a6003059fed641955


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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