Merge branch 'vsock-test-improve-transport_uaf-test'

Michal Luczaj says:

====================
vsock/test: Improve transport_uaf test

Increase the coverage of a test implemented in commit 301a62dfb0
("vsock/test: Add test for UAF due to socket unbinding"). Take this
opportunity to factor out some utility code, drop a redundant sync between
client and server, and introduce a /proc/kallsyms harvesting logic for
auto-detecting registered vsock transports.

v2: https://lore.kernel.org/20250528-vsock-test-inc-cov-v2-0-8f655b40d57c@rbox.co
v1: https://lore.kernel.org/20250523-vsock-test-inc-cov-v1-1-fa3507941bbd@rbox.co
====================

Link: https://patch.msgid.link/20250611-vsock-test-inc-cov-v3-0-5834060d9c20@rbox.co
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2025-06-17 14:50:37 -07:00
commit d74520f39c
3 changed files with 181 additions and 22 deletions

View File

@ -7,6 +7,7 @@
* Author: Stefan Hajnoczi <stefanha@redhat.com>
*/
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdint.h>
@ -23,6 +24,9 @@
#include "control.h"
#include "util.h"
#define KALLSYMS_PATH "/proc/kallsyms"
#define KALLSYMS_LINE_LEN 512
/* Install signal handlers */
void init_signals(void)
{
@ -121,15 +125,17 @@ bool vsock_wait_sent(int fd)
return !ret;
}
/* Create socket <type>, bind to <cid, port> and return the file descriptor. */
int vsock_bind(unsigned int cid, unsigned int port, int type)
/* 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) {
@ -138,6 +144,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);
}
@ -836,3 +858,55 @@ void enable_so_linger(int fd, int timeout)
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;
}

View File

@ -3,8 +3,36 @@
#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);
/* Tests can either run as the client or the server */
enum test_mode {
TEST_MODE_UNSET,
@ -44,6 +72,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);
@ -81,4 +110,5 @@ 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 */

View File

@ -1718,16 +1718,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)) {
@ -1735,38 +1746,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);
}
if (errno == ENODEV && cid == VMADDR_CID_HOST) {
ret = false;
goto cleanup;
}
if (errno != EADDRNOTAVAIL) {
perror("Unexpected connect() errno");
exit(EXIT_FAILURE);
}
/* Vulnerable system may crash now. */
if (!vsock_connect_fd(fd, VMADDR_CID_HOST, VMADDR_PORT_ANY)) {
perror("Unexpected connect() #2 success");
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)
@ -2034,7 +2090,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()",