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 <noreply@anthropic.com>
This commit is contained in:
milescoviello 2026-10-12 08:03:38 +00:00
parent 8c4a295ffb
commit 19bfa3db29
2 changed files with 36 additions and 3 deletions

View File

@ -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<char> buffer;
try {
buffer.resize(static_cast<std::size_t>(total) + (datagram ? 1u : 0u));
buffer.resize(static_cast<std::size_t>(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<sockaddr*>(&native), &size);
if (received < 0) {
#ifdef _WIN32
if (!datagram || WSAGetLastError() != WSAEMSGSIZE) return Fail(NativeError());
if (!records || WSAGetLastError() != WSAEMSGSIZE) return Fail(NativeError());
received = static_cast<std::int64_t>(buffer.size());
#else
return Fail(NativeError());

View File

@ -1,6 +1,7 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
@ -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<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