diff --git a/core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp b/core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp index 8fcd31009e..e0988ce0f3 100644 --- a/core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp +++ b/core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp @@ -11,10 +11,6 @@ int APS5_VABI BIO_set_flags_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI BN_clear_free_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI BN_dup_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -260,10 +256,6 @@ int APS5_VABI ASN1_INTEGER_set_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI RSA_set0_factors_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI X509_set_pubkey_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -422,10 +414,6 @@ int APS5_VABI SSL_get0_verified_chain_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI RSA_set0_crt_params_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI EVP_PKEY_derive_nid_postfix() { NotImplemented_nid_no_patch(__func__); } @@ -633,10 +621,6 @@ int APS5_VABI EVP_aes_128_ccm_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI RSA_set0_key_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI tls_config_verify_client_nid_postfix() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceLibreSsl3/CMakeLists.txt b/core/libs/prx/libSceLibreSsl3/CMakeLists.txt index 0c49e3dfdb..ee7d8d940a 100644 --- a/core/libs/prx/libSceLibreSsl3/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl3/CMakeLists.txt @@ -236,6 +236,20 @@ if(BUILD_TESTING AND CMAKE_SYSTEM_NAME STREQUAL "Linux" AND EXISTS "${LIBS_INCLU set_tests_properties(guest_libressl3_mixed_evp PROPERTIES TIMEOUT 20) endif() +target_sources(libSceLibreSsl3 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/RsaSetters.cpp) +if(BUILD_TESTING) + add_test_executable(guest_libressl3_rsa_setters_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/RsaSetters.cpp) + target_include_directories(guest_libressl3_rsa_setters_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl3_rsa_setters_tests PRIVATE libSceLibreSsl3 libc) + configure_windows_unwind(guest_libressl3_rsa_setters_tests) + add_test(NAME guest_libressl3_rsa_setters COMMAND guest_libressl3_rsa_setters_tests) + set_tests_properties(guest_libressl3_rsa_setters PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_libressl3_rsa_setters PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + target_sources(libSceLibreSsl3 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/PkeyQueries.cpp) if(BUILD_TESTING) add_test_executable(guest_libressl3_pkey_queries_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/PkeyQueries.cpp) diff --git a/core/libs/prx/libSceLibreSsl3/include/Api.hpp b/core/libs/prx/libSceLibreSsl3/include/Api.hpp index 96cf191a71..c0830e6dd5 100644 --- a/core/libs/prx/libSceLibreSsl3/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl3/include/Api.hpp @@ -207,7 +207,7 @@ unsigned char* APS5_VABI SHA1_nid_postfix(const unsigned char* data, std::size_t int APS5_VABI BIO_clear_flags_nid_postfix(); int APS5_VABI BIO_set_flags_nid_postfix(); -int APS5_VABI BN_clear_free_nid_postfix(); +void APS5_VABI BN_clear_free_nid_postfix(LibreSsl3Bignum* number); int APS5_VABI BN_dup_nid_postfix(); int APS5_VABI ERR_func_error_string_nid_postfix(); int APS5_VABI EVP_CIPHER_iv_length_nid_postfix(const LibreSsl3EvpCipher* cipher); @@ -276,7 +276,7 @@ int APS5_VABI Unknown_3KZ_minus_L4y1gak_nid_no_patch(); int APS5_VABI EVP_chacha20_poly1305_nid_postfix(); int APS5_VABI EVP_chacha20_nid_postfix(); int APS5_VABI ASN1_INTEGER_set_nid_postfix(); -int APS5_VABI RSA_set0_factors_nid_postfix(); +int APS5_VABI RSA_set0_factors_nid_postfix(LibreSsl3Rsa* rsa, LibreSsl3Bignum* p, LibreSsl3Bignum* q); int APS5_VABI X509_set_pubkey_nid_postfix(); int APS5_VABI BIO_get_shutdown_nid_postfix(); int APS5_VABI tls_accept_socket_nid_postfix(); @@ -317,7 +317,7 @@ int APS5_VABI Unknown_PBYz0kpXwg8_nid_no_patch(); int APS5_VABI X509_new_nid_postfix(); void APS5_VABI RSA_get0_factors_nid_postfix(const LibreSsl3Rsa* rsa, const void** p, const void** q); int APS5_VABI SSL_get0_verified_chain_nid_postfix(); -int APS5_VABI RSA_set0_crt_params_nid_postfix(); +int APS5_VABI RSA_set0_crt_params_nid_postfix(LibreSsl3Rsa* rsa, LibreSsl3Bignum* dmp1, LibreSsl3Bignum* dmq1, LibreSsl3Bignum* iqmp); int APS5_VABI EVP_PKEY_derive_nid_postfix(); int APS5_VABI EVP_PKEY_sign_nid_postfix(); int APS5_VABI X509_get_signature_nid_nid_postfix(); @@ -370,7 +370,7 @@ int APS5_VABI EC_GROUP_cmp_nid_postfix(); int APS5_VABI EVP_PKEY_sign_init_nid_postfix(); int APS5_VABI EC_POINT_dup_nid_postfix(); int APS5_VABI EVP_aes_128_ccm_nid_postfix(); -int APS5_VABI RSA_set0_key_nid_postfix(); +int APS5_VABI RSA_set0_key_nid_postfix(LibreSsl3Rsa* rsa, LibreSsl3Bignum* n, LibreSsl3Bignum* e, LibreSsl3Bignum* d); int APS5_VABI tls_config_verify_client_nid_postfix(); int APS5_VABI EVP_PKEY_cmp_nid_postfix(const LibreSsl3EvpPkey* first, const LibreSsl3EvpPkey* second); int APS5_VABI d2i_ASN1_SEQUENCE_ANY_nid_postfix(); diff --git a/core/libs/prx/libSceLibreSsl3/include/Key.hpp b/core/libs/prx/libSceLibreSsl3/include/Key.hpp index f48ebc2ce8..3886d399d2 100644 --- a/core/libs/prx/libSceLibreSsl3/include/Key.hpp +++ b/core/libs/prx/libSceLibreSsl3/include/Key.hpp @@ -3,6 +3,17 @@ #include #include +struct LibreSsl3Bignum { + std::uint64_t* data; + std::int32_t top; + std::int32_t capacity; + std::int32_t negative; + std::int32_t flags; +}; + +static_assert(sizeof(LibreSsl3Bignum) == 24); +static_assert(offsetof(LibreSsl3Bignum, flags) == 20); + struct LibreSsl3EvpPkey { std::int32_t type; std::int32_t references; diff --git a/core/libs/prx/libSceLibreSsl3/src/RsaSetters.cpp b/core/libs/prx/libSceLibreSsl3/src/RsaSetters.cpp new file mode 100644 index 0000000000..84d03ae3b7 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/src/RsaSetters.cpp @@ -0,0 +1,80 @@ +#include "prx/libSceLibreSsl3/libSceLibreSsl3.hpp" +#include "prx/libc/include/Api.hpp" +#include + +namespace { + +constexpr int Allocated = 1; +constexpr int StaticData = 2; + +void Validate_nid_no_patch(const LibreSsl3Bignum* number) { + if (number && number->data && !(number->flags & StaticData) && number->capacity < 0) + throw std::invalid_argument("LibreSSL3 BIGNUM has a negative limb capacity"); +} + +void Wipe_nid_no_patch(void* pointer, std::size_t bytes) { + auto* output = static_cast(pointer); + for (std::size_t i = 0; i < bytes; ++i) output[i] = 0; +} + +void ValidateReplacement_nid_no_patch(void* previous, const LibreSsl3Bignum* replacement) { + if (replacement) Validate_nid_no_patch(static_cast(previous)); +} + +void Replace_nid_no_patch(void*& previous, LibreSsl3Bignum* replacement) { + if (!replacement) return; + BN_clear_free_nid_postfix(static_cast(previous)); + previous = replacement; +} + +} + +extern "C" { + +void APS5_VABI BN_clear_free_nid_postfix(LibreSsl3Bignum* number) { + if (!number) return; + Validate_nid_no_patch(number); + const bool allocated = (number->flags & Allocated) != 0; + if (number->data && !(number->flags & StaticData)) { + Wipe_nid_no_patch(number->data, static_cast(number->capacity) * sizeof(*number->data)); + free_nid_postfix(number->data); + } + Wipe_nid_no_patch(number, sizeof(*number)); + if (allocated) free_nid_postfix(number); +} + +int APS5_VABI RSA_set0_key_nid_postfix(LibreSsl3Rsa* rsa, LibreSsl3Bignum* n, LibreSsl3Bignum* e, LibreSsl3Bignum* d) { + if (!rsa) throw std::invalid_argument("RSA_set0_key requires a key"); + if ((!rsa->n && !n) || (!rsa->e && !e)) return 0; + ValidateReplacement_nid_no_patch(rsa->n, n); + ValidateReplacement_nid_no_patch(rsa->e, e); + ValidateReplacement_nid_no_patch(rsa->d, d); + Replace_nid_no_patch(rsa->n, n); + Replace_nid_no_patch(rsa->e, e); + Replace_nid_no_patch(rsa->d, d); + return 1; +} + +int APS5_VABI RSA_set0_factors_nid_postfix(LibreSsl3Rsa* rsa, LibreSsl3Bignum* p, LibreSsl3Bignum* q) { + if (!rsa) throw std::invalid_argument("RSA_set0_factors requires a key"); + if ((!rsa->p && !p) || (!rsa->q && !q)) return 0; + ValidateReplacement_nid_no_patch(rsa->p, p); + ValidateReplacement_nid_no_patch(rsa->q, q); + Replace_nid_no_patch(rsa->p, p); + Replace_nid_no_patch(rsa->q, q); + return 1; +} + +int APS5_VABI RSA_set0_crt_params_nid_postfix(LibreSsl3Rsa* rsa, LibreSsl3Bignum* dmp1, LibreSsl3Bignum* dmq1, LibreSsl3Bignum* iqmp) { + if (!rsa) throw std::invalid_argument("RSA_set0_crt_params requires a key"); + if ((!rsa->dmp1 && !dmp1) || (!rsa->dmq1 && !dmq1) || (!rsa->iqmp && !iqmp)) return 0; + ValidateReplacement_nid_no_patch(rsa->dmp1, dmp1); + ValidateReplacement_nid_no_patch(rsa->dmq1, dmq1); + ValidateReplacement_nid_no_patch(rsa->iqmp, iqmp); + Replace_nid_no_patch(rsa->dmp1, dmp1); + Replace_nid_no_patch(rsa->dmq1, dmq1); + Replace_nid_no_patch(rsa->iqmp, iqmp); + return 1; +} + +} diff --git a/core/libs/prx/libSceLibreSsl3/tests/RsaSetters.cpp b/core/libs/prx/libSceLibreSsl3/tests/RsaSetters.cpp new file mode 100644 index 0000000000..bb28917d12 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/tests/RsaSetters.cpp @@ -0,0 +1,217 @@ +#include "prx/libSceLibreSsl3/libSceLibreSsl3.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include +#include +#include +#include +#include +#include +#include + +using SetKey = int (APS5_VABI*)(LibreSsl3Rsa*, LibreSsl3Bignum*, LibreSsl3Bignum*, LibreSsl3Bignum*); +using SetFactors = int (APS5_VABI*)(LibreSsl3Rsa*, LibreSsl3Bignum*, LibreSsl3Bignum*); +using ClearFree = void (APS5_VABI*)(LibreSsl3Bignum*); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); + +namespace { + +std::size_t checks = 0, cases = 0, frees = 0; +struct Allocation { void* pointer; std::size_t size; }; +std::array allocations{}; + +void Require(bool condition, std::source_location where = std::source_location::current()) { + ++checks; + if (condition) return; + std::fprintf(stderr, "LibreSSL3 RSA ownership check failed at line %u\n", where.line()); + std::abort(); +} + +bool IsLive(const void* pointer) { + if (!pointer) return false; + for (const auto& allocation : allocations) + if (allocation.pointer == pointer) return true; + return false; +} + +void* APS5_VABI Allocate_nid_no_patch(std::size_t bytes) { + void* result = std::malloc(bytes + 16); + Require(result != nullptr); + std::memset(result, 0xa5, bytes + 16); + for (auto& allocation : allocations) { + if (allocation.pointer) continue; + allocation = {result, bytes}; + return result; + } + std::abort(); +} + +void APS5_VABI Free_nid_no_patch(void* pointer) { + if (!pointer) return; + for (auto& allocation : allocations) { + if (allocation.pointer != pointer) continue; + const auto* bytes = static_cast(pointer); + for (std::size_t i = 0; i < allocation.size; ++i) Require(bytes[i] == 0); + for (std::size_t i = allocation.size; i < allocation.size + 16; ++i) Require(bytes[i] == 0xa5); + allocation = {}; + ++frees; + std::free(pointer); + return; + } + Require(false); +} + +void* APS5_VABI NoCalloc_nid_no_patch(std::size_t, std::size_t) { std::abort(); } +void* APS5_VABI NoRealloc_nid_no_patch(void*, std::size_t) { std::abort(); } +void* APS5_VABI NoAlign_nid_no_patch(std::size_t, std::size_t) { std::abort(); } +void* APS5_VABI NoRealign_nid_no_patch(void*, std::size_t, std::size_t) { std::abort(); } +int APS5_VABI NoPosixAlign_nid_no_patch(void**, std::size_t, std::size_t) { std::abort(); } + +LibreSsl3Bignum* Number(int capacity) { + auto* number = static_cast(malloc_nid_postfix(sizeof(LibreSsl3Bignum))); + Require(number != nullptr); + auto* data = static_cast(malloc_nid_postfix(capacity * sizeof(std::uint64_t))); + Require(data != nullptr); + for (int i = 0; i < capacity; ++i) data[i] = 0xfedcba9876543210ULL - i; + *number = {data, capacity ? 1 : 0, capacity, 1, 1 | 4}; + return number; +} + +void Clean() { + for (const auto& allocation : allocations) Require(allocation.pointer == nullptr); +} + +template void Throws(TCall call) { + bool threw = false; + try { call(); } catch (const std::invalid_argument&) { threw = true; } + Require(threw); +} + +using Field = void* LibreSsl3Rsa::*; +void Matrix(int group) { + const std::array fields = group == 0 + ? std::array{&LibreSsl3Rsa::n, &LibreSsl3Rsa::e, &LibreSsl3Rsa::d} + : group == 1 ? std::array{&LibreSsl3Rsa::p, &LibreSsl3Rsa::q, nullptr} + : std::array{&LibreSsl3Rsa::dmp1, &LibreSsl3Rsa::dmq1, &LibreSsl3Rsa::iqmp}; + const int count = group == 1 ? 2 : 3; + const int required = group == 2 ? 7 : 3; + for (int oldMask = 0; oldMask < (1 << count); ++oldMask) { + for (int newMask = 0; newMask < (1 << count); ++newMask) { + ++cases; + struct Guarded { std::uint64_t before; LibreSsl3Rsa key; std::uint64_t after; } guarded; + std::memset(&guarded, 0xa5, sizeof(guarded)); + std::array old{}, next{}; + std::array input{}; + for (int i = 0; i < count; ++i) { + if (oldMask & (1 << i)) old[i] = Number(i + 2); + if (newMask & (1 << i)) { next[i] = Number(i + 1); input[i] = *next[i]; } + guarded.key.*fields[i] = old[i]; + } + Guarded expected; + std::memcpy(&expected, &guarded, sizeof(expected)); + const bool accepted = ((oldMask | newMask) & required) == required; + const auto before = frees; + std::size_t expectedFrees = 0; + if (accepted) { + for (int i = 0; i < count; ++i) { + if (!next[i]) continue; + expected.key.*fields[i] = next[i]; + if (old[i]) expectedFrees += 2; + } + } + const int result = group == 0 ? RSA_set0_key_nid_postfix(&guarded.key, next[0], next[1], next[2]) + : group == 1 ? RSA_set0_factors_nid_postfix(&guarded.key, next[0], next[1]) + : RSA_set0_crt_params_nid_postfix(&guarded.key, next[0], next[1], next[2]); + Require(result == int(accepted)); + Require(std::memcmp(&guarded, &expected, sizeof(guarded)) == 0); + Require(frees - before == expectedFrees); + for (int i = 0; i < count; ++i) { + if (next[i]) { + Require(IsLive(next[i])); + Require(std::memcmp(next[i], &input[i], sizeof(input[i])) == 0); + Require(next[i]->data[0] == 0xfedcba9876543210ULL); + } + if (IsLive(old[i])) BN_clear_free_nid_postfix(old[i]); + BN_clear_free_nid_postfix(next[i]); + } + Clean(); + } + } +} + +void Disposal() { + BN_clear_free_nid_postfix(nullptr); + for (int capacity : {0, 1, 7}) { + BN_clear_free_nid_postfix(Number(capacity)); + Clean(); + } + for (int flags : {0, 2, 4, 6}) { + std::array borrowed{11, 22, 33, 44}; + const auto expected = borrowed; + const bool isStatic = (flags & 2) != 0; + LibreSsl3Bignum number{isStatic ? borrowed.data() : static_cast(malloc_nid_postfix(sizeof(borrowed))), 1, 4, 1, flags}; + BN_clear_free_nid_postfix(&number); + const LibreSsl3Bignum zero{}; + Require(std::memcmp(&number, &zero, sizeof(zero)) == 0); + Require(borrowed == expected); + Clean(); + } + std::array borrowed{0x12345678, 0xabcdef}; + auto* number = static_cast(malloc_nid_postfix(sizeof(LibreSsl3Bignum))); + *number = {borrowed.data(), 2, -1, 1, 3}; + BN_clear_free_nid_postfix(number); + Require(borrowed[0] == 0x12345678 && borrowed[1] == 0xabcdef); + Clean(); + LibreSsl3Bignum empty{nullptr, -1, -1, 1, 0}; + BN_clear_free_nid_postfix(&empty); + const LibreSsl3Bignum zero{}; + Require(std::memcmp(&empty, &zero, sizeof(zero)) == 0); +} + +void Invalid() { + Throws([] { RSA_set0_key_nid_postfix(nullptr, nullptr, nullptr, nullptr); }); + Throws([] { RSA_set0_factors_nid_postfix(nullptr, nullptr, nullptr); }); + Throws([] { RSA_set0_crt_params_nid_postfix(nullptr, nullptr, nullptr, nullptr); }); + std::uint64_t limb = 0x1234; + LibreSsl3Bignum bad{&limb, 1, -1, 0, 0}; + const auto original = bad; + Throws([&] { BN_clear_free_nid_postfix(&bad); }); + Require(std::memcmp(&bad, &original, sizeof(bad)) == 0 && limb == 0x1234); + auto* old = Number(2); + auto* first = Number(3); + auto* second = Number(4); + LibreSsl3Rsa rsa{}; + rsa.n = rsa.p = rsa.dmp1 = old; + rsa.e = rsa.q = rsa.dmq1 = &bad; + rsa.iqmp = &bad; + const auto before = rsa; + const auto beforeFrees = frees; + Throws([&] { RSA_set0_key_nid_postfix(&rsa, first, second, nullptr); }); + Throws([&] { RSA_set0_factors_nid_postfix(&rsa, first, second); }); + Throws([&] { RSA_set0_crt_params_nid_postfix(&rsa, first, nullptr, second); }); + Require(std::memcmp(&rsa, &before, sizeof(rsa)) == 0 && frees == beforeFrees); + BN_clear_free_nid_postfix(old); + BN_clear_free_nid_postfix(first); + BN_clear_free_nid_postfix(second); + Clean(); +} + +} + +int main() { + void* const api[10]{reinterpret_cast(Allocate_nid_no_patch), reinterpret_cast(Free_nid_no_patch), + reinterpret_cast(NoCalloc_nid_no_patch), reinterpret_cast(NoRealloc_nid_no_patch), + reinterpret_cast(NoAlign_nid_no_patch), reinterpret_cast(NoRealign_nid_no_patch), + reinterpret_cast(NoPosixAlign_nid_no_patch)}; + ApplicationHeapRegister_nid_no_patch(api); + Matrix(0); + Matrix(1); + Matrix(2); + Disposal(); + Invalid(); + Clean(); + std::printf("RSA ownership: %zu matrix cases, %zu checks, %zu cleared allocations, zero live allocations\n", cases, checks, frees); +} diff --git a/core/libs/prx/libSceNKWebKit/Export.cpp b/core/libs/prx/libSceNKWebKit/Export.cpp index eb1ac6da77..fb8e73086f 100644 --- a/core/libs/prx/libSceNKWebKit/Export.cpp +++ b/core/libs/prx/libSceNKWebKit/Export.cpp @@ -14350,13 +14350,7 @@ int APS5_VABI WKOpenPanelParametersGetAllowsMultipleFiles() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKOpenPanelResultListenerCancel() { - NotImplemented_nid_no_patch(__func__); -} -int APS5_VABI WKOpenPanelResultListenerChooseFiles() { - NotImplemented_nid_no_patch(__func__); -} int APS5_VABI WKPageAreScrollbarAnimationsSuppressed() { NotImplemented_nid_no_patch(__func__); diff --git a/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp b/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp index 621fa20969..d58bf74ec8 100644 --- a/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp +++ b/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp @@ -524,8 +524,6 @@ int APS5_VABI WKOpenPanelParametersCopySelectedFileNames(); int APS5_VABI WKOpenPanelParametersGetAllowsDirectories(); int APS5_VABI WKOpenPanelParametersGetAllowsMultipleFiles(); int APS5_VABI WKOpenPanelParametersGetMediaCaptureType(); -int APS5_VABI WKOpenPanelResultListenerCancel(); -int APS5_VABI WKOpenPanelResultListenerChooseFiles(); int APS5_VABI WKPageAreScrollbarAnimationsSuppressed(); int APS5_VABI WKPageBackgroundExtendsBeyondPage(); int APS5_VABI WKPageBeginPrinting(); diff --git a/core/libs/prx/libSceNKWebKit/include/WebKitValueExports.hpp b/core/libs/prx/libSceNKWebKit/include/WebKitValueExports.hpp index 2e56433948..53b4f617e9 100644 --- a/core/libs/prx/libSceNKWebKit/include/WebKitValueExports.hpp +++ b/core/libs/prx/libSceNKWebKit/include/WebKitValueExports.hpp @@ -104,6 +104,8 @@ std::uint32_t APS5_VABI WKNotificationGetTypeID(); std::uint32_t APS5_VABI WKNotificationManagerGetTypeID(); std::uint32_t APS5_VABI WKNotificationPermissionRequestGetTypeID(); std::uint32_t APS5_VABI WKOpenPanelParametersGetTypeID(); +void APS5_VABI WKOpenPanelResultListenerCancel(const void* listenerRef); +void APS5_VABI WKOpenPanelResultListenerChooseFiles(const void* listenerRef, const void* fileURLsRef, const void* allowedMimeTypesRef); std::uint32_t APS5_VABI WKOpenPanelResultListenerGetTypeID(); std::uint32_t APS5_VABI WKPageConfigurationGetTypeID(); std::uint32_t APS5_VABI WKPageGetTypeID(); diff --git a/core/libs/prx/libSceNKWebKit/src/OpenPanelResultListener.cpp b/core/libs/prx/libSceNKWebKit/src/OpenPanelResultListener.cpp new file mode 100644 index 0000000000..f49ec753d4 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/src/OpenPanelResultListener.cpp @@ -0,0 +1,36 @@ +#include "prx/libSceNKWebKit/src/Types.hpp" +#include "prx/libSceNKWebKit/include/WebKitValueExports.hpp" + +extern "C" { + +void APS5_VABI WKOpenPanelResultListenerCancel(const void* listenerRef) { + auto* listener = NKWebKit::Cast(listenerRef, __func__); + listener->cancelled = true; + listener->chosenFiles.clear(); +} + +void APS5_VABI WKOpenPanelResultListenerChooseFiles(const void* listenerRef, const void* fileURLsRef, const void* allowedMimeTypesRef) { + auto* listener = NKWebKit::Cast(listenerRef, __func__); + listener->cancelled = false; + listener->chosenFiles.clear(); + const auto* array = NKWebKit::CastOrNull(fileURLsRef, __func__); + if (array) { + for (const auto& item : array->elements) { + if (const auto* url = NKWebKit::CastOrNull(item.Get(), __func__)) { + std::string path = url->text; + if (path.rfind("file://", 0) == 0) { + path = path.substr(7); + } + listener->chosenFiles.push_back(NKWebKit::String::FromUtf8(path.c_str())); + } else if (const auto* str = NKWebKit::CastOrNull(item.Get(), __func__)) { + std::string path = str->Utf8(); + if (path.rfind("file://", 0) == 0) { + path = path.substr(7); + } + listener->chosenFiles.push_back(NKWebKit::String::FromUtf8(path.c_str())); + } + } + } +} + +} diff --git a/core/libs/prx/libSceNKWebKit/src/Types.hpp b/core/libs/prx/libSceNKWebKit/src/Types.hpp index 3de727d40f..ad0e79faae 100644 --- a/core/libs/prx/libSceNKWebKit/src/Types.hpp +++ b/core/libs/prx/libSceNKWebKit/src/Types.hpp @@ -128,4 +128,10 @@ public: bool isContentEditable = false; }; +class OpenPanelResultListener final : public ObjectOf { +public: + bool cancelled = false; + std::vector> chosenFiles; +}; + } // namespace NKWebKit diff --git a/core/libs/prx/libSceNKWebKit/tests/Base.cpp b/core/libs/prx/libSceNKWebKit/tests/Base.cpp index a1697267e7..3cd1dcaf56 100644 --- a/core/libs/prx/libSceNKWebKit/tests/Base.cpp +++ b/core/libs/prx/libSceNKWebKit/tests/Base.cpp @@ -622,6 +622,48 @@ static void TestHitTestResult() { WKRelease(ref); } +static void TestOpenPanelResultListener() { + Require(WKOpenPanelResultListenerGetTypeID() == 80); + + auto listener = NKWebKit::Make(); + const void* ref = listener.Get(); + + Require(!listener->cancelled); + Require(listener->chosenFiles.empty()); + + WKOpenPanelResultListenerCancel(ref); + Require(listener->cancelled); + + auto urlObj = NKWebKit::Make("file:///path/to/file.txt"); + const void* urlValues[] = { urlObj.Get() }; + const void* fileArray = WKArrayCreate(urlValues, 1); + + WKOpenPanelResultListenerChooseFiles(ref, fileArray, nullptr); + Require(!listener->cancelled); + Require(listener->chosenFiles.size() == 1); + Require(listener->chosenFiles[0]->Utf8() == "/path/to/file.txt"); + WKRelease(fileArray); + + WKOpenPanelResultListenerChooseFiles(ref, nullptr, nullptr); + Require(listener->chosenFiles.empty()); + + bool threw = false; + try { + WKOpenPanelResultListenerCancel(nullptr); + } catch (const std::invalid_argument&) { + threw = true; + } + Require(threw); + + threw = false; + try { + WKOpenPanelResultListenerChooseFiles(nullptr, nullptr, nullptr); + } catch (const std::invalid_argument&) { + threw = true; + } + Require(threw); +} + static void TestCertificateInfo() { Require(WKCertificateInfoGetTypeID() == 0); Require(WKCertificateInfoCreateWithCertficateChain(nullptr) == nullptr); @@ -645,6 +687,7 @@ int main() { TestURLRequestResponse(); TestError(); TestHitTestResult(); + TestOpenPanelResultListener(); TestCertificateInfo(); std::printf("All libSceNKWebKit Base tests passed!\n"); return 0; diff --git a/docs/dev/debt/libSceLibreSsl3.md b/docs/dev/debt/libSceLibreSsl3.md index c649a187c4..048cf8d3bb 100644 --- a/docs/dev/debt/libSceLibreSsl3.md +++ b/docs/dev/debt/libSceLibreSsl3.md @@ -41,7 +41,7 @@ ### Additional callable export inventory -- [Additional exports](../../../core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp) declare 171 remaining callable exports: 161 named and 10 known only by NID. Every entry throws. Parameterless `int` declarations are unknown-signature placeholders, not verified ABI contracts. Names/NIDs match the 13.60 export table. No functional implementation or title unblock is claimed. +- [Additional exports](../../../core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp) declare 167 remaining callable exports: 157 named and 10 known only by NID. Every entry throws. Parameterless `int` declarations are unknown-signature placeholders, not verified ABI contracts. Names/NIDs match the 13.60 export table. No functional implementation or title unblock is claimed. | Name | NID | | --- | --- | @@ -62,7 +62,6 @@ | `BIO_clear_flags` | `3sypCuBDdQs` | | `EVP_chacha20` | `3tGgkjBQ82A` | | `ASN1_INTEGER_set` | `3v2Omd4sqcU` | -| `RSA_set0_factors` | `3wlK0tLONvk` | | `X509_set_pubkey` | `409ieWBMPUk` | | `BIO_get_shutdown` | `44M+SEbpNdQ` | | `tls_accept_socket` | `4Q3bvOy4LY8` | @@ -82,7 +81,6 @@ | `unknown` | `A-Hs-8mau+8` | | `ERR_load_X509_strings` | `A0lZtFjhcTw` | | `tls_config_set_key_mem` | `A65b+ZhOt9k` | -| `BN_clear_free` | `ABcJv8S1KEM` | | `unknown` | `AiNud8LR8B4` | | `X509_cmp_current_time` | `Ax+Uu++rnrA` | | `SSL_CTX_set_timeout` | `BBOgwZLvXHA` | @@ -119,7 +117,6 @@ | `unknown` | `PBYz0kpXwg8` | | `X509_new` | `PNfwr3s6dEs` | | `SSL_get0_verified_chain` | `QKIOxHfZLh8` | -| `RSA_set0_crt_params` | `QTV9Nyi7ryg` | | `EVP_PKEY_derive` | `QYEjNnn6PT4` | | `SSL_CIPHER_get_id` | `QaiPrM-RrrM` | | `EVP_PKEY_sign` | `QhYzE+Jdvuk` | @@ -196,7 +193,6 @@ | `EC_POINT_dup` | `uITUdhDDAg8` | | `HKDF` | `uJ2ZXq4z3As` | | `EVP_aes_128_ccm` | `uM9UnC8KFjY` | -| `RSA_set0_key` | `uihDBp74nLI` | | `EVP_PKEY_CTX_free` | `ulzAPWCIBkw` | | `tls_free` | `uz3bWkbHZu4` | | `tls_config_verify_client` | `v1vHv4jOJxQ` | @@ -217,4 +213,10 @@ | `ECDSA_SIG_get0` | `z8fsXF8JuL0` | | `EVP_MD_block_size` | `zCa75KLWWn4` | -- 111 of the 126 exports the 13.60 table lists only by NID carry, in the table above and in [Additional exports](../../../core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp), the LibreSSL names whose `tools/nid_names.py` hash equals the NID; the names were taken from the identifiers of the LibreSSL 3.8.4 and 4.0.0 `include`, `crypto`, `ssl` and `tls` sources. A name match does not show that the console function has LibreSSL's prototype or behavior; each keeps the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`. `AES_wrap_key`, `ECDSA_SIG_free`, `ECDSA_do_verify`, `ERR_load_ERR_strings`, `ERR_print_errors_fp` and `i2d_PUBKEY` also hash to the NIDs of six `unknown` rows, which keep their NID names; `PBYz0kpXwg8` hashes to no identifier of those sources. +- 107 of the 126 exports the 13.60 table lists only by NID carry, in the table above and in [Additional exports](../../../core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp), the LibreSSL names whose `tools/nid_names.py` hash equals the NID; the names were taken from the identifiers of the LibreSSL 3.8.4 and 4.0.0 `include`, `crypto`, `ssl` and `tls` sources. A name match does not show that the console function has LibreSSL's prototype or behavior; each keeps the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`. `AES_wrap_key`, `ECDSA_SIG_free`, `ECDSA_do_verify`, `ERR_load_ERR_strings`, `ERR_print_errors_fp` and `i2d_PUBKEY` also hash to the NIDs of six `unknown` rows, which keep their NID names; `PBYz0kpXwg8` hashes to no identifier of those sources. + +### Functional + +- [RSA_set0_key, RSA_set0_factors, RSA_set0_crt_params and BN_clear_free](../../../core/libs/prx/libSceLibreSsl3/src/RsaSetters.cpp) have no console verification or key-constructor/destructor interoperability measurement. They use the existing 152-byte RSA layout and the 24-byte BIGNUM layout of [LibreSSL 4.0.0](https://github.com/libressl/openbsd/tree/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto), following `rsa/rsa_lib.c` and `bn/bn_lib.c`: a successful setter owns supplied components, retains omitted components and clears replaced components through the guest allocator; static limb arrays are borrowed. Missing required components return zero before mutation. Null RSA objects and negative owned limb capacities throw instead of dereferencing invalid memory or erasing an unbounded range. Malformed pointers and aliased component ownership are unverified. Closes when: compared with the console and complete key lifetimes. + +- [Forwarded BN_new and BN_free](../../../core/libs/prx/libSceLibreSsl3/ParentExports.cpp) still throw in the parent library. Future implementations must allocate owned BIGNUM limbs and structs from the same guest heap used by the RSA setters and set `BN_FLG_MALLOCED` on heap-owned structs. Constructor/setter/destructor interoperability remains unverified. diff --git a/docs/dev/debt/libSceNKWebKit.md b/docs/dev/debt/libSceNKWebKit.md index af48717fb5..785ed4fba2 100644 --- a/docs/dev/debt/libSceNKWebKit.md +++ b/docs/dev/debt/libSceNKWebKit.md @@ -53,7 +53,7 @@ Inventory prose retains historical counts; use the [generated progress report](h - The [revision-pinned identity inventory](https://gist.github.com/kyon-eth/d44995f181e1818977bd38f0e6fc0f34/8afcb14b0707a09b6274ff7f7bcf33ef46cfe2e7) records the module, library, symbol type and NIDs for the declarations in [Export.cpp](../../../core/libs/prx/libSceNKWebKit/Export.cpp); it is a metadata summary rather than independent verification. For the 255 absent from the public catalogue, [per-image ELF records](https://gist.github.com/kyon-eth/147be8dd556e9cced2577ccbed548972/1a659ecfe819e724675991a574a1aebb5fbb48e5) include the raw qualified names, module and library identities, symbol type, values and sizes; the record file SHA-256 is `44bdc4d6073192a8f41b7be359b75364b3d41dbbd11ce649924b8cebdd4ffe51`. Image labels are not authenticated. -- [Additional callable exports](../../../core/libs/prx/libSceNKWebKit/Export.cpp) - 7060 of the 8703 inventory declarations listed below throw through `NotImplemented_nid_no_patch`; signatures and behavior are unverified. The later 13.60-labelled image contains all 8703; 8448 match the public 3.20 catalogue, while 255 are absent and their presence in retail 3.20 is unverified. Per-image records show the latter in a separate 3.20-labelled image, but neither image label is authenticated. Identified names match NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). Data exports need separate analysis. +- [Additional callable exports](../../../core/libs/prx/libSceNKWebKit/Export.cpp) - 7058 of the 8703 inventory declarations listed below throw through `NotImplemented_nid_no_patch`; signatures and behavior are unverified. The later 13.60-labelled image contains all 8703; 8448 match the public 3.20 catalogue, while 255 are absent and their presence in retail 3.20 is unverified. Per-image records show the latter in a separate 3.20-labelled image, but neither image label is authenticated. Identified names match NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). Data exports need separate analysis. | Name | NID | | --- | --- | @@ -517,8 +517,6 @@ Inventory prose retains historical counts; use the [generated progress report](h | `WKOpenPanelParametersCopySelectedFileNames` | `Oa8DwUsT8yg` | | `WKOpenPanelParametersGetAllowsDirectories` | `yZ8MpxQizWM` | | `WKOpenPanelParametersGetAllowsMultipleFiles` | `6JUqQUan7cM` | -| `WKOpenPanelResultListenerCancel` | `O0YiYsUaleQ` | -| `WKOpenPanelResultListenerChooseFiles` | `2ChSmFDC91I` | | `WKPageAreScrollbarAnimationsSuppressed` | `OQYQvowtC6I` | | `WKPageBackgroundExtendsBeyondPage` | `qKJ2G1pGKmU` | | `WKPageBeginPrinting` | `hujPhSbtd4A` | @@ -7133,3 +7131,4 @@ Inventory prose retains historical counts; use the [generated progress report](h - [WTF decimal parsing limits](../../../core/libs/prx/libSceNKWebKit/CharactersToNumber.cpp) - inputs of about 1 GiB or more whose decimal exponent leaves the range of WebKit's [`int` exponent](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/dtoa/double-conversion.cc#L488-L490) throw, where WebKit overflows. A UTF-16 `charactersTo*` length above `UINT32_MAX` after leading spaces throws where WebKit's [conversion Vector](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/Vector.h#L291-L293) crashes. - [WTF StringBuilder](../../../core/libs/prx/libSceNKWebKit/src/StringBuilder.cpp) (libSceNKWebKit) - throws where WebKit crashes: an overflow of a builder that crashes on overflow, a length sum above `UINT32_MAX`, `toString` after an overflow, and null characters with a nonzero length. It also throws where a debug assertion guards memory WebKit would corrupt: `reserveCapacity` on an empty 16-bit builder without a buffer, and [`appendQuotedJSONString`](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/text/StringBuilderJSON.cpp#L127-L128) of a 16-bit string into an 8-bit builder that holds an adopted string and no buffer. A capacity above the tree's `StringImpl` limit records an overflow, as a failed allocation does. Growing or shrinking a buffer with one reference copies it instead of calling `fastRealloc`, so its address always changes. - [WTF TextStream pointers](../../../core/libs/prx/libSceNKWebKit/src/TextStream.cpp) (libSceNKWebKit) - `operator<<(const void*)` formats the pointer as the tree's libc formats `%p` ([`snprintf`](https://github.com/WebKit/WebKit/blob/c55364d53f97/Source/WTF/wtf/text/TextStream.cpp#L114-L119) in WebKit); the console libc's output is unverified. +- [WKOpenPanelResultListener functions](../../../core/libs/prx/libSceNKWebKit/src/OpenPanelResultListener.cpp) (libSceNKWebKit) - nothing in the library creates a WKOpenPanelResultListener (no UI client requests open panels); stored results are not consumed by a file picker dialog; upstream invalidates the listener after the first call, and a null file array is treated as empty.