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:
parent
8c4a295ffb
commit
19bfa3db29
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue