Chrome · V8
CVE-2025-6191
Integer Overflow in V8
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifsrc/objects/js-regexp.cc |
modified |
Files Changed
src/objects/js-regexp.cc
Patch
From 62ee3244f3b212b92e22b8e2651afbed35de9768 Mon Sep 17 00:00:00 2001
From: pthier <pthier@chromium.org>
Date: Tue, 03 Jun 2025 09:16:02 +0200
Subject: [PATCH] [regexp] Use uint32_t over int in EscapeRegExpSource
While escaping the regexp source, a regular signed int might overflow,
while an uint32_t can always hold the maximum length for the escaped
source.
Fixed: 420697404
Change-Id: I61084b93d51a2729204332b4285922fd82ee8fc7
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6611425
Reviewed-by: Jakob Linke <jgruber@chromium.org>
Auto-Submit: Patrick Thier <pthier@chromium.org>
Commit-Queue: Patrick Thier <pthier@chromium.org>
Cr-Commit-Position: refs/heads/main@{#100636}
---
diff --git a/src/objects/js-regexp.cc b/src/objects/js-regexp.cc
index e365ac4..6049036 100644
--- a/src/objects/js-regexp.cc
+++ b/src/objects/js-regexp.cc
@@ -189,10 +189,14 @@
// WriteEscapedRegExpSource into a single function to deduplicate dispatch logic
// and move related code closer to each other.
template <typename Char>
-int CountAdditionalEscapeChars(DirectHandle<String> source,
- bool* needs_escapes_out) {
+uint32_t CountAdditionalEscapeChars(DirectHandle<String> source,
+ bool* needs_escapes_out) {
DisallowGarbageCollection no_gc;
- int escapes = 0;
+ uint32_t escapes = 0;
+ // The maximum growth-factor is 5 (for \u2028 and \u2029). Make sure that we
+ // won't overflow |escapes| given the current constraints on string length.
+ static_assert(uint64_t{String::kMaxLength} * 5 <
+ std::numeric_limits<decltype(escapes)>::max());
bool needs_escapes = false;
bool in_character_class = false;
base::Vector<const Char> src = source->GetCharVector<Char>(no_gc);
@@ -231,14 +235,14 @@
}
}
DCHECK(!in_character_class);
- DCHECK_GE(escapes, 0);
DCHECK_IMPLIES(escapes != 0, needs_escapes);
*needs_escapes_out = needs_escapes;
return escapes;
}
template <typename Char>
-void WriteStringToCharVector(base::Vector<Char> v, int* d, const char* string) {
+void WriteStringToCharVector(base::Vector<Char> v, uint32_t* d,
+ const char* string) {
int s = 0;
while (string[s] != '\0') v[(*d)++] = string[s++];
}
@@ -249,13 +253,13 @@
DisallowGarbageCollection no_gc;
base::Vector<const Char> src = source->GetCharVector<Char>(no_gc);
base::Vector<Char> dst(result->GetChars(no_gc), result->length());
- int s = 0;
- int d = 0;
+ uint32_t s = 0;
+ uint32_t d = 0;
bool in_character_class = false;
- while (s < src.length()) {
+ while (s < src.size()) {
const Char c = src[s];
if (c == '\\') {
- if (s + 1 < src.length() && IsLineTerminator(src[s + 1])) {
+ if (s + 1 < src.size() && IsLineTerminator(src[s + 1])) {
// This '\' is ignored since the next character itself will be escaped.
s++;
continue;
@@ -263,7 +267,7 @@
// Escape. Copy this and next character.
dst[d++] = src[s++];
}
- if (s == src.length()) break;
+ if (s == src.size()) break;
} else if (c == '/' && !in_character_class) {
// Not escaped forward-slash needs escape.
dst[d++] = '\\';
@@ -303,11 +307,13 @@
if (source->length() == 0) return isolate->factory()->query_colon_string();
bool one_byte = String::IsOneByteRepresentationUnderneath(*source);
bool needs_escapes = false;
- int additional_escape_chars =
+ uint32_t additional_escape_chars =
one_byte ? CountAdditionalEscapeChars<uint8_t>(source, &needs_escapes)
: CountAdditionalEscapeChars<base::uc16>(source, &needs_escapes);
if (!needs_escapes) return source;
- int length = source->length() + additional_escape_chars;
+ DCHECK_LE(static_cast<uint64_t>(source->length()) + additional_escape_chars,
+ std::numeric_limits<uint32_t>::max());
+ uint32_t length = source->length() + additional_escape_chars;
if (one_byte) {
DirectHandle<SeqOneByteString> result;
ASSIGN_RETURN_ON_EXCEPTION(isolate, result,
Loading diff…
Original Bug Report
reported by fa...@gmail.com
Debug check failed: escapes >= 0 (-2005397586 vs. 0)
VULNERABILITY DETAILS
#
# Fatal error in ..\..\src\objects\js-regexp.cc, line 234
# Debug check failed: escapes >= 0 (-2005397586 vs. 0).
#
#
#
#FailureMessage Object: 00000012179FD550
==== C stack trace ===============================
v8::base::debug::StackTrace::StackTrace [0x0x7fff2bb5b685+37] (D:\Browser\v8\v8\src\base\debug\stack_trace_win.cc:173)
v8::platform::`anonymous namespace'::PrintStackTrace [0x0x7fff2bcdab89+57] (D:\Browser\v8\v8\src\libplatform\default-platform.cc:28)
V8_Fatal [0x0x7fff2bb35463+323] (D:\Browser\v8\v8\src\base\logging.cc:214)
v8::base::`anonymous namespace'::DefaultDcheckHandler [0x0x7fff2bb34e8c+44] (D:\Browser\v8\v8\src\base\logging.cc:59)
V8_Dcheck [0x0x7fff2bb35571+81] (D:\Browser\v8\v8\src\base\logging.cc:228)
v8::internal::`anonymous namespace'::CountAdditionalEscapeChars<unsigned short> [0x0x7ffeaae0b824+740] (D:\Browser\v8\v8\src\objects\js-regexp.cc:234)
v8::internal::`anonymous namespace'::EscapeRegExpSource [0x0x7ffeaae08824+420] (D:\Browser\v8\v8\src\objects\js-regexp.cc:306)
v8::internal::JSRegExp::Initialize [0x0x7ffeaae07dfa+618] (D:\Browser\v8\v8\src\objects\js-regexp.cc:344)
v8::internal::JSRegExp::Initialize [0x0x7ffeaae08581+529] (D:\Browser\v8\v8\src\objects\js-regexp.cc:179)
v8::internal::__RT_impl_Runtime_RegExpInitializeAndCompile [0x0x7ffeab3fe615+549] (D:\Browser\v8\v8\src\runtime\runtime-regexp.cc:2147)
v8::internal::Runtime_RegExpInitializeAndCompile [0x0x7ffeab3fe10d+413] (D:\Browser\v8\v8\src\runtime\runtime-regexp.cc:2137)
Builtins_CEntry_Return1_ArgvOnStack_NoBuiltinExit [0x0x7ffeaf6b5b41+65]
Builtins_RegExpConstructor [0x0x7ffeaf5f40db+7579]
Builtins_InterpreterPushArgsThenFastConstructFunction [0x0x7ffeaf247501+1089]
Builtins_ConstructHandler [0x0x7ffeafe522c5+8261]
Builtins_InterpreterEntryTrampoline [0x0x7ffeaf2467f2+370]
Builtins_InterpreterEntryTrampoline [0x0x7ffeaf2467f2+370]
Builtins_JSEntryTrampoline [0x0x7ffeaf239ce7+103]
Builtins_JSEntry [0x0x7ffeaf23983f+255]
v8::internal::GeneratedCode<unsigned long long,unsigned long long,unsigned long long,unsigned long long,unsigned long long,long long,unsigned long long **>::Call [0x0x7ffea9f5438c+108] (D:\Browser\v8\v8\src\execution\simulator.h:212)
v8::internal::`anonymous namespace'::Invoke [0x0x7ffea9f503cd+5469] (D:\Browser\v8\v8\src\execution\execution.cc:441)
v8::internal::Execution::CallScript [0x0x7ffea9f50c61+513] (D:\Browser\v8\v8\src\execution\execution.cc:542)
v8::Script::Run [0x0x7ffea978b5cf+1135] (D:\Browser\v8\v8\src\api\api.cc:1964)
v8::Script::Run [0x0x7ffea978b148+120] (D:\Browser\v8\v8\src\api\api.cc:1929)
v8::Shell::ExecuteString [0x0x7ff6ae1d21b6+3142] (D:\Browser\v8\v8\src\d8\d8.cc:1030)
v8::SourceGroup::Execute [0x0x7ff6ae1f7e26+1174] (D:\Browser\v8\v8\src\d8\d8.cc:5073)
v8::Shell::RunMainIsolate [0x0x7ff6ae1ff287+631] (D:\Browser\v8\v8\src\d8\d8.cc:6027)
v8::Shell::RunMain [0x0x7ff6ae1feced+205] (D:\Browser\v8\v8\src\d8\d8.cc:5935)
v8::Shell::Main [0x0x7ff6ae201901+3873] (D:\Browser\v8\v8\src\d8\d8.cc:6801)
main [0x0x7ff6ae202333+35] (D:\Browser\v8\v8\src\d8\d8.cc:6893)
invoke_main [0x0x7ff6ae368489+57] (D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:79)
__scrt_common_main_seh [0x0x7ff6ae3685c2+306] (D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288)
__scrt_common_main [0x0x7ff6ae36864e+14] (D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:331)
mainCRTStartup [0x0x7ff6ae36866e+14] (D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_main.cpp:17)
BaseThreadInitThunk [0x0x7fff6adfe8d7+23]
RtlUserThreadStart [0x0x7fff6cafc5dc+44]
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
References
On This Page