CVE-2025-11215
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forsrc/runtime/runtime-regexp.cc |
modified |
Files Changed
src/runtime/runtime-regexp.cc
Patch
From 5666bca708c0a2f888b78e34281b00ed20356cb2 Mon Sep 17 00:00:00 2001
From: pthier <pthier@chromium.org>
Date: Thu, 21 Aug 2025 18:08:08 +0200
Subject: [PATCH] [regexp] Fix OOB read in RegExpMatchGlobalAtom_OneCharPattern
We were loading (just not accessing) a character potentially beyond
the allocated string subject buffer.
Fixed: 439758498
Change-Id: I250670122c765640602b20e6640d604c9d33be03
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6871496
Commit-Queue: Patrick Thier <pthier@chromium.org>
Commit-Queue: Igor Sheludko <ishell@chromium.org>
Auto-Submit: Patrick Thier <pthier@chromium.org>
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Cr-Commit-Position: refs/heads/main@{#101982}
---
diff --git a/src/runtime/runtime-regexp.cc b/src/runtime/runtime-regexp.cc
index dbc9ae1..e2fe84e 100644
--- a/src/runtime/runtime-regexp.cc
+++ b/src/runtime/runtime-regexp.cc
@@ -2236,7 +2236,8 @@
// last_match_block already contains the last match position, so use a special
// vector with lane 0 set to extract the last_match_index later.
const auto scalar_last_match_vec = hw::FirstN(tag, 1);
- for (SChar c = *block; block < end; c = *(++block)) {
+ for (; block < end; ++block) {
+ SChar c = *block;
if (c != static_cast<const SChar>(pattern)) continue;
matches++;
last_match_block = block;
Original Bug Report
V8: off-by-one error in RegExp matching leads to OOB read
Bug Details
There is an off-by-one error in the implementation of RegExp matching for the case of a single-character atom, specifically in the function RegExpMatchGlobalAtom_OneCharPattern [1]. This function is optimized for SIMD and processes multiple characters at the same time. In case the input string is not an exact multiple of the stride, a scalar loop processes the remaining characters at the end [2]:
for (SChar c = *block; block < end; c = *(++block)) {
...
}
Here, since the pointer is first incremented and then dereferenced, it will cause an out-of-bounds read in the last iteration. The below test case demonstrated this by causing a segmentation fault when reading past the end of a page. However, as the bug only leads to an out-of-bounds read, and the read value is never used, this bug should not have any security implications apart from the potential to cause a crash.
Affected Version(s)
The issue has been successfully reproduced:
- at HEAD (commit 625de227c339c41b4a947a03fb74a30eba0afe3f)
- in stable release 13.9.205.16 (b07b4e9376489c7f7c0ff2af5eceb4261b3bb784)
Reproduction
Test Case
const length = 4;
const pattern = /a/g;
for (let i = 0; i < 500000; i++) {
let subject = 'b'.repeat(length);
subject.match(pattern);
}
Build Instructions
Follow the instructions at https://v8.dev/docs/build. The crash was verified on a debug build:
gm.py x64.debug
Command
./out/x64.debug/d8 crash.js
ASan Report
AddressSanitizer:DEADLYSIGNAL
=================================================================
==754958==ERROR: AddressSanitizer: SEGV on unknown address 0x79fd002c0000 (pc 0x7f3e7493423f bp 0x7fff9cec6140 sp 0x7fff9cec5d00 T0)
==754958==The signal is caused by a READ memory access.
#0 0x7f3e7493423f in void v8::internal::(anonymous namespace)::RegExpMatchGlobalAtom_OneCharPattern<unsigned char, unsigned char>(v8::internal::Isolate*, v8::base::Vector<unsigned char const>, unsigned char, int, int*, int*, v8::internal::PerThreadAssertScope<false, (v8::internal::PerThreadAssertType)1, (v8::internal::PerThreadAssertType)2> const&) src/runtime/runtime-regexp.cc:2241:43
#1 0x7f3e74933595 in v8::internal::(anonymous namespace)::RegExpMatchGlobalAtom_Dispatch(v8::internal::Isolate*, v8::internal::String::FlatContent const&, v8::internal::String::FlatContent const&, bool, int, int*, int*, v8::internal::PerThreadAssertScope<false, (v8::internal::PerThreadAssertType)1, (v8::internal::PerThreadAssertType)2> const&) src/runtime/runtime-regexp.cc:2326:9
#2 0x7f3e7491f871 in v8::internal::__RT_impl_Runtime_RegExpMatchGlobalAtom(v8::internal::Arguments<(v8::internal::ArgumentsType)0>, v8::internal::Isolate*) src/runtime/runtime-regexp.cc:2404:5
#3 0x7f3e7491ea48 in v8::internal::Runtime_RegExpMatchGlobalAtom(int, unsigned long*, v8::internal::Isolate*) src/runtime/runtime-regexp.cc:2358:1
#4 0x7f3e710e5f7c in Builtins_CEntry_Return1_ArgvOnStack_NoBuiltinExit setup-isolate-deserialize.cc
#5 0x7f3e71541280 in Builtins_RegExpMatchFast setup-isolate-deserialize.cc
#6 0x7f3e7169f8b8 in Builtins_StringPrototypeMatch setup-isolate-deserialize.cc
#7 0x7f3ed0c00fee (<unknown module>)
#8 0x7f3e70c72966 in Builtins_JSEntryTrampoline setup-isolate-deserialize.cc
#9 0x7f3e70c726aa in Builtins_JSEntry setup-isolate-deserialize.cc
#10 0x7f3e727468d6 in v8::internal::GeneratedCode<unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, long, unsigned long**>::Call(unsigned long, unsigned long, unsigned long, unsigned long, long, unsigned long**) src/execution/simulator.h:212:12
#11 0x7f3e7273eeee in v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) src/execution/execution.cc:442:22
#12 0x7f3e7273fd96 in v8::internal::Execution::CallScript(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::JSFunction>, v8::internal::DirectHandle<v8::internal::Object>, v8::internal::DirectHandle<v8::internal::Object>) src/execution/execution.cc:542:10
#13 0x7f3e71aefb98 in v8::Script::Run(v8::Local<v8::Context>, v8::Local<v8::Data>) src/api/api.cc:1968:7
#14 0x7f3e71aef41d in v8::Script::Run(v8::Local<v8::Context>) src/api/api.cc:1932:10
#15 0x55c8d09def2c in v8::Shell::ExecuteString(v8::Isolate*, v8::Local<v8::String>, v8::Local<v8::String>, v8::Shell::ReportExceptions, v8::Global<v8::Value>*) src/d8/d8.cc:1033:44
#16 0x55c8d0a14f7b in v8::SourceGroup::Execute(v8::Isolate*) src/d8/d8.cc:5322:10
#17 0x55c8d0a20ec0 in v8::Shell::RunMainIsolate(v8::Isolate*, bool) src/d8/d8.cc:6274:37
#18 0x55c8d0a2075e in v8::Shell::RunMain(v8::Isolate*, bool) src/d8/d8.cc:6182:18
#19 0x55c8d0a23544 in v8::Shell::Main(int, char**) src/d8/d8.cc:7049:18
#20 0x55c8d0a23f81 in main src/d8/d8.cc:7141:43
#21 0x7f3e66143ca7 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
==754958==Register values:
rax = 0x000079fd002c0000 rbx = 0x00007fff9cec5d00 rcx = 0x0000000000000061 rdx = 0xf5f5f5f5f5f5f5f5
rdi = 0x00007b3e63d717e0 rsi = 0x00007fff9cec5bc0 rbp = 0x00007fff9cec6140 rsp = 0x00007fff9cec5d00
r8 = 0x00000ffff39d8b74 r9 = 0x00007fff9cec5baf r10 = 0x00000ffff39d8b75 r11 = 0x00001000739d0b70
r12 = 0x00007b3e053c0000 r13 = 0x00007e4e651e1080 r14 = 0x00000f67cc7ae2d0 r15 = 0xf3f30000f1f1f1f1
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV src/runtime/runtime-regexp.cc:2241:43 in void v8::internal::(anonymous namespace)::RegExpMatchGlobalAtom_OneCharPattern<unsigned char, unsigned char>(v8::internal::Isolate*, v8::base::Vector<unsigned char const>, unsigned char, int, int*, int*, v8::internal::PerThreadAssertScope<false, (v8::internal::PerThreadAssertType)1, (v8::internal::PerThreadAssertType)2> const&)
==754958==ABORTING
Reporter Credit
Google Big Sleep
Disclosure Policy
Our assessment concluded that this finding has NO security impact. Therefore, we are NOT applying a disclosure deadline to this report.
However, if your internal review indicates a security risk to your users, please email big-sleep-vuln-reports@google.com immediately to arrange a standard disclosure deadline.
For more information, visit https://goo.gle/bigsleep
- https://goo.gle/bigsleep
- https://source.chromium.org/chromium/chromium/src/+/main:v8/src/runtime/runtime-regexp.cc;l=2165;drc=efb8f67f80edf4fa39c5488d07b45b1e690c0c6f
- https://source.chromium.org/chromium/chromium/src/+/main:v8/src/runtime/runtime-regexp.cc;l=2241;drc=efb8f67f80edf4fa39c5488d07b45b1e690c0c6f
- https://v8.dev/docs/build