CVE-2026-43795
Overview
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 thecredPropsextension and itsrk(resident key) boolean.- CBOR map iteration
cbor::CBORValue::getMap()returns the underlying map;find(key)returns an iterator, andend()is the map’s own past-the-end sentinel. Dereferencingend()or comparing iterators from different maps is invalid.- `it`
- A single iterator variable reused for both the outer
decodedMapand the innercredPropsmap 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.
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
- 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. - Craft CBOR
Encode a client-outputs map containing
credPropsas a map that omits therkkey ({"credProps": {}}). - Mismatch
fromCBORsetsitto the inner map’sfind("rk"), which returns the inner map’send(), and theit != decodedMap.end()guard fails to catch it. - Invalid deref
The code evaluates
it->second.isBool()on a past-the-end iterator, reading memory beyond the valid map entries. - 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
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.cppTools/TestWebKitAPI/Tests/WebCore/CBORReaderTest.cpp
Audit Directions
- Reused iterators across containersSearch WebAuthn/CBOR decoders (and other
getMap().find(...)call sites) for a single iterator variable reused for nested maps and compared against the wrong container’send(). - Nested CBOR optional fieldsAudit other optional sub-map fields (extension outputs, attestation maps) for the same missing-key path that could dereference
end(). - Iterator lifetime on temporariesReview places that call
findon a map returned by value/reference fromgetMap()without binding the container, which can also produce dangling iterators.
Patch
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)
Regression Test / PoC
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)