Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions core/libs/prx/libkernel/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1388,9 +1388,10 @@ if(BUILD_TESTING)
target_link_libraries(guest_socket_pair_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_socket_pair_tests)
add_test(NAME guest_socket_pair COMMAND guest_socket_pair_tests)
set_tests_properties(guest_socket_pair PROPERTIES TIMEOUT 20)
add_test(NAME guest_recvmsg_memory COMMAND guest_socket_pair_tests recvmsg-memory)
set_tests_properties(guest_socket_pair guest_recvmsg_memory PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_socket_pair PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(guest_socket_pair guest_recvmsg_memory PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_kernel_app_archive_tests ${LIBS_INCLUDE_DIR}/tests/GuestKernelAppArchive.cpp)
Expand Down
110 changes: 97 additions & 13 deletions core/libs/prx/libkernel/Socket/src/Udp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
#include <poll.h>
#include <unistd.h>
#include <cerrno>
#ifdef __linux__
#include <sys/uio.h>
#elif defined(__APPLE__)
#include <mach/mach.h>
#include <mach/mach_vm.h>
#endif
#endif
#include "SceTypes.hpp"
#include "prx/libc/include/GuestArena.hpp"
Expand Down Expand Up @@ -1321,8 +1327,80 @@ std::int64_t APS5_VABI sendmsg_nid_postfix(int descriptor, const GuestMsghdr* me
}
return sendto_nid_postfix(descriptor, buffer.data(), buffer.size(), flags, message->name, message->name ? message->nameLength : 0u);
}
#ifdef _WIN32
static bool GuestPagesAllow(std::uintptr_t start, std::size_t bytes, DWORD allowed) {
const auto end = start + bytes;
for (auto address = start; address < end;) {
MEMORY_BASIC_INFORMATION memory{};
if (VirtualQuery(reinterpret_cast<LPCVOID>(address), &memory, sizeof(memory)) != sizeof(memory)) return false;
if (memory.State != MEM_COMMIT || (memory.Protect & PAGE_GUARD) != 0 || (memory.Protect & allowed) == 0) return false;
address = reinterpret_cast<std::uintptr_t>(memory.BaseAddress) + memory.RegionSize;
}
return true;
}
#elif defined(__linux__)
static bool Transferred(ssize_t copied, std::size_t bytes, const char* call) {
if (copied >= 0) return static_cast<std::size_t>(copied) == bytes;
if (errno == EFAULT) return false;
throw std::runtime_error(std::string("recvmsg: ") + call + " failed with errno " + std::to_string(errno));
}
#endif
static bool GuestRange(const void* pointer, std::size_t bytes) {
const auto start = reinterpret_cast<std::uintptr_t>(pointer);
return start != 0 && bytes <= UINTPTR_MAX - start;
}
static bool ReadGuest(void* destination, const void* source, std::size_t bytes) {
if (bytes == 0) return true;
if (!GuestRange(source, bytes)) return false;
GuestArena::GuestArenaPrepareHostRead_nid_no_patch(source, bytes);
#ifdef _WIN32
if (!GuestPagesAllow(reinterpret_cast<std::uintptr_t>(source), bytes,
PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) return false;
std::memcpy(destination, source, bytes);
return true;
#elif defined(__linux__)
iovec local{destination, bytes};
iovec remote{const_cast<void*>(source), bytes};
return Transferred(::process_vm_readv(::getpid(), &local, 1, &remote, 1, 0), bytes, "process_vm_readv");
#elif defined(__APPLE__)
mach_vm_size_t copied = 0;
return ::mach_vm_read_overwrite(::mach_task_self(), reinterpret_cast<mach_vm_address_t>(source), bytes,
reinterpret_cast<mach_vm_address_t>(destination), &copied) == KERN_SUCCESS && copied == bytes;
#else
std::memcpy(destination, source, bytes);
return true;
#endif
}
static bool WriteGuest(void* destination, const void* source, std::size_t bytes) {
if (bytes == 0) return true;
if (!GuestRange(destination, bytes)) return false;
const GuestArena::HostWrite guard(destination, bytes);
if (!guard.Open()) return false;
#ifdef _WIN32
if (!GuestPagesAllow(reinterpret_cast<std::uintptr_t>(destination), bytes,
PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) return false;
std::memcpy(destination, source, bytes);
return true;
#elif defined(__linux__)
iovec local{const_cast<void*>(source), bytes};
iovec remote{destination, bytes};
return Transferred(::process_vm_writev(::getpid(), &local, 1, &remote, 1, 0), bytes, "process_vm_writev");
#elif defined(__APPLE__)
return ::mach_vm_write(::mach_task_self(), reinterpret_cast<mach_vm_address_t>(destination),
reinterpret_cast<vm_offset_t>(source), static_cast<mach_msg_type_number_t>(bytes)) == KERN_SUCCESS;
#else
std::memcpy(destination, source, bytes);
return true;
#endif
}
std::int64_t APS5_VABI recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, int flags) {
const auto total = MessageLength(message);
GuestMsghdr header{};
if (!message || !ReadGuest(&header, message, sizeof(header))) return Fail(14);
if (header.iovLength < 0 || header.iovLength > 1024) return Fail(40);
std::vector<GuestIovec> vectors(static_cast<std::size_t>(header.iovLength));
if (!ReadGuest(vectors.data(), header.iov, vectors.size() * sizeof(GuestIovec))) return Fail(14);
header.iov = vectors.data();
const auto total = MessageLength(&header);
if (total < 0) return -1;
const auto socket = Lookup(descriptor);
if (!socket) return -1;
Expand All @@ -1347,27 +1425,33 @@ std::int64_t APS5_VABI recvmsg_nid_postfix(int descriptor, GuestMsghdr* message,
return Fail(NativeError());
#endif
}
message->flags = 0;
header.flags = 0;
if (received > total) {
received = total;
message->flags = 0x10;
header.flags = 0x10;
}
std::size_t offset = 0;
for (int i = 0; i < message->iovLength && offset < static_cast<std::size_t>(received); ++i) {
const auto count = std::min<std::size_t>(message->iov[i].length, static_cast<std::size_t>(received) - offset);
std::memcpy(message->iov[i].base, buffer.data() + offset, count);
for (const auto& vector : vectors) {
if (offset >= static_cast<std::size_t>(received)) break;
const auto count = std::min<std::size_t>(vector.length, static_cast<std::size_t>(received) - offset);
if (!WriteGuest(vector.base, buffer.data() + offset, count)) return Fail(14);
offset += count;
}
if (message->name && socket->family == GuestUnix) {
if (socket->type == 1) message->nameLength = 0; else UnnamedAddress(message->name, &message->nameLength);
} else if (message->name) {
if (size > 0 && (native.ss_family == AF_INET || native.ss_family == AF_INET6)) {
GuestAddress(native, message->name, &message->nameLength);
if (header.name) {
unsigned char address[28]{};
if (socket->family == GuestUnix) {
if (socket->type == 1) header.nameLength = 0; else UnnamedAddress(address, &header.nameLength);
} else if (size > 0 && (native.ss_family == AF_INET || native.ss_family == AF_INET6)) {
GuestAddress(native, address, &header.nameLength);
} else {
message->nameLength = 0;
header.nameLength = 0;
}
if (!WriteGuest(header.name, address, header.nameLength)) return Fail(14);
if (!WriteGuest(&message->nameLength, &header.nameLength, sizeof(header.nameLength))) return Fail(14);
}
message->controlLength = 0;
header.controlLength = 0;
constexpr auto results = offsetof(GuestMsghdr, controlLength);
if (!WriteGuest(reinterpret_cast<char*>(message) + results, reinterpret_cast<const char*>(&header) + results, sizeof(header) - results)) return Fail(14);
return received;
}
struct GuestSendfileHeaders {
Expand Down
82 changes: 81 additions & 1 deletion core/libs/tests/GuestSocketPair.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ int APS5_VABI poll_nid_postfix(PollDescriptor*, std::uint32_t, int);
int APS5_VABI close_nid_postfix(int);
std::int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int, FileStat*);
int APS5_VABI sceKernelFstat(int, FileStat*);
int APS5_VABI sceKernelMapFlexibleMemory(void**, std::size_t, int, int);
int APS5_VABI sceKernelMprotect(const void*, std::size_t, int);
int APS5_VABI sceKernelMunmap(void*, std::size_t);
int* APS5_VABI __error_nid_postfix();
}

Expand Down Expand Up @@ -90,7 +93,83 @@ static bool Unnamed(int descriptor, int (APS5_VABI *query)(int, void*, std::uint
return query(descriptor, name, &length) == 0 && length == 4 && name[0] == 16 && name[1] == 1 && name[4] == 0xaa;
}

int main() {
static bool ReceiveFaults(int descriptor, Msghdr* message) {
*__error_nid_postfix() = 0;
return recvmsg_nid_postfix(descriptor, message, 0) == -1 && *__error_nid_postfix() == 14;
}

static bool Queue(int descriptor, int receiver, const char* text) {
const auto length = static_cast<std::int64_t>(std::strlen(text));
return send_nid_postfix(descriptor, text, static_cast<std::uint64_t>(length), 0) == length && Readable(receiver);
}

static void CheckReceiveMessageFaults() {
constexpr std::size_t page = 0x4000;
void* memory = nullptr;
Require(sceKernelMapFlexibleMemory(&memory, page * 3, 3, 0) == 0 && memory != nullptr);
auto* writable = static_cast<char*>(memory);
auto* readOnly = writable + page;
auto* inaccessible = writable + 2 * page;
char data[16]{};
Iovec good{data, sizeof(data)};
auto* readOnlyMessage = reinterpret_cast<Msghdr*>(readOnly);
*readOnlyMessage = {nullptr, 0, &good, 1, nullptr, 0, -1};
auto* readOnlyVector = reinterpret_cast<Iovec*>(readOnly + 64);
*readOnlyVector = {data, sizeof(data)};
Require(sceKernelMprotect(readOnly, page, 1) == 0 && sceKernelMprotect(inaccessible, page, 0) == 0);
int pair[2] = {-1, -1};
Require(socketpair_nid_postfix(Unix, Datagram, 0, pair) == 0);

Require(Queue(pair[0], pair[1], "x"));
Require(ReceiveFaults(pair[1], reinterpret_cast<Msghdr*>(0x1000)));
Require(ReceiveFaults(pair[1], reinterpret_cast<Msghdr*>(inaccessible)));
Msghdr message{nullptr, 0, reinterpret_cast<Iovec*>(0x1000), 1, nullptr, 0, -1};
Require(ReceiveFaults(pair[1], &message));
message.iov = reinterpret_cast<Iovec*>(inaccessible);
Require(ReceiveFaults(pair[1], &message));
char peeked[4]{};
Require(recv_nid_postfix(pair[1], peeked, sizeof(peeked), Peek) == 1 && peeked[0] == 'x');

Iovec vector{reinterpret_cast<void*>(0x1000), sizeof(data)};
message = {nullptr, 0, &vector, 1, nullptr, 0, -1};
Require(ReceiveFaults(pair[1], &message));
vector.base = inaccessible;
Require(Queue(pair[0], pair[1], "x") && ReceiveFaults(pair[1], &message));
vector.base = readOnly + 128;
Require(Queue(pair[0], pair[1], "x") && ReceiveFaults(pair[1], &message));
message = {reinterpret_cast<void*>(0x1000), 16, &good, 1, nullptr, 0, -1};
Require(Queue(pair[0], pair[1], "x") && ReceiveFaults(pair[1], &message));
message.name = readOnly + 128;
Require(Queue(pair[0], pair[1], "x") && ReceiveFaults(pair[1], &message));
Require(Queue(pair[0], pair[1], "x") && ReceiveFaults(pair[1], readOnlyMessage));
Require(fcntl_nid_postfix(pair[1], 4, 4) == 0);
Require(recv_nid_postfix(pair[1], peeked, sizeof(peeked), 0) == -1 && *__error_nid_postfix() == 35);
Require(fcntl_nid_postfix(pair[1], 4, 0) == 0);

std::uint8_t name[32];
std::memset(name, 0xa5, sizeof(name));
vector = {writable, 1u << 20};
message = {name, UINT32_MAX, &vector, 1, nullptr, 7, -1};
Require(Queue(pair[0], pair[1], "abcd") && recvmsg_nid_postfix(pair[1], &message, 0) == 4);
Require(std::memcmp(writable, "abcd", 4) == 0 && message.flags == 0 && message.controlLength == 0);
Require(message.nameLength == 16 && name[0] == 16 && name[1] == Unix && name[16] == 0xa5);
Iovec vectors[2]{{writable, 2}, {inaccessible, 16}};
message = {nullptr, 0, vectors, 2, nullptr, 0, -1};
Require(Queue(pair[0], pair[1], "ef") && recvmsg_nid_postfix(pair[1], &message, 0) == 2 && std::memcmp(writable, "ef", 2) == 0);
message = {readOnly + 128, 0, readOnlyVector, 1, nullptr, 0, -1};
Require(Queue(pair[0], pair[1], "gh") && recvmsg_nid_postfix(pair[1], &message, 0) == 2);
Require(std::memcmp(data, "gh", 2) == 0 && message.nameLength == 0);

Require(close_nid_postfix(pair[0]) == 0 && close_nid_postfix(pair[1]) == 0);
Require(sceKernelMprotect(memory, page * 3, 3) == 0 && sceKernelMunmap(memory, page * 3) == 0);
}

int main(int argc, char** argv) {
if (argc == 2 && std::strcmp(argv[1], "recvmsg-memory") == 0) {
CheckReceiveMessageFaults();
return 0;
}
Require(argc == 1);
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]);
Expand Down Expand Up @@ -194,6 +273,7 @@ int main() {
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
CheckReceiveMessageFaults();

Require(Fails(2, Stream, 0, 45));
Require(Fails(28, Datagram, 0, 45));
Expand Down
1 change: 1 addition & 0 deletions docs/dev/debt/libkernel.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@
- [nmount](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) throws: mounting file systems is not supported. Prospero Themes (PPSA90210) imports it and fails to load without it; it opens its theme library without calling it, and its strings (`/dev/ssd0.system_ex`, `/system_ex`, `exfatfs`, `fstype`, `fspath`, `from`, `ignoreacl`) point to remounting the console's `/system_ex` partition to write theme files there.
- [socketpair](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) - the pair is the host's `AF_UNIX` pair on Linux and, on Windows, a loopback TCP connection for `SOCK_STREAM` or two connected UDP sockets for `SOCK_DGRAM`; `SOCK_SEQPACKET` throws on Windows. A datagram sent to a full receiver gets `ENOBUFS` on the console, waits or gets `EAGAIN` on Linux and is dropped on Windows, and on Windows a datagram `recv` after the peer closed reports `ECONNRESET` instead of the console's `ENOTCONN`, and a stream `send` after the peer closed reports `ECONNABORTED` instead of the console's `EPIPE`. `read` and `write` on a pair end go to the host like on every guest socket. `getpeername` on a pair end succeeds after the peer closed, where the console returns `ENOTCONN`, and `connect`, `bind` and `sendto` with an address on a pair end report an address-family error instead of the console's `EISCONN`. On Windows `poll` on a stream end reports `POLLHUP` without `POLLIN` after the peer's `shutdown(SHUT_WR)` or `close`, where the console reports `POLLIN`, with `POLLHUP` only after `close`
- [UDP recv/recvfrom](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) and [sceNetRecv/sceNetRecvfrom](../../../core/libs/prx/libSceNet/Export.cpp) translate Windows WSAEMSGSIZE into the copied byte count for datagram sockets, including MSG_PEEK, and retain the sender address. This follows FreeBSD 11.4 recv(2) and Winsock's documented truncated-datagram behavior; console behavior and existing title impact are unverified.
- [recvmsg](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) loses received stream data on an output copy fault: outputs are checked after the host receive. FreeBSD 11.4 leaves unread stream data queued, though it also drops a datagram on a copy fault ([`soreceive_dgram`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L2308-L2312), [`soreceive_generic`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L1758-L1774)). A Linux `process_vm_readv` or `process_vm_writev` failure other than `EFAULT` throws
- [MSG_DONTWAIT](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) in `send`, `sendto`, `sendmsg`, `recv`, `recvfrom` and `recvmsg` makes that one call non-blocking through the host's `MSG_DONTWAIT`, as FreeBSD 11.4's `soreceive_generic` returns `EWOULDBLOCK` for it (`sys/kern/uipc_socket.c`). Winsock has no per-call non-blocking flag, so on Windows these calls still fail with `EOPNOTSUPP` for it
- [send, sendto](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) - send and sendto without an address on an unconnected datagram socket map Windows `WSAENOTCONN` to FreeBSD's `EDESTADDRREQ` (39).
- [read, write and vector I/O](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) on a guest socket use `recv` and `send` without flags; `sceKernelReadv` and `sceKernelWritev` gather or scatter through one socket operation, preserving stream short reads and datagram boundaries. The positioned `pread`, `pwrite`, `preadv` and `pwritev` forms, and their `sceKernel` forms, return `ESPIPE` for an open guest socket and `EBADF` for a closed one. `EPIPE` from a write raises the host's `SIGPIPE` on Linux unless the socket has `SO_NOSIGPIPE` set, and the guest cannot ignore it: `signal(SIGPIPE, SIG_IGN)` and `sigaction` record the disposition, as they do for every number from 1 to 128, but only the six signals with host equivalents (2, 4, 6, 8, 11 and 15) take effect on the host or are delivered, so the host `SIGPIPE` is not stopped; nothing is raised on Windows; a `write` longer than `INT_MAX` bytes is `EMSGSIZE` where the console sends it. AIO on a guest socket still goes to the host, which has no such descriptor
Expand Down
Loading