Merge pull request #13760 from hyvvs/feat/libressl-general-name-free

feat(libSceLibreSsl): free GENERAL_NAME and GENERAL_NAMES allocations
This commit is contained in:
Adrià authored and GitHub committed 2026-11-01 00:54:58 +01:00
commit 8a4fc62318
6 files changed
+471 -12

No files matched your search

@@ -106,6 +106,17 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceLibreSsl>")
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:$<TARGET_FILE_DIR:libSceLibreSsl>")
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)
@@ -253,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)
-10
View File
@@ -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;
+7 -2
View File
@@ -9,6 +9,11 @@ struct Asn1String {
std::int64_t flags;
};
struct LibreSslGeneralName {
std::int32_t type;
void* value;
};
struct LibreSslX509VerifyParamId {
void* hosts;
std::uint32_t hostFlags;
@@ -395,8 +400,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);
@@ -0,0 +1,185 @@
#include "prx/libSceLibreSsl/include/Api.hpp"
#include "prx/libc/include/Api.hpp"
#include <cstddef>
#include <cstdint>
#include <stdexcept>
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);
void Wipe(void* pointer, std::size_t size) {
auto* bytes = static_cast<volatile unsigned char*>(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<std::size_t>(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<char*>(object->shortName));
free_nid_postfix(const_cast<char*>(object->longName));
object->shortName = nullptr;
object->longName = nullptr;
}
if (object->flags & DynamicData) {
if (object->data) {
if (object->length < 0) throw std::invalid_argument("ASN1_OBJECT_free: negative length");
Wipe(const_cast<unsigned char*>(object->data), static_cast<std::size_t>(object->length));
}
free_nid_postfix(const_cast<unsigned char*>(object->data));
object->data = nullptr;
object->length = 0;
}
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<LibreSslAsn1Object*>(any->value));
else if (any->type == Asn1Any) FreeAny(static_cast<Asn1Type*>(any->value));
else if (any->type != Asn1Boolean && any->type != Asn1Null) FreeString(static_cast<Asn1String*>(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<X509NameEntry*>(pointer);
if (!entry) return;
FreeString(entry->value);
FreeObject(entry->object);
free_nid_postfix(entry);
});
free_nid_postfix(name->canonical);
free_nid_postfix(name);
}
}
extern "C" {
void APS5_VABI GENERAL_NAME_free_nid_postfix(LibreSslGeneralName* name) {
if (!name) return;
switch (name->type) {
case 0: {
auto* other = static_cast<OtherName*>(name->value);
if (other) {
FreeAny(other->value);
FreeObject(other->typeId);
free_nid_postfix(other);
}
break;
}
case 1:
case 2:
case 3:
case 6:
case 7:
FreeString(static_cast<Asn1String*>(name->value));
break;
case 4:
FreeX509Name(static_cast<X509Name*>(name->value));
break;
case 5: {
auto* edi = static_cast<EdiPartyName*>(name->value);
if (edi) {
FreeString(edi->partyName);
FreeString(edi->nameAssigner);
free_nid_postfix(edi);
}
break;
}
case 8:
FreeObject(static_cast<LibreSslAsn1Object*>(name->value));
break;
default:
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<LibreSslGeneralName*>(pointer));
});
}
}
@@ -0,0 +1,266 @@
#include "prx/libSceLibreSsl/include/Api.hpp"
#include "prx/libc/include/Api.hpp"
#include "prx/libc/include/ApplicationHeap.hpp"
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <source_location>
#include <stdexcept>
#include <type_traits>
#include <unordered_map>
#include <vector>
using FreeName = void (APS5_VABI*)(LibreSslGeneralName*);
using FreeNames = void (APS5_VABI*)(LibreSslStack*);
static_assert(std::is_same_v<decltype(&GENERAL_NAME_free_nid_postfix), FreeName>);
static_assert(std::is_same_v<decltype(&GENERAL_NAMES_free_nid_postfix), FreeNames>);
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<void*, std::size_t> live;
std::unordered_map<void*, std::size_t> wipes;
std::vector<void*> 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);
const auto it = wipes.find(pointer);
if (it != wipes.end()) {
const auto* bytes = static_cast<const unsigned char*>(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<void*>(Allocate), reinterpret_cast<void*>(Free),
reinterpret_cast<void*>(NoCalloc), reinterpret_cast<void*>(NoRealloc),
reinterpret_cast<void*>(NoAlign), reinterpret_cast<void*>(NoRealign),
reinterpret_cast<void*>(NoPosixAlign)};
ApplicationHeapRegister_nid_no_patch(api);
}
template<typename T> T* New() {
auto* pointer = static_cast<T*>(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<unsigned char*>(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<Asn1String>();
result->length = size;
result->type = 22;
result->flags = flags;
if (size > 0 && !(flags & 0x10)) result->data = Bytes(static_cast<std::size_t>(size), static_cast<std::size_t>(size));
return result;
}
LibreSslAsn1Object* Object(int flags = 0x0d) {
auto* object = New<LibreSslAsn1Object>();
object->flags = flags;
if (flags & 0x04) {
object->shortName = reinterpret_cast<char*>(Bytes(6));
object->longName = reinterpret_cast<char*>(Bytes(9));
}
if (flags & 0x08) {
object->length = 5;
object->data = Bytes(5, 5);
}
return object;
}
LibreSslGeneralName* Name(int type, void* value) {
auto* result = New<LibreSslGeneralName>();
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<void*>& elements) {
auto* stack = New<LibreSslStack>();
stack->num = static_cast<int>(elements.size());
stack->numAlloc = stack->num;
stack->data = reinterpret_cast<char**>(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<char*>(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();
std::array<unsigned char, 6> borrowed{1, 2, 3, 4, 5, 6};
string = String(0, 0x10);
string->length = static_cast<int>(borrowed.size());
string->data = borrowed.data();
GENERAL_NAME_free_nid_postfix(Name(2, string));
Require(borrowed == (std::array<unsigned char, 6>{1, 2, 3, 4, 5, 6}));
CheckClean();
}
void TestOtherName() {
for (int type : {1, 5, 6, 12, -4}) {
auto* other = New<Other>();
other->typeId = Object();
other->value = New<Any>();
other->value->type = type;
if (type == 6) other->value->value = Object(0x09);
if (type == 12) other->value->value = String();
if (type == -4) {
auto* nested = New<Any>();
nested->type = 12;
nested->value = String();
other->value->value = nested;
}
GENERAL_NAME_free_nid_postfix(Name(0, other));
CheckClean();
}
auto* other = New<Other>();
static LibreSslAsn1Object staticOid{};
other->typeId = &staticOid;
GENERAL_NAME_free_nid_postfix(Name(0, other));
CheckClean();
}
void TestEdiPartyName() {
auto* edi = New<Edi>();
edi->assigner = String();
edi->party = String();
GENERAL_NAME_free_nid_postfix(Name(5, edi));
CheckClean();
edi = New<Edi>();
edi->party = String();
GENERAL_NAME_free_nid_postfix(Name(5, edi));
CheckClean();
}
void TestDirectoryName() {
auto* name = New<X509Name>();
auto* buffer = New<Buffer>();
buffer->capacity = 16;
buffer->length = 4;
buffer->data = reinterpret_cast<char*>(Bytes(16, 16));
name->bytes = buffer;
auto* entry = New<NameEntry>();
entry->object = Object();
entry->value = String();
auto* another = New<NameEntry>();
another->object = Object(0x01);
another->value = String();
name->entries = Stack({entry, nullptr, another});
name->canonical = Bytes(12);
name->canonicalLength = 12;
GENERAL_NAME_free_nid_postfix(Name(4, name));
CheckClean();
}
void TestOidAndStack() {
GENERAL_NAME_free_nid_postfix(Name(8, Object()));
CheckClean();
static LibreSslAsn1Object staticOid{};
staticOid.length = 5;
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<char*>(Bytes(6));
staticNamedOid.longName = reinterpret_cast<char*>(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());
auto* last = Name(7, String());
freed.clear();
GENERAL_NAMES_free_nid_postfix(Stack({first, nullptr, last}));
CheckClean();
Require(freed.size() >= 8);
GENERAL_NAMES_free_nid_postfix(Stack({}));
CheckClean();
auto* unusual = Name(42, nullptr);
GENERAL_NAME_free_nid_postfix(unusual);
CheckClean();
}
}
int main() {
InitializeHeap();
TestSimpleNames();
TestOtherName();
TestEdiPartyName();
TestDirectoryName();
TestOidAndStack();
std::puts("libSceLibreSsl GENERAL_NAME tests passed");
}
+1
View File
@@ -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