LibWeb: Don’t crash on a detached publicExponent in generateKey

Problem: Crash when generating an RSA key — or serializing one —
whose publicExponent is a typed array whose backing ArrayBuffer has
been detached; for example, by calling transfer() on it.

Cause: Two places with big_integer_from_api_big_integer() reading the
bytes of the backing ArrayBuffer directly. But reading the bytes of a
detached buffer aborts.

Fix: Read the bytes with WebIDL get_buffer_source_copy() — which yields
an empty copy for a detached, or OOB resizable, buffer. The empty array
is already mapped to zero — so generation rejects the zero exponent with
an error, rather than crashing.

Fixes https://github.com/LadybirdBrowser/ladybird/issues/9991
This commit is contained in:
sideshowbarker 2026-06-21 16:45:32 +09:00 committed by Shannon Booth
parent 9183c2a029
commit 9ffd3e48c3
6 changed files with 52 additions and 3 deletions

View file

@ -91,8 +91,7 @@ static ::Crypto::UnsignedBigInteger big_integer_from_api_big_integer(GC::Ptr<JS:
// (that is, at most 7 leading zero bits, except the value 0 which shall have length 8 bits).
// The API SHALL accept values with any number of leading zero bits, including the empty array, which represents zero.
auto buffer = big_integer->viewed_array_buffer()->bytes();
auto buffer = MUST(WebIDL::get_buffer_source_copy(*big_integer));
if (!buffer.is_empty())
return ::Crypto::UnsignedBigInteger::import_data(buffer);
return ::Crypto::UnsignedBigInteger(0);

View file

@ -14,6 +14,7 @@
#include <LibWeb/Crypto/CryptoKey.h>
#include <LibWeb/Crypto/KeyAlgorithms.h>
#include <LibWeb/HTML/StructuredSerialize.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
namespace Web::Crypto {
@ -44,7 +45,7 @@ enum class KeyAlgorithmTag : u8 {
::Crypto::UnsignedBigInteger big_integer_from_api_big_integer(JS::Uint8Array const& big_integer)
{
auto buffer = big_integer.viewed_array_buffer()->bytes().slice(big_integer.byte_offset(), big_integer.byte_length().length());
auto buffer = MUST(WebIDL::get_buffer_source_copy(big_integer));
if (!buffer.is_empty())
return ::Crypto::UnsignedBigInteger::import_data(buffer);
return ::Crypto::UnsignedBigInteger(0);

View file

@ -0,0 +1 @@
rejected: OperationError

View file

@ -0,0 +1 @@
PASS (didn't crash): cloned

View file

@ -0,0 +1,25 @@
<!DOCTYPE html>
<script src="../include.js"></script>
<script>
asyncTest(async done => {
const exponentBuffer = new ArrayBuffer(3);
const publicExponent = new Uint8Array(exponentBuffer);
publicExponent.set([0x01, 0x00, 0x01]);
// transfer() detaches exponentBuffer, so publicExponent views a detached buffer (byteLength 0).
exponentBuffer.transfer();
try {
await crypto.subtle.generateKey({
name: "RSASSA-PKCS1-v1_5",
modulusLength: 1024,
publicExponent,
hash: "SHA-256",
}, true, ["sign", "verify"]);
println("resolved");
} catch (e) {
println("rejected: " + e.name);
}
done();
});
</script>

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<script src="../include.js"></script>
<script>
asyncTest(async done => {
const keyPair = await crypto.subtle.generateKey({
name: "RSASSA-PKCS1-v1_5",
modulusLength: 1024,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: "SHA-256",
}, true, ["sign", "verify"]);
// Detach the buffer backing the stored publicExponent, then serialize the key by cloning it.
keyPair.publicKey.algorithm.publicExponent.buffer.transfer();
try {
structuredClone(keyPair.publicKey);
println("PASS (didn't crash): cloned");
} catch (e) {
println("PASS (didn't crash): " + e.name);
}
done();
});
</script>