diff --git a/Makefile b/Makefile index 3262c84..e382cc4 100644 --- a/Makefile +++ b/Makefile @@ -172,6 +172,11 @@ $(BUILD_DIR)/test-pthread: tests/test-pthread.c | $(BUILD_DIR) @echo " CROSS $< (with -lpthread)" $(Q)$(CROSS_COMPILE)gcc -D_GNU_SOURCE -static -O2 -o $@ $< -lpthread +# test-scm-creds blocks accept in a pthread while the listener option changes. +$(BUILD_DIR)/test-scm-creds: tests/test-scm-creds.c | $(BUILD_DIR) + @echo " CROSS $< (with -lpthread)" + $(Q)$(CROSS_COMPILE)gcc -D_GNU_SOURCE -static -O2 -o $@ $< -lpthread + # test-shim-cred-race spawns a pthread reader while the main thread # toggles setresuid; the reader spins on the identity fast path. $(BUILD_DIR)/test-shim-cred-race: tests/test-shim-cred-race.c | $(BUILD_DIR) diff --git a/src/syscall/abi.h b/src/syscall/abi.h index b87848e..1cea435 100644 --- a/src/syscall/abi.h +++ b/src/syscall/abi.h @@ -682,6 +682,12 @@ enum { SOCK_OPT_TCP_KEEPINTVL, SOCK_OPT_IPV6_V6ONLY, SOCK_OPT_PASSCRED, + SOCK_OPT_IP_TOS, + SOCK_OPT_IP_TTL, + SOCK_OPT_IP_HDRINCL, + SOCK_OPT_IP_PKTINFO, + SOCK_OPT_IP_RECVTTL, + SOCK_OPT_IP_RECVTOS, /* IP_MTU_DISCOVER value stored verbatim so getsockopt round-trips the * Linux PMTUD mode the guest set. The host accepts the value but does * not honour every Linux mode; see sys_setsockopt for the IP_DONTFRAG @@ -697,10 +703,11 @@ typedef struct { } sock_opt_cache_t; typedef struct { - int type; /* FD_CLOSED, FD_STDIO, FD_REGULAR, FD_DIR */ - int host_fd; /* Underlying macOS file descriptor */ - int linux_flags; /* Linux open flags (for CLOEXEC tracking) */ - void *dir; /* DIR* for FD_DIR entries (NULL otherwise) */ + int type; /* FD_CLOSED, FD_STDIO, FD_REGULAR, FD_DIR */ + int host_fd; /* Underlying macOS file descriptor */ + uint64_t generation; /* Bumped each time this guest fd slot is reused. */ + int linux_flags; /* Linux open flags (for CLOEXEC tracking) */ + void *dir; /* DIR* for FD_DIR entries (NULL otherwise) */ char proc_path[FD_VIRTUAL_PATH_MAX]; /* Virtual /proc dir root for *at */ int seals; /* F_SEAL_* bits (non-zero only for memfd_create fds) */ sock_opt_cache_t sock; /* Socket option cache (zeroed for non-sockets) */ diff --git a/src/syscall/fdtable.c b/src/syscall/fdtable.c index ff62307..3c061b7 100644 --- a/src/syscall/fdtable.c +++ b/src/syscall/fdtable.c @@ -31,6 +31,7 @@ pthread_mutex_t fd_lock = PTHREAD_MUTEX_INITIALIZER; /* Lock order: 3 */ /* FD table. */ fd_entry_t fd_table[FD_TABLE_SIZE]; +static uint64_t fd_next_generation = 1; /* RLIMIT_NOFILE tracking. */ /* Guest-side soft limit for RLIMIT_NOFILE. fd_alloc checks this. @@ -77,6 +78,7 @@ static inline void fd_init_entry(int fd, fd_bitmap_set_used(fd); fd_table[fd].type = type; fd_table[fd].host_fd = host_fd; + fd_table[fd].generation = fd_next_generation++; fd_table[fd].linux_flags = 0; fd_table[fd].dir = NULL; fd_table[fd].proc_path[0] = '\0'; @@ -154,9 +156,16 @@ void fdtable_init(void) memset(fd_free_bitmap, 0xFF, sizeof(fd_free_bitmap)); /* Pre-open stdin/stdout/stderr */ - fd_table[0] = (fd_entry_t) {.type = FD_STDIO, .host_fd = STDIN_FILENO}; - fd_table[1] = (fd_entry_t) {.type = FD_STDIO, .host_fd = STDOUT_FILENO}; - fd_table[2] = (fd_entry_t) {.type = FD_STDIO, .host_fd = STDERR_FILENO}; + fd_next_generation = 1; + fd_table[0] = (fd_entry_t) {.type = FD_STDIO, + .host_fd = STDIN_FILENO, + .generation = fd_next_generation++}; + fd_table[1] = (fd_entry_t) {.type = FD_STDIO, + .host_fd = STDOUT_FILENO, + .generation = fd_next_generation++}; + fd_table[2] = (fd_entry_t) {.type = FD_STDIO, + .host_fd = STDERR_FILENO, + .generation = fd_next_generation++}; fd_bitmap_set_used(0); fd_bitmap_set_used(1); fd_bitmap_set_used(2); diff --git a/src/syscall/net-abi.c b/src/syscall/net-abi.c index 5dafea0..932dbb9 100644 --- a/src/syscall/net-abi.c +++ b/src/syscall/net-abi.c @@ -19,8 +19,11 @@ int socket_small_int_normalize(int level, int optname, int value) (optname == LINUX_SO_KEEPALIVE || optname == LINUX_SO_REUSEADDR || optname == LINUX_SO_ACCEPTCONN || optname == LINUX_SO_REUSEPORT || optname == LINUX_SO_BROADCAST || optname == LINUX_SO_DONTROUTE || - optname == LINUX_SO_OOBINLINE)) || + optname == LINUX_SO_OOBINLINE || optname == LINUX_SO_PASSCRED)) || (level == LINUX_IPPROTO_TCP && optname == LINUX_TCP_NODELAY) || + (level == LINUX_IPPROTO_IP && + (optname == LINUX_IP_HDRINCL || optname == LINUX_IP_PKTINFO || + optname == LINUX_IP_RECVTTL || optname == LINUX_IP_RECVTOS)) || (level == LINUX_IPPROTO_IPV6 && optname == LINUX_IPV6_V6ONLY)) return value != 0; @@ -46,6 +49,7 @@ int socket_opt_uses_small_int(int level, int optname) case LINUX_SO_SNDBUF: case LINUX_SO_TYPE: case LINUX_SO_ERROR: + case LINUX_SO_PASSCRED: return 1; default: return 0; @@ -64,6 +68,20 @@ int socket_opt_uses_small_int(int level, int optname) } } + if (level == LINUX_IPPROTO_IP) { + switch (optname) { + case LINUX_IP_TOS: + case LINUX_IP_TTL: + case LINUX_IP_HDRINCL: + case LINUX_IP_PKTINFO: + case LINUX_IP_RECVTTL: + case LINUX_IP_RECVTOS: + return 1; + default: + return 0; + } + } + return level == LINUX_IPPROTO_IPV6 && optname == LINUX_IPV6_V6ONLY; } @@ -136,6 +154,12 @@ int translate_small_int_sockopt(int level, } } + if (level == LINUX_IPPROTO_IP) { + *mac_level = IPPROTO_IP; + *mac_optname = translate_ip_sockopt_to_mac(optname); + return *mac_optname >= 0; + } + if (level == LINUX_IPPROTO_IPV6 && optname == LINUX_IPV6_V6ONLY) { *mac_level = IPPROTO_IPV6; *mac_optname = IPV6_V6ONLY; diff --git a/src/syscall/net-msg.c b/src/syscall/net-msg.c index d388969..686f5a5 100644 --- a/src/syscall/net-msg.c +++ b/src/syscall/net-msg.c @@ -28,6 +28,18 @@ /* Linux SCM_MAX_FD: maximum number of file descriptors in SCM_RIGHTS */ #define LINUX_SCM_MAX_FD 253 +/* Linux only delivers SCM_CREDENTIALS on AF_UNIX sockets even when + * SO_PASSCRED is set, so PASSCRED toggled on AF_INET / AF_INET6 must + * stay a no-op. + */ +static bool host_socket_is_unix(int host_fd) +{ + struct sockaddr_storage ss; + socklen_t slen = sizeof(ss); + return getsockname(host_fd, (struct sockaddr *) &ss, &slen) == 0 && + ss.ss_family == AF_UNIX; +} + static int translate_scm_rights_fds(int *fds, size_t nfds) { if (nfds > LINUX_SCM_MAX_FD) @@ -597,7 +609,7 @@ int64_t sys_recvmsg(guest_t *g, int fd, uint64_t msg_gva, int flags) int passcred_val = 0; if (net_socket_cached_int_get(fd, LINUX_SOL_SOCKET, LINUX_SO_PASSCRED, &passcred_val) && - passcred_val) { + passcred_val && host_socket_is_unix(host_ref.fd)) { linux_ucred_t cred = { .pid = (int32_t) proc_get_pid(), .uid = proc_get_uid(), @@ -655,7 +667,7 @@ int64_t sys_recvmsg(guest_t *g, int fd, uint64_t msg_gva, int flags) int injected = 0, passcred_val = 0; if (net_socket_cached_int_get(fd, LINUX_SOL_SOCKET, LINUX_SO_PASSCRED, &passcred_val) && - passcred_val) { + passcred_val && host_socket_is_unix(host_ref.fd)) { linux_ucred_t cred = { .pid = (int32_t) proc_get_pid(), .uid = proc_get_uid(), diff --git a/src/syscall/net-sockopt.c b/src/syscall/net-sockopt.c index d52fc3d..254cf5b 100644 --- a/src/syscall/net-sockopt.c +++ b/src/syscall/net-sockopt.c @@ -124,8 +124,26 @@ static int net_sock_opt_index_for(int level, int optname) } if (level == LINUX_IPPROTO_IPV6 && optname == LINUX_IPV6_V6ONLY) return SOCK_OPT_IPV6_V6ONLY; - if (level == LINUX_IPPROTO_IP && optname == LINUX_IP_MTU_DISCOVER) - return SOCK_OPT_IP_MTU_DISCOVER; + if (level == LINUX_IPPROTO_IP) { + switch (optname) { + case LINUX_IP_TOS: + return SOCK_OPT_IP_TOS; + case LINUX_IP_TTL: + return SOCK_OPT_IP_TTL; + case LINUX_IP_HDRINCL: + return SOCK_OPT_IP_HDRINCL; + case LINUX_IP_PKTINFO: + return SOCK_OPT_IP_PKTINFO; + case LINUX_IP_RECVTTL: + return SOCK_OPT_IP_RECVTTL; + case LINUX_IP_RECVTOS: + return SOCK_OPT_IP_RECVTOS; + case LINUX_IP_MTU_DISCOVER: + return SOCK_OPT_IP_MTU_DISCOVER; + default: + return -1; + } + } return -1; } @@ -140,6 +158,34 @@ int net_socket_cached_int_get(int guest_fd, int level, int optname, int *value) return net_sock_cache_get(guest_fd, idx, value); } +int net_socket_cached_int_get_if_generation(int guest_fd, + uint64_t generation, + int level, + int optname, + int *value) +{ + if (level == LINUX_SOL_SOCKET && optname == LINUX_SO_ERROR) + return 0; + + int idx = net_sock_opt_index_for(level, optname); + if (idx < 0 || !RANGE_CHECK(guest_fd, 0, FD_TABLE_SIZE) || !value) + return 0; + + if (thread_is_single_active()) { + fd_entry_t *entry = &fd_table[guest_fd]; + if (entry->type == FD_SOCKET && entry->generation == generation) + return sock_opt_get(entry, idx, value); + return 0; + } + + pthread_mutex_lock(&fd_lock); + fd_entry_t *entry = &fd_table[guest_fd]; + bool ok = entry->type == FD_SOCKET && entry->generation == generation && + sock_opt_get(entry, idx, value); + pthread_mutex_unlock(&fd_lock); + return ok; +} + void net_socket_cached_int_set(int guest_fd, int level, int optname, int value) { if (level == LINUX_SOL_SOCKET && optname == LINUX_SO_ERROR) @@ -155,7 +201,7 @@ void net_socket_cache_init_defaults(int guest_fd, int domain, int real_type) static const int zero_opts[] = { SOCK_OPT_KEEPALIVE, SOCK_OPT_REUSEADDR, SOCK_OPT_ACCEPTCONN, SOCK_OPT_REUSEPORT, SOCK_OPT_BROADCAST, SOCK_OPT_DONTROUTE, - SOCK_OPT_OOBINLINE, + SOCK_OPT_OOBINLINE, SOCK_OPT_PASSCRED, }; net_socket_cache_set_many_zero(guest_fd, zero_opts, ARRAY_SIZE(zero_opts)); @@ -168,7 +214,7 @@ void net_socket_cache_init_defaults(int guest_fd, int domain, int real_type) net_socket_cache_set_index(guest_fd, SOCK_OPT_IPV6_V6ONLY, 0); } -void net_socket_cache_init_accept(int guest_fd) +void net_socket_cache_init_accept(int guest_fd, int inherit_passcred) { static const int zero_opts[] = { SOCK_OPT_ACCEPTCONN, SOCK_OPT_REUSEPORT, SOCK_OPT_BROADCAST, @@ -176,4 +222,11 @@ void net_socket_cache_init_accept(int guest_fd) }; net_socket_cache_set_many_zero(guest_fd, zero_opts, ARRAY_SIZE(zero_opts)); + + /* AF_UNIX accept inherits SO_PASSCRED from the listener. For local + * connects the accept path receives the value captured when the + * connection was queued; otherwise it falls back to the listener value. + */ + net_socket_cache_set_index(guest_fd, SOCK_OPT_PASSCRED, + inherit_passcred ? 1 : 0); } diff --git a/src/syscall/net-sockopt.h b/src/syscall/net-sockopt.h index 45b2d04..b1a5a74 100644 --- a/src/syscall/net-sockopt.h +++ b/src/syscall/net-sockopt.h @@ -6,9 +6,16 @@ #pragma once +#include + int net_socket_fd_is_valid(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, + int level, + int optname, + int *value); void net_socket_cached_int_set(int guest_fd, int level, int optname, int value); void net_socket_cache_set_index(int guest_fd, int idx, int value); void net_socket_cache_init_defaults(int guest_fd, int domain, int real_type); -void net_socket_cache_init_accept(int guest_fd); +void net_socket_cache_init_accept(int guest_fd, int inherit_passcred); diff --git a/src/syscall/net.c b/src/syscall/net.c index 05b0c76..7b03cab 100644 --- a/src/syscall/net.c +++ b/src/syscall/net.c @@ -112,7 +112,7 @@ int64_t sys_socket(guest_t *g, int domain, int type, int protocol) return linux_errno(); } - int gfd = fd_alloc(FD_SOCKET, fd, NULL); + int gfd = fd_alloc(FD_SOCKET, fd, absock_unregister_fd); if (gfd < 0) { close(fd); return -LINUX_EMFILE; @@ -138,7 +138,7 @@ int64_t sys_socket(guest_t *g, int domain, int type, int protocol) int one = 1; setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)); - int gfd = fd_alloc(FD_SOCKET, fd, NULL); + int gfd = fd_alloc(FD_SOCKET, fd, absock_unregister_fd); if (gfd < 0) { close(fd); return -LINUX_EMFILE; @@ -180,13 +180,13 @@ int64_t sys_socketpair(guest_t *g, setsockopt(fds[i], SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)); } - int gfd0 = fd_alloc(FD_SOCKET, fds[0], NULL); + int gfd0 = fd_alloc(FD_SOCKET, fds[0], absock_unregister_fd); if (gfd0 < 0) { close(fds[0]); close(fds[1]); return -LINUX_EMFILE; } - int gfd1 = fd_alloc(FD_SOCKET, fds[1], NULL); + int gfd1 = fd_alloc(FD_SOCKET, fds[1], absock_unregister_fd); if (gfd1 < 0) { fd_mark_closed(gfd0); close(fds[0]); @@ -268,6 +268,7 @@ int64_t sys_bind(guest_t *g, int fd, uint64_t addr_gva, uint32_t addrlen) } if (absock_idx >= 0) absock_bind_commit(absock_idx); + host_fd_ref_close(&host_ref); return 0; } @@ -295,10 +296,41 @@ static int64_t do_accept(guest_t *g, int nonblock, int cloexec) { - host_fd_ref_t host_ref; - if (host_fd_ref_open(fd, &host_ref) < 0) + if (!RANGE_CHECK(fd, 0, FD_TABLE_SIZE)) return -LINUX_EBADF; + host_fd_ref_t host_ref = {.fd = -1, .owned = false}; + uint64_t listener_generation = 0; + int listener_passcred_fallback = 0; + int listener_type = FD_CLOSED; + + if (thread_is_single_active()) { + if (host_fd_ref_open(fd, &host_ref) < 0) + return -LINUX_EBADF; + listener_type = fd_table[fd].type; + listener_generation = fd_table[fd].generation; + if (listener_type == FD_SOCKET) + (void) sock_opt_get(&fd_table[fd], SOCK_OPT_PASSCRED, + &listener_passcred_fallback); + } else { + fd_entry_t listener_snap = {.type = FD_CLOSED}; + int host_fd = fd_snapshot_and_dup(fd, &listener_snap); + if (host_fd < 0) + return -LINUX_EBADF; + host_ref.fd = host_fd; + host_ref.owned = true; + listener_type = listener_snap.type; + listener_generation = listener_snap.generation; + if (listener_type == FD_SOCKET) + (void) sock_opt_get(&listener_snap, SOCK_OPT_PASSCRED, + &listener_passcred_fallback); + } + + if (listener_type != FD_SOCKET) { + host_fd_ref_close(&host_ref); + return -LINUX_ENOTSOCK; + } + struct sockaddr_storage mac_sa; socklen_t mac_len = sizeof(mac_sa); @@ -307,6 +339,11 @@ static int64_t do_accept(guest_t *g, if (new_fd < 0) return linux_errno(); + int listener_passcred = listener_passcred_fallback; + (void) net_socket_cached_int_get_if_generation( + fd, listener_generation, LINUX_SOL_SOCKET, LINUX_SO_PASSCRED, + &listener_passcred); + if ((nonblock && fd_set_nonblock(new_fd) < 0) || (cloexec && fd_set_cloexec(new_fd) < 0)) { close(new_fd); @@ -316,13 +353,13 @@ static int64_t do_accept(guest_t *g, int one = 1; setsockopt(new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)); - int gfd = fd_alloc(FD_SOCKET, new_fd, NULL); + int gfd = fd_alloc(FD_SOCKET, new_fd, absock_unregister_fd); if (gfd < 0) { close(new_fd); return -LINUX_EMFILE; } fd_table[gfd].linux_flags = cloexec ? LINUX_O_CLOEXEC : 0; - net_socket_cache_init_accept(gfd); + net_socket_cache_init_accept(gfd, listener_passcred); /* Write back peer address if requested. The accept has already succeeded * and gfd is valid; EFAULT here mirrors Linux kernel behavior (close the @@ -469,7 +506,7 @@ int64_t sys_connect(guest_t *g, int fd, uint64_t addr_gva, uint32_t addrlen) return linux_errno(); } - if (fd_alloc_at(fd, FD_SOCKET, pair[0], NULL) < 0) { + if (fd_alloc_at(fd, FD_SOCKET, pair[0], absock_unregister_fd) < 0) { close(pair[0]); close(pair[1]); host_fd_ref_close(&host_ref); @@ -793,11 +830,12 @@ int64_t sys_setsockopt(guest_t *g, if (level == LINUX_SOL_SOCKET && optname == LINUX_SO_PASSCRED) { if (!net_socket_fd_is_valid(fd)) return -LINUX_EBADF; + if (optlen == 0 || optlen > sizeof(int)) + return -LINUX_EINVAL; int value = 0; - if (optlen > 0 && optlen <= sizeof(int)) { - if (guest_read_small(g, optval_gva, &value, optlen) < 0) - return -LINUX_EFAULT; - } + if (guest_read_small(g, optval_gva, &value, optlen) < 0) + return -LINUX_EFAULT; + value = socket_small_int_normalize(level, optname, value); net_socket_cached_int_set(fd, LINUX_SOL_SOCKET, LINUX_SO_PASSCRED, value); return 0; @@ -817,10 +855,10 @@ int64_t sys_setsockopt(guest_t *g, */ if (!net_socket_fd_is_valid(fd)) return -LINUX_EBADF; - if (optlen > sizeof(int)) + if (optlen == 0 || optlen > sizeof(int)) return -LINUX_EINVAL; int value = 0; - if (optlen > 0 && guest_read_small(g, optval_gva, &value, optlen) < 0) + if (guest_read_small(g, optval_gva, &value, optlen) < 0) return -LINUX_EFAULT; net_socket_cached_int_set(fd, LINUX_IPPROTO_IP, LINUX_IP_MTU_DISCOVER, value); @@ -841,20 +879,18 @@ int64_t sys_setsockopt(guest_t *g, */ if (!net_socket_fd_is_valid(fd)) return -LINUX_EBADF; - if (optlen > sizeof(int)) + if (optlen == 0 || optlen > sizeof(int)) return -LINUX_EINVAL; - if (optlen > 0) { - int value = 0; - if (guest_read_small(g, optval_gva, &value, optlen) < 0) - return -LINUX_EFAULT; - (void) value; - } + int value = 0; + if (guest_read_small(g, optval_gva, &value, optlen) < 0) + return -LINUX_EFAULT; + (void) value; return 0; } - if (optlen <= sizeof(int) && small_int_opt) { + if (optlen > 0 && optlen <= sizeof(int) && small_int_opt) { int value = 0; - if (optlen > 0 && guest_read_small(g, optval_gva, &value, optlen) < 0) + if (guest_read_small(g, optval_gva, &value, optlen) < 0) return -LINUX_EFAULT; value = socket_small_int_normalize(level, optname, value); @@ -918,8 +954,12 @@ int64_t sys_setsockopt(guest_t *g, setsockopt_translated: if (optlen <= sizeof(int) && small_int_opt) { + if (optlen == 0) { + host_fd_ref_close(&host_ref); + return -LINUX_EINVAL; + } int value = 0; - if (optlen > 0 && guest_read_small(g, optval_gva, &value, optlen) < 0) { + if (guest_read_small(g, optval_gva, &value, optlen) < 0) { host_fd_ref_close(&host_ref); return -LINUX_EFAULT; } @@ -938,8 +978,14 @@ int64_t sys_setsockopt(guest_t *g, } } + /* Linux accepts shorter optlen for many int-valued options and + * zero-extends the value. macOS rejects optlen < sizeof(int) with + * EINVAL (notably for IP_TOS / IP_TTL / IP_PKTINFO / IP_RECVTTL / + * IP_RECVTOS). The value has already been zero-extended into an int, + * so always call the host with sizeof(int). + */ if (setsockopt(host_ref.fd, mac_level, mac_optname, &value, - (socklen_t) optlen) < 0) { + sizeof(value)) < 0) { log_debug("setsockopt(fd=%d, level=%d/%d, opt=%d/%d, len=%u): %s", fd, level, mac_level, optname, mac_optname, optlen, strerror(errno)); @@ -973,6 +1019,21 @@ int64_t sys_setsockopt(guest_t *g, return 0; } +/* Mirrors Linux ip_sockglue copyval: when an IP-level getsockopt has a + * caller buffer shorter than int and the int-sized value fits in a byte, + * report and write a single byte. Otherwise leaves actual_len untouched. + */ +static inline uint32_t ip_copyval_clamp(int level, + uint32_t guest_optlen, + int value, + uint32_t actual_len) +{ + if (level == LINUX_IPPROTO_IP && guest_optlen > 0 && + guest_optlen < sizeof(int) && value >= 0 && value <= 255) + return 1; + return actual_len; +} + int64_t sys_getsockopt(guest_t *g, int fd, int level, @@ -1032,13 +1093,16 @@ int64_t sys_getsockopt(guest_t *g, return 0; } - if (guest_optlen <= sizeof(int)) { + if (socket_opt_uses_small_int(level, optname)) { int value = 0; if (net_socket_cached_int_get(fd, level, optname, &value)) { - uint32_t actual_len = sizeof(int), write_len = actual_len; + uint32_t actual_len = + ip_copyval_clamp(level, guest_optlen, value, sizeof(int)); + uint32_t write_len = actual_len; if (write_len > guest_optlen) write_len = guest_optlen; - if (guest_write_small(g, optval_gva, &value, write_len) < 0) + if (write_len > 0 && + guest_write_small(g, optval_gva, &value, write_len) < 0) return -LINUX_EFAULT; if (guest_write_small(g, optlen_gva, &actual_len, sizeof(actual_len)) < 0) @@ -1107,7 +1171,7 @@ int64_t sys_getsockopt(guest_t *g, return -LINUX_EFAULT; } - if (guest_optlen <= sizeof(int) && small_int_opt) { + if (small_int_opt) { int value = 0; socklen_t mac_optlen = sizeof(value); uint32_t actual_len, write_len; @@ -1133,6 +1197,7 @@ int64_t sys_getsockopt(guest_t *g, } actual_len = used_cache ? sizeof(int) : (uint32_t) mac_optlen; + actual_len = ip_copyval_clamp(level, guest_optlen, value, actual_len); write_len = actual_len; if (write_len > guest_optlen) write_len = guest_optlen; diff --git a/src/syscall/poll.c b/src/syscall/poll.c index 09507c5..2fe9923 100644 --- a/src/syscall/poll.c +++ b/src/syscall/poll.c @@ -58,49 +58,19 @@ void wakeup_pipe_signal(void) /* polling/select. */ -#define PPOLL_FD_CACHE_SIZE 512 - typedef struct { int host_fd; uint16_t word; uint8_t bit_index; + short events; + short revents; + host_fd_ref_t ref; } pselect_req_t; -static int ppoll_lookup_host_fd(const int *cached_guest_fds, - const int *cached_host_fds, - int guest_fd) +static inline void host_fd_refs_close(host_fd_ref_t *refs, uint32_t n) { - unsigned int slot = - ((unsigned int) guest_fd * 2654435761u) & (PPOLL_FD_CACHE_SIZE - 1); - - for (int probes = 0; probes < PPOLL_FD_CACHE_SIZE; probes++) { - if (cached_guest_fds[slot] == -1) - return -1; - if (cached_guest_fds[slot] == guest_fd) - return cached_host_fds[slot]; - slot = (slot + 1) & (PPOLL_FD_CACHE_SIZE - 1); - } - - return -1; -} - -static void ppoll_cache_host_fd(int *cached_guest_fds, - int *cached_host_fds, - int guest_fd, - int host_fd) -{ - unsigned int slot = - ((unsigned int) guest_fd * 2654435761u) & (PPOLL_FD_CACHE_SIZE - 1); - - for (int probes = 0; probes < PPOLL_FD_CACHE_SIZE; probes++) { - if (cached_guest_fds[slot] == -1 || - cached_guest_fds[slot] == guest_fd) { - cached_guest_fds[slot] = guest_fd; - cached_host_fds[slot] = host_fd; - return; - } - slot = (slot + 1) & (PPOLL_FD_CACHE_SIZE - 1); - } + for (uint32_t i = 0; i < n; i++) + host_fd_ref_close(&refs[i]); } int64_t sys_ppoll(guest_t *g, @@ -122,37 +92,19 @@ int64_t sys_ppoll(guest_t *g, /* Translate guest FDs to host FDs */ struct pollfd host_fds[256]; - int cached_guest_fds[PPOLL_FD_CACHE_SIZE]; - int cached_host_fds[PPOLL_FD_CACHE_SIZE]; - int seen_guest_fds[32], seen_host_fds[32]; - uint32_t seen_count = 0; - bool use_linear_cache = (nfds <= 32); - - if (!use_linear_cache) - memset(cached_guest_fds, 0xff, sizeof(cached_guest_fds)); - + host_fd_ref_t host_refs[256]; + bool need_pollnval[256] = {false}; + uint32_t invalid_count = 0; for (uint32_t i = 0; i < nfds; i++) { - int guest_fd = guest_fds[i].fd, host_fd = -1; - if (use_linear_cache) { - for (uint32_t j = 0; j < seen_count; j++) { - if (seen_guest_fds[j] == guest_fd) { - host_fd = seen_host_fds[j]; - break; - } - } - if (host_fd < 0) { - host_fd = fd_to_host(guest_fd); - seen_guest_fds[seen_count] = guest_fd; - seen_host_fds[seen_count] = host_fd; - seen_count++; - } - } else { - host_fd = ppoll_lookup_host_fd(cached_guest_fds, cached_host_fds, - guest_fd); - if (host_fd < 0) { - host_fd = fd_to_host(guest_fd); - ppoll_cache_host_fd(cached_guest_fds, cached_host_fds, guest_fd, - host_fd); + host_refs[i] = (host_fd_ref_t) {.fd = -1, .owned = false}; + int guest_fd = guest_fds[i].fd; + int host_fd = -1; + if (guest_fd >= 0) { + if (host_fd_ref_open_io(guest_fd, &host_refs[i]) < 0) { + need_pollnval[i] = true; + invalid_count++; + } else { + host_fd = host_refs[i].fd; } } host_fds[i].fd = host_fd; @@ -198,11 +150,15 @@ int64_t sys_ppoll(guest_t *g, int timeout_ms = -1; /* Infinite by default */ if (timeout_gva != 0) { linux_timespec_t lts; - if (guest_read_small(g, timeout_gva, <s, sizeof(lts)) < 0) + if (guest_read_small(g, timeout_gva, <s, sizeof(lts)) < 0) { + host_fd_refs_close(host_refs, nfds); return -LINUX_EFAULT; + } /* Linux returns EINVAL for negative timeout values */ - if (lts.tv_sec < 0 || !RANGE_CHECK(lts.tv_nsec, 0, 1000000000LL)) + if (lts.tv_sec < 0 || !RANGE_CHECK(lts.tv_nsec, 0, 1000000000LL)) { + host_fd_refs_close(host_refs, nfds); return -LINUX_EINVAL; + } /* Guard against overflow: tv_sec * 1000 can exceed INT64_MAX */ int64_t ms64; if (lts.tv_sec > INT64_MAX / 1000) @@ -217,12 +173,13 @@ int64_t sys_ppoll(guest_t *g, bool mask_installed = false; if (sigmask_gva != 0) { uint64_t new_mask; - if (guest_read_small(g, sigmask_gva, &new_mask, sizeof(new_mask)) == - 0) { - saved_mask = signal_save_blocked(); - signal_set_blocked(new_mask); - mask_installed = true; + if (guest_read_small(g, sigmask_gva, &new_mask, sizeof(new_mask)) < 0) { + host_fd_refs_close(host_refs, nfds); + return -LINUX_EFAULT; } + saved_mask = signal_save_blocked(); + signal_set_blocked(new_mask); + mask_installed = true; } /* For indefinite polls, add the wakeup pipe so exit_group can @@ -238,10 +195,19 @@ int64_t sys_ppoll(guest_t *g, added_wakeup = true; } + /* When any guest fd is invalid, Linux still polls the rest and returns + * POLLNVAL on the bad ones alongside revents on the good ones. Force a + * non-blocking poll() so valid fds with pending events still get reported + * in the same call. + */ + int poll_timeout_ms = timeout_ms; + if (invalid_count > 0) + poll_timeout_ms = 0; + int ret; do { ret = poll(host_fds, nfds + added_wakeup, - timeout_ms < 0 ? 200 : timeout_ms); + poll_timeout_ms < 0 ? 200 : poll_timeout_ms); /* Check for exit_group / futex_interrupt after waking */ if (proc_exit_group_requested() || futex_interrupt_pending()) { @@ -254,7 +220,18 @@ int64_t sys_ppoll(guest_t *g, * and nothing happened, loop back. If the caller had a real timeout, * poll emulation only called poll once with that timeout, so break. */ - } while (ret == 0 && timeout_ms < 0); + } while (ret == 0 && poll_timeout_ms < 0); + + /* POSIX poll() ignores entries with fd < 0 and resets revents to 0, + * so re-stamp POLLNVAL on the invalid slots and credit them to the + * return count. + */ + if (ret >= 0 && invalid_count > 0) { + for (uint32_t i = 0; i < nfds; i++) + if (need_pollnval[i]) + host_fds[i].revents = POLLNVAL; + ret += (int) invalid_count; + } int saved_errno = errno; @@ -273,6 +250,8 @@ int64_t sys_ppoll(guest_t *g, if (mask_installed) signal_restore_blocked(saved_mask); + host_fd_refs_close(host_refs, nfds); + if (ret < 0) { errno = saved_errno; return linux_errno(); @@ -400,10 +379,22 @@ int64_t sys_pselect6(guest_t *g, int bit_index = bit_ctz64(requested); int i = word * 64 + bit_index; uint64_t bit = BIT64(bit_index); - int host_fd = fd_to_host(i); + host_fd_ref_t ref = {.fd = -1, .owned = false}; + if (host_fd_ref_open_io(i, &ref) < 0) + goto pselect_badf; + int host_fd = ref.fd; reqs[req_count].host_fd = host_fd; reqs[req_count].word = (uint16_t) word; reqs[req_count].bit_index = (uint8_t) bit_index; + reqs[req_count].events = 0; + reqs[req_count].revents = 0; + if (rbits && (rbits[word] & bit)) + reqs[req_count].events |= POLLIN; + if (wbits && (wbits[word] & bit)) + reqs[req_count].events |= POLLOUT; + if (ebits && (ebits[word] & bit)) + reqs[req_count].events |= POLLPRI; + reqs[req_count].ref = ref; req_count++; if (RANGE_CHECK(host_fd, 0, FD_SETSIZE)) { if (host_fd > max_host_fd) @@ -444,15 +435,18 @@ int64_t sys_pselect6(guest_t *g, struct { uint64_t ss, ss_len; } ssarg; - if (guest_read_small(g, sigmask_gva, &ssarg, sizeof(ssarg)) == 0 && - ssarg.ss != 0 && ssarg.ss_len == 8) { + if (guest_read_small(g, sigmask_gva, &ssarg, sizeof(ssarg)) < 0) + goto pselect_fault; + if (ssarg.ss != 0) { + /* Linux requires ss_len == sizeof(sigset_t). */ + if (ssarg.ss_len != 8) + goto pselect_inval; uint64_t new_mask; - if (guest_read_small(g, ssarg.ss, &new_mask, sizeof(new_mask)) == - 0) { - saved_blocked = signal_save_blocked(); - signal_set_blocked(new_mask); - mask_applied = true; - } + if (guest_read_small(g, ssarg.ss, &new_mask, sizeof(new_mask)) < 0) + goto pselect_fault; + saved_blocked = signal_save_blocked(); + signal_set_blocked(new_mask); + mask_applied = true; } } @@ -461,9 +455,11 @@ int64_t sys_pselect6(guest_t *g, */ bool added_wakeup = false; if (!has_timeout && wakeup_pipe_rd >= 0) { - FD_SET(wakeup_pipe_rd, &read_set); - if (wakeup_pipe_rd > max_host_fd) - max_host_fd = wakeup_pipe_rd; + if (RANGE_CHECK(wakeup_pipe_rd, 0, FD_SETSIZE)) { + FD_SET(wakeup_pipe_rd, &read_set); + if (wakeup_pipe_rd > max_host_fd) + max_host_fd = wakeup_pipe_rd; + } added_wakeup = true; read_setp = &read_set; } @@ -484,7 +480,18 @@ int64_t sys_pselect6(guest_t *g, saved_except = except_set; } + bool use_poll_fallback = false; + for (int i = 0; i < req_count; i++) { + if (!RANGE_CHECK(reqs[i].host_fd, 0, FD_SETSIZE)) { + use_poll_fallback = true; + break; + } + } + if (added_wakeup && !RANGE_CHECK(wakeup_pipe_rd, 0, FD_SETSIZE)) + use_poll_fallback = true; + int ret; + bool poll_wakeup_fired = false; do { if (!has_timeout) { if (read_setp) @@ -495,8 +502,52 @@ int64_t sys_pselect6(guest_t *g, except_set = saved_except; } - ret = pselect(max_host_fd + 1, read_setp, write_setp, except_setp, - has_timeout ? &ts : &poll_ts, NULL); + if (use_poll_fallback) { + struct pollfd poll_stack[64]; + struct pollfd *poll_fds = poll_stack; + struct pollfd *poll_heap = NULL; + int poll_count = req_count + (added_wakeup ? 1 : 0); + if (poll_count > (int) ARRAY_SIZE(poll_stack)) { + poll_heap = malloc((size_t) poll_count * sizeof(*poll_heap)); + if (!poll_heap) { + ret = -1; + errno = ENOMEM; + break; + } + poll_fds = poll_heap; + } + for (int i = 0; i < req_count; i++) { + poll_fds[i].fd = reqs[i].host_fd; + poll_fds[i].events = reqs[i].events; + poll_fds[i].revents = 0; + } + if (added_wakeup) { + poll_fds[req_count].fd = wakeup_pipe_rd; + poll_fds[req_count].events = POLLIN; + poll_fds[req_count].revents = 0; + } + + const struct timespec *wait_ts = has_timeout ? &ts : &poll_ts; + int64_t ms64; + if (wait_ts->tv_sec > INT64_MAX / 1000) + ms64 = INT64_MAX; + else + ms64 = wait_ts->tv_sec * (int64_t) 1000 + + (wait_ts->tv_nsec + 999999) / 1000000; + int timeout_ms = (ms64 > INT_MAX) ? INT_MAX : (int) ms64; + + ret = poll(poll_fds, (nfds_t) poll_count, timeout_ms); + if (ret >= 0) { + for (int i = 0; i < req_count; i++) + reqs[i].revents = poll_fds[i].revents; + poll_wakeup_fired = + added_wakeup && (poll_fds[req_count].revents & POLLIN); + } + free(poll_heap); + } else { + ret = pselect(max_host_fd + 1, read_setp, write_setp, except_setp, + has_timeout ? &ts : &poll_ts, NULL); + } if (proc_exit_group_requested() || futex_interrupt_pending()) { ret = -1; @@ -510,11 +561,16 @@ int64_t sys_pselect6(guest_t *g, /* Drain wakeup pipe if it fired, and subtract from count since the wakeup * pipe is not visible to the guest. */ - if (added_wakeup && FD_ISSET(wakeup_pipe_rd, &read_set)) { + bool wakeup_fired = + added_wakeup && + (use_poll_fallback ? poll_wakeup_fired + : FD_ISSET(wakeup_pipe_rd, &read_set)); + if (wakeup_fired) { uint8_t drain; while (read(wakeup_pipe_rd, &drain, 1) > 0) ; - FD_CLR(wakeup_pipe_rd, &read_set); + if (!use_poll_fallback) + FD_CLR(wakeup_pipe_rd, &read_set); if (ret > 0) ret--; } @@ -523,6 +579,9 @@ int64_t sys_pselect6(guest_t *g, if (mask_applied) signal_restore_blocked(saved_blocked); + for (int i = 0; i < req_count; i++) + host_fd_ref_close(&reqs[i].ref); + if (ret < 0) { errno = save_errno; free(reqs_heap); @@ -551,7 +610,15 @@ int64_t sys_pselect6(guest_t *g, for (int i = 0; i < req_count; i++) { int host_fd = reqs[i].host_fd, word = reqs[i].word; uint64_t bit = BIT64(reqs[i].bit_index); - if (RANGE_CHECK(host_fd, 0, FD_SETSIZE)) { + if (use_poll_fallback) { + short revents = reqs[i].revents; + if (rbits && (revents & (POLLIN | POLLHUP | POLLERR))) + rbits[word] |= bit; + if (wbits && (revents & (POLLOUT | POLLHUP | POLLERR))) + wbits[word] |= bit; + if (ebits && (revents & POLLPRI)) + ebits[word] |= bit; + } else if (RANGE_CHECK(host_fd, 0, FD_SETSIZE)) { if (rbits && FD_ISSET(host_fd, &read_set)) rbits[word] |= bit; if (wbits && FD_ISSET(host_fd, write_setp)) @@ -572,13 +639,20 @@ int64_t sys_pselect6(guest_t *g, free(reqs_heap); return ret; + int64_t err; pselect_fault: - free(reqs_heap); - return -LINUX_EFAULT; - + err = -LINUX_EFAULT; + goto pselect_cleanup; pselect_inval: + err = -LINUX_EINVAL; + goto pselect_cleanup; +pselect_badf: + err = -LINUX_EBADF; +pselect_cleanup: + for (int i = 0; i < req_count; i++) + host_fd_ref_close(&reqs[i].ref); free(reqs_heap); - return -LINUX_EINVAL; + return err; } /* epoll emulation via kqueue diff --git a/src/syscall/signal.c b/src/syscall/signal.c index 6f6f4e1..5e4c820 100644 --- a/src/syscall/signal.c +++ b/src/syscall/signal.c @@ -1607,7 +1607,7 @@ int signal_rt_sigreturn(hv_vcpu_t vcpu, guest_t *g) /* Read the rt_sigframe from guest stack */ linux_rt_sigframe_t frame; - if (guest_read(g, sp, &frame, sizeof(frame)) < 0) + if (guest_read_small(g, sp, &frame, sizeof(frame)) < 0) return -LINUX_EFAULT; /* Validate SROP cookie from uc_flags. Zero means the cookie was diff --git a/tests/test-net.c b/tests/test-net.c index 1d41006..301f37d 100644 --- a/tests/test-net.c +++ b/tests/test-net.c @@ -24,6 +24,20 @@ #include "test-harness.h" +static int ip_bool_roundtrip(int sock, int optname) +{ + int val = 42; + if (setsockopt(sock, IPPROTO_IP, optname, &val, sizeof(val)) < 0) + return -1; + + val = 0; + socklen_t len = sizeof(val); + if (getsockopt(sock, IPPROTO_IP, optname, &val, &len) < 0) + return -1; + + return val; +} + int main(void) { int passes = 0, fails = 0; @@ -339,6 +353,21 @@ ipv6_done:; FAIL("socket failed"); } + TEST("IP boolean sockopts normalize to 1"); + { + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock >= 0) { + int pktinfo = ip_bool_roundtrip(sock, IP_PKTINFO); + int recvttl = ip_bool_roundtrip(sock, IP_RECVTTL); + int recvtos = ip_bool_roundtrip(sock, IP_RECVTOS); + + EXPECT_TRUE(pktinfo == 1 && recvttl == 1 && recvtos == 1, + "IP boolean sockopt returned non-normalized value"); + close(sock); + } else + FAIL("socket failed"); + } + SUMMARY("test-net"); return fails > 0 ? 1 : 0; } diff --git a/tests/test-scm-creds.c b/tests/test-scm-creds.c index 1be6247..aa24421 100644 --- a/tests/test-scm-creds.c +++ b/tests/test-scm-creds.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,196 @@ struct linux_iov { unsigned long iov_base, iov_len; }; +struct accept_thread_args { + int srv; + int acc; + int ready; + pthread_mutex_t lock; + pthread_cond_t cond; +}; + +static void *accept_thread_main(void *opaque) +{ + struct accept_thread_args *args = opaque; + + pthread_mutex_lock(&args->lock); + args->ready = 1; + pthread_cond_signal(&args->cond); + pthread_mutex_unlock(&args->lock); + + args->acc = accept(args->srv, NULL, NULL); + return NULL; +} + +static void test_accept_inherits_passcred(void) +{ + TEST("accept inherits SO_PASSCRED"); + + int srv = -1, cli = -1, acc = -1; + char path[sizeof(((struct sockaddr_un *) 0)->sun_path)]; + snprintf(path, sizeof(path), "/tmp/elfuse-scm-creds-%ld.sock", + (long) getpid()); + unlink(path); + + srv = socket(AF_UNIX, SOCK_STREAM, 0); + if (srv < 0) { + FAIL("socket server failed"); + goto done; + } + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", path); + + if (bind(srv, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + FAIL("bind failed"); + goto done; + } + if (listen(srv, 1) < 0) { + FAIL("listen failed"); + goto done; + } + + int one = 1; + if (setsockopt(srv, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) { + FAIL("setsockopt listener SO_PASSCRED failed"); + goto done; + } + + cli = socket(AF_UNIX, SOCK_STREAM, 0); + if (cli < 0) { + FAIL("socket client failed"); + goto done; + } + if (connect(cli, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + FAIL("connect failed"); + goto done; + } + + acc = accept(srv, NULL, NULL); + if (acc < 0) { + FAIL("accept failed"); + goto done; + } + + int val = 0; + socklen_t len = sizeof(val); + if (getsockopt(acc, SOL_SOCKET, SO_PASSCRED, &val, &len) == 0 && val == 1) + PASS(); + else + FAIL("accepted socket did not inherit SO_PASSCRED"); + +done: + if (acc >= 0) + close(acc); + if (cli >= 0) + close(cli); + if (srv >= 0) + close(srv); + unlink(path); +} + +static void test_blocked_accept_sees_passcred_toggle(void) +{ + TEST("blocked accept observes SO_PASSCRED toggle"); + + int srv = -1, cli = -1; + pthread_t thread; + int thread_started = 0; + struct accept_thread_args args = { + .srv = -1, + .acc = -1, + .ready = 0, + .lock = PTHREAD_MUTEX_INITIALIZER, + .cond = PTHREAD_COND_INITIALIZER, + }; + char path[sizeof(((struct sockaddr_un *) 0)->sun_path)]; + snprintf(path, sizeof(path), "/tmp/elfuse-scm-creds-toggle-%ld.sock", + (long) getpid()); + unlink(path); + + srv = socket(AF_UNIX, SOCK_STREAM, 0); + if (srv < 0) { + FAIL("socket server failed"); + goto done; + } + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", path); + + if (bind(srv, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + FAIL("bind failed"); + goto done; + } + if (listen(srv, 1) < 0) { + FAIL("listen failed"); + goto done; + } + + args.srv = srv; + if (pthread_create(&thread, NULL, accept_thread_main, &args) != 0) { + FAIL("pthread_create failed"); + goto done; + } + thread_started = 1; + + pthread_mutex_lock(&args.lock); + while (!args.ready) + pthread_cond_wait(&args.cond, &args.lock); + pthread_mutex_unlock(&args.lock); + + int one = 1; + if (setsockopt(srv, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) { + FAIL("setsockopt listener SO_PASSCRED failed"); + goto done; + } + + cli = socket(AF_UNIX, SOCK_STREAM, 0); + if (cli < 0) { + FAIL("socket client failed"); + goto done; + } + if (connect(cli, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + FAIL("connect failed"); + goto done; + } + + pthread_join(thread, NULL); + thread_started = 0; + if (args.acc < 0) { + FAIL("accept failed"); + goto done; + } + + int val = 0; + socklen_t len = sizeof(val); + if (getsockopt(args.acc, SOL_SOCKET, SO_PASSCRED, &val, &len) == 0 && + val == 1) + PASS(); + else + FAIL("accepted socket used stale SO_PASSCRED"); + +done: + if (thread_started && args.acc < 0 && srv >= 0) { + close(srv); + srv = -1; + } + if (thread_started) + pthread_join(thread, NULL); + if (args.acc >= 0) + close(args.acc); + if (cli >= 0) + close(cli); + if (srv >= 0) + close(srv); + pthread_cond_destroy(&args.cond); + pthread_mutex_destroy(&args.lock); + unlink(path); +} + int main(void) { printf("test-scm-creds: SCM_CREDENTIALS / SO_PASSCRED\n"); @@ -74,6 +265,15 @@ int main(void) if (r < 0) goto done; + TEST("getsockopt SO_PASSCRED default"); + { + int val = -1, len = sizeof(val); + r = raw_syscall5(__NR_getsockopt, sv[1], LINUX_SOL_SOCKET, + LINUX_SO_PASSCRED, (long) &val, (long) &len); + EXPECT_TRUE(r == 0 && val == 0, + "SO_PASSCRED default returned wrong value"); + } + /* Set SO_PASSCRED on receiver */ TEST("setsockopt SO_PASSCRED"); int one = 1; @@ -91,6 +291,23 @@ int main(void) "getsockopt SO_PASSCRED returned wrong value"); } + TEST("SO_PASSCRED normalizes boolean value"); + { + int val = 42; + r = raw_syscall5(__NR_setsockopt, sv[1], LINUX_SOL_SOCKET, + LINUX_SO_PASSCRED, (long) &val, sizeof(val)); + if (r < 0) { + FAIL("setsockopt failed"); + } else { + val = 0; + int len = sizeof(val); + r = raw_syscall5(__NR_getsockopt, sv[1], LINUX_SOL_SOCKET, + LINUX_SO_PASSCRED, (long) &val, (long) &len); + EXPECT_TRUE(r == 0 && val == 1, + "SO_PASSCRED did not normalize to 1"); + } + } + /* Get SO_PEERCRED */ TEST("getsockopt SO_PEERCRED"); { @@ -179,6 +396,8 @@ int main(void) raw_syscall1(__NR_close, sv[1]); done: + test_accept_inherits_passcred(); + test_blocked_accept_sees_passcred_toggle(); SUMMARY("test-scm-creds"); return fails ? 1 : 0; }