mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-02 15:43:35 -04:00
Merge branch 'vsock-test-tests-for-memory-leaks'
Michal Luczaj says: ==================== vsock/test: Tests for memory leaks Series adds tests for recently fixed memory leaks[1]: commitd7b0ff5a86("virtio/vsock: Fix accept_queue memory leak") commitfbf7085b3a("vsock: Fix sk_error_queue memory leak") commit60cf6206a1("virtio/vsock: Improve MSG_ZEROCOPY error handling") Patch 1 is a non-functional preparatory cleanup. Patch 2 is a test suite extension for picking specific tests. Patch 3 explains the need of kmemleak scans. Patch 4 adapts utility functions to handle MSG_ZEROCOPY. Patches 5-6-7 add the tests. NOTE: Test in the last patch ("vsock/test: Add test for MSG_ZEROCOPY completion memory leak") may stop working even before this series is merged. See changes proposed in [2]. The failslab variant would be unaffected. [1] https://lore.kernel.org/20241107-vsock-mem-leaks-v2-0-4e21bfcfc818@rbox.co [2] https://lore.kernel.org/CANn89i+oL+qoPmbbGvE_RT3_3OWgeck7cCPcTafeehKrQZ8kyw@mail.gmail.com v3: https://lore.kernel.org/20241218-test-vsock-leaks-v3-0-f1a4dcef9228@rbox.co v2: https://lore.kernel.org/20241216-test-vsock-leaks-v2-0-55e1405742fc@rbox.co v1: https://lore.kernel.org/20241206-test-vsock-leaks-v1-0-c31e8c875797@rbox.co ==================== Link: https://patch.msgid.link/20241219-test-vsock-leaks-v4-0-a416e554d9d7@rbox.co Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
@@ -36,6 +36,21 @@ Invoke test binaries in both directions as follows:
|
||||
--control-port=1234 \
|
||||
--peer-cid=3
|
||||
|
||||
Some tests are designed to produce kernel memory leaks. Leaks detection,
|
||||
however, is deferred to Kernel Memory Leak Detector. It is recommended to enable
|
||||
kmemleak (CONFIG_DEBUG_KMEMLEAK=y) and explicitly trigger a scan after each test
|
||||
suite run, e.g.
|
||||
|
||||
# echo clear > /sys/kernel/debug/kmemleak
|
||||
# $TEST_BINARY ...
|
||||
# echo "wait for any grace periods" && sleep 2
|
||||
# echo scan > /sys/kernel/debug/kmemleak
|
||||
# echo "wait for kmemleak" && sleep 5
|
||||
# echo scan > /sys/kernel/debug/kmemleak
|
||||
# cat /sys/kernel/debug/kmemleak
|
||||
|
||||
For more information see Documentation/dev-tools/kmemleak.rst.
|
||||
|
||||
vsock_perf utility
|
||||
-------------------
|
||||
'vsock_perf' is a simple tool to measure vsock performance. It works in
|
||||
|
||||
@@ -401,7 +401,7 @@ void recv_buf(int fd, void *buf, size_t len, int flags, ssize_t expected_ret)
|
||||
*/
|
||||
void send_byte(int fd, int expected_ret, int flags)
|
||||
{
|
||||
const uint8_t byte = 'A';
|
||||
static const uint8_t byte = 'A';
|
||||
|
||||
send_buf(fd, &byte, sizeof(byte), flags, expected_ret);
|
||||
}
|
||||
@@ -420,7 +420,7 @@ void recv_byte(int fd, int expected_ret, int flags)
|
||||
recv_buf(fd, &byte, sizeof(byte), flags, expected_ret);
|
||||
|
||||
if (byte != 'A') {
|
||||
fprintf(stderr, "unexpected byte read %c\n", byte);
|
||||
fprintf(stderr, "unexpected byte read 0x%02x\n", byte);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
@@ -486,8 +486,7 @@ void list_tests(const struct test_case *test_cases)
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
void skip_test(struct test_case *test_cases, size_t test_cases_len,
|
||||
const char *test_id_str)
|
||||
static unsigned long parse_test_id(const char *test_id_str, size_t test_cases_len)
|
||||
{
|
||||
unsigned long test_id;
|
||||
char *endptr = NULL;
|
||||
@@ -505,9 +504,35 @@ void skip_test(struct test_case *test_cases, size_t test_cases_len,
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
return test_id;
|
||||
}
|
||||
|
||||
void skip_test(struct test_case *test_cases, size_t test_cases_len,
|
||||
const char *test_id_str)
|
||||
{
|
||||
unsigned long test_id = parse_test_id(test_id_str, test_cases_len);
|
||||
test_cases[test_id].skip = true;
|
||||
}
|
||||
|
||||
void pick_test(struct test_case *test_cases, size_t test_cases_len,
|
||||
const char *test_id_str)
|
||||
{
|
||||
static bool skip_all = true;
|
||||
unsigned long test_id;
|
||||
|
||||
if (skip_all) {
|
||||
unsigned long i;
|
||||
|
||||
for (i = 0; i < test_cases_len; ++i)
|
||||
test_cases[i].skip = true;
|
||||
|
||||
skip_all = false;
|
||||
}
|
||||
|
||||
test_id = parse_test_id(test_id_str, test_cases_len);
|
||||
test_cases[test_id].skip = false;
|
||||
}
|
||||
|
||||
unsigned long hash_djb2(const void *data, size_t len)
|
||||
{
|
||||
unsigned long hash = 5381;
|
||||
|
||||
@@ -62,6 +62,8 @@ void run_tests(const struct test_case *test_cases,
|
||||
void list_tests(const struct test_case *test_cases);
|
||||
void skip_test(struct test_case *test_cases, size_t test_cases_len,
|
||||
const char *test_id_str);
|
||||
void pick_test(struct test_case *test_cases, size_t test_cases_len,
|
||||
const char *test_id_str);
|
||||
unsigned long hash_djb2(const void *data, size_t len);
|
||||
size_t iovec_bytes(const struct iovec *iov, size_t iovnum);
|
||||
unsigned long iovec_hash_djb2(const struct iovec *iov, size_t iovnum);
|
||||
|
||||
@@ -22,12 +22,17 @@
|
||||
#include <signal.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/sockios.h>
|
||||
#include <linux/time64.h>
|
||||
|
||||
#include "vsock_test_zerocopy.h"
|
||||
#include "timeout.h"
|
||||
#include "control.h"
|
||||
#include "util.h"
|
||||
|
||||
/* Basic messages for control_writeulong(), control_readulong() */
|
||||
#define CONTROL_CONTINUE 1
|
||||
#define CONTROL_DONE 0
|
||||
|
||||
static void test_stream_connection_reset(const struct test_opts *opts)
|
||||
{
|
||||
union {
|
||||
@@ -559,7 +564,7 @@ static time_t current_nsec(void)
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
return (ts.tv_sec * 1000000000ULL) + ts.tv_nsec;
|
||||
return (ts.tv_sec * NSEC_PER_SEC) + ts.tv_nsec;
|
||||
}
|
||||
|
||||
#define RCVTIMEO_TIMEOUT_SEC 1
|
||||
@@ -599,7 +604,7 @@ static void test_seqpacket_timeout_client(const struct test_opts *opts)
|
||||
}
|
||||
|
||||
read_overhead_ns = current_nsec() - read_enter_ns -
|
||||
1000000000ULL * RCVTIMEO_TIMEOUT_SEC;
|
||||
NSEC_PER_SEC * RCVTIMEO_TIMEOUT_SEC;
|
||||
|
||||
if (read_overhead_ns > READ_OVERHEAD_NSEC) {
|
||||
fprintf(stderr,
|
||||
@@ -1473,6 +1478,236 @@ static void test_stream_cred_upd_on_set_rcvlowat(const struct test_opts *opts)
|
||||
test_stream_credit_update_test(opts, false);
|
||||
}
|
||||
|
||||
/* The goal of test leak_acceptq is to stress the race between connect() and
|
||||
* close(listener). Implementation of client/server loops boils down to:
|
||||
*
|
||||
* client server
|
||||
* ------ ------
|
||||
* write(CONTINUE)
|
||||
* expect(CONTINUE)
|
||||
* listen()
|
||||
* write(LISTENING)
|
||||
* expect(LISTENING)
|
||||
* connect() close()
|
||||
*/
|
||||
#define ACCEPTQ_LEAK_RACE_TIMEOUT 2 /* seconds */
|
||||
|
||||
static void test_stream_leak_acceptq_client(const struct test_opts *opts)
|
||||
{
|
||||
time_t tout;
|
||||
int fd;
|
||||
|
||||
tout = current_nsec() + ACCEPTQ_LEAK_RACE_TIMEOUT * NSEC_PER_SEC;
|
||||
do {
|
||||
control_writeulong(CONTROL_CONTINUE);
|
||||
|
||||
fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
} while (current_nsec() < tout);
|
||||
|
||||
control_writeulong(CONTROL_DONE);
|
||||
}
|
||||
|
||||
/* Test for a memory leak. User is expected to run kmemleak scan, see README. */
|
||||
static void test_stream_leak_acceptq_server(const struct test_opts *opts)
|
||||
{
|
||||
int fd;
|
||||
|
||||
while (control_readulong() == CONTROL_CONTINUE) {
|
||||
fd = vsock_stream_listen(VMADDR_CID_ANY, opts->peer_port);
|
||||
control_writeln("LISTENING");
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
/* Test for a memory leak. User is expected to run kmemleak scan, see README. */
|
||||
static void test_stream_msgzcopy_leak_errq_client(const struct test_opts *opts)
|
||||
{
|
||||
struct pollfd fds = { 0 };
|
||||
int fd;
|
||||
|
||||
fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
|
||||
if (fd < 0) {
|
||||
perror("connect");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
enable_so_zerocopy_check(fd);
|
||||
send_byte(fd, 1, MSG_ZEROCOPY);
|
||||
|
||||
fds.fd = fd;
|
||||
fds.events = 0;
|
||||
if (poll(&fds, 1, -1) < 0) {
|
||||
perror("poll");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void test_stream_msgzcopy_leak_errq_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);
|
||||
}
|
||||
|
||||
recv_byte(fd, 1, 0);
|
||||
vsock_wait_remote_close(fd);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
/* Test msgzcopy_leak_zcskb is meant to exercise sendmsg() error handling path,
|
||||
* that might leak an skb. The idea is to fail virtio_transport_init_zcopy_skb()
|
||||
* by hitting net.core.optmem_max limit in sock_omalloc(), specifically
|
||||
*
|
||||
* vsock_connectible_sendmsg
|
||||
* virtio_transport_stream_enqueue
|
||||
* virtio_transport_send_pkt_info
|
||||
* virtio_transport_init_zcopy_skb
|
||||
* . msg_zerocopy_realloc
|
||||
* . msg_zerocopy_alloc
|
||||
* . sock_omalloc
|
||||
* . sk_omem_alloc + size > sysctl_optmem_max
|
||||
* return -ENOMEM
|
||||
*
|
||||
* We abuse the implementation detail of net/socket.c:____sys_sendmsg().
|
||||
* sk_omem_alloc can be precisely bumped by sock_kmalloc(), as it is used to
|
||||
* fetch user-provided control data.
|
||||
*
|
||||
* While this approach works for now, it relies on assumptions regarding the
|
||||
* implementation and configuration (for example, order of net.core.optmem_max
|
||||
* can not exceed MAX_PAGE_ORDER), which may not hold in the future. A more
|
||||
* resilient testing could be implemented by leveraging the Fault injection
|
||||
* framework (CONFIG_FAULT_INJECTION), e.g.
|
||||
*
|
||||
* client# echo N > /sys/kernel/debug/failslab/ignore-gfp-wait
|
||||
* client# echo 0 > /sys/kernel/debug/failslab/verbose
|
||||
*
|
||||
* void client(const struct test_opts *opts)
|
||||
* {
|
||||
* char buf[16];
|
||||
* int f, s, i;
|
||||
*
|
||||
* f = open("/proc/self/fail-nth", O_WRONLY);
|
||||
*
|
||||
* for (i = 1; i < 32; i++) {
|
||||
* control_writeulong(CONTROL_CONTINUE);
|
||||
*
|
||||
* s = vsock_stream_connect(opts->peer_cid, opts->peer_port);
|
||||
* enable_so_zerocopy_check(s);
|
||||
*
|
||||
* sprintf(buf, "%d", i);
|
||||
* write(f, buf, strlen(buf));
|
||||
*
|
||||
* send(s, &(char){ 0 }, 1, MSG_ZEROCOPY);
|
||||
*
|
||||
* write(f, "0", 1);
|
||||
* close(s);
|
||||
* }
|
||||
*
|
||||
* control_writeulong(CONTROL_DONE);
|
||||
* close(f);
|
||||
* }
|
||||
*
|
||||
* void server(const struct test_opts *opts)
|
||||
* {
|
||||
* int fd;
|
||||
*
|
||||
* while (control_readulong() == CONTROL_CONTINUE) {
|
||||
* fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);
|
||||
* vsock_wait_remote_close(fd);
|
||||
* close(fd);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Refer to Documentation/fault-injection/fault-injection.rst.
|
||||
*/
|
||||
#define MAX_PAGE_ORDER 10 /* usually */
|
||||
#define PAGE_SIZE 4096
|
||||
|
||||
/* Test for a memory leak. User is expected to run kmemleak scan, see README. */
|
||||
static void test_stream_msgzcopy_leak_zcskb_client(const struct test_opts *opts)
|
||||
{
|
||||
size_t optmem_max, ctl_len, chunk_size;
|
||||
struct msghdr msg = { 0 };
|
||||
struct iovec iov;
|
||||
char *chunk;
|
||||
int fd, res;
|
||||
FILE *f;
|
||||
|
||||
f = fopen("/proc/sys/net/core/optmem_max", "r");
|
||||
if (!f) {
|
||||
perror("fopen(optmem_max)");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (fscanf(f, "%zu", &optmem_max) != 1) {
|
||||
fprintf(stderr, "fscanf(optmem_max) failed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
|
||||
if (fd < 0) {
|
||||
perror("connect");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
enable_so_zerocopy_check(fd);
|
||||
|
||||
ctl_len = optmem_max - 1;
|
||||
if (ctl_len > PAGE_SIZE << MAX_PAGE_ORDER) {
|
||||
fprintf(stderr, "Try with net.core.optmem_max = 100000\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
chunk_size = CMSG_SPACE(ctl_len);
|
||||
chunk = malloc(chunk_size);
|
||||
if (!chunk) {
|
||||
perror("malloc");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
memset(chunk, 0, chunk_size);
|
||||
|
||||
iov.iov_base = &(char){ 0 };
|
||||
iov.iov_len = 1;
|
||||
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = chunk;
|
||||
msg.msg_controllen = ctl_len;
|
||||
|
||||
errno = 0;
|
||||
res = sendmsg(fd, &msg, MSG_ZEROCOPY);
|
||||
if (res >= 0 || errno != ENOMEM) {
|
||||
fprintf(stderr, "Expected ENOMEM, got errno=%d res=%d\n",
|
||||
errno, res);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void test_stream_msgzcopy_leak_zcskb_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 struct test_case test_cases[] = {
|
||||
{
|
||||
.name = "SOCK_STREAM connection reset",
|
||||
@@ -1603,6 +1838,21 @@ static struct test_case test_cases[] = {
|
||||
.run_client = test_seqpacket_unsent_bytes_client,
|
||||
.run_server = test_seqpacket_unsent_bytes_server,
|
||||
},
|
||||
{
|
||||
.name = "SOCK_STREAM leak accept queue",
|
||||
.run_client = test_stream_leak_acceptq_client,
|
||||
.run_server = test_stream_leak_acceptq_server,
|
||||
},
|
||||
{
|
||||
.name = "SOCK_STREAM MSG_ZEROCOPY leak MSG_ERRQUEUE",
|
||||
.run_client = test_stream_msgzcopy_leak_errq_client,
|
||||
.run_server = test_stream_msgzcopy_leak_errq_server,
|
||||
},
|
||||
{
|
||||
.name = "SOCK_STREAM MSG_ZEROCOPY leak completion skb",
|
||||
.run_client = test_stream_msgzcopy_leak_zcskb_client,
|
||||
.run_server = test_stream_msgzcopy_leak_zcskb_server,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
@@ -1643,6 +1893,11 @@ static const struct option longopts[] = {
|
||||
.has_arg = required_argument,
|
||||
.val = 's',
|
||||
},
|
||||
{
|
||||
.name = "pick",
|
||||
.has_arg = required_argument,
|
||||
.val = 't',
|
||||
},
|
||||
{
|
||||
.name = "help",
|
||||
.has_arg = no_argument,
|
||||
@@ -1680,6 +1935,8 @@ static void usage(void)
|
||||
" --peer-cid <cid> CID of the other side\n"
|
||||
" --peer-port <port> AF_VSOCK port used for the test [default: %d]\n"
|
||||
" --list List of tests that will be executed\n"
|
||||
" --pick <test_id> Test ID to execute selectively;\n"
|
||||
" use multiple --pick options to select more tests\n"
|
||||
" --skip <test_id> Test ID to skip;\n"
|
||||
" use multiple --skip options to skip more tests\n",
|
||||
DEFAULT_PEER_PORT
|
||||
@@ -1736,6 +1993,10 @@ int main(int argc, char **argv)
|
||||
skip_test(test_cases, ARRAY_SIZE(test_cases) - 1,
|
||||
optarg);
|
||||
break;
|
||||
case 't':
|
||||
pick_test(test_cases, ARRAY_SIZE(test_cases) - 1,
|
||||
optarg);
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
usage();
|
||||
|
||||
Reference in New Issue
Block a user