diff --git a/.gitmodules b/.gitmodules index 64a98a2f94..6388438273 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,9 @@ [submodule "3rdparty/SDL2"] path = 3rdparty/SDL2 url = https://github.com/libsdl-org/SDL.git +[submodule "3rdparty/libressl-openbsd"] + path = 3rdparty/libressl-openbsd + url = https://github.com/libressl/openbsd.git [submodule "3rdparty/Vulkan-Headers"] path = 3rdparty/Vulkan-Headers url = https://github.com/KhronosGroup/Vulkan-Headers.git diff --git a/3rdparty/libressl-openbsd b/3rdparty/libressl-openbsd new file mode 160000 index 0000000000..078b3d1df8 --- /dev/null +++ b/3rdparty/libressl-openbsd @@ -0,0 +1 @@ +Subproject commit 078b3d1df8ca2011c1605cd40418c58fa0f5b143 diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index f84fa93f40..a6a4209dc7 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -186,6 +186,7 @@ foreach(LIB_DIR ${LIBS_DIRS}) endif() endforeach() +include(prx/libSceLibreSsl/ErrorQueue.cmake) target_link_libraries(libSceUsbStorageDialog PRIVATE libSceUserService) if(APS5_ENABLE_TRACY) diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt deleted file mode 100644 index 572e1cacd5..0000000000 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp) -target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR}) -configure_windows_unwind(libSceLibreSsl) - -if(BUILD_TESTING) - add_test_executable(guest_libressl_error_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslError.cpp) - target_include_directories(guest_libressl_error_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_libressl_error_tests PRIVATE libSceLibreSsl libc) - configure_windows_unwind(guest_libressl_error_tests) - add_test(NAME guest_libressl_error COMMAND guest_libressl_error_tests) - set_tests_properties(guest_libressl_error PROPERTIES TIMEOUT 30) - if(WIN32) - set_tests_properties(guest_libressl_error PROPERTIES ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") - endif() -endif() diff --git a/core/libs/prx/libSceLibreSsl/ErrorQueue.cmake b/core/libs/prx/libSceLibreSsl/ErrorQueue.cmake new file mode 100644 index 0000000000..d97fcd55cf --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/ErrorQueue.cmake @@ -0,0 +1,37 @@ +find_package(Python3 REQUIRED COMPONENTS Interpreter) +set(libreSslSource ${PROJECT_SOURCE_DIR}/3rdparty/libressl-openbsd/src/lib/libcrypto) +if(NOT EXISTS ${libreSslSource}/err/err.c) + message(FATAL_ERROR "Initialize 3rdparty/libressl-openbsd with git submodule update --init") +endif() +file(GLOB_RECURSE libreSslStringSources CONFIGURE_DEPENDS ${libreSslSource}/*.h ${libreSslSource}/*.c) +set(libreSslStrings ${CMAKE_CURRENT_BINARY_DIR}/prx/libSceLibreSsl/ErrorStrings.inc) +add_custom_command(OUTPUT ${libreSslStrings} + COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/GenerateErrorStrings.py ${libreSslSource} ${libreSslStrings} + DEPENDS ${CMAKE_CURRENT_LIST_DIR}/GenerateErrorStrings.py ${libreSslStringSources} + VERBATIM) +target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src/Error.cpp + ${CMAKE_CURRENT_LIST_DIR}/ErrorQueue.cpp ${libreSslStrings}) +target_include_directories(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/prx/libSceLibreSsl) +target_link_libraries(libSceLibreSsl3 PRIVATE libSceLibreSsl) + +if(BUILD_TESTING) + add_test_executable(guest_libressl_error_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslError.cpp) + target_include_directories(guest_libressl_error_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_error_tests PRIVATE libSceLibreSsl libSceLibreSsl3) + configure_windows_unwind(guest_libressl_error_tests) + add_test(NAME guest_libressl_error COMMAND guest_libressl_error_tests) + add_test_executable(guest_libressl_error_string_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslErrors.cpp) + target_include_directories(guest_libressl_error_string_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_error_string_tests PRIVATE libSceLibreSsl libSceLibreSsl3) + configure_windows_unwind(guest_libressl_error_string_tests) + add_test(NAME guest_libressl_errors COMMAND guest_libressl_error_string_tests) + add_test(NAME guest_libressl_free_strings COMMAND guest_libressl_error_string_tests free) + add_test(NAME guest_libressl_free_strings_first COMMAND guest_libressl_error_string_tests free-first) + set_tests_properties(guest_libressl_error guest_libressl_errors guest_libressl_free_strings + guest_libressl_free_strings_first PROPERTIES TIMEOUT 15) + if(WIN32) + set_tests_properties(guest_libressl_error guest_libressl_errors guest_libressl_free_strings + guest_libressl_free_strings_first PROPERTIES + ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceLibreSsl/ErrorQueue.cpp b/core/libs/prx/libSceLibreSsl/ErrorQueue.cpp new file mode 100644 index 0000000000..713520b667 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/ErrorQueue.cpp @@ -0,0 +1,102 @@ +#include "ErrorQueue.hpp" +#include "prx/libc/include/General.hpp" +#include +#include +#include +#include +#include + +extern "C" int APS5_VABI strerror_r_nid_postfix(int error, char* buffer, std::size_t length); + +namespace { +struct ErrorString { + std::uint32_t code; + const char* text; +}; + +#include "ErrorStrings.inc" + +enum class StringState { Uninitialized, Loaded, Freed }; +std::atomic stringState{StringState::Uninitialized}; +char errorBuffer[256]; + +constexpr std::uint64_t Pack(int library, int function, int reason) { + return ((static_cast(library) & 255) << 24) | + ((static_cast(function) & 4095) << 12) | + (static_cast(reason) & 4095); +} + +const char* Lookup(std::uint32_t code) { + const auto entry = std::lower_bound(std::begin(CryptoStrings), std::end(CryptoStrings), code, + [](const ErrorString& value, std::uint32_t key) { return value.code < key; }); + return entry != std::end(CryptoStrings) && entry->code == code ? entry->text : nullptr; +} +} + +extern "C" { +void LibreSslLoadErrorStrings_nid_no_patch() { + auto expected = StringState::Uninitialized; + stringState.compare_exchange_strong(expected, StringState::Loaded); +} + +char* APS5_VABI ERR_error_string_nid_postfix(std::uint64_t error, char* buffer) { + if (buffer == nullptr) buffer = errorBuffer; + ERR_error_string_n_nid_postfix(error, buffer, sizeof(errorBuffer)); + return buffer; +} + +void APS5_VABI ERR_error_string_n_nid_postfix(std::uint64_t error, char* buffer, std::size_t length) { + if (buffer == nullptr && length != 0) NotImplemented_nid_no_patch(__func__); + const int library = (error >> 24) & 255; + const int function = (error >> 12) & 4095; + const int reason = error & 4095; + LibreSslLoadErrorStrings_nid_no_patch(); + const bool loaded = stringState.load() == StringState::Loaded; + const char* libraryText = loaded ? Lookup(static_cast(Pack(library, 0, 0))) : nullptr; + const char* functionText = loaded ? Lookup(static_cast(Pack(library, function, 0))) : nullptr; + const char* reasonText = loaded ? Lookup(static_cast(Pack(library, 0, reason))) : nullptr; + char libraryBuffer[30]; + char functionBuffer[30]; + char reasonBuffer[32]; + if (loaded && library == 2 && reason >= 1 && reason <= 127) { + char message[128]; + strerror_r_nid_postfix(reason, message, sizeof(message)); + std::memcpy(reasonBuffer, message, std::min(std::strlen(message) + 1, sizeof(reasonBuffer))); + reasonBuffer[sizeof(reasonBuffer) - 1] = '\0'; + reasonText = reasonBuffer; + } + if (reasonText == nullptr && loaded) reasonText = Lookup(reason); + if (libraryText == nullptr) { + std::snprintf(libraryBuffer, sizeof(libraryBuffer), "lib(%d)", library); + libraryText = libraryBuffer; + } + if (functionText == nullptr) { + std::snprintf(functionBuffer, sizeof(functionBuffer), "func(%d)", function); + functionText = functionBuffer; + } + if (reasonText == nullptr) { + std::snprintf(reasonBuffer, sizeof(reasonBuffer), "reason(%d)", reason); + reasonText = reasonBuffer; + } + const int written = std::snprintf(buffer, length, "error:%08llX:%s:%s:%s", + static_cast(error), libraryText, functionText, reasonText); + if (written >= 0 && static_cast(written) >= length && length > 4) { + char* next = buffer; + for (std::size_t index = 0; index < 4; ++index) { + char* colon = std::strchr(next, ':'); + char* last = buffer + length - 5 + index; + if (colon == nullptr || colon > last) { + colon = last; + *colon = ':'; + } + next = colon + 1; + } + } +} + +void APS5_VABI ERR_free_strings_nid_postfix() { + LibreSslLoadErrorStrings_nid_no_patch(); + stringState.store(StringState::Freed); +} + +} diff --git a/core/libs/prx/libSceLibreSsl/ErrorQueue.hpp b/core/libs/prx/libSceLibreSsl/ErrorQueue.hpp new file mode 100644 index 0000000000..3137a735c1 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/ErrorQueue.hpp @@ -0,0 +1,22 @@ +#ifndef CORE_LIBS_PRX_LIBSCELIBRESSL_ERRORQUEUE_HPP +#define CORE_LIBS_PRX_LIBSCELIBRESSL_ERRORQUEUE_HPP + +#include +#include +#include "include/Error.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +extern "C" { +std::uint64_t LibreSslPeekLastError_nid_no_patch(); +void LibreSslLoadErrorStrings_nid_no_patch(); + +void APS5_VABI ERR_clear_error_nid_postfix(); +char* APS5_VABI ERR_error_string_nid_postfix(std::uint64_t error, char* buffer); +void APS5_VABI ERR_error_string_n_nid_postfix(std::uint64_t error, char* buffer, std::size_t length); +void APS5_VABI ERR_free_strings_nid_postfix(); +std::uint64_t APS5_VABI ERR_get_error_nid_postfix(); +std::uint64_t APS5_VABI ERR_peek_error_nid_postfix(); +void APS5_VABI ERR_remove_thread_state_nid_postfix(const void* id); +} + +#endif diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index 42a74528f2..1b7c846324 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -506,21 +506,6 @@ int APS5_VABI ENGINE_set_default_nid_postfix(void) { return 0; } -int APS5_VABI ERR_error_string_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI ERR_error_string_n_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI ERR_free_strings_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI EVP_CIPHER_CTX_cleanup_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibreSsl/GenerateErrorStrings.py b/core/libs/prx/libSceLibreSsl/GenerateErrorStrings.py new file mode 100644 index 0000000000..15f265bef2 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/GenerateErrorStrings.py @@ -0,0 +1,97 @@ +import ast +import json +import operator +from pathlib import Path +import re +import sys + + +source = Path(sys.argv[1]) +destination = Path(sys.argv[2]) +macros = {} +operations = {ast.BitOr: operator.or_, ast.BitAnd: operator.and_, ast.LShift: operator.lshift, + ast.RShift: operator.rshift, ast.Add: operator.add, ast.Sub: operator.sub} + + +def uncomment(text): + return re.sub(r"/\*.*?\*/", "", text, flags=re.S) + + +for header in sorted(source.rglob("*.h")): + for name, value in re.findall(r"^#\s*define\s+(\w+)\s+([^\n]+)", uncomment(header.read_text(encoding="utf-8")), re.M): + if re.fullmatch(r"ERR_(?:LIB|R)_\w+|\w+_[RF]_\w+", name): + if name in macros and macros[name] != value.strip(): + raise ValueError(f"Conflicting definition: {name}") + macros[name] = value.strip() + + +def evaluate(expression): + def visit(node): + if isinstance(node, ast.Constant) and type(node.value) is int: + return node.value + if isinstance(node, ast.Name): + return evaluate(macros[node.id]) + if isinstance(node, ast.BinOp) and type(node.op) in operations: + return operations[type(node.op)](visit(node.left), visit(node.right)) + raise ValueError(f"Unsupported constant: {expression}") + return visit(ast.parse(expression, mode="eval").body) + + +def pack(library, function, reason): + return ((library & 255) << 24) | ((function & 4095) << 12) | (reason & 4095) + + +def code(expression, library): + call = re.fullmatch(r"ERR_(PACK|FUNC|REASON)\((.*)\)", expression.strip()) + if call: + values = [evaluate(value.strip()) for value in call[2].split(",")] + if call[1] == "PACK": + return pack(*values) + if call[1] == "FUNC": + return pack(library, values[0], 0) + return pack(library, 0, values[0]) + return evaluate(expression.strip()) + + +loader = uncomment((source / "err/err_all.c").read_text(encoding="utf-8")) +loaded = set(re.findall(r"\bERR_load_(\w+)_strings\(\);", loader)) - {"ERR"} +strings = {} +licenses = [] +found = set() +for path in sorted(source.rglob("*.c")): + text = path.read_text(encoding="latin-1") + clean = uncomment(text) + library = 0 + if path != source / "err/err.c": + names = set(re.findall(r"\bERR_load_(\w+)_strings\(void\)", clean)) & loaded + if not names: + continue + found.update(names) + library_name = re.search(r"#define ERR_FUNC\(func\)\s+ERR_PACK\((\w+),", clean) + if not library_name: + raise ValueError(f"Missing library: {path}") + library = evaluate(library_name[1]) + licenses.append(text[:text.index("#include")]) + tables = re.findall(r"static ERR_STRING_DATA (\w+)\[\]\s*=\s*\{(.*?)\n\};", clean, re.S) + if not tables: + raise ValueError(f"Missing string tables: {path}") + for name, body in tables: + entries = re.findall(r'\{\s*([^{}]+?),\s*("(?:[^"\\]|\\.)*"|NULL)\s*\}', body, re.S) + remainder = re.sub(r'\{\s*([^{}]+?),\s*("(?:[^"\\]|\\.)*"|NULL)\s*\}', "", body, flags=re.S) + if remainder.strip(" \t\r\n,"): + raise ValueError(f"Unsupported table: {path}: {name}") + for expression, message in entries: + value = code(expression, library) + if value == 0 and message == "NULL": + continue + if message == "NULL": + raise ValueError(f"Missing message: {path}: {expression}") + if name == "ERR_str_functs": + value |= pack(evaluate("ERR_LIB_SYS"), 0, 0) + strings[value] = json.loads(message) +if found != loaded: + raise ValueError(f"Missing libraries: {loaded - found}") +destination.parent.mkdir(parents=True, exist_ok=True) +destination.write_text("\n".join(licenses) + "\nconstexpr ErrorString CryptoStrings[] = {\n" + + "".join(f" {{0x{value:08X}, {json.dumps(message)}}},\n" + for value, message in sorted(strings.items())) + "};\n", encoding="utf-8") diff --git a/core/libs/prx/libSceLibreSsl/include/Error.hpp b/core/libs/prx/libSceLibreSsl/include/Error.hpp index 0c7a23712f..95d5c2732d 100644 --- a/core/libs/prx/libSceLibreSsl/include/Error.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Error.hpp @@ -3,7 +3,7 @@ extern "C" { -void LibreSslPutError_nid_no_patch(int library, int function, int reason); +void LibreSslPutError_nid_no_patch(int library, int function, int reason, const char* file = nullptr, int line = 0); } diff --git a/core/libs/prx/libSceLibreSsl/src/Error.cpp b/core/libs/prx/libSceLibreSsl/src/Error.cpp index 5de962c436..6ca8ca6164 100644 --- a/core/libs/prx/libSceLibreSsl/src/Error.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Error.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include "prx/libSceLibreSsl/include/Error.hpp" @@ -9,6 +10,8 @@ constexpr int QueueSize = 16; struct ErrorQueue { std::array codes; + std::array files; + std::array lines; int top; int bottom; }; @@ -24,6 +27,8 @@ std::uint64_t Oldest(bool remove) { if (remove) { queue.bottom = index; queue.codes[index] = 0; + queue.files[index] = nullptr; + queue.lines[index] = 0; } return code; } @@ -35,12 +40,20 @@ void Clear() { extern "C" { -void LibreSslPutError_nid_no_patch(int library, int function, int reason) { +void LibreSslPutError_nid_no_patch(int library, int function, int reason, const char* file, int line) { + const int savedError = errno; queue.top = (queue.top + 1) % QueueSize; if (queue.top == queue.bottom) queue.bottom = (queue.bottom + 1) % QueueSize; queue.codes[queue.top] = (static_cast(library) & 0xff) << 24 | (static_cast(function) & 0xfff) << 12 | (static_cast(reason) & 0xfff); + queue.files[queue.top] = file; + queue.lines[queue.top] = line; + errno = savedError; +} + +std::uint64_t LibreSslPeekLastError_nid_no_patch() { + return queue.top == queue.bottom ? 0 : queue.codes[queue.top]; } std::uint64_t APS5_VABI ERR_get_error_nid_postfix() { @@ -56,7 +69,7 @@ void APS5_VABI ERR_clear_error_nid_postfix() { } void APS5_VABI ERR_remove_thread_state_nid_postfix(const void* threadId) { - if (threadId != nullptr) NotImplemented_nid_no_patch("ERR_remove_thread_state of a CRYPTO_THREADID"); + if (threadId != nullptr) NotImplemented_nid_no_patch(__func__); Clear(); } diff --git a/core/libs/prx/libSceLibreSsl3/Export.cpp b/core/libs/prx/libSceLibreSsl3/Export.cpp index d3289b659b..33329ec44a 100644 --- a/core/libs/prx/libSceLibreSsl3/Export.cpp +++ b/core/libs/prx/libSceLibreSsl3/Export.cpp @@ -1,6 +1,7 @@ #include #include #include "SceTypes.hpp" +#include "prx/libSceLibreSsl/ErrorQueue.hpp" #include "prx/libc/include/General.hpp" extern "C" { @@ -61,14 +62,12 @@ int APS5_VABI EC_KEY_up_ref_nid_postfix(void) { return 0; } -int APS5_VABI ERR_load_crypto_strings_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI ERR_load_crypto_strings_nid_postfix() { + LibreSslLoadErrorStrings_nid_no_patch(); } -int APS5_VABI ERR_peek_last_error_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::uint64_t APS5_VABI ERR_peek_last_error_nid_postfix() { + return LibreSslPeekLastError_nid_no_patch(); } int APS5_VABI ERR_print_errors_cb_nid_postfix(void) { diff --git a/core/libs/tests/GuestLibreSslError.cpp b/core/libs/tests/GuestLibreSslError.cpp index b1523228ac..0e3ce4aa3c 100644 --- a/core/libs/tests/GuestLibreSslError.cpp +++ b/core/libs/tests/GuestLibreSslError.cpp @@ -1,4 +1,5 @@ #include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceLibreSsl/include/Error.hpp" #include #include #include @@ -10,7 +11,6 @@ std::uint64_t APS5_VABI ERR_get_error_nid_postfix(); std::uint64_t APS5_VABI ERR_peek_error_nid_postfix(); void APS5_VABI ERR_clear_error_nid_postfix(); void APS5_VABI ERR_remove_thread_state_nid_postfix(const void*); -void LibreSslPutError_nid_no_patch(int library, int function, int reason); } static void Require(bool value, const char* what) { diff --git a/core/libs/tests/GuestLibreSslErrors.cpp b/core/libs/tests/GuestLibreSslErrors.cpp new file mode 100644 index 0000000000..551475270a --- /dev/null +++ b/core/libs/tests/GuestLibreSslErrors.cpp @@ -0,0 +1,139 @@ +#include "prx/libSceLibreSsl/ErrorQueue.hpp" +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +void APS5_VABI ERR_load_crypto_strings_nid_postfix(); +std::uint64_t APS5_VABI ERR_peek_last_error_nid_postfix(); +int* APS5_VABI __error_nid_postfix(); +} + +static void Require(bool value, const char* message) { + if (value) return; + std::fputs(message, stderr); + std::fputc('\n', stderr); + std::abort(); +} + +static void ExpectString(std::uint64_t code, const char* expected) { + std::array buffer{}; + Require(ERR_error_string_nid_postfix(code, buffer.data()) == buffer.data(), "caller buffer returned"); + Require(std::strcmp(buffer.data(), expected) == 0, buffer.data()); +} + +static void TestStrings() { + ERR_load_crypto_strings_nid_postfix(); + ExpectString(0, "error:00000000:lib(0):func(0):reason(0)"); + ExpectString(0x20fff043, "error:20FFF043:BIO routines:CRYPTO_internal:passed a null parameter"); + ExpectString(0x20fff07e, "error:20FFF07E:BIO routines:CRYPTO_internal:write to read only BIO"); + ExpectString(0x03fff06c, "error:03FFF06C:bignum routines:CRYPTO_internal:no inverse"); + ExpectString(0xe1abc789, "error:E1ABC789:lib(225):func(2748):reason(1929)"); + ExpectString(0x12345678e1abc789, "error:12345678E1ABC789:lib(225):func(2748):reason(1929)"); + ExpectString(0x02002023, "error:02002023:system library:connect:Resource temporarily unavailabl"); + auto* shared = ERR_error_string_nid_postfix(0xe1abc789, nullptr); + Require(shared != nullptr, "static buffer exists"); + Require(ERR_error_string_nid_postfix(0, nullptr) == shared, "static buffer reused"); + Require(std::strcmp(shared, "error:00000000:lib(0):func(0):reason(0)") == 0, "static buffer overwritten"); + constexpr auto expected = "error:E1ABC789:lib(225):func(2748):reason(1929)"; + for (std::size_t size = 0; size <= std::strlen(expected) + 2; ++size) { + std::array buffer; + buffer.fill('#'); + ERR_error_string_n_nid_postfix(0xe1abc789, buffer.data() + 1, size); + Require(buffer.front() == '#' && buffer[size + 1] == '#', "bounded formatting canaries"); + if (size == 0) continue; + const auto count = std::strlen(buffer.data() + 1); + Require(count < size, "bounded formatting terminates"); + if (size > 4 && size <= std::strlen(expected)) { + std::size_t colons = 0; + for (std::size_t index = 1; index <= count; ++index) colons += buffer[index] == ':'; + Require(colons == 4, "truncation keeps four colons"); + } + if (size > std::strlen(expected)) Require(std::strcmp(buffer.data() + 1, expected) == 0, "full bounded string"); + } + char tiny[6]{}; + ERR_error_string_n_nid_postfix(0xe1abc789, tiny, sizeof(tiny)); + Require(std::strcmp(tiny, "e::::") == 0, "historical truncation placement"); + ERR_error_string_n_nid_postfix(0, nullptr, 0); +} + +static void TestQueue() { + ERR_clear_error_nid_postfix(); + Require(ERR_get_error_nid_postfix() == 0 && ERR_peek_error_nid_postfix() == 0 && + ERR_peek_last_error_nid_postfix() == 0, "initially empty"); + auto* guestError = __error_nid_postfix(); + errno = EDOM; + const int savedGuestError = *guestError; + LibreSslPutError_nid_no_patch(32, 0xfff, 67, "producer", 19); + Require(errno == EDOM, "publication preserves host errno"); + Require(*guestError == savedGuestError, "publication preserves guest errno"); + LibreSslPutError_nid_no_patch(3, 0xfff, 108, nullptr, 0); + Require(ERR_peek_error_nid_postfix() == 0x20fff043 && ERR_peek_error_nid_postfix() == 0x20fff043, "peek oldest without removal"); + Require(ERR_peek_last_error_nid_postfix() == 0x03fff06c && ERR_peek_last_error_nid_postfix() == 0x03fff06c, "cross-library peek newest"); + Require(ERR_get_error_nid_postfix() == 0x20fff043 && ERR_get_error_nid_postfix() == 0x03fff06c, "FIFO removal"); + Require(ERR_get_error_nid_postfix() == 0 && ERR_peek_last_error_nid_postfix() == 0, "drained queue"); + LibreSslPutError_nid_no_patch(-1, -1, -1, nullptr, 0); + Require(ERR_get_error_nid_postfix() == 0xffffffff, "pack masks negative fields"); + LibreSslPutError_nid_no_patch(0x132, 0x2345, 0x3456, nullptr, 0); + Require(ERR_get_error_nid_postfix() == 0x32345456, "pack masks oversized fields"); + LibreSslPutError_nid_no_patch(0, 0, 0, nullptr, 0); + LibreSslPutError_nid_no_patch(0, 0, 1, nullptr, 0); + Require(ERR_get_error_nid_postfix() == 0 && ERR_get_error_nid_postfix() == 1, "zero code occupies a slot"); + for (int index = 1; index <= 100; ++index) LibreSslPutError_nid_no_patch(128, index, index, nullptr, index); + for (std::uint64_t index = 86; index <= 100; ++index) { + Require(ERR_get_error_nid_postfix() == (0x80000000 | (index << 12) | index), "retain newest fifteen after overflow"); + } + Require(ERR_get_error_nid_postfix() == 0, "overflow queue drained"); + LibreSslPutError_nid_no_patch(3, 1, 2, nullptr, 0); + ERR_clear_error_nid_postfix(); + Require(ERR_peek_error_nid_postfix() == 0 && ERR_peek_last_error_nid_postfix() == 0, "clear both queue ends"); + LibreSslPutError_nid_no_patch(32, 0xfff, 67, nullptr, 0); + ERR_remove_thread_state_nid_postfix(nullptr); + Require(ERR_get_error_nid_postfix() == 0, "remove current state"); + LibreSslPutError_nid_no_patch(32, 0xfff, 67, nullptr, 0); + Require(ERR_get_error_nid_postfix() == 0x20fff043, "reuse after remove"); +} + +static void TestThreads() { + ERR_clear_error_nid_postfix(); + LibreSslPutError_nid_no_patch(128, 7, 19, nullptr, 0); + std::atomic ready{0}; + std::array threads; + for (std::size_t index = 0; index < threads.size(); ++index) { + threads[index] = std::thread([&, index] { + Require(ERR_get_error_nid_postfix() == 0, "new thread starts empty"); + LibreSslPutError_nid_no_patch(129, 0, static_cast(index + 1), nullptr, 0); + ++ready; + while (ready.load() != static_cast(threads.size())) std::this_thread::yield(); + Require(ERR_peek_last_error_nid_postfix() == 0x81000001 + index, "thread-local newest"); + if (index % 2) ERR_clear_error_nid_postfix(); + else ERR_remove_thread_state_nid_postfix(nullptr); + Require(ERR_get_error_nid_postfix() == 0, "thread-local cleanup"); + LibreSslPutError_nid_no_patch(130, 1, 3, nullptr, 0); + }); + } + for (auto& thread : threads) thread.join(); + Require(ERR_get_error_nid_postfix() == 0x80007013, "child queues leave parent unchanged"); + std::thread replacement([] { Require(ERR_get_error_nid_postfix() == 0, "no stale state after thread exit"); }); + replacement.join(); +} + +int main(int argc, char** argv) { + if (argc == 2) { + if (std::strcmp(argv[1], "free") == 0) TestStrings(); + LibreSslPutError_nid_no_patch(32, 0xfff, 67, nullptr, 0); + ERR_free_strings_nid_postfix(); + ERR_free_strings_nid_postfix(); + ERR_load_crypto_strings_nid_postfix(); + ExpectString(0x20fff043, "error:20FFF043:lib(32):func(4095):reason(67)"); + Require(ERR_get_error_nid_postfix() == 0x20fff043, "free strings preserves queue"); + return 0; + } + TestStrings(); + TestQueue(); + TestThreads(); +} diff --git a/docs/dev/debt/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index bcd1bf8f73..c30d2c0338 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -1,6 +1,10 @@ # libSceLibreSsl technical debt +### Functional + +- [ERR queues and strings](../../../core/libs/prx/libSceLibreSsl/ErrorQueue.cpp) have no PS5 equivalence verification. Contracts follow LibreSSL 3.4.1's [err.c](https://github.com/libressl/openbsd/blob/078b3d1df8ca2011c1605cd40418c58fa0f5b143/src/lib/libcrypto/err/err.c), err.h, crypto.h and err_all.c at that revision: LP64 error arguments/results, 15 queued errors and historical crypto strings. System reasons use guest libc's FreeBSD errno messages, truncated to 31 bytes; console wording is unverified. Explicit thread IDs in `ERR_remove_thread_state` throw because guest pthread identity mapping is unverified. Null IDs clear the calling thread. SSL-specific strings are not loaded; SSL initialization remains unimplemented. +- [Error publication](../../../core/libs/prx/libSceLibreSsl/ErrorQueue.hpp) has no implemented production callers on this branch. BIOs and the digest, AES, allocator/RAND, DES, BIGNUM and EVP/HMAC work in pending branches require separate integration. `LibreSslPutError_nid_no_patch(int library, int function, int reason, const char* file, int line)` has C linkage and the native host calling convention, accepts upstream numeric fields and publishes to the calling thread's queue. Three-argument source calls default file/line to null/zero. It stores file/line without taking ownership; producers must supply a filename with static lifetime. Guest ERR entry points use `APS5_VABI`. Unsupported producer exports continue throwing; the local tests publish through this helper. + ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 395 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. -- [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) (libSceLibreSsl) - no export of this library adds an error yet, so the queue stays empty for guest code. The per-thread queue of 16 slots holding 15 codes, its order and the `ERR_PACK` layout follow LibreSSL's [err.c](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.c) and [err.h](https://github.com/libressl/openbsd/blob/bc9483725c5815efb64b6d9acc52fc5fba139212/src/lib/libcrypto/err/err.h); the module's LibreSSL version is unknown and nothing is checked on the console. The queue keeps no file, line or data, as no export reads them. `ERR_remove_thread_state` with a thread id throws: that version asserts a null id, while older versions remove the state of the thread the id names +- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 399 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. diff --git a/docs/dev/debt/libSceLibreSsl3.md b/docs/dev/debt/libSceLibreSsl3.md index 3fbd00e901..475b4136f5 100644 --- a/docs/dev/debt/libSceLibreSsl3.md +++ b/docs/dev/debt/libSceLibreSsl3.md @@ -1,5 +1,9 @@ # libSceLibreSsl3 technical debt +### Functional + +- [ERR_load_crypto_strings and ERR_peek_last_error](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) share libSceLibreSsl's string registry and calling-thread queue. This relationship and PS5 equivalence are unverified; the public contracts follow LibreSSL 3.4.1 at OpenBSD commit `078b3d1df8ca2011c1605cd40418c58fa0f5b143`. `ERR_print_errors_cb` remains throwing: the public callback contract is known, but PS5 thread identifiers and diagnostic metadata have not been established. + ### Unknown function info - [libSceLibreSsl3 pending exports](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) - 87 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibreSsl3.c) and were cross-checked against the same module in PS5 firmware 13.60. Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`.