fix(libkernel): close() closes libSceNet sockets
A libSceNet socket is a kernel descriptor on the console, so titles close it with close(). libSceNet keeps its sockets in its own table, and libkernel's close passed their numbers to the CRT, which reported an invalid parameter and left the socket open. libSceNet now registers a closer with libkernel, as it registers its poller for select. PPSA01289 (Sackboy: A Big Adventure) closes its P2P socket this way each time it tears its network session down, leaking one open socket per attempt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
bab7b32852
commit
12b18af550
|
|
@ -439,6 +439,19 @@ int PollSockets(KernelSocketPoll::Entry* entries, int count, int timeoutMillisec
|
|||
|
||||
const bool g_socketPollerRegistered = (KernelSetSocketPoller_nid_no_patch(&PollSockets), true);
|
||||
|
||||
extern "C" int APS5_VABI sceNetSocketClose(int s);
|
||||
|
||||
bool CloseSocketForKernel(int descriptor, int* error) {
|
||||
{
|
||||
std::lock_guard lk(g_mutex);
|
||||
if (g_socks.count(descriptor) == 0) return false;
|
||||
}
|
||||
*error = sceNetSocketClose(descriptor) == 0 ? 0 : *errno_slot();
|
||||
return true;
|
||||
}
|
||||
|
||||
const bool g_socketCloserRegistered = (KernelSetSocketCloser_nid_no_patch(&CloseSocketForKernel), true);
|
||||
|
||||
int APS5_VABI sceNetInit_nid_postfix(void) {
|
||||
std::lock_guard lk(g_mutex);
|
||||
g_net_inited = true;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include "prx/libkernel/File/include/RandomDevice.hpp"
|
||||
#include "prx/libkernel/KernelErrors.hpp"
|
||||
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
|
||||
#include "prx/libkernel/Socket/include/SocketPoll.hpp"
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <cstdarg>
|
||||
|
|
@ -466,6 +467,12 @@ int APS5_VABI chmod_nid_postfix(const char* path, int mode) {
|
|||
|
||||
int APS5_VABI close_nid_postfix(int d) {
|
||||
if (d >= GuestSockets::FirstDescriptor) return GuestSockets::Close(d);
|
||||
int socketError = 0;
|
||||
if (KernelCloseSocket_nid_no_patch(d, &socketError)) {
|
||||
if (socketError == 0) return 0;
|
||||
*__error_nid_postfix() = socketError;
|
||||
return -1;
|
||||
}
|
||||
File::ForgetRandomDevice(d);
|
||||
#ifdef _WIN32
|
||||
File::ForgetDirectoryDescriptor(d);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ struct Entry {
|
|||
};
|
||||
|
||||
using Poller = int (*)(Entry* entries, int count, int timeoutMilliseconds);
|
||||
using Closer = bool (*)(int descriptor, int* error);
|
||||
}
|
||||
|
||||
extern "C" void KernelSetSocketPoller_nid_no_patch(KernelSocketPoll::Poller poller);
|
||||
extern "C" void KernelSetSocketCloser_nid_no_patch(KernelSocketPoll::Closer closer);
|
||||
extern "C" bool KernelCloseSocket_nid_no_patch(int descriptor, int* error);
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ struct GuestTimeval {
|
|||
};
|
||||
|
||||
std::atomic<KernelSocketPoll::Poller> g_socketPoller{nullptr};
|
||||
std::atomic<KernelSocketPoll::Closer> g_socketCloser{nullptr};
|
||||
|
||||
bool DescriptorSet(const void* set, int descriptor) {
|
||||
return set != nullptr && ((static_cast<const std::uint64_t*>(set)[descriptor / 64] >> (descriptor % 64)) & 1u) != 0;
|
||||
|
|
@ -105,6 +106,15 @@ void KernelSetSocketPoller_nid_no_patch(KernelSocketPoll::Poller poller) {
|
|||
g_socketPoller.store(poller);
|
||||
}
|
||||
|
||||
void KernelSetSocketCloser_nid_no_patch(KernelSocketPoll::Closer closer) {
|
||||
g_socketCloser.store(closer);
|
||||
}
|
||||
|
||||
bool KernelCloseSocket_nid_no_patch(int descriptor, int* error) {
|
||||
const auto closer = g_socketCloser.load();
|
||||
return closer != nullptr && closer(descriptor, error);
|
||||
}
|
||||
|
||||
int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* exceptfds, const void* timeout) {
|
||||
const auto* limit = static_cast<const GuestTimeval*>(timeout);
|
||||
if (nfds < 0 || nfds > SelectDescriptorLimit || (limit != nullptr && (limit->seconds < 0 || limit->microseconds < 0 || limit->microseconds >= 1000000))) {
|
||||
|
|
|
|||
|
|
@ -338,6 +338,11 @@ int main() {
|
|||
CheckLinkLocalAllNodesIpv6();
|
||||
CheckUnspecifiedIpv6();
|
||||
|
||||
const int closed = sceNetSocket("closed-by-kernel", 2, 2, 17);
|
||||
Require(closed >= 0);
|
||||
Require(close_nid_postfix(closed) == 0);
|
||||
Require(Failed(sceNetSocketClose(closed), 9));
|
||||
|
||||
const int listener = sceNetSocket(nullptr, 2, 1, 6);
|
||||
Require(listener >= 0);
|
||||
std::array<std::uint8_t, 16> address{16, 2, 0, 0, 127, 0, 0, 1};
|
||||
|
|
|
|||
|
|
@ -422,6 +422,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [Shared memory](../../core/libs/prx/libkernel/File/src/SharedMemory.cpp) (libkernel) on Linux implements anonymous and named objects, bounded descriptor writes and seeks, and `shm_unlink` with effective read/write permission checks from FreeBSD 11. Named paths up to 1023 bytes use `/anyps5-shm-` plus SHA-1; collisions are not detected, and the namespace is shared across processes in the host mount namespace. Named creation applies the guest umask without changing the host mask. Linux procfs and `/dev/shm` are required. Operations use an advisory directory lock; external host operations bypass it, unrelated objects serialize, and descriptor operations require two spare host descriptors and compare device/inode with the initial descriptor identity on both the pinned-object and ordinary-file fallback paths. [Guest mappings](../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) support shared and private mappings of these descriptors through the allocation tracker and pin a duplicate descriptor during validation and mapping; mapping requires one spare host descriptor; file-backed mappings use the write-watch scan fallback because aliases and descriptor writes bypass per-mapping tracking. Named objects and file-backed mappings on Windows remain unsupported. Invalid non-null name pointers, console behaviour and title compatibility remain unverified.
|
||||
- [sceKernelAddReadEvent and sceKernelAddWriteEvent](../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel): a write event reports the socket's send buffer size (`SO_SNDBUF`) in `data`, not the free space left in it, as Windows has no query for that space. Only guest sockets are supported; other descriptors and a low watermark above 1 throw
|
||||
- [sceNetSendmsg and sceNetRecvmsg](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) copy the scatter/gather list through one host buffer and accept only the flags `sceNetSend`/`sceNetRecv` accept. Sending control data throws; a receive never returns control data (`msg_controllen` 0), since no socket option that queues it is supported
|
||||
- [close](../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) closes a [libSceNet](../../core/libs/prx/libSceNet/Export.cpp) socket through a hook libSceNet registers, as `select` polls them. libSceNet numbers its sockets from 32 in its own table, so a CRT file descriptor and a socket can share a number; `close` then closes the socket. On the console both are one kernel descriptor space
|
||||
- [Additional content](../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) is not installed or mounted: `sceAppContentAddcontMount` answers `NOT_FOUND` for every entitlement label and `sceAppContentAddcontUnmount` for every mount point, as no add-content mount is ever handed out; `sceAppContentGetAddcontInfo` answers `DRM_NO_ENTITLEMENT` for every label and `sceAppContentGetAddcontInfoList` reports 0 entries
|
||||
- [Content export sessions](../../core/libs/prx/libSceContentExport/Export.cpp) (libSceContentExport): `sceContentExportStart` hands out positive session ids, at most 10 live, which `sceContentExportFinish` closes. The signatures follow [Orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json) and the limit `MaxExecution` [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Content/ContentExport.cs); the parameter layouts follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/443f022450f6857256e780daa3d5a14029a748f7/src/core/libraries/content_export/content_export.h). The host has no media library, so `sceContentExportFromDataWithThumbnail` throws once the session is checked, before calling either data callback, and no export ever runs. `sceContentExportGetProgress` and `sceContentExportCancel` throw for a live session, as their result with no running export is unknown. An eleventh `sceContentExportStart` and `sceContentExportTerm` with live sessions throw: whether the console answers `EXECUTION_MAX` or `BUSY` there, or ends the sessions, is unverified. Session id values and error precedence are unverified
|
||||
- [Runtime-loaded modules](../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) (`--module-dir`) that another module needs are initialized at startup with no start arguments, before the module that needs them is loaded; the console is assumed to start them when that module is loaded.
|
||||
|
|
|
|||
Loading…
Reference in New Issue