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
15 changes: 13 additions & 2 deletions src/syscall/io.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
#include "syscall/io.h"
#include "syscall/net.h"
#include "syscall/net-identity.h"
#include "syscall/net-sockopt.h"
#include "syscall/poll.h"
#include "syscall/proc.h"
#include "syscall/signal.h"
Expand Down Expand Up @@ -468,6 +469,7 @@ static int64_t rosetta_vz_ioctl(guest_t *g, uint64_t request, uint64_t arg)
static const char fake_sock_path[] = ROSETTAD_SOCKET_PATH;
memcpy(&caps[ROSETTA_CAPS_SOCKET_PATH], fake_sock_path,
sizeof(fake_sock_path));

/* Snapshot the caps binary path under the rosetta path lock so a
* concurrent execve cannot tear the string between length probe and
* copy. Inline buffer matches the cap exactly; the snapshot helper
Expand Down Expand Up @@ -615,6 +617,7 @@ static uint32_t mac_oflag_to_linux(tcflag_t mf)
#define LINUX_PARODD 0x0200
#define LINUX_HUPCL 0x0400
#define LINUX_CLOCAL 0x0800

/* LINUX_CBAUD 0x0000100f and LINUX_CBAUDEX 0x00001000 encode baud in c_cflag;
* macOS uses dedicated speed fields, so termios translation ignores CBAUD on
* translation. TCGETS2/TCSETS2 use BOTHER to signal numeric c_ispeed/c_ospeed.
Expand Down Expand Up @@ -650,6 +653,7 @@ static speed_t linux_cbaud_to_speed(uint32_t cbaud)
static tcflag_t linux_cflag_to_mac(uint32_t lf)
{
tcflag_t mf = 0;

/* CSIZE: Linux CS5=0x00, CS6=0x10, CS7=0x20, CS8=0x30
* macOS CS5=0x00, CS6=0x100, CS7=0x200, CS8=0x300
*/
Expand Down Expand Up @@ -1088,8 +1092,9 @@ int64_t sys_read(guest_t *g, int fd, uint64_t buf_gva, uint64_t count)
}

ssize_t ret = read(host_ref.fd, buf, count);
int64_t result = ret < 0 ? recv_eof_or_errno(host_ref.fd, fd) : ret;
host_fd_ref_close(&host_ref);
return ret < 0 ? linux_errno() : ret;
return result;
}

int64_t sys_pread64(guest_t *g,
Expand Down Expand Up @@ -1212,6 +1217,7 @@ static int64_t build_host_iov(guest_t *g,
free(heap);
return -LINUX_EFAULT;
}

/* Cap to contiguous permitted bytes. When the guest iov entry spans a
* non-contiguous boundary (different mapping or permission), zero every
* subsequent host iov length so the host readv/writev returns a
Expand Down Expand Up @@ -1386,6 +1392,7 @@ int64_t sys_readv(guest_t *g, int fd, uint64_t iov_gva, int iovcnt)
return err;
int64_t ret = urandom_fill_iov(fd, host_iov.iov, iovcnt);
host_iov_free(&host_iov);

/* Mirror sys_read's slow-path refill so a readv consumer that drains
* the shim ring leaves it ready for the next call, instead of forcing
* every subsequent EL1 fast-path attempt back through HVC until some
Expand All @@ -1398,6 +1405,7 @@ int64_t sys_readv(guest_t *g, int fd, uint64_t iov_gva, int iovcnt)
type == FD_INOTIFY) {
if (iovcnt <= 0)
return -LINUX_EINVAL;

/* Use guest_read for the iov array since guest_ptr alone is unsafe if
* the array spans a 2MiB block boundary.
*/
Expand Down Expand Up @@ -1444,7 +1452,7 @@ int64_t sys_readv(guest_t *g, int fd, uint64_t iov_gva, int iovcnt)
}

ssize_t ret = readv(host_ref.fd, host_iov.iov, iovcnt);
int64_t result = ret < 0 ? linux_errno() : ret;
int64_t result = ret < 0 ? recv_eof_or_errno(host_ref.fd, fd) : ret;
host_iov_free(&host_iov);
host_fd_ref_close(&host_ref);
return result;
Expand Down Expand Up @@ -2127,6 +2135,7 @@ int64_t sys_ioctl(guest_t *g, int fd, uint64_t request, uint64_t arg)
t.c_cflag = linux_cflag_to_mac(lt2.c_cflag);
t.c_lflag = linux_lflag_to_mac(lt2.c_lflag);
termios_copy_cc_to_mac(t.c_cc, lt2.c_cc);

/* Resolve baud rate: BOTHER means use numeric c_ispeed/c_ospeed;
* otherwise decode the standard CBAUD index to a numeric rate.
*/
Expand Down Expand Up @@ -2283,6 +2292,7 @@ int64_t sys_ioctl(guest_t *g, int fd, uint64_t request, uint64_t arg)
close(host_slave_fd);
return -LINUX_EMFILE;
}

/* Track CLOEXEC + accmode in the guest table so exec honors them; the
* host fd's own FD_CLOEXEC is per-descriptor and would be lost on the
* dup that host_fd_ref hands multi-threaded callers.
Expand Down Expand Up @@ -2359,6 +2369,7 @@ int64_t sys_fallocate(int fd, int mode, int64_t offset, int64_t len)
host_fd_ref_close(&host_ref);
return 0;
}

/* EINVAL: misaligned, sub-block, or non-regular file. pwrite zeros only
* through the current EOF so KEEP_SIZE remains guest-visible. Any other
* host errno propagates verbatim.
Expand Down
60 changes: 55 additions & 5 deletions src/syscall/net-msg.c
Original file line number Diff line number Diff line change
Expand Up @@ -462,8 +462,17 @@ int64_t sys_recvmsg(guest_t *g, int fd, uint64_t msg_gva, int flags)

ssize_t ret = recvmsg(host_ref.fd, &msg, mac_flags);
if (ret < 0) {
host_fd_ref_close(&host_ref);
return linux_errno();
int64_t r = recv_eof_or_errno(host_ref.fd, fd);
if (r != 0) {
host_fd_ref_close(&host_ref);
return r;
}

/* SEQPACKET-over-DGRAM EOF: fall through with an empty result so
* the guest sees msg_controllen 0 and msg_flags 0 like a real EOF.
*/
ret = 0;
msg.msg_flags = 0;
}
uint64_t zero64 = 0;
int32_t mflags = mac_to_linux_msg_flags(msg.msg_flags);
Expand Down Expand Up @@ -545,10 +554,33 @@ int64_t sys_recvmsg(guest_t *g, int fd, uint64_t msg_gva, int flags)

ssize_t ret = recvmsg(host_ref.fd, &msg, mac_flags);
if (ret < 0) {
int64_t r = recv_eof_or_errno(host_ref.fd, fd);
free(mac_ctrl_heap);
host_iov_free(&host_iov);
if (r == 0) {
/* SEQPACKET-over-DGRAM EOF: report an empty control buffer and
* flags, matching a real 0-length recvmsg. Do not fall through --
* recvmsg left msg unmodified, so the control loop would walk the
* stale (uninitialized) mac_ctrl buffer. Surface EFAULT on an
* unwritable msghdr like the success path.
*/
uint64_t zero64 = 0;
uint32_t zero32 = 0;
int32_t zflags = 0;
if ((lmsg.msg_name &&
guest_write_small(
g, msg_gva + offsetof(linux_msghdr_t, msg_namelen),
&zero32, sizeof(zero32)) < 0) ||
guest_write_small(
g, msg_gva + offsetof(linux_msghdr_t, msg_controllen),
&zero64, sizeof(zero64)) < 0 ||
guest_write_small(g,
msg_gva + offsetof(linux_msghdr_t, msg_flags),
&zflags, sizeof(zflags)) < 0)
r = -LINUX_EFAULT;
}
host_fd_ref_close(&host_ref);
return linux_errno();
return r;
}

if (lmsg.msg_name) {
Expand Down Expand Up @@ -960,8 +992,17 @@ int64_t sys_recvmmsg(guest_t *g,
}
ssize_t ret = recvmsg(host_ref.fd, &host_msg, mac_flags);
if (ret < 0) {
host_fd_ref_close(&host_ref);
return linux_errno();
int64_t r = recv_eof_or_errno(host_ref.fd, fd);
if (r != 0) {
host_fd_ref_close(&host_ref);
return r;
}

/* SEQPACKET-over-DGRAM EOF: record one empty message, matching
* how the general loop path counts a 0-length recvmsg.
*/
ret = 0;
host_msg.msg_flags = 0;
}
uint32_t msg_len = (uint32_t) ret;
int32_t out_flags =
Expand Down Expand Up @@ -1024,6 +1065,15 @@ int64_t sys_recvmmsg(guest_t *g,
if (write_linux_mmsghdr_len(g, hdr_gva, msg_len) < 0)
return received > 0 ? (int64_t) received : -LINUX_EFAULT;
received++;
/* A zero-length message ends the batch. On a connection-oriented
* socket it is EOF; the macOS SEQPACKET-over-DGRAM substitute reports
* that EOF only once (then EAGAIN), so a further blocking iteration
* would hang waiting for a persistent EOF that never arrives. Stop
* here, matching the vlen==1 fast path -- recvmmsg may return fewer
* than vlen messages and callers loop for more.
*/
if (ret == 0)
break;
}
return (int64_t) received;
}
53 changes: 53 additions & 0 deletions src/syscall/net-sockopt.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

#include <errno.h>
#include <pthread.h>
#include <stdbool.h>
#include <sys/socket.h>
Expand Down Expand Up @@ -159,6 +160,58 @@ int net_socket_cached_int_get(int guest_fd, int level, int optname, int *value)
return net_sock_cache_get(guest_fd, idx, value);
}

/* True when the host fd is an AF_UNIX datagram socket. Used together with the
* guest's cached SEQPACKET type to pin down the sys_socketpair substitute.
*/
static bool host_fd_is_unix_dgram(int host_fd)
{
int st = 0;
socklen_t sl = sizeof(st);
if (getsockopt(host_fd, SOL_SOCKET, SO_TYPE, &st, &sl) < 0 ||
st != SOCK_DGRAM)
return false;
struct sockaddr_storage ss;
socklen_t al = sizeof(ss);
return getsockname(host_fd, (struct sockaddr *) &ss, &al) == 0 &&
ss.ss_family == AF_UNIX;
}

/* Fold a failed host recv/read into Linux semantics: the SEQPACKET-over-DGRAM
* substitute reports ECONNRESET on peer close where Linux SEQPACKET returns a
* clean EOF, so report EOF (0) for exactly that case and the translated
* negative errno otherwise.
*
* The fold fires only when the guest asked for SOCK_SEQPACKET (cached SO_TYPE)
* and the host fd is an AF_UNIX datagram socket -- the two facts that together
* identify the socketpair substitute. Deliberately left alone:
* - genuine AF_UNIX SOCK_DGRAM sockets (cached type DGRAM): Linux never
* returns EOF there; after draining it blocks or reports EAGAIN, so a
* macOS peer-close ECONNRESET must stay an error, not a fake end-of-stream;
* - the AF_UNIX SOCK_STREAM substitute used for socket()/accept SEQPACKET
* (host type STREAM): a genuine peer abort there is a real reset;
* - INET UDP (host type DGRAM but not AF_UNIX): recv can report a real
* network error, which must stay an error.
*
* Call only when the host call returned < 0, with the pinned host fd still
* open. Probing host_fd is race-free against guest fd reuse; the cached-type
* read keys on guest_fd, but a stale hit is bounded by the AF_UNIX-datagram
* host check and can only mis-map a peer close between two AF_UNIX sockets --
* the same racy-but-benign window the read fast path documents. Both probes run
* only on the rare ECONNRESET tail, off the hot path. errno is preserved.
*/
int64_t recv_eof_or_errno(int host_fd, int guest_fd)
{
int e = errno;
int cached_type = 0;
bool fold = e == ECONNRESET &&
net_socket_cached_int_get(guest_fd, LINUX_SOL_SOCKET,
LINUX_SO_TYPE, &cached_type) &&
cached_type == LINUX_SOCK_SEQPACKET &&
host_fd_is_unix_dgram(host_fd);
errno = e; /* undo the getsockopt/getsockname clobber on both paths */
return fold ? 0 : linux_errno();
}

int net_socket_cached_int_get_if_generation(int guest_fd,
uint64_t generation,
int level,
Expand Down
11 changes: 11 additions & 0 deletions src/syscall/net-sockopt.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,20 @@

#pragma once

#include <stdbool.h>
#include <stdint.h>

int net_socket_fd_is_valid(int guest_fd);

/* Fold a failed host recv/read into Linux semantics: returns 0 (EOF) only for
* the SEQPACKET-over-DGRAM socketpair substitute's peer-close ECONNRESET (guest
* cached SO_TYPE SEQPACKET and host fd AF_UNIX SOCK_DGRAM), which Linux reports
* as a clean end-of-stream; otherwise the translated negative Linux errno.
* Genuine AF_UNIX datagram sockets are left untouched. Call only when the host
* call returned < 0, on the pinned host fd; errno is preserved across the
* internal socket probes.
*/
int64_t recv_eof_or_errno(int host_fd, int guest_fd);
int net_socket_cached_int_get(int guest_fd, int level, int optname, int *value);
int net_socket_cached_int_get_if_generation(int guest_fd,
uint64_t generation,
Expand Down
25 changes: 16 additions & 9 deletions src/syscall/net.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ int64_t net_wait_or_interrupted(int host_fd, short events, int msg_flags)
* the macOS host call does. (read() is the exception: sock_read_iter returns 0
* for a zero count, so sys_read stays untouched.) Gate the host call on
* readability: an interruptible wait for blocking callers, a zero-timeout
* readiness probe for nonblocking ones. EOF counts as readable in both, and
* the host call then returns 0 like Linux.
* readiness probe for nonblocking ones. EOF counts as readable in both, and the
* host call then returns 0 like Linux.
*
* Returns 0 to proceed or a negative Linux errno (EINTR/EAGAIN).
*/
Expand Down Expand Up @@ -831,6 +831,7 @@ int64_t sys_sendto(guest_t *g,
len = avail;

int mac_flags = translate_msg_flags(linux_flags);

/* MSG_NOSIGNAL (0x4000): suppress SIGPIPE on EPIPE. macOS has no
* MSG_NOSIGNAL; elfuse handles it by not queuing SIGPIPE.
*/
Expand Down Expand Up @@ -911,9 +912,9 @@ int64_t sys_recvfrom(guest_t *g,
int mac_flags = translate_msg_flags(flags);

/* A single interruptible wait (not a MSG_DONTWAIT probe loop) preserves
* MSG_WAITALL semantics; the tiny ready-then-stolen window can still
* block, matching sys_read. A zero-length recv takes the readiness gate
* instead: unlike read(), Linux blocks it on an empty socket.
* MSG_WAITALL semantics; the tiny ready-then-stolen window can still block,
* matching sys_read. A zero-length recv takes the readiness gate instead:
* unlike read(), Linux blocks it on an empty socket.
*/
int64_t waited = len > 0
? net_wait_or_interrupted(host_ref.fd, POLLIN, flags)
Expand All @@ -934,8 +935,13 @@ int64_t sys_recvfrom(guest_t *g,
ret = recv(host_ref.fd, buf, len, mac_flags);
}
if (ret < 0) {
host_fd_ref_close(&host_ref);
return linux_errno();
int64_t result = recv_eof_or_errno(host_ref.fd, fd);
if (result < 0) {
host_fd_ref_close(&host_ref);
return result;
}
ret = 0;
mac_len = 0;
}

/* Write back source address if requested. */
Expand All @@ -954,15 +960,16 @@ int64_t sys_recvfrom(guest_t *g,
int out_len =
mac_to_linux_sockaddr((struct sockaddr *) &mac_sa, mac_len,
linux_sa, (uint32_t) sizeof(linux_sa));
if (out_len > 0) {
uint32_t actual_len = (uint32_t) out_len;
if (out_len > 0 || mac_len == 0) {
uint32_t actual_len = out_len > 0 ? (uint32_t) out_len : 0;
uint32_t write_len = actual_len;
if (write_len > guest_addrlen)
write_len = guest_addrlen;
if (guest_write_small(g, src_gva, linux_sa, write_len) < 0) {
host_fd_ref_close(&host_ref);
return -LINUX_EFAULT;
}

/* Write back actual length (Linux returns full size even if the
* address was truncated to fit the buffer).
*/
Expand Down
Loading
Loading