From 4337b43f54bc5183700a18063c9fd09ebffe7119 Mon Sep 17 00:00:00 2001 From: Tharun Balaji Date: Sun, 11 Oct 2026 18:06:32 +0530 Subject: [PATCH] fix(libSceHttp2): validate SSL setter handles --- core/libs/prx/libSceHttp2/Export.cpp | 99 +++++++++++++++++++++------- core/libs/tests/GuestHttp2.cpp | 56 ++++++++++++++++ docs/dev/TechnicalDebt.md | 2 +- 3 files changed, 132 insertions(+), 25 deletions(-) diff --git a/core/libs/prx/libSceHttp2/Export.cpp b/core/libs/prx/libSceHttp2/Export.cpp index e4869b148..29b452b27 100644 --- a/core/libs/prx/libSceHttp2/Export.cpp +++ b/core/libs/prx/libSceHttp2/Export.cpp @@ -6,19 +6,54 @@ #include "prx/libkernel/Equeue/Equeue.hpp" #include #include -#include #include #include #include +#include // No network is emulated: contexts, templates and requests can be created, but any request // that would touch the network fails with the library's network error. static constexpr int ERROR_NETWORK = static_cast(0x80436063); static constexpr int ERROR_INVALID_ID = static_cast(0x817B1100); static constexpr int ERROR_NULL_POINTER = static_cast(0x817B1225); + +enum class Http2Kind : std::uint8_t { Context, Template, Request, CookieBox }; + +struct Http2Handle { + Http2Kind kind; + int owner; + std::size_t poolSize; +}; + static std::atomic g_nextHandle{1}; -static std::mutex g_poolsMutex; -static std::map g_pools; +static std::mutex g_handlesMutex; +static std::unordered_map g_handles; + +static Http2Handle* FindHandle(int id, Http2Kind kind) { + const auto entry = g_handles.find(id); + return entry != g_handles.end() && entry->second.kind == kind ? &entry->second : nullptr; +} + +static Http2Handle* FindAnyHandle(int id) { + const auto entry = g_handles.find(id); + return entry != g_handles.end() ? &entry->second : nullptr; +} + +static void ReleaseOwnedHandles(int owner) { + std::vector owned; + for (const auto& entry : g_handles) { + if (entry.second.owner == owner) owned.push_back(entry.first); + } + for (const int id : owned) { + ReleaseOwnedHandles(id); + g_handles.erase(id); + } +} + +static bool HandleIsLive(int id) { + std::lock_guard lock(g_handlesMutex); + return FindAnyHandle(id) != nullptr; +} struct Http2MemoryPoolStats { size_t pool_size; @@ -58,29 +93,43 @@ int APS5_VABI sceHttp2AddRequestHeader(int id, const char* name, const char* val } int APS5_VABI sceHttp2CreateRequestWithURL(int tmpl_id, const char* method, const char* url, uint64_t content_length) { - (void)tmpl_id; (void)method; (void)url; (void)content_length; - return g_nextHandle.fetch_add(1, std::memory_order_relaxed); + std::lock_guard lock(g_handlesMutex); + if (FindHandle(tmpl_id, Http2Kind::Template) == nullptr) return ERROR_INVALID_ID; + const int id = g_nextHandle.fetch_add(1, std::memory_order_relaxed); + g_handles[id] = {Http2Kind::Request, tmpl_id, 0}; + return id; } int APS5_VABI sceHttp2CreateTemplate(int lib_http2_ctx_id, const char* user_agent, int http_ver, int is_auto_proxy_conf) { - (void)lib_http2_ctx_id; (void)user_agent; (void)http_ver; (void)is_auto_proxy_conf; - return g_nextHandle.fetch_add(1, std::memory_order_relaxed); + std::lock_guard lock(g_handlesMutex); + if (FindHandle(lib_http2_ctx_id, Http2Kind::Context) == nullptr) return ERROR_INVALID_ID; + const int id = g_nextHandle.fetch_add(1, std::memory_order_relaxed); + g_handles[id] = {Http2Kind::Template, lib_http2_ctx_id, 0}; + return id; } int APS5_VABI sceHttp2DeleteRequest(int req_id) { + { + std::lock_guard lock(g_handlesMutex); + if (FindHandle(req_id, Http2Kind::Request) == nullptr) return ERROR_INVALID_ID; + g_handles.erase(req_id); + } std::lock_guard lock(g_completionsMutex); g_completions.erase(req_id); return 0; } int APS5_VABI sceHttp2DeleteTemplate(int tmpl_id) { - (void)tmpl_id; + std::lock_guard lock(g_handlesMutex); + if (FindHandle(tmpl_id, Http2Kind::Template) == nullptr) return ERROR_INVALID_ID; + ReleaseOwnedHandles(tmpl_id); + g_handles.erase(tmpl_id); return 0; } @@ -109,8 +158,8 @@ int APS5_VABI sceHttp2Init(int libnet_mem_id, int libssl_ctx_id, size_t pool_siz (void)libssl_ctx_id; (void)max_concurrent_request; const int id = g_nextHandle.fetch_add(1, std::memory_order_relaxed); - std::lock_guard lock(g_poolsMutex); - g_pools[id] = pool_size; + std::lock_guard lock(g_handlesMutex); + g_handles[id] = {Http2Kind::Context, 0, pool_size}; return id; } @@ -219,26 +268,25 @@ int APS5_VABI sceHttp2SetTimeOut(int id, uint32_t usec) { } int APS5_VABI sceHttp2SslDisableOption(int id, uint32_t ssl_flags) { - (void)id; (void)ssl_flags; - return 0; + return HandleIsLive(id) ? 0 : ERROR_INVALID_ID; } int APS5_VABI sceHttp2SslEnableOption(int id, uint32_t ssl_flags) { - (void)id; (void)ssl_flags; - return 0; + return HandleIsLive(id) ? 0 : ERROR_INVALID_ID; } int APS5_VABI sceHttp2SetMinSslVersion(int id, uint32_t ssl_version) { - (void)id; (void)ssl_version; - return 0; + return HandleIsLive(id) ? 0 : ERROR_INVALID_ID; } int APS5_VABI sceHttp2Term(int lib_http2_ctx_id) { - std::lock_guard lock(g_poolsMutex); - g_pools.erase(lib_http2_ctx_id); + std::lock_guard lock(g_handlesMutex); + if (FindHandle(lib_http2_ctx_id, Http2Kind::Context) == nullptr) return ERROR_INVALID_ID; + ReleaseOwnedHandles(lib_http2_ctx_id); + g_handles.erase(lib_http2_ctx_id); return 0; } @@ -260,13 +308,13 @@ int APS5_VABI sceHttp2AbortRequest(int req_id) { int APS5_VABI sceHttp2GetMemoryPoolStats(int lib_http2_ctx_id, Http2MemoryPoolStats* stats) { if (stats == nullptr) return ERROR_NULL_POINTER; - std::lock_guard lock(g_poolsMutex); - const auto pool = g_pools.find(lib_http2_ctx_id); - if (pool == g_pools.end()) { + std::lock_guard lock(g_handlesMutex); + const Http2Handle* handle = FindHandle(lib_http2_ctx_id, Http2Kind::Context); + if (handle == nullptr) { std::memset(stats, 0, sizeof(*stats)); return ERROR_INVALID_ID; } - *stats = {pool->second, 0, 0, 0}; + *stats = {handle->poolSize, 0, 0, 0}; return 0; } @@ -276,8 +324,11 @@ int APS5_VABI sceHttp2CookieFlush(int id) { } int APS5_VABI sceHttp2CreateCookieBox(int lib_http2_ctx_id) { - (void)lib_http2_ctx_id; - return g_nextHandle.fetch_add(1, std::memory_order_relaxed); + std::lock_guard lock(g_handlesMutex); + if (FindHandle(lib_http2_ctx_id, Http2Kind::Context) == nullptr) return ERROR_INVALID_ID; + const int id = g_nextHandle.fetch_add(1, std::memory_order_relaxed); + g_handles[id] = {Http2Kind::CookieBox, lib_http2_ctx_id, 0}; + return id; } int APS5_VABI sceHttp2SetCookieBox(int id, int cookie_box_id) { diff --git a/core/libs/tests/GuestHttp2.cpp b/core/libs/tests/GuestHttp2.cpp index 4bfb83c4f..b766155e3 100644 --- a/core/libs/tests/GuestHttp2.cpp +++ b/core/libs/tests/GuestHttp2.cpp @@ -15,6 +15,10 @@ int APS5_VABI sceHttp2SendRequest(int, const void*, std::size_t); int APS5_VABI sceHttp2SendRequestAsync(int, const void*, std::size_t, Http2AsyncOption*, void*); int APS5_VABI sceHttp2WaitAsync(int, Http2AsyncResult*, std::uint32_t*, void*); int APS5_VABI sceHttp2DeleteRequest(int); +int APS5_VABI sceHttp2DeleteTemplate(int); +int APS5_VABI sceHttp2SetMinSslVersion(int, std::uint32_t); +int APS5_VABI sceHttp2SslEnableOption(int, std::uint32_t); +int APS5_VABI sceHttp2SslDisableOption(int, std::uint32_t); int APS5_VABI sceHttp2Term(int); int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name); int APS5_VABI sceKernelDeleteEqueue(KernelEqueue eq); @@ -40,6 +44,14 @@ int APS5_VABI sceHttp2WebSocketSetPingInterval(); static void Require(bool value) { if (!value) std::abort(); } +static constexpr int InvalidId = static_cast(0x817B1100); + +static void RequireSslSetters(int id, int expected) { + Require(sceHttp2SetMinSslVersion(id, 0) == expected); + Require(sceHttp2SslEnableOption(id, 0) == expected); + Require(sceHttp2SslDisableOption(id, 0) == expected); +} + template static bool Throws(TCall call) { try { @@ -72,6 +84,50 @@ int main() { Require(sceHttp2CookieFlush(context) == 0); Require(sceHttp2SendRequest(request, nullptr, 0) == static_cast(0x80436063)); + RequireSslSetters(0, InvalidId); + RequireSslSetters(-1, InvalidId); + RequireSslSetters(0x7fffffff, InvalidId); + RequireSslSetters(context, 0); + RequireSslSetters(box, 0); + RequireSslSetters(other, 0); + RequireSslSetters(tmpl, 0); + RequireSslSetters(request, 0); + + const int doomedRequest = sceHttp2CreateRequestWithURL(tmpl, "GET", "https://example.com/doomed", 0); + Require(doomedRequest > 0); + Require(sceHttp2DeleteRequest(doomedRequest) == 0); + RequireSslSetters(doomedRequest, InvalidId); + Require(sceHttp2DeleteRequest(doomedRequest) == InvalidId); + + const int doomedTemplate = sceHttp2CreateTemplate(context, "agent", 2, 0); + Require(doomedTemplate > 0); + const int doomedTemplateRequest = sceHttp2CreateRequestWithURL(doomedTemplate, "GET", "https://example.com/child", 0); + Require(doomedTemplateRequest > 0); + Require(sceHttp2DeleteTemplate(doomedTemplate) == 0); + RequireSslSetters(doomedTemplate, InvalidId); + RequireSslSetters(doomedTemplateRequest, InvalidId); + Require(sceHttp2DeleteTemplate(doomedTemplate) == InvalidId); + + const int doomedContext = sceHttp2Init(1, 1, 0x2000, 4); + Require(doomedContext > 0); + const int doomedContextBox = sceHttp2CreateCookieBox(doomedContext); + const int doomedContextTemplate = sceHttp2CreateTemplate(doomedContext, "agent", 2, 0); + Require(doomedContextBox > 0 && doomedContextTemplate > 0); + const int doomedContextRequest = sceHttp2CreateRequestWithURL(doomedContextTemplate, "GET", "https://example.com/descendant", 0); + Require(doomedContextRequest > 0); + RequireSslSetters(doomedContext, 0); + Require(sceHttp2Term(doomedContext) == 0); + RequireSslSetters(doomedContext, InvalidId); + RequireSslSetters(doomedContextBox, InvalidId); + RequireSslSetters(doomedContextTemplate, InvalidId); + RequireSslSetters(doomedContextRequest, InvalidId); + Require(sceHttp2Term(doomedContext) == InvalidId); + + const int freshContext = sceHttp2Init(1, 1, 0x2000, 4); + Require(freshContext > 0); + RequireSslSetters(freshContext, 0); + Require(sceHttp2Term(freshContext) == 0); + KernelEqueue eq = 0; Require(sceKernelCreateEqueue(&eq, "http2") == 0); Require(sceKernelAddUserEventEdge(eq, request) == 0); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 0c72b62a7..b35c64b71 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -36,7 +36,7 @@ Throughout the project, every function at every stage either **does exactly what - [libSceAudio3d](../../core/libs/prx/libSceAudio3d/Export.cpp) - the port produces no sound: objects can be reserved and `sceAudio3dObjectSetAttributes` accepts their attributes, but the library has no bed exports or mixing to give them audio, the attributes have no effect and `sceAudio3dPortPush` only paces the queue at `granularity` samples per frame at 48 kHz. `sceAudio3dPortSetAttribute` accepts the late reverb level and the downmix spread attributes and ignores them. `sceAudio3dObjectSetAttributes` validates the port, the reserved object and the attribute array (an object id that was never reserved returns the invalid-object error, while shadPS4 returns success for it and the error only for a released object; neither is measured on a console), treating the reset-state attribute as valid with a null value and requiring a value for the others; the attribute identifiers and the value of each are not interpreted. `sceAudio3dPortFlush` drops the frames queued by `sceAudio3dPortAdvance` instead of mixing and submitting them to an output, as there is no output - [libSceTextToSpeech2](../../core/libs/prx/libSceTextToSpeech2/Export.cpp) has no speech engine: `sceTextToSpeech2Initialize` fails and the other exports throw. PPSA01317 skips its narration set-up when initialization fails - [libSceAvPlayer](../../core/libs/prx/libSceAvPlayer/Export.cpp): `sceAvPlayerSetLogCallback` accepts a callback that is never called, as the player produces no log messages, and `sceAvPlayerSetAvailableBandwidth` has no effect, as it governs HLS sources, which `sceAvPlayerAddSource` does not implement. -- [libSceHttp2](../../core/libs/prx/libSceHttp2/Export.cpp) - no request reaches the network, so `sceHttp2SetMinSslVersion` has no TLS connection to restrict and returns success for any `id`, like `sceHttp2SslEnableOption` (prosper returns the invalid-id error for an unknown one) +- [libSceHttp2](../../core/libs/prx/libSceHttp2/Export.cpp) - no request reaches the network, so `sceHttp2SetMinSslVersion` has no TLS connection to restrict and its value is ignored, as for `sceHttp2SslEnableOption` and `sceHttp2SslDisableOption`. The three setters validate `id` against the live handle registry and return `SCE_HTTP2_ERROR_INVALID_ID` (`0x817B1100`) for a handle that was never allocated, was deleted, or belonged to a terminated context, following [prosper](https://github.com/mattias800/prosper). Whether the console restricts the object kind these setters accept, and what it does with an id from a previous init/term cycle, is unverified, so any live handle kind is accepted and ids are never reused - [libSceNpTrophy2](../../core/libs/prx/libSceNpTrophy2/src/GameInfo.cpp): `sceNpTrophy2RegisterUnlockCallback` accepts a callback that is never called, as no trophy is ever unlocked, `sceNpTrophy2UnregisterUnlockCallback` only returns success, and `sceNpTrophy2ShowTrophyList` returns success without showing a list (there is no system trophy UI; seen on PPSA15552). - [sceAvPlayerSetTrickSpeed](../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) with a negative speed runs the clock backwards but delivers no frames; when a forward speed is set again, playback resumes from the rewound time. - [sceRudpActivate](../../core/libs/prx/libSceRudp/Export.cpp) (libSceRudp) - there is no reliable-UDP stack on the host to activate, so it returns success without doing anything, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/rudp/rudp.cpp) does for its own `sceRudpActivate`. PPSA01325 calls it with no arguments during start-up; what the console activates, and whether it reports an error before `sceRudpInit`, is unverified