[9] SubtleCrypto RSA import OOB read from missing ASN.1 bounds check
Severity: Medium | Component: WebCore SubtleCrypto | bbec6de
Rated Medium because the observable effect is a reliable renderer crash (deterministic SIGTRAP in hardened builds) reachable from any web page via crypto.subtle.importKey(), with no commit-backed evidence of information disclosure — escalation to an OOB heap read in non-hardened builds is projected but the read data flows only into CCRSACryptorImport which rejects it, limiting info-leak potential.
CryptoKeyRSA::importSpki() and CryptoKeyRSA::importPkcs8() now check keyData.size() < headerSize after the final ASN.1 header size computation, before calling keyData.subspan(headerSize).
Source/WebCore/crypto/cocoa/CryptoKeyRSAMac.cpp
if (keyData.size() < headerSize + 1)
return nullptr;
headerSize += bytesUsedToEncodedLength(keyData[headerSize]) + sizeof(InitialOctet);
+ if (keyData.size() < headerSize)
+ return nullptr;
CCRSACryptorRef ccPublicKey = nullptr;
auto dataAfterHeader = keyData.subspan(headerSize);
if (keyData.size() < headerSize + 1)
return nullptr;
headerSize += bytesUsedToEncodedLength(keyData[headerSize]);
+ if (keyData.size() < headerSize)
+ return nullptr;
CCRSACryptorRef ccPrivateKey = nullptr;
auto dataAfterHeader = keyData.subspan(headerSize);
LayoutTests/crypto/subtle/rsa-import-pkcs8-truncated-key.html
+ // headerSize computation:
+ // 5. headerSize = 21 + 3 = 24
+ // 6. subspan(24) on 22-byte buffer -> CRASH (before fix)
+ var truncatedPkcs8Key = hexStringToUint8Array("30000000000000000000000000000000000000000082");
+ shouldReject('crypto.subtle.importKey("pkcs8", truncatedPkcs8Key, {name: "RSA-OAEP", hash: "sha-256"}, extractable, ["decrypt"])');
Patch Details
Two identical one-line additions in importSpki() and importPkcs8(). Each adds if (keyData.size() < headerSize) return nullptr; after the final headerSize += bytesUsedToEncodedLength(...) computation and before the keyData.subspan(headerSize) call. The existing intermediate bounds checks on headerSize remain unchanged.
Background
ASN.1 uses a tag-length-value (TLV) encoding. Length fields can be "short form" (single byte ≤ 127) or "long form" (first byte = 0x80 + N, followed by N bytes encoding the actual length). bytesUsedToEncodedLength() returns the total number of bytes consumed by the length field — for long-form, this is (firstByte - 128) + 1. WebKit's RSA key import functions manually strip the ASN.1 header by incrementally computing a headerSize offset, then slice the input buffer at that offset using std::span::subspan().
In hardened C++ standard library builds, subspan() with an out-of-range offset triggers a deterministic trap (EXC_BREAKPOINT / SIGTRAP). In non-hardened builds, subspan() with an out-of-range offset silently creates a span starting past the buffer end.
Analysis
Missing final bounds check after incremental ASN.1 header size computation, before span slicing.
Both importSpki() and importPkcs8() performed intermediate bounds checks on headerSize as it was built up from ASN.1 length fields, but the final increment — adding bytesUsedToEncodedLength(keyData[headerSize]) — had no subsequent check. A crafted key with a large length-of-length indicator at the final position (e.g., 0x82 means 2 additional length bytes, so the function returns 3) could push headerSize past keyData.size(). The test case demonstrates this directly: a 22-byte PKCS#8 key where the final 0x82 byte causes headerSize to reach 24, and subspan(24) on a 22-byte buffer crashes.
Aaa Aaa Aaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaaaaaaaa Aaaa Aaa Aaa Aaaa Aaa Aaaaaaaaaaa Aa Aaaaaaaa Aaa Aaaaa a Aaaaaaaaa Aaa Aaa Aaaa Aaaa Aaaaaaa Aaa Aaaaaaaaaa Aaaaaaa Aaaaaaaa Aaaa a Aaaaaa Aaaaaaaaaaaaa Aaaaa Aa Aaaaaaaa Aaaaaa Aaa Aaaaa Aa Aaaaaaaaaaaaaa Aa Aaaaaaaaaaaa Aaaaaaa Aaa Aaa Aaaaaaa Aaaaa Aaaa Aaaa Aaa Aaaaaa Aaaa Aaaaaaaa Aaaa Aaaaaaa Aaaaaa Aaa Aaaa Aaaaa Aaaa Aa Aaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaa Aaaaa Aaaaa Aaaaaa Aa a Aaaaaaaa Aaaaaaaaaaaaaaaa Aaaaaaaaaa
Aaaa Aaaaaaaaaaaaa Aaaaaaa Aaaaaaaa Aaaaaaa Aaaaaaaaaaaaa Aaa Aaa Aaaa Aaaaaa Aa Aaaaaaa Aa Aaa Aaaaaaaaaaaaaaaa Aaaaaaaa Aaaa Aaaa Aaaaa Aaaa Aaa Aaaaaaaa Aaaaaaaa Aaa Aaaa Aaaaaa Aaaaaaaaaa Aaaaaa Aaa Aa Aaaaaaaa Aaaaa Aaaa Aaa Aaaa Aaaa Aaaaa Aaaa Aaaaaaaaaaaaaa Aaaaaa Aaaaaaaa Aaaaaa Aaaa Aaaa Aa Aaa Aaaaaaaaa
Aaaa Aa a Aaaaaaa Aaaaaaa Aa Aaaaaaaaaaa Aaaaa Aaaaaaaa Aaaaaaaaaaaa Aaaaaa Aaaaaa Aaaaa Aaa Aaa Aaaaa Aaaaaaa Aaaaaaaaaaaaaaa Aaaaaaaa Aa Aaa Aaaaaaaaaaaa Aaaaaaa Aaa Aaaaaa Aaaaa
🔒Explores the exploitability boundaries of this OOB access and what limits escalation beyond a crash
Subscribe to read more
Audit directions
a Aaaaaaaa Aaaaaaaaaaa Aaaaaaaaaaa Aaaaaaaaaaaa Aa Aaaaaa Aaaaaaaaa Aaaaaaa Aaaaa Aaaa Aaaaaaa Aaaaaa Aa Aaaaa Aa a Aaaaaaa Aaaaaaa Aaa Aaa Aaaaa Aaaaaaaaaaa Aaaaaa Aa Aaa Aaaaaaaaaaaaaa Aaaaaa Aaaa Aaaaa Aaa Aaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaaaaaa Aaaaaa Aaaa a Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaa a Aaa Aaaaaa Aaaa Aaaaa Aaa Aa Aaaaaaaa Aa a Aaaaaa Aaaaa Aaaaaa Aaa Aaaaaa Aa Aaaa Aaa Aaaa Aaaaaaa Aa Aaaaaaa Aaaaaaaaaaa
a Aaaaaaaa Aaaaaaaaaaaaaaaaaaaaaa Aaaaaa Aaaa a Aaaaaaaa Aaaaaa Aaaaaaa Aaaa Aaaaaaaaa Aaaaaa Aaaa Aaa Aaaaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaa Aaaaa Aaaaa Aaaaa Aaa Aaaaaa Aa Aaaaaaa Aaaa Aaaaaa Aaaaaa Aaaa Aaaaaaa Aaaaa Aaa Aaaaaaaaaaa Aaaaaaa a Aaaaaaaaa Aaaaaa Aaaaaa
a Aaaaa Aaaaa Aaa Aaaaaa Aaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaa Aaaa Aaaaaaaa Aa Aaa Aaa Aaa Aaaaaaa Aaaa Aaaaa Aaaaa Aaaaaaa Aaaaaaaa a Aaaaa Aaaaaaa Aaaaa Aaaaaaaaaaaa Aaaaaaaaaaaa Aaaa Aaaaaaaa Aaaaaaaa Aaaaaa Aaaaaaa
🔒Multiple audit patterns identified for ASN.1 parsing across WebKit's crypto subsystem, with concrete search targets
Subscribe to read more