Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in V8
DescriptionUse after free in V8
ComponentV8
Sub-componentInspector
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.
Tracker511270083
Fix commitbb96b8e67ac4 (v8/v8) +1/-0
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
CreditedGoogle
Reported2026-05-08 (per vendor advisory)
Fix landed2026-05-13 (public git)
Disclosed2026-06-08
Patch gap26 days — fix public in source before the advisory

Background

`v8-deep-serializer.cc`
the Inspector component that deeply serializes JavaScript values into the protocol’s structured SerializedValue form (used by the BiDi/Runtime value-serialization path).
`Response`
the Inspector result type that carries either success or an error; response.IsSuccess() distinguishes the two, and a non-success Response means the serialization did not complete normally.
`serializedValue`
the out-parameter (&serializedValue) the serialization helper fills in; on failure it may be left unset, incomplete, or in a moved-from/invalid state.
`result.setValue("value", std::move(serializedValue))`
moves the serialized value into the outgoing result object, consuming serializedValue.

Root Cause Analysis

In the serialization routine (the enclosing function is not shown in the @@ context, so it is not named here), the code invoked the deep-serialization helper with &serializedValue and captured its Response into response, but then unconditionally executed result.setValue("value", std::move(serializedValue)) and returned Response::Success() without ever inspecting response. When the helper returned an error, serializedValue had not been validly populated, yet it was still moved into result and reported as success. The violated invariant is that a failed Response must be propagated and its incompletely-produced output must not be consumed. Consuming/serializing a value that a failed deep-serialization left in an invalid state is what the advisory attributes to a use-after-free, i.e. the error path had left a dangling or invalidated reference that the subsequent setValue then used (inferred, since the freeing site is not in this hunk).

The fix inserts if (!response.IsSuccess()) return response; immediately after the serialization call and before result.setValue, so on any serialization error the function returns the error early and never touches the invalid serializedValue.

Key insight
The single mistake was ignoring the Response from deep serialization and consuming serializedValue even on failure; the fix adds an early if (!response.IsSuccess()) return response; so a failed serialization is propagated instead of letting the invalid result be used.

Attack Path

  1. Request deep value serialization The attacker drives the Inspector/BiDi path (e.g. a Runtime command that deep-serializes a value) so this serialization routine runs on an attacker-chosen object.
  2. Force a serialization failure By supplying a value/structure that makes the deep-serialization helper return a non-success Response (leaving serializedValue invalid), the attacker reaches the unguarded error path.
  3. Trigger use of the invalid value Before the fix, execution falls through to result.setValue("value", std::move(serializedValue)), consuming the incompletely-produced / invalidated serializedValue, which the advisory attributes to a use-after-free (the specific freed object is not shown in the diff, inferred).
  4. Escalate via reclaimed memory Reallocating the freed backing memory with controlled data could convert the dangling use into a corruption primitive (inferred); the patch demonstrates only the missing error check.

Impact Assessment

An attacker who can drive the Inspector value-serialization path (via DevTools/BiDi or an equivalent client) gains a use-after-free in the process running V8’s inspector, per the advisory’s classification of a use-after-free in V8. The precondition is causing the deep-serialization helper to fail while a value is being serialized. Any progression from the UAF to controlled read/write or code execution is (inferred) and not shown by the patch.

Files Changed

  • src/inspector/v8-deep-serializer.cc

Audit Directions

  • Ignored `Response`/status returns
    Search for sites that capture a Response/status from a fallible call but then use the out-parameter and return success without checking IsSuccess().
  • Out-parameter use on failure
    Audit serializer/converter helpers that fill a &out value, ensuring callers do not std::move/consume that out-parameter when the call reported an error.
  • Deep/recursive serialization paths
    Review other V8DeepSerializer/V8ValueSerializer recursion points where a nested failure must abort the whole serialization rather than proceeding with partially-built results.
From bb96b8e67ac4a4482ce812ff27d8ec3e4a36116c Mon Sep 17 00:00:00 2001
From: Philip Pfaffe <[email protected]>
Date: Wed, 13 May 2026 11:13:38 +0000
Subject: [PATCH] [inspector] Return error response in value serialization

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

diff --git a/src/inspector/v8-deep-serializer.cc b/src/inspector/v8-deep-serializer.cc
index 2bb04a6..cf83fb9 100644
--- a/src/inspector/v8-deep-serializer.cc
+++ b/src/inspector/v8-deep-serializer.cc
@@ -205,6 +205,7 @@
                                             additionalParameters,
 
                                             duplicateTracker, &serializedValue);
+    if (!response.IsSuccess()) return response;
     result.setValue("value", std::move(serializedValue));
   }
   return Response::Success();
Loading diff…

Original Bug Report

reported by [email protected]

UAF write in V8 Inspector deep serialization via ignored SerializeSet error

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 potential Use-After-Free (UAF) exists in the V8 inspector’s deep serialization when V8DeepSerializer::SerializeSet ignores errors. This leaves raw, dangling pointers in the duplicate tracker, which can be exploited for arbitrary memory corruption and remote code execution if the freed memory is reclaimed.

Affected files:

  • v8/src/inspector/v8-deep-serializer.cc
  • v8/src/inspector/v8-serialization-duplicate-tracker.cc
  • v8/src/inspector/value-mirror.cc
  • third_party/blink/renderer/core/inspector/thread_debugger_common_impl.cc

Estimated timestamp from git blame: 2023-06-19

Potential Consequence: Exploitation yields a highly controllable Use-After-Free (UAF) write primitive, potentially leading to arbitrary memory corruption and Remote Code Execution (RCE) in the renderer process.

Technical Summary: Initial logic and parameters are validated: an attacker can trigger deep serialization via CDP (Runtime.evaluate) on a crafted structure containing a Set and a custom JS getter. The V8SerializationDuplicateTracker accurately registers object references using v8::External mappings (circumventing MiraclePtr protections).

Standard processing applies during serialization, where a deeply nested DOM tree legitimately exhausts maxDepth, forcing a Response::ServerError and the destruction of the local ListValue containing previously processed Set elements.

The vulnerability triggers when V8DeepSerializer::SerializeSet unconditionally ignores this error state and returns Response::Success(). This bypass allows the outer serialization routine to immediately invoke the attacker-controlled JS getter. The getter performs targeted heap reclamation over the freed DictionaryValue memory and returns the original Set object. The V8SerializationDuplicateTracker blindly fetches the now-dangling raw pointer, executing setInteger operations directly on attacker-controlled std::unordered_map structures, finalizing the UAF write.

Potential Steps to Reproduce: (Note: These are potential steps as our tooling agent cannot currently execute code).

  1. Construct an Array where index 0 is a Set containing a target object and a deeply nested DOM tree.
  2. Assign a custom JS getter to index 1 of the Array.
  3. Trigger deep serialization via CDP (Runtime.evaluate with serializationOptions: {serialization: "deep"}).
  4. The deeply nested DOM tree causes an intentional serialization failure, freeing the target object’s DictionaryValue.
  5. SerializeSet ignores the error, advancing to index 1 and invoking the JS getter.
  6. The JS getter reclaims the freed memory with payload data and returns the target object, forcing a UAF write via the duplicate tracker.

Suggested Fix: Update V8DeepSerializer::SerializeSet in v8/src/inspector/v8-deep-serializer.cc to explicitly check and return the Response from SerializeArrayValue:

Response response = SerializeArrayValue(value->AsArray(), context, maxDepth, additionalParameters, duplicateTracker, &serializedValue);
if (!response.IsSuccess()) return response;

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