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 <[email protected]> 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 <[email protected]> Auto-Submit: Igor Sheludko <[email protected]> Commit-Queue: Patrick Thier <[email protected]> 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