Chrome · V8
CVE-2025-2137
OOB in V8
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifsrc/extensions/externalize-string-extension.cc |
modified |
Files Changed
src/extensions/externalize-string-extension.cc
Patch
From 812f91c5821259311a8d321b23654e7f74284a52 Mon Sep 17 00:00:00 2001 From: pthier <[email protected]> Date: Thu, 27 Feb 2025 12:40:24 +0100 Subject: [PATCH] [test] Add createExternalizableTwoByteString to extension Allow to explicitly create an external 2-byte string for 1-byte only contents via the externalize-string extension. Bug: 398999390 Change-Id: I1fe11186c686a31e1d68d662af156709967f499f Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6304942 Commit-Queue: Patrick Thier <[email protected]> Reviewed-by: Leszek Swirski <[email protected]> Auto-Submit: Patrick Thier <[email protected]> Commit-Queue: Leszek Swirski <[email protected]> Cr-Commit-Position: refs/heads/main@{#98969} --- diff --git a/src/extensions/externalize-string-extension.cc b/src/extensions/externalize-string-extension.cc index a5b450a..bfbd58f 100644 --- a/src/extensions/externalize-string-extension.cc +++ b/src/extensions/externalize-string-extension.cc @@ -54,6 +54,7 @@ base::SNPrintF(base::VectorOf(buf, size), "native function externalizeString();" "native function createExternalizableString();" + "native function createExternalizableTwoByteString();" "native function isOneByteString();" "let kExternalStringMinOneByteLength = %d;" "let kExternalStringMinTwoByteLength = %d;" @@ -73,6 +74,10 @@ "createExternalizableString") == 0) { return v8::FunctionTemplate::New( isolate, ExternalizeStringExtension::CreateExternalizableString); + } else if (strcmp(*v8::String::Utf8Value(isolate, str), + "createExternalizableTwoByteString") == 0) { + return v8::FunctionTemplate::New( + isolate, ExternalizeStringExtension::CreateExternalizableTwoByteString); } else { DCHECK_EQ(strcmp(*v8::String::Utf8Value(isolate, str), "isOneByteString"), 0); @@ -151,6 +156,80 @@ AllocationType::kOld); } +MaybeDirectHandle<String> CreateExternalizableString( + v8::Isolate* isolate, DirectHandle<String> string, + v8::String::Encoding encoding) { + Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate); + DCHECK_IMPLIES(encoding == v8::String::Encoding::ONE_BYTE_ENCODING, + string->IsOneByteRepresentation()); + if (string->SupportsExternalization(encoding)) { + return string; + } + // Return the string if it is already externalized. + if (StringShape(*string).IsExternal()) { + return string; + } + + // Read-only strings are never externalizable. Don't try to copy them as + // some parts of the code might rely on some strings being in RO space (i.e. + // empty string). + if (HeapLayout::InReadOnlySpace(*string)) { + isolate->ThrowError("Read-only strings cannot be externalized."); + return kNullMaybeHandle; + } +#ifdef V8_COMPRESS_POINTERS + // Small strings may not be in-place externalizable. + if (string->Size() < static_cast<int>(sizeof(UncachedExternalString))) { + isolate->ThrowError("String is too short to be externalized."); + return kNullMaybeHandle; + } +#endif + + // Special handling for ConsStrings, as the ConsString -> ExternalString + // migration is special for GC (Tagged pointers to Untagged pointers). + // Skip if the ConsString is flat, as we won't be guaranteed a string in old + // space in that case. Note that this is also true for non-canonicalized + // ConsStrings that TurboFan might create (the first part is empty), so we + // explicitly check for that case as well. + if (IsConsString(*string, i_isolate) && !string->IsFlat() && + Cast<ConsString>(string)->first()->length() != 0) { + DirectHandle<String> result; + if (CopyConsStringToOld(i_isolate, Cast<ConsString>(string)) + .ToHandle(&result)) { + DCHECK(result->SupportsExternalization(encoding)); + return result; + } + } + // All other strings can be implicitly flattened. + if (encoding == v8::String::ONE_BYTE_ENCODING) { + MaybeDirectHandle<SeqOneByteString> maybe_result = + i_isolate->factory()->NewRawOneByteString(string->length(), + AllocationType::kOld); + DirectHandle<SeqOneByteString> result; + if (maybe_result.ToHandle(&result)) { + DisallowGarbageCollection no_gc; + String::WriteToFlat(*string, result->GetChars(no_gc), 0, + string->length()); + DCHECK(result->SupportsExternalization(encoding)); + return result; + } + } else { + MaybeDirectHandle<SeqTwoByteString> maybe_result = + i_isolate->factory()->NewRawTwoByteString(string->length(), + AllocationType::kOld); + DirectHandle<SeqTwoByteString> result; + if (maybe_result.ToHandle(&result)) { + DisallowGarbageCollection no_gc; + String::WriteToFlat(*string, result->GetChars(no_gc), 0, + string->length()); + DCHECK(result->SupportsExternalization(encoding)); + return result; + } + } + isolate->ThrowError("Unable to create string"); + return kNullMaybeHandle; +} + } // namespace void ExternalizeStringExtension::CreateExternalizableString( @@ -161,82 +240,43 @@ "First parameter to createExternalizableString() must be a string."); return; } + v8::Isolate* isolate = info.GetIsolate(); DirectHandle<String> string = Utils::OpenDirectHandle(*info[0].As<v8::String>()); - Isolate* isolate = reinterpret_cast<Isolate*>(info.GetIsolate()); v8::String::Encoding encoding = string->IsOneByteRepresentation() ? v8::String::Encoding::ONE_BYTE_ENCODING : v8::String::Encoding::TWO_BYTE_ENCODING; - if (string->SupportsExternalization(encoding)) { - info.GetReturnValue().Set(Utils::ToLocal(string)); - return; - } - // Return the string if it is already externalized. - if (StringShape(*string).IsExternal()) { - info.GetReturnValue().Set(Utils::ToLocal(string)); - return; - } - - // Read-only strings are never externalizable. Don't try to copy them as - // some parts of the code might rely on some strings being in RO space (i.e. - // empty string). - if (HeapLayout::InReadOnlySpace(*string)) { - info.GetIsolate()->ThrowError("Read-only strings cannot be externalized."); - return; - } -#ifdef V8_COMPRESS_POINTERS - // Small strings may not be in-place externalizable. - if (string->Size() < static_cast<int>(sizeof(UncachedExternalString))) { - info.GetIsolate()->ThrowError("String is too short to be externalized."); - return; - } -#endif - - // Special handling for ConsStrings, as the ConsString -> ExternalString - // migration is special for GC (Tagged pointers to Untagged pointers). - // Skip if the ConsString is flat, as we won't be guaranteed a string in old - // space in that case. Note that this is also true for non-canonicalized - // ConsStrings that TurboFan might create (the first part is empty), so we - // explicitly check for that case as well. - if (IsConsString(*string, isolate) && !string->IsFlat() && - Cast<ConsString>(string)->first()->length() != 0) { - DirectHandle<String> result; - if (CopyConsStringToOld(isolate, Cast<ConsString>(string)) - .ToHandle(&result)) { - DCHECK(result->SupportsExternalization(encoding)); - info.GetReturnValue().Set(Utils::ToLocal(result)); - return; - } - } - // All other strings can be implicitly flattened. - if (encoding == v8::String::ONE_BYTE_ENCODING) { - MaybeDirectHandle<SeqOneByteString> maybe_result = - isolate->factory()->NewRawOneByteString(string->length(), - AllocationType::kOld); - DirectHandle<SeqOneByteString> result; - if (maybe_result.ToHandle(&result)) { - DisallowGarbageCollection no_gc; - String::WriteToFlat(*string, result->GetChars(no_gc), 0, - string->length()); - DCHECK(result->SupportsExternalization(encoding)); - info.GetReturnValue().Set(Utils::ToLocal(Cast<String>(result))); - return; - } + MaybeDirectHandle<String> maybe_result = + i::CreateExternalizableString(isolate, string, encoding); + DirectHandle<String> result; + if (maybe_result.ToHandle(&result)) { + DCHECK(!isolate->HasPendingException()); + info.GetReturnValue().Set(Utils::ToLocal(result)); } else { - MaybeDirectHandle<SeqTwoByteString> maybe_result = - isolate->factory()->NewRawTwoByteString(string->length(), - AllocationType::kOld); - DirectHandle<SeqTwoByteString> result; - if (maybe_result.ToHandle(&result)) {
Loading diff…
Original Bug Report
reported by [email protected]
OOB read in JsonStringifier::SerializeString
VULNERABILITY DETAILS
After updating the parent of a SlicedString, its map object may become outdated, leading to Out-of-Band memory reads during JSON serialization.
VERSION
V8 commit: be6d95e1118064c533c56e0547627374bd2bd434
REPRODUCTION CASE
Build args:
is_component_build = false
is_debug = false
target_cpu = "arm64"
v8_target_cpu = "arm64"
v8_enable_sandbox = true
v8_enable_backtrace = true
v8_enable_disassembler = true
v8_enable_object_print = true
v8_enable_verify_heap = true
dcheck_always_on = false
Shell args:
./out/arm64.release/d8 --allow-natives-syntax bug.js
Shell output:
expect: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
found: "扢扢扢扢扢扢扢扢扢扢扢扢扢扢啢\u0001切Β\u0000猀牴唀\u0001昀Ἤߺ\u0000猀"
CREDIT INFORMATION Reporter credit: [email protected]
References
On This Page