From de2b1890bf261dd90d3bda53647b40dc6cc208b2 Mon Sep 17 00:00:00 2001 From: hyvvs Date: Sat, 31 Oct 2026 05:29:08 +0100 Subject: [PATCH 1/6] feat(libSceLibreSsl): free GENERAL_NAME and GENERAL_NAMES allocations --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 13 +- core/libs/prx/libSceLibreSsl/Export.cpp | 10 - core/libs/prx/libSceLibreSsl/include/Api.hpp | 10 +- .../prx/libSceLibreSsl/src/GeneralName.cpp | 187 +++++++++++++ core/libs/tests/GuestLibreSslGeneralName.cpp | 250 ++++++++++++++++++ docs/dev/debt/libSceLibreSsl.md | 1 + 6 files changed, 458 insertions(+), 13 deletions(-) create mode 100644 core/libs/prx/libSceLibreSsl/src/GeneralName.cpp create mode 100644 core/libs/tests/GuestLibreSslGeneralName.cpp diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index f22572c2ed..334cfe98e3 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -2,7 +2,7 @@ add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/E target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) -target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Asn1.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/ExData.cpp) +target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Asn1.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/ExData.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/GeneralName.cpp) if(BUILD_TESTING) add_test_executable(guest_libressl_x509_errors_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslX509Errors.cpp) @@ -106,6 +106,17 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_libressl_general_name_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslGeneralName.cpp) + target_include_directories(guest_libressl_general_name_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_general_name_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_general_name_tests) + add_test(NAME guest_libressl_general_name COMMAND guest_libressl_general_name_tests) + set_tests_properties(guest_libressl_general_name PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_general_name PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_libressl_ui_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslUi.cpp) target_include_directories(guest_libressl_ui_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_libressl_ui_tests PRIVATE libSceLibreSsl libc) diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 78d3b801d2..fc78f64744 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -719,16 +719,6 @@ int APS5_VABI EVP_sha512_nid_postfix(void) { return 0; } -int APS5_VABI GENERAL_NAMES_free_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI GENERAL_NAME_free_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI HMAC_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 7e8b8b4d10..19e6f7a373 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -9,6 +9,12 @@ struct Asn1String { std::int64_t flags; }; +// LibreSSL 2.5.4 GENERAL_NAME: a 32-bit CHOICE selector and a pointer union. +struct LibreSslGeneralName { + std::int32_t type; + void* value; +}; + struct LibreSslX509VerifyParamId { void* hosts; std::uint32_t hostFlags; @@ -395,8 +401,8 @@ int APS5_VABI EVP_md5_nid_postfix(void); int APS5_VABI EVP_sha256_nid_postfix(void); int APS5_VABI EVP_sha384_nid_postfix(void); int APS5_VABI EVP_sha512_nid_postfix(void); -int APS5_VABI GENERAL_NAMES_free_nid_postfix(void); -int APS5_VABI GENERAL_NAME_free_nid_postfix(void); +void APS5_VABI GENERAL_NAMES_free_nid_postfix(LibreSslStack* names); +void APS5_VABI GENERAL_NAME_free_nid_postfix(LibreSslGeneralName* name); int APS5_VABI HMAC_nid_postfix(void); int APS5_VABI HMAC_CTX_cleanup_nid_postfix(void); int APS5_VABI HMAC_CTX_init_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/src/GeneralName.cpp b/core/libs/prx/libSceLibreSsl/src/GeneralName.cpp new file mode 100644 index 0000000000..d6650e9c9d --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/GeneralName.cpp @@ -0,0 +1,187 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include +#include +#include + +// LibreSSL 2.5.4: x509v3/v3_genn.c, asn1/tasn_fre.c, asn1/asn1_lib.c, +// asn1/a_object.c, and asn1/x_name.c (openbsd 2e2c612066aa993717528a0249c15c51128d61cb). +// The free routines operate on guest allocations, not host OpenSSL objects. + +static_assert(sizeof(LibreSslGeneralName) == 16); +static_assert(offsetof(LibreSslGeneralName, value) == 8); +static_assert(sizeof(Asn1String) == 24); +static_assert(sizeof(LibreSslAsn1Object) == 40); + +namespace { + +constexpr std::int64_t Asn1StringNdef = 0x10; +constexpr int Asn1Boolean = 1; +constexpr int Asn1Null = 5; +constexpr int Asn1Object = 6; +constexpr int Asn1Any = -4; +constexpr int DynamicObject = 0x01; +constexpr int DynamicStrings = 0x04; +constexpr int DynamicData = 0x08; + +struct Asn1Type { + int type; + void* value; +}; + +struct OtherName { + LibreSslAsn1Object* typeId; + Asn1Type* value; +}; + +struct EdiPartyName { + Asn1String* nameAssigner; + Asn1String* partyName; +}; + +struct Buffer { + std::size_t length; + char* data; + std::size_t capacity; +}; + +struct X509NameEntry { + LibreSslAsn1Object* object; + Asn1String* value; + int set; + int size; +}; + +struct X509Name { + LibreSslStack* entries; + int modified; + Buffer* bytes; + unsigned char* canonical; + int canonicalLength; +}; + +static_assert(sizeof(Asn1Type) == 16 && offsetof(Asn1Type, value) == 8); +static_assert(sizeof(OtherName) == 16 && sizeof(EdiPartyName) == 16); +static_assert(sizeof(Buffer) == 24 && offsetof(Buffer, capacity) == 16); +static_assert(sizeof(X509NameEntry) == 24 && offsetof(X509NameEntry, set) == 16); +static_assert(sizeof(X509Name) == 40 && offsetof(X509Name, bytes) == 16); +static_assert(offsetof(X509Name, canonical) == 24); + +// Keep writes observable: unlike a normal memset this cannot be eliminated +// after the guest allocator is called, matching LibreSSL's explicit_bzero. +void Wipe(void* pointer, std::size_t size) { + auto* bytes = static_cast(pointer); + for (std::size_t i = 0; i < size; ++i) bytes[i] = 0; +} + +void FreeString(Asn1String* string) { + if (!string) return; + if (string->data && !(string->flags & Asn1StringNdef)) { + if (string->length < 0) throw std::invalid_argument("ASN1_STRING_free: negative length"); + Wipe(string->data, static_cast(string->length)); + free_nid_postfix(string->data); + } + free_nid_postfix(string); +} + +void FreeObject(LibreSslAsn1Object* object) { + if (!object) return; + if (object->flags & DynamicStrings) { + free_nid_postfix(const_cast(object->shortName)); + free_nid_postfix(const_cast(object->longName)); + } + if (object->flags & DynamicData) { + if (object->data) { + if (object->length < 0) throw std::invalid_argument("ASN1_OBJECT_free: negative length"); + Wipe(const_cast(object->data), static_cast(object->length)); + } + free_nid_postfix(const_cast(object->data)); + } + if (object->flags & DynamicObject) free_nid_postfix(object); +} + +void FreeAny(Asn1Type* any) { + if (!any) return; + if (any->value) { + if (any->type == Asn1Object) FreeObject(static_cast(any->value)); + else if (any->type == Asn1Any) FreeAny(static_cast(any->value)); + else if (any->type != Asn1Boolean && any->type != Asn1Null) FreeString(static_cast(any->value)); + } + free_nid_postfix(any); +} + +void FreeBuffer(Buffer* buffer) { + if (!buffer) return; + if (buffer->data) { + Wipe(buffer->data, buffer->capacity); + free_nid_postfix(buffer->data); + } + free_nid_postfix(buffer); +} + +void FreeX509Name(X509Name* name) { + if (!name) return; + FreeBuffer(name->bytes); + sk_pop_free_nid_postfix(name->entries, +[](void* pointer) APS5_VABI { + auto* entry = static_cast(pointer); + if (!entry) return; + // ASN1 SEQUENCE fields are released in reverse declaration order. + FreeString(entry->value); + FreeObject(entry->object); + free_nid_postfix(entry); + }); + free_nid_postfix(name->canonical); + free_nid_postfix(name); +} + +} // namespace + +extern "C" { + +void APS5_VABI GENERAL_NAME_free_nid_postfix(LibreSslGeneralName* name) { + if (!name) return; + switch (name->type) { + case 0: { // OTHERNAME: object identifier and an ASN1_TYPE + auto* other = static_cast(name->value); + if (other) { + FreeAny(other->value); + FreeObject(other->typeId); + free_nid_postfix(other); + } + break; + } + case 1: // EMAIL + case 2: // DNS + case 3: // X400 (raw ASN.1 sequence) + case 6: // URI + case 7: // IP address + FreeString(static_cast(name->value)); + break; + case 4: // X509_NAME: cached bytes, entries, canonical encoding + FreeX509Name(static_cast(name->value)); + break; + case 5: { // EDIPARTYNAME + auto* edi = static_cast(name->value); + if (edi) { + FreeString(edi->partyName); + FreeString(edi->nameAssigner); + free_nid_postfix(edi); + } + break; + } + case 8: // Registered OID + FreeObject(static_cast(name->value)); + break; + default: // Unknown ASN.1 choice: LibreSSL frees only the enclosing CHOICE + break; + } + free_nid_postfix(name); +} + +void APS5_VABI GENERAL_NAMES_free_nid_postfix(LibreSslStack* names) { + sk_pop_free_nid_postfix(names, +[](void* pointer) APS5_VABI { + GENERAL_NAME_free_nid_postfix(static_cast(pointer)); + }); +} + +} diff --git a/core/libs/tests/GuestLibreSslGeneralName.cpp b/core/libs/tests/GuestLibreSslGeneralName.cpp new file mode 100644 index 0000000000..c5bab21258 --- /dev/null +++ b/core/libs/tests/GuestLibreSslGeneralName.cpp @@ -0,0 +1,250 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using FreeName = void (APS5_VABI*)(LibreSslGeneralName*); +using FreeNames = void (APS5_VABI*)(LibreSslStack*); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(sizeof(LibreSslGeneralName) == 16); + +namespace { + +void Require(bool success, std::source_location where = std::source_location::current()) { + if (success) return; + std::fprintf(stderr, "GENERAL_NAME free test failed at line %u\n", where.line()); + std::abort(); +} + +std::unordered_map live; +std::unordered_map wipes; +std::vector freed; + +void* APS5_VABI Allocate(std::size_t bytes) { + void* pointer = std::malloc(bytes); + if (pointer) live.emplace(pointer, bytes); + return pointer; +} + +void APS5_VABI Free(void* pointer) { + if (!pointer) return; + Require(live.erase(pointer) == 1); // catches wrong-allocator and double-free + const auto it = wipes.find(pointer); + if (it != wipes.end()) { + const auto* bytes = static_cast(pointer); + for (std::size_t i = 0; i < it->second; ++i) Require(bytes[i] == 0); + wipes.erase(it); + } + freed.push_back(pointer); + std::free(pointer); +} + +void* APS5_VABI NoCalloc(std::size_t, std::size_t) { throw std::runtime_error("unexpected calloc"); } +void* APS5_VABI NoRealloc(void*, std::size_t) { throw std::runtime_error("unexpected realloc"); } +void* APS5_VABI NoAlign(std::size_t, std::size_t) { throw std::runtime_error("unexpected align"); } +void* APS5_VABI NoRealign(void*, std::size_t, std::size_t) { throw std::runtime_error("unexpected realign"); } +int APS5_VABI NoPosixAlign(void**, std::size_t, std::size_t) { throw std::runtime_error("unexpected posix_memalign"); } + +void InitializeHeap() { + void* const api[10] {reinterpret_cast(Allocate), reinterpret_cast(Free), + reinterpret_cast(NoCalloc), reinterpret_cast(NoRealloc), + reinterpret_cast(NoAlign), reinterpret_cast(NoRealign), + reinterpret_cast(NoPosixAlign)}; + ApplicationHeapRegister_nid_no_patch(api); +} + +template T* New() { + auto* pointer = static_cast(malloc_nid_postfix(sizeof(T))); + Require(pointer != nullptr); + std::memset(pointer, 0, sizeof(T)); + return pointer; +} + +unsigned char* Bytes(std::size_t size, std::size_t wipeLength = 0) { + auto* pointer = static_cast(malloc_nid_postfix(size)); + Require(pointer != nullptr); + std::memset(pointer, 0xa5, size); + if (wipeLength) wipes.emplace(pointer, wipeLength); + return pointer; +} + +Asn1String* String(int size = 7, int flags = 0) { + auto* result = New(); + result->length = size; + result->type = 22; // ASN1_IA5STRING + result->flags = flags; + if (size > 0 && !(flags & 0x10)) result->data = Bytes(static_cast(size), static_cast(size)); + return result; +} + +LibreSslAsn1Object* Object(int flags = 0x0d) { + auto* object = New(); + object->flags = flags; + if (flags & 0x04) { + object->shortName = reinterpret_cast(Bytes(6)); + object->longName = reinterpret_cast(Bytes(9)); + } + if (flags & 0x08) { + object->length = 5; + object->data = Bytes(5, 5); + } + return object; +} + +LibreSslGeneralName* Name(int type, void* value) { + auto* result = New(); + result->type = type; + result->value = value; + return result; +} + +struct Any { int type; void* value; }; +struct Other { LibreSslAsn1Object* typeId; Any* value; }; +struct Edi { Asn1String* assigner; Asn1String* party; }; +struct Buffer { std::size_t length; char* data; std::size_t capacity; }; +struct NameEntry { LibreSslAsn1Object* object; Asn1String* value; int set; int size; }; +struct X509Name { LibreSslStack* entries; int modified; Buffer* bytes; unsigned char* canonical; int canonicalLength; }; + +static_assert(sizeof(Any) == 16 && sizeof(Other) == 16 && sizeof(Edi) == 16); +static_assert(sizeof(Buffer) == 24 && sizeof(NameEntry) == 24 && sizeof(X509Name) == 40); + +LibreSslStack* Stack(const std::vector& elements) { + auto* stack = New(); + stack->num = static_cast(elements.size()); + stack->numAlloc = stack->num; + stack->data = reinterpret_cast(malloc_nid_postfix(sizeof(void*) * (elements.size() ? elements.size() : 1))); + Require(stack->data != nullptr); + for (std::size_t i = 0; i < elements.size(); ++i) stack->data[i] = static_cast(elements[i]); + return stack; +} + +void CheckClean() { + Require(live.empty()); + Require(wipes.empty()); +} + +void TestSimpleNames() { + GENERAL_NAME_free_nid_postfix(nullptr); + GENERAL_NAMES_free_nid_postfix(nullptr); + for (int choice : {1, 2, 3, 6, 7}) { + GENERAL_NAME_free_nid_postfix(Name(choice, String())); + CheckClean(); + } + auto* string = String(0); + GENERAL_NAME_free_nid_postfix(Name(1, string)); + CheckClean(); + + // NDEF strings borrow their buffer. They release only the string wrapper. + std::array borrowed{1, 2, 3, 4, 5, 6}; + string = String(0, 0x10); + string->length = static_cast(borrowed.size()); + string->data = borrowed.data(); + GENERAL_NAME_free_nid_postfix(Name(2, string)); + Require(borrowed == (std::array{1, 2, 3, 4, 5, 6})); + CheckClean(); +} + +void TestOtherName() { + for (int type : {1, 5, 6, 12, -4}) { + auto* other = New(); + other->typeId = Object(); + other->value = New(); + other->value->type = type; + if (type == 6) other->value->value = Object(0x09); // owned OID, borrowed names + if (type == 12) other->value->value = String(); + if (type == -4) { + auto* nested = New(); + nested->type = 12; + nested->value = String(); + other->value->value = nested; + } + GENERAL_NAME_free_nid_postfix(Name(0, other)); + CheckClean(); + } + // The enclosing OID may be static while the OTHERNAME structure is owned. + auto* other = New(); + static LibreSslAsn1Object staticOid{}; + other->typeId = &staticOid; + GENERAL_NAME_free_nid_postfix(Name(0, other)); + CheckClean(); +} + +void TestEdiPartyName() { + auto* edi = New(); + edi->assigner = String(); + edi->party = String(); + GENERAL_NAME_free_nid_postfix(Name(5, edi)); + CheckClean(); + edi = New(); + edi->party = String(); + GENERAL_NAME_free_nid_postfix(Name(5, edi)); + CheckClean(); +} + +void TestDirectoryName() { + auto* name = New(); + auto* buffer = New(); + buffer->capacity = 16; + buffer->length = 4; + buffer->data = reinterpret_cast(Bytes(16, 16)); // wipe capacity, not length + name->bytes = buffer; + auto* entry = New(); + entry->object = Object(); + entry->value = String(); + auto* another = New(); + another->object = Object(0x01); + another->value = String(); + name->entries = Stack({entry, nullptr, another}); + name->canonical = Bytes(12); // X509_NAME frees without wiping canon_enc + name->canonicalLength = 12; + GENERAL_NAME_free_nid_postfix(Name(4, name)); + CheckClean(); +} + +void TestOidAndStack() { + GENERAL_NAME_free_nid_postfix(Name(8, Object())); + CheckClean(); + // A dynamically created OID can own its data but not its wrapper. + static LibreSslAsn1Object staticOid{}; + staticOid.length = 5; + staticOid.flags = 0x08; + staticOid.data = Bytes(5, 5); + GENERAL_NAME_free_nid_postfix(Name(8, &staticOid)); + CheckClean(); + + auto* first = Name(1, String()); + auto* last = Name(7, String()); + freed.clear(); + GENERAL_NAMES_free_nid_postfix(Stack({first, nullptr, last})); + CheckClean(); + Require(freed.size() >= 8); // children, their data, the element array and stack + GENERAL_NAMES_free_nid_postfix(Stack({})); + CheckClean(); + + auto* unusual = Name(42, nullptr); + GENERAL_NAME_free_nid_postfix(unusual); // out-of-range choice still frees wrapper + CheckClean(); +} + +} // namespace + +int main() { + InitializeHeap(); + TestSimpleNames(); + TestOtherName(); + TestEdiPartyName(); + TestDirectoryName(); + TestOidAndStack(); + std::puts("libSceLibreSsl GENERAL_NAME tests passed"); +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 99dd5f9578..ad3e0e8359 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -4,6 +4,7 @@ - [SSL_select_next_proto](../../../core/libs/prx/libSceLibreSsl/src/ProtocolSelection.cpp) (libSceLibreSsl) follows LibreSSL 3.4.1 [`ssl_lib.c`](https://github.com/libressl/openbsd/blob/d7513e7d4daf94905fd4cb0a5e5c89109d2874f7/src/lib/libssl/ssl_lib.c), which validates neither list: a zero-length entry is negotiated, no overlap returns `supported + 1` with the unchecked length byte `supported[0]`, read even for an empty supported list, and each pair of entries with equal length bytes is compared over that many bytes, past the end of a truncated list. [Firmware 13.42 host-PC evidence](https://github.com/boykopovar/AnyPS5/pull/9785#issuecomment-6406712390) of `libSceLibreSsl.sprx` (`0xda6d0`) reports the same status, pointer and length for 700,000 valid pairs and for the six unvalidated cases. Null outputs, a null supported list and a null peer list with a non-zero length throw, where the module faults; nothing is measured on a console. - [SHA384_Init and SHA512_Init](../../../core/libs/prx/libSceLibreSsl/src/Digest.cpp) (libSceLibreSsl) - clear the 128-byte block buffer of the context, which LibreSSL 2.5.4 leaves as it was; digests are unaffected. `guest_libressl_oracle` (`ANYPS5_LIBRESSL_ORACLE`) compares the rest of the context with LibreSSL 2.5.4 +- [GENERAL_NAME_free and GENERAL_NAMES_free](../../../core/libs/prx/libSceLibreSsl/src/GeneralName.cpp) (libSceLibreSsl) - release all nine GENERAL_NAME choices and STACK_OF(GENERAL_NAME) using the guest allocator, following [LibreSSL 2.5.4 `v3_genn.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509v3/v3_genn.c) and its [ASN.1 free dispatcher](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/tasn_fre.c). ASN1_STRING and dynamically allocated OID contents are wiped before freeing; NDEF strings and static OIDs retain their borrowed memory. Nested X509_NAME entries and cached data are freed as in 2.5.4. Guest allocator tests check owned/borrowed memory and disposal of nested allocations. PS5 native module behavior and malformed-memory failure cases are not verified; negative owned data lengths throw rather than risk out-of-bounds memory erasure. ### Unknown function info From a2ff5301b3c72e532defb72adf0556dfc9c2dd53 Mon Sep 17 00:00:00 2001 From: hyvvs Date: Sat, 31 Oct 2026 05:32:11 +0100 Subject: [PATCH 2/6] build(libSceLibreSsl): isolate general name CMake registration --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 334cfe98e3..61da30f5d2 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -2,7 +2,7 @@ add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/E target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) configure_windows_unwind(libSceLibreSsl) -target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Asn1.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/ExData.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/GeneralName.cpp) +target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Asn1.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/ExData.cpp) if(BUILD_TESTING) add_test_executable(guest_libressl_x509_errors_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslX509Errors.cpp) @@ -335,3 +335,5 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/GeneralName.cpp) From bdd38e30640894ba01d5bbd91a0388053960a72d Mon Sep 17 00:00:00 2001 From: hyvvs Date: Sat, 31 Oct 2026 05:54:26 +0100 Subject: [PATCH 3/6] style(libSceLibreSsl): keep implementation commentary in debt docs --- .../prx/libSceLibreSsl/src/GeneralName.cpp | 34 +++++++++---------- core/libs/tests/GuestLibreSslGeneralName.cpp | 22 ++++++------ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/src/GeneralName.cpp b/core/libs/prx/libSceLibreSsl/src/GeneralName.cpp index d6650e9c9d..a4d084527a 100644 --- a/core/libs/prx/libSceLibreSsl/src/GeneralName.cpp +++ b/core/libs/prx/libSceLibreSsl/src/GeneralName.cpp @@ -4,9 +4,9 @@ #include #include -// LibreSSL 2.5.4: x509v3/v3_genn.c, asn1/tasn_fre.c, asn1/asn1_lib.c, -// asn1/a_object.c, and asn1/x_name.c (openbsd 2e2c612066aa993717528a0249c15c51128d61cb). -// The free routines operate on guest allocations, not host OpenSSL objects. + + + static_assert(sizeof(LibreSslGeneralName) == 16); static_assert(offsetof(LibreSslGeneralName, value) == 8); @@ -67,8 +67,8 @@ static_assert(sizeof(X509NameEntry) == 24 && offsetof(X509NameEntry, set) == 16) static_assert(sizeof(X509Name) == 40 && offsetof(X509Name, bytes) == 16); static_assert(offsetof(X509Name, canonical) == 24); -// Keep writes observable: unlike a normal memset this cannot be eliminated -// after the guest allocator is called, matching LibreSSL's explicit_bzero. + + void Wipe(void* pointer, std::size_t size) { auto* bytes = static_cast(pointer); for (std::size_t i = 0; i < size; ++i) bytes[i] = 0; @@ -125,7 +125,7 @@ void FreeX509Name(X509Name* name) { sk_pop_free_nid_postfix(name->entries, +[](void* pointer) APS5_VABI { auto* entry = static_cast(pointer); if (!entry) return; - // ASN1 SEQUENCE fields are released in reverse declaration order. + FreeString(entry->value); FreeObject(entry->object); free_nid_postfix(entry); @@ -134,14 +134,14 @@ void FreeX509Name(X509Name* name) { free_nid_postfix(name); } -} // namespace +} extern "C" { void APS5_VABI GENERAL_NAME_free_nid_postfix(LibreSslGeneralName* name) { if (!name) return; switch (name->type) { - case 0: { // OTHERNAME: object identifier and an ASN1_TYPE + case 0: { auto* other = static_cast(name->value); if (other) { FreeAny(other->value); @@ -150,17 +150,17 @@ void APS5_VABI GENERAL_NAME_free_nid_postfix(LibreSslGeneralName* name) { } break; } - case 1: // EMAIL - case 2: // DNS - case 3: // X400 (raw ASN.1 sequence) - case 6: // URI - case 7: // IP address + case 1: + case 2: + case 3: + case 6: + case 7: FreeString(static_cast(name->value)); break; - case 4: // X509_NAME: cached bytes, entries, canonical encoding + case 4: FreeX509Name(static_cast(name->value)); break; - case 5: { // EDIPARTYNAME + case 5: { auto* edi = static_cast(name->value); if (edi) { FreeString(edi->partyName); @@ -169,10 +169,10 @@ void APS5_VABI GENERAL_NAME_free_nid_postfix(LibreSslGeneralName* name) { } break; } - case 8: // Registered OID + case 8: FreeObject(static_cast(name->value)); break; - default: // Unknown ASN.1 choice: LibreSSL frees only the enclosing CHOICE + default: break; } free_nid_postfix(name); diff --git a/core/libs/tests/GuestLibreSslGeneralName.cpp b/core/libs/tests/GuestLibreSslGeneralName.cpp index c5bab21258..36ff336975 100644 --- a/core/libs/tests/GuestLibreSslGeneralName.cpp +++ b/core/libs/tests/GuestLibreSslGeneralName.cpp @@ -39,7 +39,7 @@ void* APS5_VABI Allocate(std::size_t bytes) { void APS5_VABI Free(void* pointer) { if (!pointer) return; - Require(live.erase(pointer) == 1); // catches wrong-allocator and double-free + Require(live.erase(pointer) == 1); const auto it = wipes.find(pointer); if (it != wipes.end()) { const auto* bytes = static_cast(pointer); @@ -82,7 +82,7 @@ unsigned char* Bytes(std::size_t size, std::size_t wipeLength = 0) { Asn1String* String(int size = 7, int flags = 0) { auto* result = New(); result->length = size; - result->type = 22; // ASN1_IA5STRING + result->type = 22; result->flags = flags; if (size > 0 && !(flags & 0x10)) result->data = Bytes(static_cast(size), static_cast(size)); return result; @@ -145,7 +145,7 @@ void TestSimpleNames() { GENERAL_NAME_free_nid_postfix(Name(1, string)); CheckClean(); - // NDEF strings borrow their buffer. They release only the string wrapper. + std::array borrowed{1, 2, 3, 4, 5, 6}; string = String(0, 0x10); string->length = static_cast(borrowed.size()); @@ -161,7 +161,7 @@ void TestOtherName() { other->typeId = Object(); other->value = New(); other->value->type = type; - if (type == 6) other->value->value = Object(0x09); // owned OID, borrowed names + if (type == 6) other->value->value = Object(0x09); if (type == 12) other->value->value = String(); if (type == -4) { auto* nested = New(); @@ -172,7 +172,7 @@ void TestOtherName() { GENERAL_NAME_free_nid_postfix(Name(0, other)); CheckClean(); } - // The enclosing OID may be static while the OTHERNAME structure is owned. + auto* other = New(); static LibreSslAsn1Object staticOid{}; other->typeId = &staticOid; @@ -197,7 +197,7 @@ void TestDirectoryName() { auto* buffer = New(); buffer->capacity = 16; buffer->length = 4; - buffer->data = reinterpret_cast(Bytes(16, 16)); // wipe capacity, not length + buffer->data = reinterpret_cast(Bytes(16, 16)); name->bytes = buffer; auto* entry = New(); entry->object = Object(); @@ -206,7 +206,7 @@ void TestDirectoryName() { another->object = Object(0x01); another->value = String(); name->entries = Stack({entry, nullptr, another}); - name->canonical = Bytes(12); // X509_NAME frees without wiping canon_enc + name->canonical = Bytes(12); name->canonicalLength = 12; GENERAL_NAME_free_nid_postfix(Name(4, name)); CheckClean(); @@ -215,7 +215,7 @@ void TestDirectoryName() { void TestOidAndStack() { GENERAL_NAME_free_nid_postfix(Name(8, Object())); CheckClean(); - // A dynamically created OID can own its data but not its wrapper. + static LibreSslAsn1Object staticOid{}; staticOid.length = 5; staticOid.flags = 0x08; @@ -228,16 +228,16 @@ void TestOidAndStack() { freed.clear(); GENERAL_NAMES_free_nid_postfix(Stack({first, nullptr, last})); CheckClean(); - Require(freed.size() >= 8); // children, their data, the element array and stack + Require(freed.size() >= 8); GENERAL_NAMES_free_nid_postfix(Stack({})); CheckClean(); auto* unusual = Name(42, nullptr); - GENERAL_NAME_free_nid_postfix(unusual); // out-of-range choice still frees wrapper + GENERAL_NAME_free_nid_postfix(unusual); CheckClean(); } -} // namespace +} int main() { InitializeHeap(); From 8be94c330d7a4a97a792b05bb13e3a0b6b67a31f Mon Sep 17 00:00:00 2001 From: hyvvs Date: Sat, 31 Oct 2026 05:58:16 +0100 Subject: [PATCH 4/6] fix(libSceLibreSsl): clear retained OID ownership after free --- .../libs/prx/libSceLibreSsl/src/GeneralName.cpp | 4 ++++ core/libs/tests/GuestLibreSslGeneralName.cpp | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/core/libs/prx/libSceLibreSsl/src/GeneralName.cpp b/core/libs/prx/libSceLibreSsl/src/GeneralName.cpp index a4d084527a..7ebbf9cef3 100644 --- a/core/libs/prx/libSceLibreSsl/src/GeneralName.cpp +++ b/core/libs/prx/libSceLibreSsl/src/GeneralName.cpp @@ -89,6 +89,8 @@ void FreeObject(LibreSslAsn1Object* object) { if (object->flags & DynamicStrings) { free_nid_postfix(const_cast(object->shortName)); free_nid_postfix(const_cast(object->longName)); + object->shortName = nullptr; + object->longName = nullptr; } if (object->flags & DynamicData) { if (object->data) { @@ -96,6 +98,8 @@ void FreeObject(LibreSslAsn1Object* object) { Wipe(const_cast(object->data), static_cast(object->length)); } free_nid_postfix(const_cast(object->data)); + object->data = nullptr; + object->length = 0; } if (object->flags & DynamicObject) free_nid_postfix(object); } diff --git a/core/libs/tests/GuestLibreSslGeneralName.cpp b/core/libs/tests/GuestLibreSslGeneralName.cpp index 36ff336975..978f0f0b09 100644 --- a/core/libs/tests/GuestLibreSslGeneralName.cpp +++ b/core/libs/tests/GuestLibreSslGeneralName.cpp @@ -221,6 +221,23 @@ void TestOidAndStack() { staticOid.flags = 0x08; staticOid.data = Bytes(5, 5); GENERAL_NAME_free_nid_postfix(Name(8, &staticOid)); + Require(staticOid.data == nullptr); + Require(staticOid.length == 0); + GENERAL_NAME_free_nid_postfix(Name(8, &staticOid)); + CheckClean(); + + static LibreSslAsn1Object staticNamedOid{}; + staticNamedOid.flags = 0x0c; + staticNamedOid.shortName = reinterpret_cast(Bytes(6)); + staticNamedOid.longName = reinterpret_cast(Bytes(9)); + staticNamedOid.length = 7; + staticNamedOid.data = Bytes(7, 7); + GENERAL_NAME_free_nid_postfix(Name(8, &staticNamedOid)); + Require(staticNamedOid.shortName == nullptr); + Require(staticNamedOid.longName == nullptr); + Require(staticNamedOid.data == nullptr); + Require(staticNamedOid.length == 0); + GENERAL_NAME_free_nid_postfix(Name(8, &staticNamedOid)); CheckClean(); auto* first = Name(1, String()); From 464f2ade8c75649a610dfdffee5b04e175971cbd Mon Sep 17 00:00:00 2001 From: hyvvs Date: Sat, 31 Oct 2026 06:00:19 +0100 Subject: [PATCH 5/6] build(libSceLibreSsl): keep general-name source registration independent --- core/libs/prx/libSceLibreSsl/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index 61da30f5d2..e4635639c7 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -264,6 +264,7 @@ if(BUILD_TESTING) endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/X509VerifyParam.cpp) +target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/GeneralName.cpp) if(BUILD_TESTING) add_test_executable(guest_libressl_x509_verify_param_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/X509VerifyParam.cpp) @@ -335,5 +336,3 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() - -target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/GeneralName.cpp) From 3fa90e655ad37ba7dc4292fa4c4a3f051eec40d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0?= Date: Sat, 31 Oct 2026 22:43:58 +0100 Subject: [PATCH 6/6] style(libSceLibreSsl): drop code comment and stray blank lines Co-Authored-By: Claude Sonnet 5.5 --- core/libs/prx/libSceLibreSsl/include/Api.hpp | 1 - core/libs/prx/libSceLibreSsl/src/GeneralName.cpp | 6 ------ core/libs/tests/GuestLibreSslGeneralName.cpp | 1 - 3 files changed, 8 deletions(-) diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 19e6f7a373..3e3d9b7601 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -9,7 +9,6 @@ struct Asn1String { std::int64_t flags; }; -// LibreSSL 2.5.4 GENERAL_NAME: a 32-bit CHOICE selector and a pointer union. struct LibreSslGeneralName { std::int32_t type; void* value; diff --git a/core/libs/prx/libSceLibreSsl/src/GeneralName.cpp b/core/libs/prx/libSceLibreSsl/src/GeneralName.cpp index 7ebbf9cef3..6c9d5bcabb 100644 --- a/core/libs/prx/libSceLibreSsl/src/GeneralName.cpp +++ b/core/libs/prx/libSceLibreSsl/src/GeneralName.cpp @@ -4,10 +4,6 @@ #include #include - - - - static_assert(sizeof(LibreSslGeneralName) == 16); static_assert(offsetof(LibreSslGeneralName, value) == 8); static_assert(sizeof(Asn1String) == 24); @@ -67,8 +63,6 @@ static_assert(sizeof(X509NameEntry) == 24 && offsetof(X509NameEntry, set) == 16) static_assert(sizeof(X509Name) == 40 && offsetof(X509Name, bytes) == 16); static_assert(offsetof(X509Name, canonical) == 24); - - void Wipe(void* pointer, std::size_t size) { auto* bytes = static_cast(pointer); for (std::size_t i = 0; i < size; ++i) bytes[i] = 0; diff --git a/core/libs/tests/GuestLibreSslGeneralName.cpp b/core/libs/tests/GuestLibreSslGeneralName.cpp index 978f0f0b09..ccc3e2093a 100644 --- a/core/libs/tests/GuestLibreSslGeneralName.cpp +++ b/core/libs/tests/GuestLibreSslGeneralName.cpp @@ -145,7 +145,6 @@ void TestSimpleNames() { GENERAL_NAME_free_nid_postfix(Name(1, string)); CheckClean(); - std::array borrowed{1, 2, 3, 4, 5, 6}; string = String(0, 0x10); string->length = static_cast(borrowed.size());