mirror of
https://github.com/torvalds/linux.git
synced 2026-06-02 03:24:19 +02:00
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:
commit
d74520f39c
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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()",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user