High patch-now ~ plausible CVSS 8.8 chrome Type Confusion 🔧 Commit mapped

Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactType confusion in IndexedDB
DescriptionType confusion in IndexedDB
ComponentIndexedDB
Sub-componentIDB
Bug ClassType Confusion
Action tierpatch-now — corruption primitive, web-reachable in default config (plausible) — upheld by independent review by AI
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Independent review by AIPlausible
Reviewer notes: Commit frames this as spec-compliance (SAB is a valid key) plus atomic-read hardening against torn shared reads; a corruption-yielding type confusion is not demonstrated in the diff. Needs cross-origin isolation.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker558764482
Fix commit8984e9c2ced8 (chromium/src) +107/-2
CWECWE-843 (Type confusion)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedHoneyBee
Reported2026-09-08 (per vendor advisory)
Fix landed2026-09-15 (public git)
Disclosed2026-09-22
Patch gap7 days — fix public in source before the advisory

Background

`SharedArrayBuffer` (SAB)
a JavaScript buffer whose backing memory is shared and can be mutated concurrently by other threads while script runs.
`IDBKey`
the internal Blink representation of an IndexedDB key, including a binary variant built via IDBKey::CreateBinary from a copied Vector<char>.
Buffer source type
the spec category (ArrayBuffer, ArrayBufferView, and SharedArrayBuffer) that IndexedDB accepts as a valid key, distinct from the structured-serialization path used for values that bans SAB.
`AtomicReadMemcpy`
a WTF helper that copies from possibly-shared memory atomically, so a concurrent writer cannot tear the copy mid-read.

Root Cause Analysis

Verify before building a PoC

This analysis is consistent with the fixing commit but not fully provable from it: part of the vulnerable path lives in code the fix commit does not touch. Confirm the trigger and the sink against the source before relying on it for exploit development.
What the commit does not establish: Commit frames this as spec-compliance (SAB is a valid key) plus atomic-read hardening against torn shared reads; a corruption-yielding type confusion is not demonstrated in the diff. Needs cross-origin isolation.

The key-creation path in v8_binding_for_modules.cc converted a V8 value into an IDBKey by branching on IsArrayBuffer and IsArrayBufferView, but had no branch for IsSharedArrayBuffer and did not distinguish shared from non-shared views. For a shared ArrayBufferView, the code fell through to the non-shared logic and read the bytes via view->ByteSpan() (and checked view->buffer()->IsDetached()), treating shared-backed memory as if it were an ordinary detached-checkable DOMArrayBuffer — a type/state confusion between shared and non-shared buffer sources. Because the copy out of shared memory was a plain, non-atomic read, another thread could mutate the SharedArrayBuffer during conversion, and the mismatched view/buffer typing meant the wrong invariants (detachment, single-threaded ownership) were assumed.

The fix adds an explicit IsSharedArrayBuffer branch that materializes a DOMSharedArrayBuffer and copies its bytes with AtomicReadMemcpy over ByteSpanMaybeShared(), and adds an IsShared() check inside the view branch that copies via BaseAddressMaybeShared() with AtomicReadMemcpy before the detached-buffer logic. This works because each buffer-source category is now routed to the code that matches its true type, and the shared cases are read atomically into an owned Vector<char> snapshot.

Key insight
The single mistake was failing to recognize SharedArrayBuffer-backed inputs as their own buffer-source type, so shared memory was processed by the non-shared ArrayBufferView/ArrayBuffer path; the fix distinguishes them explicitly and snapshots the bytes with AtomicReadMemcpy.

Attack Path

  1. Cross-origin isolate the page Serve the document with COOP/COEP so crossOriginIsolated is true and SharedArrayBuffer construction is allowed in script.
  2. Use a SAB as an IndexedDB key Create a SharedArrayBuffer (or a TypedArray view over one) and pass it as the key to store.put(value, key), driving execution into the un-handled key-conversion path in CreateIDBKeyFromValue.
  3. Trigger the type/state confusion The pre-patch code routes the shared buffer through the non-shared IsArrayBufferView/IsArrayBuffer logic, reading ByteSpan() and applying IsDetached assumptions that do not hold for shared memory. (inferred)
  4. Race a concurrent mutation From a worker thread, mutate the same SharedArrayBuffer during the non-atomic copy so the key bytes are read while changing, defeating the intended immutable-key snapshot. (inferred)
  5. Reach the type-confusion sink The mismatched typing of shared vs non-shared buffer sources yields memory unsafety in the key-encoding path exploitable for further corruption. (inferred)

Impact Assessment

An attacker running script on a cross-origin-isolated page gains a type confusion in IndexedDB within the renderer process, with the precondition that SharedArrayBuffer is available (cross-origin isolation) and used as an IndexedDB key. Beyond the confusion itself, any escalation to a controlled read/write primitive, sandbox escape, or code execution is (inferred) and not demonstrated by the patch. The vendor advisory rates this high severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.8.

Files Changed

  • third_party/blink/renderer/bindings/modules/v8/v8_binding_for_modules.cc
  • third_party/blink/web_tests/external/wpt/IndexedDB/sharedarraybuffer-key.https.html.headers
  • third_party/blink/web_tests/external/wpt/IndexedDB/sharedarraybuffer-key.https.html

Audit Directions

  • Buffer-source branching
    Audit other V8-to-Blink conversions that switch on IsArrayBuffer/IsArrayBufferView but omit an IsSharedArrayBuffer/IsShared() case, since these silently misroute shared memory.
  • Non-atomic reads of shared memory
    Look for ByteSpan()/BaseAddress() reads that should use ByteSpanMaybeShared()/BaseAddressMaybeShared() with AtomicReadMemcpy to avoid tearing when a concurrent thread mutates a SharedArrayBuffer.
  • Detachment assumptions
    Review code that gates on IsDetached() or single-threaded ownership before copying buffer bytes, as those invariants do not hold for shared buffers and can mask a state confusion.
From 8984e9c2ced8db0a71c5a7e6469087b70ee9041b Mon Sep 17 00:00:00 2001
From: Evan Stade <[email protected]>
Date: Mon, 14 Sep 2026 18:36:19 -0700
Subject: [PATCH] IDB: correctly handle SharedArrayBuffer as key.

The spec says any "buffer source type" is a valid key, and that includes
SharedArrayBuffer. This is in contrast to values, which use the
structured serialization algorithm, which specifically bans SAB when
serializing for storage.

Bug: 558764482
Change-Id: I790db66baf596154c3139cc6e9621b90f37d19b0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8393497
Commit-Queue: Evan Stade <[email protected]>
Reviewed-by: Evan Stade <[email protected]>
Reviewed-by: Nate Chapin <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1697719}
---
diff --git a/third_party/blink/renderer/bindings/modules/v8/v8_binding_for_modules.cc b/third_party/blink/renderer/bindings/modules/v8/v8_binding_for_modules.cc
index f303c965..9496f60 100644
--- a/third_party/blink/renderer/bindings/modules/v8/v8_binding_for_modules.cc
+++ b/third_party/blink/renderer/bindings/modules/v8/v8_binding_for_modules.cc
@@ -47,6 +47,7 @@
 #include "third_party/blink/renderer/core/frame/web_feature.h"
 #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
 #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer_view.h"
+#include "third_party/blink/renderer/core/typed_arrays/dom_shared_array_buffer.h"
 #include "third_party/blink/renderer/modules/indexeddb/idb_any.h"
 #include "third_party/blink/renderer/modules/indexeddb/idb_cursor.h"
 #include "third_party/blink/renderer/modules/indexeddb/idb_cursor_with_value.h"
@@ -57,6 +58,7 @@
 #include "third_party/blink/renderer/modules/indexeddb/idb_value.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
+#include "third_party/blink/renderer/platform/wtf/atomic_operations.h"
 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
@@ -106,19 +108,49 @@
             Vector<char>(base::as_chars(buffer->ByteSpan()))));
   }
 
+  if (value->IsSharedArrayBuffer()) {
+    DOMSharedArrayBuffer* buffer =
+        NativeValueTraits<DOMSharedArrayBuffer>::NativeValue(isolate, value,
+                                                             exception_state);
+    if (exception_state.HadException()) {
+      return IDBKey::CreateInvalid();
+    }
+    base::span<uint8_t> shared_bytes = buffer->ByteSpanMaybeShared();
+    SCOPED_CRASH_KEY_NUMBER("CreateIDBKey", "ArrayBuffer", shared_bytes.size());
+    Vector<char> bytes(base::checked_cast<wtf_size_t>(shared_bytes.size()));
+    // SAFETY: `bytes` and `shared_bytes` have the same size.
+    UNSAFE_BUFFERS(AtomicReadMemcpy(bytes.data(), shared_bytes.data(),
+                                    shared_bytes.size()));
+    return IDBKey::CreateBinary(
+        base::MakeRefCounted<base::RefCountedData<Vector<char>>>(
+            std::move(bytes)));
+  }
+
   if (value->IsArrayBufferView()) {
     DOMArrayBufferView* view =
         NativeValueTraits<MaybeShared<DOMArrayBufferView>>::NativeValue(
             isolate, value, exception_state)
             .Get();
-    if (exception_state.HadException())
+    if (exception_state.HadException()) {
       return IDBKey::CreateInvalid();
+    }
+    SCOPED_CRASH_KEY_NUMBER("CreateIDBKey", "ArrayBuffer", view->byteLength());
+    if (view->IsShared()) {
+      Vector<char> bytes(base::checked_cast<wtf_size_t>(view->byteLength()));
+      // SAFETY: `byteLength()` returns the number of bytes at
+      // `BaseAddressMaybeShared()`.
+      UNSAFE_BUFFERS(AtomicReadMemcpy(
+          bytes.data(), view->BaseAddressMaybeShared(), bytes.size()));
+      return IDBKey::CreateBinary(
+          base::MakeRefCounted<base::RefCountedData<Vector<char>>>(
+              std::move(bytes)));
+    }
+
     if (view->buffer()->IsDetached()) {
       exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
                                         "The viewed ArrayBuffer is detached.");
       return IDBKey::CreateInvalid();
     }
-    SCOPED_CRASH_KEY_NUMBER("CreateIDBKey", "ArrayBuffer", view->byteLength());
     return IDBKey::CreateBinary(
         base::MakeRefCounted<base::RefCountedData<Vector<char>>>(
             Vector<char>(base::as_chars(view->ByteSpan()))));
diff --git a/third_party/blink/web_tests/external/wpt/IndexedDB/sharedarraybuffer-key.https.html b/third_party/blink/web_tests/external/wpt/IndexedDB/sharedarraybuffer-key.https.html
new file mode 100644
index 0000000..9de6a6f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/IndexedDB/sharedarraybuffer-key.https.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<title>IndexedDB: SharedArrayBuffer keys</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/support.js"></script>
+<script>
+'use strict';
+
+const key_data = [1, 2, 3, 4];
+
+function create_object_store(t, db) {
+  db.createObjectStore('store');
+}
+
+function create_shared_buffer() {
+  const buffer = new SharedArrayBuffer(key_data.length);
+  const view = new Uint8Array(buffer);
+  view.set(key_data);
+  return {buffer, view};
+}
+
+function put_and_validate_key(t, db, key, view_on_sab, key_copy) {
+  assert_true(
+      self.crossOriginIsolated, 'The page should be cross-origin isolated');
+
+  // Put the SAB-backed key in the store.
+  const tx = db.transaction('store', 'readwrite');
+  const store = tx.objectStore('store');
+  const put_request = store.put('value', key);
+  put_request.onerror = t.unreached_func('put should succeed');
+
+  // Modifying the SAB after put should have no effect.
+  view_on_sab.fill(0);
+
+  // Reading the record back out of the key should work.
+  const get_request = store.get(key_copy);
+  get_request.onerror = t.unreached_func('get should succeed');
+  get_request.onsuccess = t.step_func(() => {
+    assert_equals(get_request.result, 'value');
+  });
+
+  tx.oncomplete = t.step_func_done();
+}
+
+indexeddb_test(
+    create_object_store,
+    (t, db) => {
+      const {buffer, view} = create_shared_buffer();
+      put_and_validate_key(
+          t, db, buffer, view, new Uint8Array(key_data));
+    },
+    'A SharedArrayBuffer can be used as an IndexedDB key');
+
+indexeddb_test(
+    create_object_store,
+    (t, db) => {
+      const {view} = create_shared_buffer();
+      put_and_validate_key(
+          t, db, view, view, new Uint8Array(key_data));
+    },
+    'A SharedArrayBuffer-backed view can be used as an IndexedDB key');
+
+indexeddb_test(
+    create_object_store,
+    (t, db) => {
+      const {buffer, view} = create_shared_buffer();
+      put_and_validate_key(
+          t, db, [buffer], view, [new Uint8Array(key_data)]);
+    },
+    'A SharedArrayBuffer in an array can be used as an IndexedDB key');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/IndexedDB/sharedarraybuffer-key.https.html.headers b/third_party/blink/web_tests/external/wpt/IndexedDB/sharedarraybuffer-key.https.html.headers
new file mode 100644
index 0000000..5f8621ef8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/IndexedDB/sharedarraybuffer-key.https.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Opener-Policy: same-origin
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/web_tests/external/wpt/IndexedDB/sharedarraybuffer-key.https.html b/third_party/blink/web_tests/external/wpt/IndexedDB/sharedarraybuffer-key.https.html
new file mode 100644
index 0000000..9de6a6f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/IndexedDB/sharedarraybuffer-key.https.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<title>IndexedDB: SharedArrayBuffer keys</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/support.js"></script>
+<script>
+'use strict';
+
+const key_data = [1, 2, 3, 4];
+
+function create_object_store(t, db) {
+  db.createObjectStore('store');
+}
+
+function create_shared_buffer() {
+  const buffer = new SharedArrayBuffer(key_data.length);
+  const view = new Uint8Array(buffer);
+  view.set(key_data);
+  return {buffer, view};
+}
+
+function put_and_validate_key(t, db, key, view_on_sab, key_copy) {
+  assert_true(
+      self.crossOriginIsolated, 'The page should be cross-origin isolated');
+
+  // Put the SAB-backed key in the store.
+  const tx = db.transaction('store', 'readwrite');
+  const store = tx.objectStore('store');
+  const put_request = store.put('value', key);
+  put_request.onerror = t.unreached_func('put should succeed');
+
+  // Modifying the SAB after put should have no effect.
+  view_on_sab.fill(0);
+
+  // Reading the record back out of the key should work.
+  const get_request = store.get(key_copy);
+  get_request.onerror = t.unreached_func('get should succeed');
+  get_request.onsuccess = t.step_func(() => {
+    assert_equals(get_request.result, 'value');
+  });
+
+  tx.oncomplete = t.step_func_done();
+}
+
+indexeddb_test(
+    create_object_store,
+    (t, db) => {
+      const {buffer, view} = create_shared_buffer();
+      put_and_validate_key(
+          t, db, buffer, view, new Uint8Array(key_data));
+    },
+    'A SharedArrayBuffer can be used as an IndexedDB key');
+
+indexeddb_test(
+    create_object_store,
+    (t, db) => {
+      const {view} = create_shared_buffer();
+      put_and_validate_key(
+          t, db, view, view, new Uint8Array(key_data));
+    },
+    'A SharedArrayBuffer-backed view can be used as an IndexedDB key');
+
+indexeddb_test(
+    create_object_store,
+    (t, db) => {
+      const {buffer, view} = create_shared_buffer();
+      put_and_validate_key(
+          t, db, [buffer], view, [new Uint8Array(key_data)]);
+    },
+    'A SharedArrayBuffer in an array can be used as an IndexedDB key');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/IndexedDB/sharedarraybuffer-key.https.html.headers b/third_party/blink/web_tests/external/wpt/IndexedDB/sharedarraybuffer-key.https.html.headers
new file mode 100644
index 0000000..5f8621ef8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/IndexedDB/sharedarraybuffer-key.https.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Opener-Policy: same-origin
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.