Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds read in V8
DescriptionOut of bounds read in V8
ComponentV8
Bug ClassOOB
Tracker478659010
Fix commit134c3696cd53 (v8/v8) +1/-1
CISA KEVNot listed
Creditedqymag1c
Disclosed2026-03-10

Files Changed

  • src/builtins/builtins-regexp.cc
From 134c3696cd53593d7a668bf3cc88a06a1750c67d Mon Sep 17 00:00:00 2001
From: pthier <[email protected]>
Date: Tue, 27 Jan 2026 16:58:47 +0100
Subject: [PATCH] Fix RegExp.escape byte check

RegExp.escape wrongly used IsOneByteRepresentation() instead of
IsOneByteRepresentationUnderneath().

Fixed: 478659010
Bug: 353856236
Change-Id: Icbee422d6e76f423c447877956554156287b72d8
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7520539
Commit-Queue: Jakob Linke <[email protected]>
Auto-Submit: Patrick Thier <[email protected]>
Reviewed-by: Jakob Linke <[email protected]>
Cr-Commit-Position: refs/heads/main@{#104948}
---

diff --git a/src/builtins/builtins-regexp.cc b/src/builtins/builtins-regexp.cc
index fc2ddc1..8566a84 100644
--- a/src/builtins/builtins-regexp.cc
+++ b/src/builtins/builtins-regexp.cc
@@ -370,7 +370,7 @@
   // A copy of the input characters is needed because RegExpEscapeImpl builds up
   // the escaped string using IncrementalStringBuilder, which may allocate.
   str = String::Flatten(isolate, str);
-  if (str->IsOneByteRepresentation()) {
+  if (String::IsOneByteRepresentationUnderneath(*str)) {
     base::OwnedVector<const uint8_t> copy;
     {
       DisallowGarbageCollection no_gc;
Loading diff…

Original Bug Report

reported by [email protected]

RegExp.escape byteness mismatch OOB read

#VULNERABILITY DETAILS RegExp.escape determines whether to copy the input as one-byte or two-byte based on the string’s map (String::IsOneByteRepresentation). A crafted SlicedString can keep a two-byte map while its underlying storage becomes one-byte after internalization/thinning, causing RegExp.escape to read the string as UC16 from one-byte backing storage. This results in an out-of-bounds read.

#VERSION d8 Version: [14.6.0] + [candidate] Operating System: [Ubuntu 24.04 x64]

#Root cause RegExp.escape flattens the input, then branches on IsOneByteRepresentation():

- builtins-regexp.cc:352-388
  - String::Flatten(isolate, str)
  - if (str->IsOneByteRepresentation())
      copy = OwnedCopyOf(str->GetFlatContent().ToOneByteVector())
    else
      copy = OwnedCopyOf(str->GetFlatContent().ToUC16Vector())

IsOneByteRepresentation() is map-based (string shape), not content-based:

- string-inl.h:521-523
  String::IsOneByteRepresentation() -> InstanceTypeChecker::IsOneByteString(map)

When a non-internalized string is internalized, it can become a ThinString that points to a canonical internalized string whose map encoding is one-byte:

- string.cc:155-200
  String::MakeThin uses internalized->IsOneByteRepresentation() to select the
  thin map and rewrites the original to ThinString(actual = internalized)

For sliced strings, the slice keeps its own map (two-byte) even if the parent string becomes thin to a one-byte internalized canonical. This creates a byteness mismatch: map says two-byte, backing storage is one-byte.

#Crash State

=================================================================
==125374==ERROR: AddressSanitizer: use-after-poison on address 0x6e12c0005028 at pc 0x57ee1e60cffb bp 0x7ffcef6cb890 sp 0x7ffcef6cb050
READ of size 40 at 0x6e12c0005028 thread T0
    #0 0x57ee1e60cffa in __asan_memcpy (/home/qy/new2/v8/out/x64.asan/d8+0x13b8ffa) (BuildId: e276665bb2c58cdf)
    #1 0x57ee1eb00b1f in MemCopy src/base/memcopy.h
    #2 0x57ee1eb00b1f in Copy<const unsigned short *, unsigned short *> src/base/algorithm.h:38:3
    #3 0x57ee1eb00b1f in NewByCopying<unsigned short> src/base/vector.h:304:5
    #4 0x57ee1eb00b1f in OwnedCopyOf<unsigned short> src/base/vector.h:386:10
    #5 0x57ee1eb00b1f in OwnedCopyOf<v8::base::Vector<const unsigned short> > src/base/vector.h:395:10
    #6 0x57ee1eb00b1f in v8::internal::Builtin_Impl_RegExpEscape(v8::internal::BuiltinArguments, v8::internal::Isolate*) src/builtins/builtins-regexp.cc:385:14
    #7 0x57ee23415235 in Builtins_CEntry_Return1_ArgvOnStack_BuiltinExit setup-isolate-deserialize.cc
    #8 0x57ee23364829 in Builtins_InterpreterEntryTrampoline setup-isolate-deserialize.cc
    #9 0x57ee233615db in Builtins_JSEntryTrampoline setup-isolate-deserialize.cc
    #10 0x57ee2336132a in Builtins_JSEntry setup-isolate-deserialize.cc
    #11 0x57ee1ed1a126 in Call src/execution/simulator.h:216:12
    #12 0x57ee1ed1a126 in v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) src/execution/execution.cc:442:22
    #13 0x57ee1ed1b5a8 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
    #14 0x57ee1e936b4b in v8::Script::Run(v8::Local<v8::Context>, v8::Local<v8::Data>) src/api/api.cc:2015:7
    #15 0x57ee1e6780e7 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:1037:44
    #16 0x57ee1e6b0549 in v8::SourceGroup::Execute(v8::Isolate*) src/d8/d8.cc:5591:10
    #17 0x57ee1e6bc84d in v8::Shell::RunMainIsolate(v8::Isolate*, bool) src/d8/d8.cc:6599:37
    #18 0x57ee1e6bbc85 in v8::Shell::RunMain(v8::Isolate*, bool) src/d8/d8.cc:6507:18
    #19 0x57ee1e6bf327 in v8::Shell::Main(int, char**) src/d8/d8.cc:7404:18
    #20 0x736220e2a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #21 0x736220e2a28a in __libc_start_main csu/../csu/libc-start.c:360:3
    #22 0x57ee1e56d029 in _start (/home/qy/new2/v8/out/x64.asan/d8+0x1319029) (BuildId: e276665bb2c58cdf)

Address 0x6e12c0005028 is a wild pointer inside of access range of size 0x000000000028.
SUMMARY: AddressSanitizer: use-after-poison (/home/qy/new2/v8/out/x64.asan/d8+0x13b8ffa) (BuildId: e276665bb2c58cdf) in __asan_memcpy
Shadow bytes around the buggy address:
  0x6e12c0004d80: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7
  0x6e12c0004e00: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7
  0x6e12c0004e80: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7
  0x6e12c0004f00: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7
  0x6e12c0004f80: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7
=>0x6e12c0005000: 00 00 00 00 00[f7]f7 f7 f7 f7 f7 f7 f7 f7 f7 f7
  0x6e12c0005080: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7
  0x6e12c0005100: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7
  0x6e12c0005180: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7
  0x6e12c0005200: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7
  0x6e12c0005280: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb

NOTE: the stack trace above identifies the code that *accessed* the poisoned memory.
To identify the code that *poisoned* the memory, try the experimental setting ASAN_OPTIONS=poison_history_size=<size>.
==125374==ABORTING
Received signal 6

==== C stack trace ===============================

out/x64.asan/d8(__interceptor_backtrace+0x46)[0x57ee1e5b4b36]
out/x64.asan/d8(+0x63976c0)[0x57ee235eb6c0]
/lib/x86_64-linux-gnu/libc.so.6(+0x45330)[0x736220e45330]
/lib/x86_64-linux-gnu/libc.so.6(pthread_kill+0x11c)[0x736220e9eb2c]
/lib/x86_64-linux-gnu/libc.so.6(gsignal+0x1e)[0x736220e4527e]
/lib/x86_64-linux-gnu/libc.so.6(abort+0xdf)[0x736220e288ff]
out/x64.asan/d8(+0x13dbc5c)[0x57ee1e62fc5c]
out/x64.asan/d8(+0x13da44e)[0x57ee1e62e44e]
out/x64.asan/d8(+0x13c188b)[0x57ee1e61588b]
out/x64.asan/d8(+0x13c366d)[0x57ee1e61766d]
out/x64.asan/d8(__asan_memcpy+0x3ab)[0x57ee1e60d02b]
out/x64.asan/d8(+0x18acb20)[0x57ee1eb00b20]
out/x64.asan/d8(+0x61c1236)[0x57ee23415236]
[end of stack trace]
Aborted
View on issue tracker