Medium webkit OOB 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactProcessing maliciously crafted web content may lead to an unexpected Safari crash
DescriptionThe issue was addressed with improved memory handling.
ComponentWebCore
Bug ClassOOB
Tracker313452
Fix commitce11a67281da (webkit) +19/-3
CISA KEVNot listed
Creditedwwwlk
Disclosed2026-09-14

Background

WebAuthn client extension outputs
AuthenticationExtensionsClientOutputs::fromCBOR (Source/WebCore/Modules/webauthn/AuthenticationExtensionsClientOutputs.cpp) decodes a CBOR map returned by an authenticator into the structured client-extension outputs, including the credProps extension and its rk (resident key) boolean.
CBOR map iteration
cbor::CBORValue::getMap() returns the underlying map; find(key) returns an iterator, and end() is the map’s own past-the-end sentinel. Dereferencing end() or comparing iterators from different maps is invalid.
`it`
A single iterator variable reused for both the outer decodedMap and the inner credProps map in the buggy code.

Root Cause Analysis

When decoding the credProps extension, the code reused the same iterator it: after confirming credProps was a map, it did it = it->second.getMap().find(cbor::CBORValue("rk")); — reassigning it to an iterator into the inner credProps map. It then tested if (it != decodedMap.end() && it->second.isBool()), comparing an iterator belonging to the inner map against the outer decodedMap’s end(). Comparing iterators from two different containers is undefined, and functionally the inner map’s past-the-end iterator is not equal to the outer map’s end(). So when the credProps map had no rk key, find returned the inner map’s end(), the guard it != decodedMap.end() wrongly passed, and it->second.isBool() dereferenced a past-the-end iterator — an invalid/out-of-bounds read of map storage.

The patch stops aliasing: it takes const auto& credPropsMap = it->second.getMap();, uses a separate auto credPropsIt = credPropsMap.find(cbor::CBORValue("rk"));, and tests it against the correct container’s end — if (credPropsIt != credPropsMap.end() && credPropsIt->second.isBool()). Holding the map in a named reference also keeps it alive while iterating. The added CBORReaderTest.AuthExtensionsFromCBOR_CredPropsWithoutRk feeds {"credProps": {}} (a credProps map with no rk) and asserts the decode succeeds with credProps present and rk false, i.e. the missing-rk path no longer dereferences an invalid iterator.

Key insight
The decoder reused one iterator for two different maps and compared the inner credProps map’s iterator against the outer map’s end(), so a credProps map lacking rk passed the guard and dereferenced a past-the-end iterator — an invalid out-of-bounds read.

Attack Path

  1. Position Attacker controls (or influences, inferred) the CBOR that reaches AuthenticationExtensionsClientOutputs::fromCBOR, i.e. the authenticator response for a WebAuthn ceremony processed by Safari’s web content.
  2. Craft CBOR Encode a client-outputs map containing credProps as a map that omits the rk key ({"credProps": {}}).
  3. Mismatch fromCBOR sets it to the inner map’s find("rk"), which returns the inner map’s end(), and the it != decodedMap.end() guard fails to catch it.
  4. Invalid deref The code evaluates it->second.isBool() on a past-the-end iterator, reading memory beyond the valid map entries.
  5. Primitive Out-of-bounds/invalid read in the renderer; observed as a crash. Any read-back of the stray bytes (info disclosure) is inferred, not shown by the diff.

Impact Assessment

Runs in the renderer (Safari web content) process at ordinary privilege while parsing WebAuthn extension outputs. The diff substantiates an invalid-iterator / out-of-bounds read: an inner-map iterator is compared to the outer map’s end(), so a credProps map without rk dereferences a past-the-end iterator. The advisory describes an “unexpected Safari crash” with “improved memory handling”; the confirmed effect is an out-of-bounds read (crash), and any escalation to information disclosure is inferred, not proven by the diff. Reachability depends on the attacker’s ability to supply the decoded CBOR (authenticator response) — plausible for web content but the exact trust boundary is not shown by the diff.

Files Changed

  • Source/WebCore/Modules/webauthn/AuthenticationExtensionsClientOutputs.cpp
  • Tools/TestWebKitAPI/Tests/WebCore/CBORReaderTest.cpp

Audit Directions

  • Reused iterators across containers
    Search WebAuthn/CBOR decoders (and other getMap().find(...) call sites) for a single iterator variable reused for nested maps and compared against the wrong container’s end().
  • Nested CBOR optional fields
    Audit other optional sub-map fields (extension outputs, attestation maps) for the same missing-key path that could dereference end().
  • Iterator lifetime on temporaries
    Review places that call find on a map returned by value/reference from getMap() without binding the container, which can also produce dangling iterators.
diff --git a/Source/WebCore/Modules/webauthn/AuthenticationExtensionsClientOutputs.cpp b/Source/WebCore/Modules/webauthn/AuthenticationExtensionsClientOutputs.cpp
index 39563d3ba66d..897f6a34e6a9 100644
--- a/Source/WebCore/Modules/webauthn/AuthenticationExtensionsClientOutputs.cpp
+++ b/Source/WebCore/Modules/webauthn/AuthenticationExtensionsClientOutputs.cpp
@@ -49,9 +49,10 @@ std::optional<AuthenticationExtensionsClientOutputs> AuthenticationExtensionsCli
     it = decodedMap.find(cbor::CBORValue("credProps"));
     if (it != decodedMap.end() && it->second.isMap()) {
         CredentialPropertiesOutput credProps;
-        it = it->second.getMap().find(cbor::CBORValue("rk"));
-        if (it != decodedMap.end() && it->second.isBool())
-            credProps.rk = it->second.getBool();
+        const auto& credPropsMap = it->second.getMap();
+        auto credPropsIt = credPropsMap.find(cbor::CBORValue("rk"));
+        if (credPropsIt != credPropsMap.end() && credPropsIt->second.isBool())
+            credProps.rk = credPropsIt->second.getBool();
         clientOutputs.credProps = credProps;
     }
 
diff --git a/Tools/TestWebKitAPI/Tests/WebCore/CBORReaderTest.cpp b/Tools/TestWebKitAPI/Tests/WebCore/CBORReaderTest.cpp
index a8af4b1fa479..3b6c2d114d04 100644
--- a/Tools/TestWebKitAPI/Tests/WebCore/CBORReaderTest.cpp
+++ b/Tools/TestWebKitAPI/Tests/WebCore/CBORReaderTest.cpp
@@ -31,6 +31,7 @@
 
 #if ENABLE(WEB_AUTHN)
 
+#include <WebCore/AuthenticationExtensionsClientOutputs.h>
 #include <WebCore/CBORReader.h>
 #include <limits>
 #include <utility>
@@ -764,6 +765,20 @@ TEST(CBORReaderTest, TestUnsupportedSimplevalue)
     }
 }
 
+TEST(CBORReaderTest, AuthExtensionsFromCBOR_CredPropsWithoutRk)
+{
+    // CBOR encoding of {"credProps": {}} — credProps map present but no "rk" key.
+    // a1                          -- map(1)
+    //    69                       -- text(9)
+    //       63726564 50726f7073   -- "credProps"
+    //    a0                       -- map(0)
+    Vector<uint8_t> cborData { 0xa1, 0x69, 0x63, 0x72, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x70, 0x73, 0xa0 };
+    auto result = WebCore::AuthenticationExtensionsClientOutputs::fromCBOR(cborData);
+    ASSERT_TRUE(result.has_value());
+    ASSERT_TRUE(result->credProps.has_value());
+    EXPECT_FALSE(result->credProps->rk);
+}
+
 } // namespace TestWebKitAPI
 
 #endif // ENABLE(WEB_AUTHN)
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/Tools/TestWebKitAPI/Tests/WebCore/CBORReaderTest.cpp b/Tools/TestWebKitAPI/Tests/WebCore/CBORReaderTest.cpp
index a8af4b1fa479..3b6c2d114d04 100644
--- a/Tools/TestWebKitAPI/Tests/WebCore/CBORReaderTest.cpp
+++ b/Tools/TestWebKitAPI/Tests/WebCore/CBORReaderTest.cpp
@@ -31,6 +31,7 @@
 
 #if ENABLE(WEB_AUTHN)
 
+#include <WebCore/AuthenticationExtensionsClientOutputs.h>
 #include <WebCore/CBORReader.h>
 #include <limits>
 #include <utility>
@@ -764,6 +765,20 @@ TEST(CBORReaderTest, TestUnsupportedSimplevalue)
     }
 }
 
+TEST(CBORReaderTest, AuthExtensionsFromCBOR_CredPropsWithoutRk)
+{
+    // CBOR encoding of {"credProps": {}} — credProps map present but no "rk" key.
+    // a1                          -- map(1)
+    //    69                       -- text(9)
+    //       63726564 50726f7073   -- "credProps"
+    //    a0                       -- map(0)
+    Vector<uint8_t> cborData { 0xa1, 0x69, 0x63, 0x72, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x70, 0x73, 0xa0 };
+    auto result = WebCore::AuthenticationExtensionsClientOutputs::fromCBOR(cborData);
+    ASSERT_TRUE(result.has_value());
+    ASSERT_TRUE(result->credProps.has_value());
+    EXPECT_FALSE(result->credProps->rk);
+}
+
 } // namespace TestWebKitAPI
 
 #endif // ENABLE(WEB_AUTHN)
Loading diff…