summaryrefslogtreecommitdiff
path: root/tools/testing/vsock
diff options
context:
space:
mode:
Diffstat (limited to 'tools/testing/vsock')
-rw-r--r--tools/testing/vsock/Makefile1
-rw-r--r--tools/testing/vsock/timeout.c18
-rw-r--r--tools/testing/vsock/timeout.h1
-rw-r--r--tools/testing/vsock/util.c130
-rw-r--r--tools/testing/vsock/util.h37
-rw-r--r--tools/testing/vsock/vsock_test.c482
6 files changed, 614 insertions, 55 deletions
diff --git a/tools/testing/vsock/Makefile b/tools/testing/vsock/Makefile
index 6e0b4e95e230..88211fd132d2 100644
--- a/tools/testing/vsock/Makefile
+++ b/tools/testing/vsock/Makefile
@@ -5,6 +5,7 @@ vsock_test: vsock_test.o vsock_test_zerocopy.o timeout.o control.o util.o msg_ze
vsock_diag_test: vsock_diag_test.o timeout.o control.o util.o
vsock_perf: vsock_perf.o msg_zerocopy_common.o
+vsock_test: LDLIBS = -lpthread
vsock_uring_test: LDLIBS = -luring
vsock_uring_test: control.o util.o vsock_uring_test.o timeout.o msg_zerocopy_common.o
diff --git a/tools/testing/vsock/timeout.c b/tools/testing/vsock/timeout.c
index 44aee49b6cee..1453d38e08bb 100644
--- a/tools/testing/vsock/timeout.c
+++ b/tools/testing/vsock/timeout.c
@@ -21,6 +21,7 @@
#include <stdbool.h>
#include <unistd.h>
#include <stdio.h>
+#include <time.h>
#include "timeout.h"
static volatile bool timeout;
@@ -28,6 +29,8 @@ static volatile bool timeout;
/* SIGALRM handler function. Do not use sleep(2), alarm(2), or
* setitimer(2) while using this API - they may interfere with each
* other.
+ *
+ * If you need to sleep, please use timeout_sleep() provided by this API.
*/
void sigalrm(int signo)
{
@@ -58,3 +61,18 @@ void timeout_end(void)
alarm(0);
timeout = false;
}
+
+/* Sleep in a timeout section.
+ *
+ * nanosleep(2) can be used with this API since POSIX.1 explicitly
+ * specifies that it does not interact with signals.
+ */
+int timeout_usleep(useconds_t usec)
+{
+ struct timespec ts = {
+ .tv_sec = usec / 1000000,
+ .tv_nsec = (usec % 1000000) * 1000,
+ };
+
+ return nanosleep(&ts, NULL);
+}
diff --git a/tools/testing/vsock/timeout.h b/tools/testing/vsock/timeout.h
index ecb7c840e65a..1c3fcad87a49 100644
--- a/tools/testing/vsock/timeout.h
+++ b/tools/testing/vsock/timeout.h
@@ -11,5 +11,6 @@ void sigalrm(int signo);
void timeout_begin(unsigned int seconds);
void timeout_check(const char *operation);
void timeout_end(void);
+int timeout_usleep(useconds_t usec);
#endif /* TIMEOUT_H */
diff --git a/tools/testing/vsock/util.c b/tools/testing/vsock/util.c
index de25892f865f..7b861a8e997a 100644
--- a/tools/testing/vsock/util.c
+++ b/tools/testing/vsock/util.c
@@ -7,6 +7,7 @@
* Author: Stefan Hajnoczi <stefanha@redhat.com>
*/
+#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdint.h>
@@ -16,12 +17,17 @@
#include <unistd.h>
#include <assert.h>
#include <sys/epoll.h>
+#include <sys/ioctl.h>
#include <sys/mman.h>
+#include <linux/sockios.h>
#include "timeout.h"
#include "control.h"
#include "util.h"
+#define KALLSYMS_PATH "/proc/kallsyms"
+#define KALLSYMS_LINE_LEN 512
+
/* Install signal handlers */
void init_signals(void)
{
@@ -96,15 +102,52 @@ void vsock_wait_remote_close(int fd)
close(epollfd);
}
-/* Create socket <type>, bind to <cid, port> and return the file descriptor. */
-int vsock_bind(unsigned int cid, unsigned int port, int type)
+/* Wait until ioctl gives an expected int value.
+ * Return false if the op is not supported.
+ */
+bool vsock_ioctl_int(int fd, unsigned long op, int expected)
+{
+ int actual, ret;
+ char name[32];
+
+ snprintf(name, sizeof(name), "ioctl(%lu)", op);
+
+ timeout_begin(TIMEOUT);
+ do {
+ ret = ioctl(fd, op, &actual);
+ if (ret < 0) {
+ if (errno == EOPNOTSUPP || errno == ENOTTY)
+ break;
+
+ perror(name);
+ exit(EXIT_FAILURE);
+ }
+ timeout_check(name);
+ } while (actual != expected);
+ timeout_end();
+
+ return ret >= 0;
+}
+
+/* Wait until transport reports no data left to be sent.
+ * Return false if transport does not implement the unsent_bytes() callback.
+ */
+bool vsock_wait_sent(int fd)
+{
+ return vsock_ioctl_int(fd, SIOCOUTQ, 0);
+}
+
+/* Create socket <type>, bind to <cid, port>.
+ * Return the file descriptor, or -1 on error.
+ */
+int vsock_bind_try(unsigned int cid, unsigned int port, int type)
{
struct sockaddr_vm sa = {
.svm_family = AF_VSOCK,
.svm_cid = cid,
.svm_port = port,
};
- int fd;
+ int fd, saved_errno;
fd = socket(AF_VSOCK, type, 0);
if (fd < 0) {
@@ -113,6 +156,22 @@ int vsock_bind(unsigned int cid, unsigned int port, int type)
}
if (bind(fd, (struct sockaddr *)&sa, sizeof(sa))) {
+ saved_errno = errno;
+ close(fd);
+ errno = saved_errno;
+ fd = -1;
+ }
+
+ return fd;
+}
+
+/* Create socket <type>, bind to <cid, port> and return the file descriptor. */
+int vsock_bind(unsigned int cid, unsigned int port, int type)
+{
+ int fd;
+
+ fd = vsock_bind_try(cid, port, type);
+ if (fd < 0) {
perror("bind");
exit(EXIT_FAILURE);
}
@@ -798,3 +857,68 @@ void enable_so_zerocopy_check(int fd)
setsockopt_int_check(fd, SOL_SOCKET, SO_ZEROCOPY, 1,
"setsockopt SO_ZEROCOPY");
}
+
+void enable_so_linger(int fd, int timeout)
+{
+ struct linger optval = {
+ .l_onoff = 1,
+ .l_linger = timeout
+ };
+
+ if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &optval, sizeof(optval))) {
+ perror("setsockopt(SO_LINGER)");
+ exit(EXIT_FAILURE);
+ }
+}
+
+static int __get_transports(void)
+{
+ char buf[KALLSYMS_LINE_LEN];
+ const char *ksym;
+ int ret = 0;
+ FILE *f;
+
+ f = fopen(KALLSYMS_PATH, "r");
+ if (!f) {
+ perror("Can't open " KALLSYMS_PATH);
+ exit(EXIT_FAILURE);
+ }
+
+ while (fgets(buf, sizeof(buf), f)) {
+ char *match;
+ int i;
+
+ assert(buf[strlen(buf) - 1] == '\n');
+
+ for (i = 0; i < TRANSPORT_NUM; ++i) {
+ if (ret & BIT(i))
+ continue;
+
+ /* Match should be followed by '\t' or '\n'.
+ * See kallsyms.c:s_show().
+ */
+ ksym = transport_ksyms[i];
+ match = strstr(buf, ksym);
+ if (match && isspace(match[strlen(ksym)])) {
+ ret |= BIT(i);
+ break;
+ }
+ }
+ }
+
+ fclose(f);
+ return ret;
+}
+
+/* Return integer with TRANSPORT_* bit set for every (known) registered vsock
+ * transport.
+ */
+int get_transports(void)
+{
+ static int tr = -1;
+
+ if (tr == -1)
+ tr = __get_transports();
+
+ return tr;
+}
diff --git a/tools/testing/vsock/util.h b/tools/testing/vsock/util.h
index d1f765ce3eee..142c02a6834a 100644
--- a/tools/testing/vsock/util.h
+++ b/tools/testing/vsock/util.h
@@ -3,8 +3,40 @@
#define UTIL_H
#include <sys/socket.h>
+#include <linux/bitops.h>
+#include <linux/kernel.h>
#include <linux/vm_sockets.h>
+/* All known vsock transports, see callers of vsock_core_register() */
+#define KNOWN_TRANSPORTS(x) \
+ x(LOOPBACK, "loopback") \
+ x(VIRTIO, "virtio") \
+ x(VHOST, "vhost") \
+ x(VMCI, "vmci") \
+ x(HYPERV, "hvs")
+
+enum transport {
+ TRANSPORT_COUNTER_BASE = __COUNTER__ + 1,
+ #define x(name, symbol) \
+ TRANSPORT_##name = BIT(__COUNTER__ - TRANSPORT_COUNTER_BASE),
+ KNOWN_TRANSPORTS(x)
+ TRANSPORT_NUM = __COUNTER__ - TRANSPORT_COUNTER_BASE,
+ #undef x
+};
+
+static const char * const transport_ksyms[] = {
+ #define x(name, symbol) "d " symbol "_transport",
+ KNOWN_TRANSPORTS(x)
+ #undef x
+};
+
+static_assert(ARRAY_SIZE(transport_ksyms) == TRANSPORT_NUM);
+static_assert(BITS_PER_TYPE(int) >= TRANSPORT_NUM);
+
+#define TRANSPORTS_G2H (TRANSPORT_VIRTIO | TRANSPORT_VMCI | TRANSPORT_HYPERV)
+#define TRANSPORTS_H2G (TRANSPORT_VHOST | TRANSPORT_VMCI)
+#define TRANSPORTS_LOCAL (TRANSPORT_LOOPBACK)
+
/* Tests can either run as the client or the server */
enum test_mode {
TEST_MODE_UNSET,
@@ -44,6 +76,7 @@ int vsock_connect(unsigned int cid, unsigned int port, int type);
int vsock_accept(unsigned int cid, unsigned int port,
struct sockaddr_vm *clientaddrp, int type);
int vsock_stream_connect(unsigned int cid, unsigned int port);
+int vsock_bind_try(unsigned int cid, unsigned int port, int type);
int vsock_bind(unsigned int cid, unsigned int port, int type);
int vsock_bind_connect(unsigned int cid, unsigned int port,
unsigned int bind_port, int type);
@@ -54,6 +87,8 @@ int vsock_stream_listen(unsigned int cid, unsigned int port);
int vsock_seqpacket_accept(unsigned int cid, unsigned int port,
struct sockaddr_vm *clientaddrp);
void vsock_wait_remote_close(int fd);
+bool vsock_ioctl_int(int fd, unsigned long op, int expected);
+bool vsock_wait_sent(int fd);
void send_buf(int fd, const void *buf, size_t len, int flags,
ssize_t expected_ret);
void recv_buf(int fd, void *buf, size_t len, int flags, ssize_t expected_ret);
@@ -79,4 +114,6 @@ void setsockopt_int_check(int fd, int level, int optname, int val,
void setsockopt_timeval_check(int fd, int level, int optname,
struct timeval val, char const *errmsg);
void enable_so_zerocopy_check(int fd);
+void enable_so_linger(int fd, int timeout);
+int get_transports(void);
#endif /* UTIL_H */
diff --git a/tools/testing/vsock/vsock_test.c b/tools/testing/vsock/vsock_test.c
index 613551132a96..d4517386e551 100644
--- a/tools/testing/vsock/vsock_test.c
+++ b/tools/testing/vsock/vsock_test.c
@@ -21,8 +21,10 @@
#include <poll.h>
#include <signal.h>
#include <sys/ioctl.h>
-#include <linux/sockios.h>
#include <linux/time64.h>
+#include <pthread.h>
+#include <fcntl.h>
+#include <linux/sockios.h>
#include "vsock_test_zerocopy.h"
#include "timeout.h"
@@ -1058,18 +1060,39 @@ static void sigpipe(int signo)
have_sigpipe = 1;
}
+#define SEND_SLEEP_USEC (10 * 1000)
+
static void test_stream_check_sigpipe(int fd)
{
ssize_t res;
have_sigpipe = 0;
- res = send(fd, "A", 1, 0);
- if (res != -1) {
- fprintf(stderr, "expected send(2) failure, got %zi\n", res);
- exit(EXIT_FAILURE);
+ /* When the other peer calls shutdown(SHUT_RD), there is a chance that
+ * the send() call could occur before the message carrying the close
+ * information arrives over the transport. In such cases, the send()
+ * might still succeed. To avoid this race, let's retry the send() call
+ * a few times, ensuring the test is more reliable.
+ */
+ timeout_begin(TIMEOUT);
+ while(1) {
+ res = send(fd, "A", 1, 0);
+ if (res == -1 && errno != EINTR)
+ break;
+
+ /* Sleep a little before trying again to avoid flooding the
+ * other peer and filling its receive buffer, causing
+ * false-negative.
+ */
+ timeout_usleep(SEND_SLEEP_USEC);
+ timeout_check("send");
}
+ timeout_end();
+ if (errno != EPIPE) {
+ fprintf(stderr, "unexpected send(2) errno %d\n", errno);
+ exit(EXIT_FAILURE);
+ }
if (!have_sigpipe) {
fprintf(stderr, "SIGPIPE expected\n");
exit(EXIT_FAILURE);
@@ -1077,12 +1100,21 @@ static void test_stream_check_sigpipe(int fd)
have_sigpipe = 0;
- res = send(fd, "A", 1, MSG_NOSIGNAL);
- if (res != -1) {
- fprintf(stderr, "expected send(2) failure, got %zi\n", res);
- exit(EXIT_FAILURE);
+ timeout_begin(TIMEOUT);
+ while(1) {
+ res = send(fd, "A", 1, MSG_NOSIGNAL);
+ if (res == -1 && errno != EINTR)
+ break;
+
+ timeout_usleep(SEND_SLEEP_USEC);
+ timeout_check("send");
}
+ timeout_end();
+ if (errno != EPIPE) {
+ fprintf(stderr, "unexpected send(2) errno %d\n", errno);
+ exit(EXIT_FAILURE);
+ }
if (have_sigpipe) {
fprintf(stderr, "SIGPIPE not expected\n");
exit(EXIT_FAILURE);
@@ -1250,7 +1282,7 @@ static void test_unsent_bytes_server(const struct test_opts *opts, int type)
static void test_unsent_bytes_client(const struct test_opts *opts, int type)
{
unsigned char buf[MSG_BUF_IOCTL_LEN];
- int ret, fd, sock_bytes_unsent;
+ int fd;
fd = vsock_connect(opts->peer_cid, opts->peer_port, type);
if (fd < 0) {
@@ -1267,22 +1299,60 @@ static void test_unsent_bytes_client(const struct test_opts *opts, int type)
/* SIOCOUTQ isn't guaranteed to instantly track sent data. Even though
* the "RECEIVED" message means that the other side has received the
* data, there can be a delay in our kernel before updating the "unsent
- * bytes" counter. Repeat SIOCOUTQ until it returns 0.
+ * bytes" counter. vsock_wait_sent() will repeat SIOCOUTQ until it
+ * returns 0.
*/
- timeout_begin(TIMEOUT);
- do {
- ret = ioctl(fd, SIOCOUTQ, &sock_bytes_unsent);
- if (ret < 0) {
- if (errno == EOPNOTSUPP) {
- fprintf(stderr, "Test skipped, SIOCOUTQ not supported.\n");
- break;
- }
- perror("ioctl");
- exit(EXIT_FAILURE);
- }
- timeout_check("SIOCOUTQ");
- } while (sock_bytes_unsent != 0);
- timeout_end();
+ if (!vsock_wait_sent(fd))
+ fprintf(stderr, "Test skipped, SIOCOUTQ not supported.\n");
+
+ close(fd);
+}
+
+static void test_unread_bytes_server(const struct test_opts *opts, int type)
+{
+ unsigned char buf[MSG_BUF_IOCTL_LEN];
+ int client_fd;
+
+ client_fd = vsock_accept(VMADDR_CID_ANY, opts->peer_port, NULL, type);
+ if (client_fd < 0) {
+ perror("accept");
+ exit(EXIT_FAILURE);
+ }
+
+ for (int i = 0; i < sizeof(buf); i++)
+ buf[i] = rand() & 0xFF;
+
+ send_buf(client_fd, buf, sizeof(buf), 0, sizeof(buf));
+ control_writeln("SENT");
+
+ close(client_fd);
+}
+
+static void test_unread_bytes_client(const struct test_opts *opts, int type)
+{
+ unsigned char buf[MSG_BUF_IOCTL_LEN];
+ int fd;
+
+ fd = vsock_connect(opts->peer_cid, opts->peer_port, type);
+ if (fd < 0) {
+ perror("connect");
+ exit(EXIT_FAILURE);
+ }
+
+ control_expectln("SENT");
+ /* The data has arrived but has not been read. The expected is
+ * MSG_BUF_IOCTL_LEN.
+ */
+ if (!vsock_ioctl_int(fd, SIOCINQ, MSG_BUF_IOCTL_LEN)) {
+ fprintf(stderr, "Test skipped, SIOCINQ not supported.\n");
+ goto out;
+ }
+
+ recv_buf(fd, buf, sizeof(buf), 0, sizeof(buf));
+ /* All data has been consumed, so the expected is 0. */
+ vsock_ioctl_int(fd, SIOCINQ, 0);
+
+out:
close(fd);
}
@@ -1306,6 +1376,26 @@ static void test_seqpacket_unsent_bytes_server(const struct test_opts *opts)
test_unsent_bytes_server(opts, SOCK_SEQPACKET);
}
+static void test_stream_unread_bytes_client(const struct test_opts *opts)
+{
+ test_unread_bytes_client(opts, SOCK_STREAM);
+}
+
+static void test_stream_unread_bytes_server(const struct test_opts *opts)
+{
+ test_unread_bytes_server(opts, SOCK_STREAM);
+}
+
+static void test_seqpacket_unread_bytes_client(const struct test_opts *opts)
+{
+ test_unread_bytes_client(opts, SOCK_SEQPACKET);
+}
+
+static void test_seqpacket_unread_bytes_server(const struct test_opts *opts)
+{
+ test_unread_bytes_server(opts, SOCK_SEQPACKET);
+}
+
#define RCVLOWAT_CREDIT_UPD_BUF_SIZE (1024 * 128)
/* This define is the same as in 'include/linux/virtio_vsock.h':
* it is used to decide when to send credit update message during
@@ -1699,16 +1789,27 @@ static void test_stream_msgzcopy_leak_zcskb_server(const struct test_opts *opts)
#define MAX_PORT_RETRIES 24 /* net/vmw_vsock/af_vsock.c */
-/* Test attempts to trigger a transport release for an unbound socket. This can
- * lead to a reference count mishandling.
- */
-static void test_stream_transport_uaf_client(const struct test_opts *opts)
+static bool test_stream_transport_uaf(int cid)
{
int sockets[MAX_PORT_RETRIES];
struct sockaddr_vm addr;
- int fd, i, alen;
+ socklen_t alen;
+ int fd, i, c;
+ bool ret;
- fd = vsock_bind(VMADDR_CID_ANY, VMADDR_PORT_ANY, SOCK_STREAM);
+ /* Probe for a transport by attempting a local CID bind. Unavailable
+ * transport (or more specifically: an unsupported transport/CID
+ * combination) results in EADDRNOTAVAIL, other errnos are fatal.
+ */
+ fd = vsock_bind_try(cid, VMADDR_PORT_ANY, SOCK_STREAM);
+ if (fd < 0) {
+ if (errno != EADDRNOTAVAIL) {
+ perror("Unexpected bind() errno");
+ exit(EXIT_FAILURE);
+ }
+
+ return false;
+ }
alen = sizeof(addr);
if (getsockname(fd, (struct sockaddr *)&addr, &alen)) {
@@ -1716,38 +1817,83 @@ static void test_stream_transport_uaf_client(const struct test_opts *opts)
exit(EXIT_FAILURE);
}
+ /* Drain the autobind pool; see __vsock_bind_connectible(). */
for (i = 0; i < MAX_PORT_RETRIES; ++i)
- sockets[i] = vsock_bind(VMADDR_CID_ANY, ++addr.svm_port,
- SOCK_STREAM);
+ sockets[i] = vsock_bind(cid, ++addr.svm_port, SOCK_STREAM);
close(fd);
- fd = socket(AF_VSOCK, SOCK_STREAM, 0);
+
+ /* Setting SOCK_NONBLOCK makes connect() return soon after
+ * (re-)assigning the transport. We are not connecting to anything
+ * anyway, so there is no point entering the main loop in
+ * vsock_connect(); waiting for timeout, checking for signals, etc.
+ */
+ fd = socket(AF_VSOCK, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
- if (!vsock_connect_fd(fd, addr.svm_cid, addr.svm_port)) {
- perror("Unexpected connect() #1 success");
+ /* Assign transport, while failing to autobind. Autobind pool was
+ * drained, so EADDRNOTAVAIL coming from __vsock_bind_connectible() is
+ * expected.
+ *
+ * One exception is ENODEV which is thrown by vsock_assign_transport(),
+ * i.e. before vsock_auto_bind(), when the only transport loaded is
+ * vhost.
+ */
+ if (!connect(fd, (struct sockaddr *)&addr, alen)) {
+ fprintf(stderr, "Unexpected connect() success\n");
exit(EXIT_FAILURE);
}
-
- /* Vulnerable system may crash now. */
- if (!vsock_connect_fd(fd, VMADDR_CID_HOST, VMADDR_PORT_ANY)) {
- perror("Unexpected connect() #2 success");
+ if (errno == ENODEV && cid == VMADDR_CID_HOST) {
+ ret = false;
+ goto cleanup;
+ }
+ if (errno != EADDRNOTAVAIL) {
+ perror("Unexpected connect() errno");
exit(EXIT_FAILURE);
}
+ /* Reassign transport, triggering old transport release and
+ * (potentially) unbinding of an unbound socket.
+ *
+ * Vulnerable system may crash now.
+ */
+ for (c = VMADDR_CID_HYPERVISOR; c <= VMADDR_CID_HOST + 1; ++c) {
+ if (c != cid) {
+ addr.svm_cid = c;
+ (void)connect(fd, (struct sockaddr *)&addr, alen);
+ }
+ }
+
+ ret = true;
+cleanup:
close(fd);
while (i--)
close(sockets[i]);
- control_writeln("DONE");
+ return ret;
}
-static void test_stream_transport_uaf_server(const struct test_opts *opts)
+/* Test attempts to trigger a transport release for an unbound socket. This can
+ * lead to a reference count mishandling.
+ */
+static void test_stream_transport_uaf_client(const struct test_opts *opts)
{
- control_expectln("DONE");
+ bool tested = false;
+ int cid, tr;
+
+ for (cid = VMADDR_CID_HYPERVISOR; cid <= VMADDR_CID_HOST + 1; ++cid)
+ tested |= test_stream_transport_uaf(cid);
+
+ tr = get_transports();
+ if (!tr)
+ fprintf(stderr, "No transports detected\n");
+ else if (tr == TRANSPORT_VIRTIO)
+ fprintf(stderr, "Setup unsupported: sole virtio transport\n");
+ else if (!tested)
+ fprintf(stderr, "No transports tested\n");
}
static void test_stream_connect_retry_client(const struct test_opts *opts)
@@ -1792,12 +1938,182 @@ static void test_stream_connect_retry_server(const struct test_opts *opts)
close(fd);
}
+#define TRANSPORT_CHANGE_TIMEOUT 2 /* seconds */
+
+static void *test_stream_transport_change_thread(void *vargp)
+{
+ pid_t *pid = (pid_t *)vargp;
+ int ret;
+
+ ret = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
+ if (ret) {
+ fprintf(stderr, "pthread_setcanceltype: %d\n", ret);
+ exit(EXIT_FAILURE);
+ }
+
+ while (true) {
+ if (kill(*pid, SIGUSR1) < 0) {
+ perror("kill");
+ exit(EXIT_FAILURE);
+ }
+ }
+ return NULL;
+}
+
+static void test_transport_change_signal_handler(int signal)
+{
+ /* We need a custom handler for SIGUSR1 as the default one terminates the process. */
+}
+
+static void test_stream_transport_change_client(const struct test_opts *opts)
+{
+ __sighandler_t old_handler;
+ pid_t pid = getpid();
+ pthread_t thread_id;
+ time_t tout;
+ int ret, tr;
+
+ tr = get_transports();
+
+ /* Print a warning if there is a G2H transport loaded.
+ * This is on a best effort basis because VMCI can be either G2H and H2G, and there is
+ * no easy way to understand it.
+ * The bug we are testing only appears when G2H transports are not loaded.
+ * This is because `vsock_assign_transport`, when using CID 0, assigns a G2H transport
+ * to vsk->transport. If none is available it is set to NULL, causing the null-ptr-deref.
+ */
+ if (tr & TRANSPORTS_G2H)
+ fprintf(stderr, "G2H Transport detected. This test will not fail.\n");
+
+ old_handler = signal(SIGUSR1, test_transport_change_signal_handler);
+ if (old_handler == SIG_ERR) {
+ perror("signal");
+ exit(EXIT_FAILURE);
+ }
+
+ ret = pthread_create(&thread_id, NULL, test_stream_transport_change_thread, &pid);
+ if (ret) {
+ fprintf(stderr, "pthread_create: %d\n", ret);
+ exit(EXIT_FAILURE);
+ }
+
+ control_expectln("LISTENING");
+
+ tout = current_nsec() + TRANSPORT_CHANGE_TIMEOUT * NSEC_PER_SEC;
+ do {
+ struct sockaddr_vm sa = {
+ .svm_family = AF_VSOCK,
+ .svm_cid = opts->peer_cid,
+ .svm_port = opts->peer_port,
+ };
+ bool send_control = false;
+ int s;
+
+ s = socket(AF_VSOCK, SOCK_STREAM, 0);
+ if (s < 0) {
+ perror("socket");
+ exit(EXIT_FAILURE);
+ }
+
+ ret = connect(s, (struct sockaddr *)&sa, sizeof(sa));
+ /* The connect can fail due to signals coming from the thread,
+ * or because the receiver connection queue is full.
+ * Ignoring also the latter case because there is no way
+ * of synchronizing client's connect and server's accept when
+ * connect(s) are constantly being interrupted by signals.
+ */
+ if (ret == -1 && (errno != EINTR && errno != ECONNRESET)) {
+ perror("connect");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Notify the server if the connect() is successful or the
+ * receiver connection queue is full, so it will do accept()
+ * to drain it.
+ */
+ if (!ret || errno == ECONNRESET)
+ send_control = true;
+
+ /* Set CID to 0 cause a transport change. */
+ sa.svm_cid = 0;
+
+ /* There is a case where this will not fail:
+ * if the previous connect() is interrupted while the
+ * connection request is already sent, this second
+ * connect() will wait for the response.
+ */
+ ret = connect(s, (struct sockaddr *)&sa, sizeof(sa));
+ if (!ret || errno == ECONNRESET)
+ send_control = true;
+
+ close(s);
+
+ if (send_control)
+ control_writeulong(CONTROL_CONTINUE);
+
+ } while (current_nsec() < tout);
+
+ control_writeulong(CONTROL_DONE);
+
+ ret = pthread_cancel(thread_id);
+ if (ret) {
+ fprintf(stderr, "pthread_cancel: %d\n", ret);
+ exit(EXIT_FAILURE);
+ }
+
+ ret = pthread_join(thread_id, NULL);
+ if (ret) {
+ fprintf(stderr, "pthread_join: %d\n", ret);
+ exit(EXIT_FAILURE);
+ }
+
+ if (signal(SIGUSR1, old_handler) == SIG_ERR) {
+ perror("signal");
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void test_stream_transport_change_server(const struct test_opts *opts)
+{
+ int s = vsock_stream_listen(VMADDR_CID_ANY, opts->peer_port);
+
+ /* Set the socket to be nonblocking because connects that have been interrupted
+ * (EINTR) can fill the receiver's accept queue anyway, leading to connect failure.
+ * As of today (6.15) in such situation there is no way to understand, from the
+ * client side, if the connection has been queued in the server or not.
+ */
+ if (fcntl(s, F_SETFL, fcntl(s, F_GETFL, 0) | O_NONBLOCK) < 0) {
+ perror("fcntl");
+ exit(EXIT_FAILURE);
+ }
+ control_writeln("LISTENING");
+
+ while (control_readulong() == CONTROL_CONTINUE) {
+ /* Must accept the connection, otherwise the `listen`
+ * queue will fill up and new connections will fail.
+ * There can be more than one queued connection,
+ * clear them all.
+ */
+ while (true) {
+ int client = accept(s, NULL, NULL);
+
+ if (client < 0) {
+ if (errno == EAGAIN)
+ break;
+
+ perror("accept");
+ exit(EXIT_FAILURE);
+ }
+
+ close(client);
+ }
+ }
+
+ close(s);
+}
+
static void test_stream_linger_client(const struct test_opts *opts)
{
- struct linger optval = {
- .l_onoff = 1,
- .l_linger = 1
- };
int fd;
fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
@@ -1806,15 +2122,58 @@ static void test_stream_linger_client(const struct test_opts *opts)
exit(EXIT_FAILURE);
}
- if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &optval, sizeof(optval))) {
- perror("setsockopt(SO_LINGER)");
+ enable_so_linger(fd, 1);
+ close(fd);
+}
+
+static void test_stream_linger_server(const struct test_opts *opts)
+{
+ int fd;
+
+ fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);
+ if (fd < 0) {
+ perror("accept");
exit(EXIT_FAILURE);
}
+ vsock_wait_remote_close(fd);
close(fd);
}
-static void test_stream_linger_server(const struct test_opts *opts)
+/* Half of the default to not risk timing out the control channel */
+#define LINGER_TIMEOUT (TIMEOUT / 2)
+
+static void test_stream_nolinger_client(const struct test_opts *opts)
+{
+ bool waited;
+ time_t ns;
+ int fd;
+
+ fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
+ if (fd < 0) {
+ perror("connect");
+ exit(EXIT_FAILURE);
+ }
+
+ enable_so_linger(fd, LINGER_TIMEOUT);
+ send_byte(fd, 1, 0); /* Left unread to expose incorrect behaviour. */
+ waited = vsock_wait_sent(fd);
+
+ ns = current_nsec();
+ close(fd);
+ ns = current_nsec() - ns;
+
+ if (!waited) {
+ fprintf(stderr, "Test skipped, SIOCOUTQ not supported.\n");
+ } else if (DIV_ROUND_UP(ns, NSEC_PER_SEC) >= LINGER_TIMEOUT) {
+ fprintf(stderr, "Unexpected lingering\n");
+ exit(EXIT_FAILURE);
+ }
+
+ control_writeln("DONE");
+}
+
+static void test_stream_nolinger_server(const struct test_opts *opts)
{
int fd;
@@ -1824,7 +2183,7 @@ static void test_stream_linger_server(const struct test_opts *opts)
exit(EXIT_FAILURE);
}
- vsock_wait_remote_close(fd);
+ control_expectln("DONE");
close(fd);
}
@@ -1976,7 +2335,6 @@ static struct test_case test_cases[] = {
{
.name = "SOCK_STREAM transport release use-after-free",
.run_client = test_stream_transport_uaf_client,
- .run_server = test_stream_transport_uaf_server,
},
{
.name = "SOCK_STREAM retry failed connect()",
@@ -1988,6 +2346,26 @@ static struct test_case test_cases[] = {
.run_client = test_stream_linger_client,
.run_server = test_stream_linger_server,
},
+ {
+ .name = "SOCK_STREAM SO_LINGER close() on unread",
+ .run_client = test_stream_nolinger_client,
+ .run_server = test_stream_nolinger_server,
+ },
+ {
+ .name = "SOCK_STREAM transport change null-ptr-deref",
+ .run_client = test_stream_transport_change_client,
+ .run_server = test_stream_transport_change_server,
+ },
+ {
+ .name = "SOCK_STREAM ioctl(SIOCINQ) functionality",
+ .run_client = test_stream_unread_bytes_client,
+ .run_server = test_stream_unread_bytes_server,
+ },
+ {
+ .name = "SOCK_SEQPACKET ioctl(SIOCINQ) functionality",
+ .run_client = test_seqpacket_unread_bytes_client,
+ .run_server = test_seqpacket_unread_bytes_server,
+ },
{},
};