From 19bfa3db29a54de045543590b6db5013971543e2 Mon Sep 17 00:00:00 2001 From: milescoviello Date: Mon, 12 Oct 2026 08:03:38 +0000 Subject: [PATCH] fix(libkernel): report MSG_TRUNC for SOCK_SEQPACKET records in recvmsg recvmsg() reads one byte more than the guest buffers hold to see whether a record did not fit, but only for SOCK_DGRAM sockets. On a SOCK_SEQPACKET socketpair a record longer than the buffers was cut short with msg_flags 0: a 6-byte record received into 2 bytes returned 2 and no MSG_TRUNC, and the rest of the record was gone. FreeBSD's SOCK_SEQPACKET protocol is atomic, sys/kern/uipc_usrreq.c: .pr_type = SOCK_SEQPACKET, ... .pr_flags = PR_ADDR|PR_ATOMIC|PR_CONNREQUIRED|PR_WANTRCVD| PR_RIGHTS, and its receive routine, soreceive_generic() in sys/kern/uipc_socket.c, flags the truncation of an atomic record and drops its rest: if (m != NULL && pr->pr_flags & PR_ATOMIC) { flags |= MSG_TRUNC; if ((flags & MSG_PEEK) == 0) (void) sbdroprecord_locked(&so->so_rcv); } which kern_recvit() hands back in msg_flags. recvmsg() now probes for a longer record on SOCK_SEQPACKET sockets as it does for SOCK_DGRAM. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libkernel/Socket/src/Udp.cpp | 6 ++-- core/libs/tests/GuestSocketPair.cpp | 33 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index e8b1df9de..7ab0fccd6 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -773,10 +773,10 @@ std::int64_t APS5_VABI recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, const auto socket = Lookup(descriptor); if (!socket) return -1; if ((flags & ~2) != 0) return Fail(45); - const bool datagram = socket->type == 2; + const bool records = socket->type == 2 || socket->type == 5; std::vector buffer; try { - buffer.resize(static_cast(total) + (datagram ? 1u : 0u)); + buffer.resize(static_cast(total) + (records ? 1u : 0u)); } catch (const std::bad_alloc&) { return Fail(55); } @@ -786,7 +786,7 @@ std::int64_t APS5_VABI recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, flags & 2 ? MSG_PEEK : 0, reinterpret_cast(&native), &size); if (received < 0) { #ifdef _WIN32 - if (!datagram || WSAGetLastError() != WSAEMSGSIZE) return Fail(NativeError()); + if (!records || WSAGetLastError() != WSAEMSGSIZE) return Fail(NativeError()); received = static_cast(buffer.size()); #else return Fail(NativeError()); diff --git a/core/libs/tests/GuestSocketPair.cpp b/core/libs/tests/GuestSocketPair.cpp index f6a29a846..561645111 100644 --- a/core/libs/tests/GuestSocketPair.cpp +++ b/core/libs/tests/GuestSocketPair.cpp @@ -1,6 +1,7 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include #include +#include #include #include @@ -10,11 +11,27 @@ struct PollDescriptor { 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); @@ -134,6 +151,22 @@ int main() { 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(truncated), static_cast(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