fix(libSceHttp2): validate SSL setter handles

This commit is contained in:
Tharun Balaji 2026-10-11 18:06:32 +05:30 committed by Adrià Franch
parent a11c8f7f70
commit 4337b43f54
3 changed files with 132 additions and 25 deletions

View File

@ -6,19 +6,54 @@
#include "prx/libkernel/Equeue/Equeue.hpp"
#include <atomic>
#include <deque>
#include <map>
#include <mutex>
#include <stdexcept>
#include <unordered_map>
#include <vector>
// 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<int>(0x80436063);
static constexpr int ERROR_INVALID_ID = static_cast<int>(0x817B1100);
static constexpr int ERROR_NULL_POINTER = static_cast<int>(0x817B1225);
enum class Http2Kind : std::uint8_t { Context, Template, Request, CookieBox };
struct Http2Handle {
Http2Kind kind;
int owner;
std::size_t poolSize;
};
static std::atomic<int> g_nextHandle{1};
static std::mutex g_poolsMutex;
static std::map<int, size_t> g_pools;
static std::mutex g_handlesMutex;
static std::unordered_map<int, Http2Handle> 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<int> 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) {

View File

@ -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<int>(0x817B1100);
static void RequireSslSetters(int id, int expected) {
Require(sceHttp2SetMinSslVersion(id, 0) == expected);
Require(sceHttp2SslEnableOption(id, 0) == expected);
Require(sceHttp2SslDisableOption(id, 0) == expected);
}
template <typename TCall>
static bool Throws(TCall call) {
try {
@ -72,6 +84,50 @@ int main() {
Require(sceHttp2CookieFlush(context) == 0);
Require(sceHttp2SendRequest(request, nullptr, 0) == static_cast<int>(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);

View File

@ -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