CVE-2025-8010
Overview
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.
afora), used to distinguishargumentsfromarguments. - `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
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.
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
- Craft escaped-keyword source
Author strict-mode code that binds the
argumentskeyword using a Unicode escape, e.g."use strict"; function f(a){ var arguments = 0; }, as in the message test. - Trigger lazy preparse
Arrange for the enclosing function to be preparsed lazily so the vulnerable
PreParserIdentifierclassification runs and returnsDefaultforarguments, accepting the binding. - Trigger later full parse
When the function is fully parsed/compiled, the parser classifies
argumentsasarguments, disagreeing with the preparse result about scope and binding validity. - 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).
- 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
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.cctest/message/fail/preparse-arguments.jstest/message/fail/preparse-arguments.out
Audit Directions
- Escape-sensitive keyword checksHunt 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 divergenceSystematically diff preparser identifier and binding logic against the full
parser.ccequivalents; any predicate present in one but not the other is a candidate for scope/type confusion. - Strict-mode binding validationAudit strict-mode “Unexpected eval or arguments” enforcement across lazy and eager paths to ensure both reject the same set of (possibly escaped) identifiers.
Patch
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
Original Bug Report
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