Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactType Confusion in V8
DescriptionType Confusion in V8
ComponentV8
Sub-componentPreparser
Bug ClassType Confusion
Action tierpatch-now — corruption primitive, web-reachable in default config (plausible) — upheld by independent review by AI
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.
Independent review by AIPlausible
Reviewer notes: Escape-check removal confirmed, but strict-mode example only yields deferred SyntaxError; analysis never links parser divergence to memory unsafety (likely arguments/scope-allocation mismatch via preparse data). Type confusion asserted from advisory, not shown.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker430344952
Fix commit0559da28e2a5 (v8/v8) +11/-3
CWECWE-843 (Type confusion)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedShaheen Fazim
Reported2025-07-09 · bounty $8000 (per vendor advisory)
Fix landed2025-07-14 (public git)
Disclosed2025-07-22
Patch gap8 days — fix public in source before the advisory

Background

Preparser
V8’s fast, lazy scanner that skims function bodies without building a full AST to speed up parsing; it must reach the same syntactic conclusions as the full parser or the two disagree.
Contextual keywords `eval` / `arguments`
identifiers that are legal names generally but are forbidden as binding targets in strict mode, so the parser must recognize them even when spelled with Unicode escapes.
`literal_contains_escapes()`
a scanner flag that is true when the just-scanned identifier used escape sequences (e.g. a for a), used to distinguish arguments from arguments.
`PreParserIdentifier`
the preparser’s lightweight identifier token whose kind (Eval, Arguments, Default, …) drives strict-mode binding checks.

Root Cause Analysis

Verify before building a PoC

This analysis is consistent with the fixing commit but not fully provable from it: part of the vulnerable path lives in code the fix commit does not touch. Confirm the trigger and the sink against the source before relying on it for exploit development.
What the commit does not establish: Escape-check removal confirmed, but strict-mode example only yields deferred SyntaxError; analysis never links parser divergence to memory unsafety (likely arguments/scope-allocation mismatch via preparse data). Type confusion asserted from advisory, not shown.

The preparser’s identifier-classification helper compared the decoded identifier string against avf->eval_string() and avf->arguments_string() to tag it as Eval or Arguments. The removed lines inserted, between those two comparisons, an early if (scanner->literal_contains_escapes()) return PreParserIdentifier::Default();, so an escaped spelling such as arguments decoded to the string arguments but was returned as Default instead of Arguments (the eval case was already handled above the removed check, so only arguments was affected). Consequently the preparser failed to flag arguments as the arguments contextual keyword, while the full parser — which does not drop escaped keywords — correctly treated it as arguments. This divergence let the preparser accept a strict-mode binding like var arguments = 0; that the full parser rejects, breaking the invariant that preparse and full-parse agree on identifier classification.

The fix deletes the escape check so the preparser recognizes escaped arguments identically to the full parser, and the shipped message test now expects SyntaxError: Unexpected eval or arguments in strict mode.

Key insight
The single mistake was silently downgrading any escaped identifier to Default in the preparser, causing escaped arguments to escape strict-mode detection and desynchronize the preparser from the full parser; removing that check restores agreement.

Attack Path

  1. Craft escaped-keyword source Author strict-mode code that binds the arguments keyword using a Unicode escape, e.g. "use strict"; function f(a){ var arguments = 0; }, as in the message test.
  2. Trigger lazy preparse Arrange for the enclosing function to be preparsed lazily so the vulnerable PreParserIdentifier classification runs and returns Default for arguments, accepting the binding.
  3. Trigger later full parse When the function is fully parsed/compiled, the parser classifies arguments as arguments, disagreeing with the preparse result about scope and binding validity.
  4. Induce parser/preparser state mismatch The differing classification yields inconsistent scope/variable metadata for the function, the type-confusion condition the advisory reports (inferred as to the exact confused object).
  5. Leverage the confusion The inconsistent scope/binding state is manipulated into a type confusion in later object handling (inferred; the patch does not show the downstream sink).

Impact Assessment

An attacker running crafted JavaScript in the renderer can make V8’s preparser and full parser disagree about whether an escaped arguments/keyword identifier is a reserved contextual keyword, producing the type confusion the advisory classifies as Type Confusion in V8. This occurs in the renderer process and requires only the ability to execute attacker-controlled script; the advisory does not report exploitation in the wild, and any escalation to a read/write primitive or code execution beyond the parser-state confusion is (inferred).

Files Changed

  • src/parsing/preparser.cc
  • test/message/fail/preparse-arguments.js
  • test/message/fail/preparse-arguments.out

Audit Directions

  • Escape-sensitive keyword checks
    Hunt for other places that use literal_contains_escapes() or raw-vs-decoded comparisons to accept/reject contextual keywords (eval, arguments, yield, await, let, static), where escapes could hide a reserved word.
  • Preparser/parser divergence
    Systematically diff preparser identifier and binding logic against the full parser.cc equivalents; any predicate present in one but not the other is a candidate for scope/type confusion.
  • Strict-mode binding validation
    Audit strict-mode “Unexpected eval or arguments” enforcement across lazy and eager paths to ensure both reject the same set of (possibly escaped) identifiers.
From 0559da28e2a55930279071a18b7444501e8d1223 Mon Sep 17 00:00:00 2001
From: Toon Verwaest <[email protected]>
Date: Mon, 14 Jul 2025 13:56:15 +0200
Subject: [PATCH] [preparser] Support escapes in arguments contextual keywords

Bug: 430344952
Change-Id: Ib35df2f992cfe5a5207109ab500a0baaadf9b988
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6732379
Reviewed-by: Leszek Swirski <[email protected]>
Commit-Queue: Leszek Swirski <[email protected]>
Commit-Queue: Toon Verwaest <[email protected]>
Auto-Submit: Toon Verwaest <[email protected]>
Cr-Commit-Position: refs/heads/main@{#101389}
---

diff --git a/src/parsing/preparser.cc b/src/parsing/preparser.cc
index 201fbdb7..70c7d452 100644
--- a/src/parsing/preparser.cc
+++ b/src/parsing/preparser.cc
@@ -44,9 +44,6 @@
   if (string == avf->eval_string()) {
     return PreParserIdentifier::Eval();
   }
-  if (scanner->literal_contains_escapes()) {
-    return PreParserIdentifier::Default();
-  }
   if (string == avf->arguments_string()) {
     return PreParserIdentifier::Arguments();
   }
diff --git a/test/message/fail/preparse-arguments.js b/test/message/fail/preparse-arguments.js
new file mode 100644
index 0000000..7b42a0b
--- /dev/null
+++ b/test/message/fail/preparse-arguments.js
@@ -0,0 +1,7 @@
+// Copyright 2025 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.
+
+"use strict";
+
+function f(a) { var \u0061rguments = 0; }
diff --git a/test/message/fail/preparse-arguments.out b/test/message/fail/preparse-arguments.out
new file mode 100644
index 0000000..2c54c31
--- /dev/null
+++ b/test/message/fail/preparse-arguments.out
@@ -0,0 +1,4 @@
+*%(basename)s:7: SyntaxError: Unexpected eval or arguments in strict mode
+function f(a) { var \u0061rguments = 0; }
+                    ^^^^^^^^^^^^^^
+SyntaxError: Unexpected eval or arguments in strict mode
Loading diff…

Original Bug Report

reported by [email protected]

Debug check failed: IsInBounds(index)



#
# Fatal error in ../../src/objects/fixed-array-inl.h, line 116
# Debug check failed: IsInBounds(index).
#
#
#
#FailureMessage Object: 0x7ffdb6786b58
==== C stack trace ===============================

    /home/user/v8/v8/out/x64.debug/libv8_libbase.so(v8::base::debug::StackTrace::StackTrace()+0x1e) [0x7f64fec3792e]
    /home/user/v8/v8/out/x64.debug/libv8_libplatform.so(+0x4b60d) [0x7f64feba460d]
    /home/user/v8/v8/out/x64.debug/libv8_libbase.so(V8_Fatal(char const*, int, char const*, ...)+0x205) [0x7f64fec10ce5]
    /home/user/v8/v8/out/x64.debug/libv8_libbase.so(+0x4d69c) [0x7f64fec1069c]
    /home/user/v8/v8/out/x64.debug/libv8_libbase.so(V8_Dcheck(char const*, int, char const*)+0x4d) [0x7f64fec10dbd]
    /home/user/v8/v8/out/x64.debug/libv8.so(v8::internal::TaggedArrayBase<v8::internal::WeakFixedArray, v8::internal::WeakFixedArrayShape, v8::internal::HeapObjectLayout>::get(int) const+0x47) [0x7f650670c1b7]
    /home/user/v8/v8/out/x64.debug/libv8.so(void v8::internal::DeclarationScope::AllocateScopeInfos<v8::internal::Isolate>(v8::internal::ParseInfo*, v8::internal::DirectHandle<v8::internal::Script>, v8::internal::Isolate*)+0x2ec) [0x7f650670ab0c]
    /home/user/v8/v8/out/x64.debug/libv8.so(+0x7c0ac9b) [0x7f6506853c9b]
    /home/user/v8/v8/out/x64.debug/libv8.so(v8::internal::Compiler::Compile(v8::internal::Isolate*, v8::internal::Handle<v8::internal::SharedFunctionInfo>, v8::internal::Compiler::ClearExceptionFlag, v8::internal::IsCompiledScope*, v8::internal::CreateSourcePositions)+0x9f0) [0x7f65068537a0]
    /home/user/v8/v8/out/x64.debug/libv8.so(v8::internal::Compiler::Compile(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::JSFunction>, v8::internal::Compiler::ClearExceptionFlag, v8::internal::IsCompiledScope*)+0x4f0) [0x7f6506854730]
    /home/user/v8/v8/out/x64.debug/libv8.so(+0x8e5a176) [0x7f6507aa3176]
    /home/user/v8/v8/out/x64.debug/libv8.so(v8::internal::Runtime_CompileLazy(int, unsigned long*, v8::internal::Isolate*)+0x151) [0x7f6507aa2bb1]
    /home/user/v8/v8/out/x64.debug/libv8.so(+0x6fca53d) [0x7f6505c1353d]
Trace/breakpoint trap (core dumped)

VERSION

V8 version 13.9.0 (candidate)

REPRODUCTION CASE

Build: python3 tools/dev/gm.py x64.debug

Run: ./d8 poc.js


Reporter credit: Shaheen Fazim

View on issue tracker
Uploaded PoC files