Chrome · V8 › Objects
CVE-2026-110140
Type Confusion in V8 (Objects)
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifsrc/objects/map.cc |
modified | |
TEST_Ftest/unittests/api/remote-object-unittest.cc |
modified |
Files Changed
src/objects/js-objects.ccsrc/objects/map.cctest/unittests/api/remote-object-unittest.cc
Patch
From 3d445698dfc94acd9027f59060404780554601b1 Mon Sep 17 00:00:00 2001
From: Igor Sheludko <ishell@chromium.org>
Date: Thu, 01 Oct 2026 20:07:11 +0200
Subject: [PATCH] [objects] Prevent map normalization of remote objects
When `Object.assign` encounters a source object with a large number of
properties, `JSReceiver::SetOrCopyDataProperties` pre-normalizes the
target object to a dictionary map.
If the target object is a remote object (a JS_SPECIAL_API_OBJECT_TYPE
whose map is contextless), it possesses no `NativeContext`.
`Map::Normalize` was previously unconditionally casting the `Null` native
context of the remote object's meta-map to a `NativeContext`, leading
to an out-of-bounds ReadOnly memory access and eventually OOB writes.
This patch updates `JSReceiver::SetOrCopyDataProperties` to exclude
contextless `JS_SPECIAL_API_OBJECT_TYPE` targets from normalization,
properly handling them alongside global proxies and shared structs,
which natively lack a context. It also replaces the potential silent
crash in `Map::Normalize` with a `DCHECK`.
Fixed: 562038980
TAG=agy
CONV=39463f19-fb84-40db-8b81-2d8d8d68ab36
Change-Id: I9116d3e144f44e7e569b609fb5c055a4640fbb57
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8502261
Reviewed-by: Patrick Thier <pthier@chromium.org>
Auto-Submit: Igor Sheludko <ishell@chromium.org>
Commit-Queue: Patrick Thier <pthier@chromium.org>
Cr-Commit-Position: refs/heads/main@{#110261}
---
diff --git a/src/objects/js-objects.cc b/src/objects/js-objects.cc
index ea767fef..2018421 100644
--- a/src/objects/js-objects.cc
+++ b/src/objects/js-objects.cc
@@ -478,6 +478,9 @@
InstanceType target_instance_type = target->map()->instance_type();
if (InstanceTypeChecker::IsJSObject(target_instance_type) &&
!InstanceTypeChecker::IsJSGlobalProxy(target_instance_type) &&
+ // Exclude remote objects (they don't have local properties anyway).
+ !(InstanceTypeChecker::IsJSSpecialApiObject(target_instance_type) &&
+ !target->GetCreationContext().has_value()) &&
!InstanceTypeChecker::IsAlwaysSharedSpaceJSObject(
target_instance_type)) {
// Convert to slow properties if we're guaranteed to overflow the number
diff --git a/src/objects/map.cc b/src/objects/map.cc
index 185c934..c45a674 100644
--- a/src/objects/map.cc
+++ b/src/objects/map.cc
@@ -1384,8 +1384,10 @@
}
DirectHandle<NormalizedMapCache> cache;
if (use_cache) {
+ Tagged<Object> maybe_native_context = meta_map->native_context_or_null();
+ DCHECK(!IsNull(maybe_native_context));
Tagged<Object> normalized_map_cache =
- meta_map->native_context()->normalized_map_cache();
+ Cast<NativeContext>(maybe_native_context)->normalized_map_cache();
use_cache = !IsUndefined(normalized_map_cache);
if (use_cache) {
cache = Cast<NormalizedMapCache>(
diff --git a/test/unittests/api/remote-object-unittest.cc b/test/unittests/api/remote-object-unittest.cc
index 7536274..7a54c98 100644
--- a/test/unittests/api/remote-object-unittest.cc
+++ b/test/unittests/api/remote-object-unittest.cc
@@ -101,5 +101,29 @@
EXPECT_STREQ("object", *result);
}
+TEST_F(RemoteObjectTest, NormalizeRemoteObject) {
+ Local<FunctionTemplate> constructor_template =
+ FunctionTemplate::New(isolate(), Constructor);
+ constructor_template->InstanceTemplate()->SetAccessCheckCallbackAndHandler(
+ AccessCheck, NamedPropertyHandlerConfiguration(NamedGetter),
+ IndexedPropertyHandlerConfiguration());
+
+ Local<Object> remote_object =
+ constructor_template->NewRemoteInstance().ToLocalChecked();
+
+ Local<Context> context = Context::New(isolate());
+ Context::Scope context_scope(context);
+
+ context->Global()
+ ->Set(context, String::NewFromUtf8Literal(isolate(), "remoteObj"),
+ remote_object)
+ .Check();
+
+ TryRunJS(
+ "const src = {};"
+ "for (let i = 0; i < 1200; i++) src['p' + i] = 1;"
+ "try { Object.assign(remoteObj, src); } catch (e) {}");
+}
+
} // namespace remote_object_unittest
} // namespace v8
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/test/unittests/api/remote-object-unittest.cc b/test/unittests/api/remote-object-unittest.cc
index 7536274..7a54c98 100644
--- a/test/unittests/api/remote-object-unittest.cc
+++ b/test/unittests/api/remote-object-unittest.cc
@@ -101,5 +101,29 @@
EXPECT_STREQ("object", *result);
}
+TEST_F(RemoteObjectTest, NormalizeRemoteObject) {
+ Local<FunctionTemplate> constructor_template =
+ FunctionTemplate::New(isolate(), Constructor);
+ constructor_template->InstanceTemplate()->SetAccessCheckCallbackAndHandler(
+ AccessCheck, NamedPropertyHandlerConfiguration(NamedGetter),
+ IndexedPropertyHandlerConfiguration());
+
+ Local<Object> remote_object =
+ constructor_template->NewRemoteInstance().ToLocalChecked();
+
+ Local<Context> context = Context::New(isolate());
+ Context::Scope context_scope(context);
+
+ context->Global()
+ ->Set(context, String::NewFromUtf8Literal(isolate(), "remoteObj"),
+ remote_object)
+ .Check();
+
+ TryRunJS(
+ "const src = {};"
+ "for (let i = 0; i < 1200; i++) src['p' + i] = 1;"
+ "try { Object.assign(remoteObj, src); } catch (e) {}");
+}
+
} // namespace remote_object_unittest
} // namespace v8
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page