diff --git a/AK/Random.cpp b/AK/Random.cpp index 7525377591..59be8d5c11 100644 --- a/AK/Random.cpp +++ b/AK/Random.cpp @@ -1,68 +1,61 @@ /* * Copyright (c) 2021, the SerenityOS developers. + * Copyright (c) 2024, the Ladybird developers. + * Copyright (c) 2025-2026, Colleirose * * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include #include -#if defined(AK_OS_WINDOWS) -# include -# include -# include -# include +#if defined(AK_OS_LINUX) +# include #endif +#if defined(AK_OS_WINDOWS) +# include +#endif + +static inline ErrorOr csprng(void* const buf, size_t size) +{ + // We shouldn't use OpenSSL's RAND_bytes function here, because we want to avoid adding dependencies to AK. + // Therefore, we will use the best platform-specific CSPRNG. +#if defined(AK_OS_SERENITY) || defined(AK_OS_ANDROID) || defined(AK_OS_BSD_GENERIC) || defined(AK_OS_HAIKU) || AK_LIBC_GLIBC_PREREQ(2, 36) + // This target also covers MacOS and iOS and they both seem to support arc4random_buf + arc4random_buf(buf, size); +#elif defined(AK_OS_LINUX) + unsigned char* out = (unsigned char*)buf; + while (size > 0u) { + // EINTR can be handled safely by just trying again. Others are fatal + // See manual for more details + int ret = getrandom(out, size, 0); + if (ret == -1 && errno != EINTR) [[unlikely]] + return Error::from_errno(errno); + + // If ret > 0 then ret indicates how much was copied + if (ret > 0) [[likely]] { + size -= ret; + out += ret; + } + } +#elif defined(AK_OS_WINDOWS) + // Documented to always return TRUE + g_system.ProcessPrng((PBYTE)buf, size); +#else + static_assert(false, "This build target doesn't have a valid CSPRNG interface specified in AK/Random.cpp."); +#endif + return {}; +} + namespace AK { -// NOTE: This function is supposed to always give a random number. If possible it is of good quality, but it can fall -// back to rand() if it fails on some systems. For high speed you should probably use a different generator. -// See MathObject::random() from LibJS. Where cryptographic security is needed use LibCrypto/SecureRandom.h. -void fill_with_random([[maybe_unused]] Bytes bytes) +void fill_with_random(Bytes bytes) { -#if defined(AK_OS_SERENITY) || defined(AK_OS_ANDROID) || defined(AK_OS_BSD_GENERIC) || defined(AK_OS_HAIKU) || AK_LIBC_GLIBC_PREREQ(2, 36) - arc4random_buf(bytes.data(), bytes.size()); -#elif defined(OSS_FUZZ) -#else - auto fill_with_random_fallback = [&]() { - for (auto& byte : bytes) - byte = rand(); - }; - -# if defined(__unix__) - // The maximum permitted value for the getentropy length argument. - static constexpr size_t getentropy_length_limit = 256; - auto iterations = bytes.size() / getentropy_length_limit; - - for (size_t i = 0; i < iterations; ++i) { - if (getentropy(bytes.data(), getentropy_length_limit) != 0) { - fill_with_random_fallback(); - return; - } - - bytes = bytes.slice(getentropy_length_limit); - } - - if (bytes.is_empty() || getentropy(bytes.data(), bytes.size()) == 0) - return; -# elif defined(AK_OS_WINDOWS) - - if (bytes.size() > NumericLimits::max()) [[unlikely]] { - fill_with_random_fallback(); - return; - } - - // NOTE: This is more secure than needed. But on modern hardware it be should more than fast enough. - NTSTATUS result = ::BCryptGenRandom(NULL, bytes.data(), bytes.size(), BCRYPT_USE_SYSTEM_PREFERRED_RNG); - if (result == STATUS_SUCCESS) - return; -# endif - - fill_with_random_fallback(); -#endif + MUST(csprng(bytes.data(), bytes.size())); } u32 get_random_uniform(u32 max_bounds) diff --git a/AK/Windows.h b/AK/Windows.h index ec7ac0b04d..9a0b1017c8 100644 --- a/AK/Windows.h +++ b/AK/Windows.h @@ -44,28 +44,37 @@ NTAPI NTSTATUS NtCreateWaitCompletionPacket( _Out_ PHANDLE WaitCompletionPacketHandle, _In_ ACCESS_MASK DesiredAccess, _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes); + +// https://learn.microsoft.com/en-us/windows/win32/seccng/processprng +BOOL WINAPI ProcessPrng(PBYTE pbData, SIZE_T cbData); } using PFN_NtCreateWaitCompletionPacket = decltype(&NtCreateWaitCompletionPacket); using PFN_NtCancelWaitCompletionPacket = decltype(&NtCancelWaitCompletionPacket); using PFN_NtAssociateWaitCompletionPacket = decltype(&NtAssociateWaitCompletionPacket); +using PFN_ProcessPrng = decltype(&ProcessPrng); inline struct SystemApi { PFN_NtAssociateWaitCompletionPacket NtAssociateWaitCompletionPacket = NULL; PFN_NtCancelWaitCompletionPacket NtCancelWaitCompletionPacket = NULL; PFN_NtCreateWaitCompletionPacket NtCreateWaitCompletionPacket = NULL; + PFN_ProcessPrng ProcessPrng = NULL; SystemApi() { + HMODULE hBcryptprimitives = LoadLibraryW(L"bcryptprimitives.dll"); HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll"); + VERIFY(hBcryptprimitives); VERIFY(hNtdll); AK_IGNORE_DIAGNOSTIC("-Wcast-function-type-mismatch", NtAssociateWaitCompletionPacket = (PFN_NtAssociateWaitCompletionPacket)GetProcAddress(hNtdll, "NtAssociateWaitCompletionPacket"); NtCancelWaitCompletionPacket = (PFN_NtCancelWaitCompletionPacket)GetProcAddress(hNtdll, "NtCancelWaitCompletionPacket"); - NtCreateWaitCompletionPacket = (PFN_NtCreateWaitCompletionPacket)GetProcAddress(hNtdll, "NtCreateWaitCompletionPacket");) + NtCreateWaitCompletionPacket = (PFN_NtCreateWaitCompletionPacket)GetProcAddress(hNtdll, "NtCreateWaitCompletionPacket"); + ProcessPrng = (PFN_ProcessPrng)GetProcAddress(hBcryptprimitives, "ProcessPrng");) VERIFY(NtAssociateWaitCompletionPacket); VERIFY(NtCancelWaitCompletionPacket); VERIFY(NtCreateWaitCompletionPacket); + VERIFY(ProcessPrng); } } g_system; diff --git a/Libraries/LibCrypto/CMakeLists.txt b/Libraries/LibCrypto/CMakeLists.txt index cd80d442dc..f4b6a609d3 100644 --- a/Libraries/LibCrypto/CMakeLists.txt +++ b/Libraries/LibCrypto/CMakeLists.txt @@ -26,7 +26,6 @@ set(SOURCES PK/MLDSA.cpp PK/MLKEM.cpp PK/RSA.cpp - SecureRandom.cpp ) ladybird_lib(LibCrypto crypto) diff --git a/Libraries/LibCrypto/PK/RSA.cpp b/Libraries/LibCrypto/PK/RSA.cpp index 98454ddf11..f0694f28f9 100644 --- a/Libraries/LibCrypto/PK/RSA.cpp +++ b/Libraries/LibCrypto/PK/RSA.cpp @@ -7,7 +7,6 @@ #include #include -#include #include #include #include diff --git a/Libraries/LibCrypto/SecureRandom.cpp b/Libraries/LibCrypto/SecureRandom.cpp deleted file mode 100644 index e5089d6e6a..0000000000 --- a/Libraries/LibCrypto/SecureRandom.cpp +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) 2024, the Ladybird developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include - -#include - -namespace Crypto { - -void fill_with_secure_random(Bytes bytes) -{ - auto const size = static_cast(bytes.size()); - - if (RAND_bytes(bytes.data(), size) != 1) - VERIFY_NOT_REACHED(); -} - -} diff --git a/Libraries/LibCrypto/SecureRandom.h b/Libraries/LibCrypto/SecureRandom.h deleted file mode 100644 index 76b37cd364..0000000000 --- a/Libraries/LibCrypto/SecureRandom.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2024, the Ladybird developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include - -namespace Crypto { - -void fill_with_secure_random(Bytes); - -template -inline T get_secure_random() -{ - T t; - fill_with_secure_random({ &t, sizeof(T) }); - return t; -} - -} diff --git a/Libraries/LibJS/Runtime/MathObject.cpp b/Libraries/LibJS/Runtime/MathObject.cpp index dc1cdd211d..7ecb30a40c 100644 --- a/Libraries/LibJS/Runtime/MathObject.cpp +++ b/Libraries/LibJS/Runtime/MathObject.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include diff --git a/Libraries/LibURL/Origin.cpp b/Libraries/LibURL/Origin.cpp index 423cb8e744..80ef0094fb 100644 --- a/Libraries/LibURL/Origin.cpp +++ b/Libraries/LibURL/Origin.cpp @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include +#include #include #include #include @@ -13,7 +13,7 @@ namespace URL { Origin Origin::create_opaque() { - return Origin { Crypto::get_secure_random() }; + return Origin { AK::get_random() }; } // https://html.spec.whatwg.org/multipage/browsers.html#same-site diff --git a/Libraries/LibWeb/Crypto/Crypto.cpp b/Libraries/LibWeb/Crypto/Crypto.cpp index 1dbccf592b..4f1dbcc61c 100644 --- a/Libraries/LibWeb/Crypto/Crypto.cpp +++ b/Libraries/LibWeb/Crypto/Crypto.cpp @@ -7,7 +7,6 @@ #include #include -#include #include #include #include @@ -67,7 +66,7 @@ WebIDL::ExceptionOr> Crypto::get_random_values return WebIDL::QuotaExceededError::create(realm(), "array's byteLength may not be greater than 65536"_utf16); // 3. Overwrite all elements of array with cryptographically strong random values of the appropriate type. - ::Crypto::fill_with_secure_random(array->viewed_array_buffer()->buffer().bytes().slice(array->byte_offset(), array->byte_length())); + fill_with_random(array->viewed_array_buffer()->buffer().bytes().slice(array->byte_offset(), array->byte_length())); // 4. Return array. return array; @@ -94,7 +93,7 @@ ErrorOr generate_random_uuid() u8 bytes[16]; // 2. Fill bytes with cryptographically secure random bytes. - ::Crypto::fill_with_secure_random(bytes); + fill_with_random(bytes); // 3. Set the 4 most significant bits of bytes[6], which represent the UUID version, to 0100. bytes[6] &= ~(1 << 7); diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index 877cc97c40..dc2bd6f630 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -29,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -274,7 +274,7 @@ static WebIDL::ExceptionOr validate_jwk_key_ops(JS::Realm& realm, Bindings static WebIDL::ExceptionOr generate_random_key(JS::VM& vm, u16 const size_in_bits) { auto key_buffer = TRY_OR_THROW_OOM(vm, ByteBuffer::create_uninitialized(size_in_bits / 8)); - ::Crypto::fill_with_secure_random(key_buffer); + fill_with_random(key_buffer); return key_buffer; } diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index e60a9b19d5..c3108ecff5 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -14,12 +14,12 @@ #include #include #include +#include #include #include #include #include #include -#include #include #include #include diff --git a/Libraries/LibWebSocket/WebSocket.cpp b/Libraries/LibWebSocket/WebSocket.cpp index 032a9dff2c..8be2851d5c 100644 --- a/Libraries/LibWebSocket/WebSocket.cpp +++ b/Libraries/LibWebSocket/WebSocket.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include @@ -205,7 +204,7 @@ void WebSocket::send_client_handshake() // 7. 16-byte nonce encoded as Base64 u8 nonce_data[16]; - Crypto::fill_with_secure_random(nonce_data); + fill_with_random(nonce_data); // FIXME: change to TRY() and make method fallible m_websocket_key = MUST(encode_base64({ nonce_data, 16 })).to_byte_string(); builder.appendff("Sec-WebSocket-Key: {}\r\n", m_websocket_key); @@ -621,7 +620,7 @@ void WebSocket::send_frame(WebSocket::OpCode op_code, ReadonlyBytes payload, boo // > Clients MUST choose a new masking key for each frame, using an algorithm // > that cannot be predicted by end applications that provide data u8 masking_key[4]; - Crypto::fill_with_secure_random(masking_key); + fill_with_random(masking_key); buf.overwrite(offset, masking_key, 4); offset += 4; // don't try to send empty payload diff --git a/Meta/gn/secondary/Userland/Libraries/LibCrypto/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibCrypto/BUILD.gn index 1346b71fe7..a1585366c3 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibCrypto/BUILD.gn +++ b/Meta/gn/secondary/Userland/Libraries/LibCrypto/BUILD.gn @@ -30,6 +30,5 @@ shared_library("LibCrypto") { "Hash/SHA1.cpp", "Hash/SHA2.cpp", "PK/RSA.cpp", - "SecureRandom.cpp", ] } diff --git a/Tests/AK/CMakeLists.txt b/Tests/AK/CMakeLists.txt index fbc6353070..31e7c0bae6 100644 --- a/Tests/AK/CMakeLists.txt +++ b/Tests/AK/CMakeLists.txt @@ -55,6 +55,7 @@ set(AK_TEST_SOURCES TestOwnPtr.cpp TestQueue.cpp TestQuickSort.cpp + TestRandom.cpp TestRedBlackTree.cpp TestRefPtr.cpp TestSegmentedVector.cpp diff --git a/Tests/AK/TestRandom.cpp b/Tests/AK/TestRandom.cpp new file mode 100644 index 0000000000..e55773d724 --- /dev/null +++ b/Tests/AK/TestRandom.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025-2026, Colleirose + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include +#include + +TEST_CASE(csprng_generates_unique_values) +{ + constexpr size_t size = 800; + constexpr size_t max_failures = 3; + size_t failures = 0; + + for (size_t i = 0; i < 3; i++) { + ByteBuffer first_buffer = MUST(ByteBuffer::create_zeroed(size)); + ByteBuffer second_buffer = MUST(ByteBuffer::create_zeroed(size)); + + AK::fill_with_random(first_buffer); + AK::fill_with_random(second_buffer); + + u32 first_u32 = AK::get_random_uniform(size); + u32 second_u32 = AK::get_random_uniform(size); + + u64 first_u64 = AK::get_random_uniform_64(size); + u64 second_u64 = AK::get_random_uniform_64(size); + + if (first_buffer == second_buffer) + failures++; + + if (first_u32 == second_u32) + failures++; + + if (first_u64 == second_u64) + failures++; + } + + EXPECT(failures < max_failures); +}