]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
vsock/test: Add test for MSG_ZEROCOPY completion memory leak
authorMichal Luczaj <mhal@rbox.co>
Thu, 19 Dec 2024 09:49:34 +0000 (10:49 +0100)
committerJakub Kicinski <kuba@kernel.org>
Mon, 23 Dec 2024 18:28:01 +0000 (10:28 -0800)
Exercise the ENOMEM error path by attempting to hit net.core.optmem_max
limit on send().

Test aims to create a memory leak, kmemleak should be employed.

Fixed by commit 60cf6206a1f5 ("virtio/vsock: Improve MSG_ZEROCOPY error
handling").

Reviewed-by: Stefano Garzarella <sgarzare@redhat.com>
Signed-off-by: Michal Luczaj <mhal@rbox.co>
Link: https://patch.msgid.link/20241219-test-vsock-leaks-v4-7-a416e554d9d7@rbox.co
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
tools/testing/vsock/vsock_test.c

index 2dec6290b075fb5f7be3a24a4d1372a980389c6a..1eebbc0d5f616bb1afab3ec3f9e59cb609f9f6e8 100644 (file)
@@ -1561,6 +1561,153 @@ static void test_stream_msgzcopy_leak_errq_server(const struct test_opts *opts)
        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",
@@ -1701,6 +1848,11 @@ static struct test_case test_cases[] = {
                .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,
+       },
        {},
 };