mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
libkernel numbered guest sockets from GuestSockets::FirstDescriptor (0x10000000), so a title that puts a socket in a 1024-bit fd_set writes fd / 8 bytes past it. The Epic Online Services SDK does this on its RtcNetworkThread and three titles stop there (#1794): Palworld PPSA23126 (queeeef, maina11c8f7f7): FATAL: unhandled exception 0xc0000005 on thread 37164 'RtcNetworkThread' rip 0x00007ffcdb1ea2be eossdk-ps5-shipping.prx.guest.prx+0xdea2be rcx 0000000010000005 rsi 0000000000400000 r12 0000000010000005 The Mound PPSA26223 (xXPBSXx, main0a8b1d32b): FATAL: unhandled exception 0xc0000005 on thread 'RtcNetworkThread', rip eossdk-ps5-shipping.prx.guest.prx+0xefc6f4, rcx = 0x10000005, rsi = 0x400000 Dying Light 2 PPSA02262 (tv7): FATAL: unhandled exception 0xc0000005 on thread 'RtcNetworkThread' rcx 0000000010000005 ... rsi 0000000000400000 ... r12 0000000010000005 On FreeBSD 11.4 (8c639945) FD_SETSIZE is 1024 (sys/sys/select.h:58-59) and sockets take the lowest free entry of the one process descriptor table that files use (kern_descrip.c:1643-1660 fdalloc, uipc_syscalls.c kern_socket through falloc). Every socket number is now a real host descriptor: a zero-access NUL handle registered with _open_osfhandle on Windows (as directory descriptors already are) and an F_DUPFD_CLOEXEC duplicate of the host socket on Linux. The socket table maps that number to the socket, so "is this a socket" is a table lookup instead of a range check at the 31 former FirstDescriptor comparisons. close releases both the socket entry and the number, dup and dup2 duplicate the number through the host table and keep the socket shared, dup2 of a file onto a socket number drops the socket entry, and sceKernelClose now closes sockets too. sceNetSocket and sceNetAccept take their ids from the same table instead of a counter from 32, so libkernel and libSceNet numbers never collide. select polls guest sockets alongside libSceNet sockets, the random device, stdio and (on Linux) host descriptors; poll throws only for an open descriptor that is not a guest socket and reports POLLNVAL otherwise. File calls that used to fail with EBADF on the socket range return FreeBSD's answer for a socket: lseek ESPIPE (kern_lseek), ftruncate and getdirentries EINVAL (invfo_truncate, getvnode), flock EOPNOTSUPP; fstat on a socket throws. pread on a closed descriptor returns EBADF. title failing on host descriptor numbers; these three titles are that case, and this change keeps host numbering for files and only moves sockets into it. Tests: guest_socket_descriptors (new: socket below 1024, select on a socket and a file together, close frees the number for a file, dup of a socket, sceKernelClose), guest_select (socket and pipe together, Linux), guest_poll, guest_dup, guest_socket_pair, guest_fstatfs, guest_posix_filesystem and guest_sce_net updated. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit 20378375b31eb5db922db6e55f45e44a4dae6445)
185 lines
8.1 KiB
C++
185 lines
8.1 KiB
C++
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
struct PollDescriptor {
|
|
int descriptor;
|
|
short events;
|
|
short revents;
|
|
};
|
|
|
|
struct Iovec {
|
|
void* base;
|
|
std::uint64_t length;
|
|
};
|
|
|
|
struct Msghdr {
|
|
void* name;
|
|
std::uint32_t nameLength;
|
|
Iovec* iov;
|
|
int iovLength;
|
|
void* control;
|
|
std::uint32_t controlLength;
|
|
int flags;
|
|
};
|
|
|
|
extern "C" {
|
|
int APS5_VABI socketpair_nid_postfix(int, int, int, int*);
|
|
std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int);
|
|
std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int);
|
|
std::int64_t APS5_VABI recvfrom_nid_postfix(int, void*, std::uint64_t, int, void*, std::uint32_t*);
|
|
std::int64_t APS5_VABI recvmsg_nid_postfix(int, Msghdr*, int);
|
|
int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*);
|
|
int APS5_VABI getpeername_nid_postfix(int, void*, std::uint32_t*);
|
|
int APS5_VABI shutdown_nid_postfix(int, int);
|
|
int APS5_VABI fcntl_nid_postfix(int, int, ...);
|
|
int APS5_VABI poll_nid_postfix(PollDescriptor*, std::uint32_t, int);
|
|
int APS5_VABI close_nid_postfix(int);
|
|
int* APS5_VABI __error_nid_postfix();
|
|
}
|
|
|
|
static void Require(bool value) { if (!value) std::abort(); }
|
|
|
|
constexpr int Unix = 1;
|
|
constexpr int Stream = 1;
|
|
constexpr int Datagram = 2;
|
|
constexpr int SequencedPacket = 5;
|
|
constexpr int CloseOnExec = 0x10000000;
|
|
constexpr int NonBlocking = 0x20000000;
|
|
constexpr int NoSignal = 0x20000;
|
|
constexpr int Peek = 2;
|
|
constexpr int DescriptorSetSize = 1024;
|
|
constexpr short In = 0x1;
|
|
constexpr short Out = 0x4;
|
|
constexpr short Hup = 0x10;
|
|
|
|
static bool Fails(int domain, int type, int protocol, int error) {
|
|
int pair[2] = {-7, -7};
|
|
*__error_nid_postfix() = 0;
|
|
const int result = socketpair_nid_postfix(domain, type, protocol, pair);
|
|
return result == -1 && *__error_nid_postfix() == error && pair[0] == -7 && pair[1] == -7;
|
|
}
|
|
|
|
static bool Readable(int descriptor, short accepted = In) {
|
|
PollDescriptor waited{descriptor, In, 0};
|
|
return poll_nid_postfix(&waited, 1, 2000) == 1 && (waited.revents & accepted) != 0;
|
|
}
|
|
|
|
static bool Exchange(int from, int to, const char* text) {
|
|
char received[32]{};
|
|
const auto length = static_cast<std::uint64_t>(std::strlen(text) + 1);
|
|
if (send_nid_postfix(from, text, length, NoSignal) != static_cast<std::int64_t>(length)) return false;
|
|
if (!Readable(to)) return false;
|
|
if (recv_nid_postfix(to, received, sizeof(received), 0) != static_cast<std::int64_t>(length)) return false;
|
|
return std::strcmp(received, text) == 0;
|
|
}
|
|
|
|
static bool Unnamed(int descriptor, int (APS5_VABI *query)(int, void*, std::uint32_t*)) {
|
|
std::uint8_t name[28];
|
|
std::memset(name, 0xaa, sizeof(name));
|
|
std::uint32_t length = sizeof(name);
|
|
if (query(descriptor, name, &length) != 0 || length != 16 || name[0] != 16 || name[1] != 1 || name[16] != 0xaa) return false;
|
|
length = 4;
|
|
std::memset(name, 0xaa, sizeof(name));
|
|
return query(descriptor, name, &length) == 0 && length == 4 && name[0] == 16 && name[1] == 1 && name[4] == 0xaa;
|
|
}
|
|
|
|
int main() {
|
|
int pair[2] = {-1, -1};
|
|
Require(socketpair_nid_postfix(Unix, Stream, 0, pair) == 0);
|
|
Require(pair[0] >= 0 && pair[0] < DescriptorSetSize && pair[1] >= 0 && pair[1] < DescriptorSetSize && pair[0] != pair[1]);
|
|
Require(Exchange(pair[0], pair[1], "one way"));
|
|
Require(Exchange(pair[1], pair[0], "other way"));
|
|
Require(Unnamed(pair[0], getsockname_nid_postfix) && Unnamed(pair[1], getpeername_nid_postfix));
|
|
|
|
PollDescriptor polled[2] = {{pair[1], In, 0}, {pair[0], Out, 0}};
|
|
Require(poll_nid_postfix(polled, 2, 0) == 1 && polled[0].revents == 0 && (polled[1].revents & Out) != 0);
|
|
const char ping[] = "ping";
|
|
Require(send_nid_postfix(pair[0], ping, sizeof(ping), 0) == sizeof(ping));
|
|
polled[0].revents = 0;
|
|
Require(poll_nid_postfix(polled, 1, 1000) == 1 && (polled[0].revents & In) != 0);
|
|
char peeked[8]{};
|
|
Require(recv_nid_postfix(pair[1], peeked, sizeof(peeked), Peek) == sizeof(ping) && std::strcmp(peeked, ping) == 0);
|
|
char received[8]{};
|
|
std::uint8_t from[28];
|
|
std::uint32_t fromLength = sizeof(from);
|
|
Require(recvfrom_nid_postfix(pair[1], received, sizeof(received), 0, from, &fromLength) == sizeof(ping) && fromLength == 0);
|
|
Require(std::strcmp(received, ping) == 0);
|
|
|
|
Require(fcntl_nid_postfix(pair[1], 3) == 2);
|
|
Require(fcntl_nid_postfix(pair[1], 4, 4) == 0);
|
|
Require(fcntl_nid_postfix(pair[1], 3) == 6);
|
|
Require(recv_nid_postfix(pair[1], received, sizeof(received), 0) == -1 && *__error_nid_postfix() == 35);
|
|
|
|
Require(shutdown_nid_postfix(pair[0], 1) == 0);
|
|
Require(Readable(pair[1], In | Hup) && recv_nid_postfix(pair[1], received, sizeof(received), 0) == 0);
|
|
Require(Exchange(pair[1], pair[0], "still open"));
|
|
Require(close_nid_postfix(pair[0]) == 0);
|
|
Require(Readable(pair[1], In | Hup) && recv_nid_postfix(pair[1], received, sizeof(received), 0) == 0);
|
|
#ifndef _WIN32
|
|
Require(send_nid_postfix(pair[1], ping, sizeof(ping), NoSignal) == -1 && *__error_nid_postfix() == 32);
|
|
#endif
|
|
Require(close_nid_postfix(pair[1]) == 0);
|
|
Require(close_nid_postfix(pair[1]) == -1 && *__error_nid_postfix() == 9);
|
|
|
|
Require(socketpair_nid_postfix(Unix, Stream | NonBlocking | CloseOnExec, 0, pair) == 0);
|
|
Require(fcntl_nid_postfix(pair[0], 3) == 6 && fcntl_nid_postfix(pair[1], 3) == 6);
|
|
Require(recv_nid_postfix(pair[0], received, sizeof(received), 0) == -1 && *__error_nid_postfix() == 35);
|
|
Require(Exchange(pair[1], pair[0], "wake up"));
|
|
Require(close_nid_postfix(pair[0]) == 0 && close_nid_postfix(pair[1]) == 0);
|
|
|
|
Require(socketpair_nid_postfix(Unix, Datagram, 0, pair) == 0);
|
|
Require(fcntl_nid_postfix(pair[0], 3) == 2);
|
|
Require(send_nid_postfix(pair[0], "a", 1, 0) == 1);
|
|
Require(send_nid_postfix(pair[0], "bb", 2, 0) == 2);
|
|
char datagram[16]{};
|
|
std::uint8_t sender[28];
|
|
std::uint32_t senderLength = sizeof(sender);
|
|
Require(Readable(pair[1]));
|
|
Require(recvfrom_nid_postfix(pair[1], datagram, sizeof(datagram), 0, sender, &senderLength) == 1 && datagram[0] == 'a');
|
|
Require(senderLength == 16 && sender[0] == 16 && sender[1] == 1);
|
|
Require(recv_nid_postfix(pair[1], datagram, sizeof(datagram), 0) == 2 && datagram[0] == 'b' && datagram[1] == 'b');
|
|
Require(Exchange(pair[1], pair[0], "back"));
|
|
Require(close_nid_postfix(pair[0]) == 0 && close_nid_postfix(pair[1]) == 0);
|
|
|
|
#ifndef _WIN32
|
|
Require(socketpair_nid_postfix(Unix, SequencedPacket, 0, pair) == 0);
|
|
Require(send_nid_postfix(pair[0], "a", 1, 0) == 1);
|
|
Require(send_nid_postfix(pair[0], "bb", 2, 0) == 2);
|
|
Require(recv_nid_postfix(pair[1], datagram, sizeof(datagram), 0) == 1);
|
|
Require(recv_nid_postfix(pair[1], datagram, sizeof(datagram), 0) == 2);
|
|
Require(send_nid_postfix(pair[0], "record", 6, 0) == 6);
|
|
Require(send_nid_postfix(pair[0], "next", 4, 0) == 4);
|
|
char part[2]{};
|
|
Iovec piece{part, sizeof(part)};
|
|
Msghdr message{nullptr, 0, &piece, 1, nullptr, 0, -1};
|
|
const auto truncated = recvmsg_nid_postfix(pair[1], &message, 0);
|
|
if (truncated != 2 || message.flags != 0x10) {
|
|
std::fprintf(stderr, "recvmsg of a 6-byte SOCK_SEQPACKET record into 2 bytes returned %lld with msg_flags 0x%x, expected 2 with MSG_TRUNC (0x10)\n",
|
|
static_cast<long long>(truncated), static_cast<unsigned>(message.flags));
|
|
std::abort();
|
|
}
|
|
Require(std::memcmp(part, "re", 2) == 0);
|
|
char whole[8]{};
|
|
piece = {whole, sizeof(whole)};
|
|
message.flags = -1;
|
|
Require(recvmsg_nid_postfix(pair[1], &message, 0) == 4 && message.flags == 0 && std::memcmp(whole, "next", 4) == 0);
|
|
Require(close_nid_postfix(pair[0]) == 0 && close_nid_postfix(pair[1]) == 0);
|
|
#endif
|
|
|
|
Require(Fails(2, Stream, 0, 45));
|
|
Require(Fails(28, Datagram, 0, 45));
|
|
Require(Fails(3, Stream, 0, 47));
|
|
Require(Fails(Unix, 0, 0, 43));
|
|
Require(Fails(Unix, 3, 0, 41));
|
|
Require(Fails(Unix, 3, 6, 43));
|
|
Require(Fails(Unix, Stream | 0x01000000, 0, 41));
|
|
Require(Fails(Unix, Stream, 6, 43));
|
|
*__error_nid_postfix() = 0;
|
|
Require(socketpair_nid_postfix(Unix, Stream, 0, nullptr) == -1 && *__error_nid_postfix() == 14);
|
|
return 0;
|
|
}
|