]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
vsock/test: add stream TX credit bounds test
authorMelbin K Mathew <mlbnkm1@gmail.com>
Wed, 21 Jan 2026 09:36:28 +0000 (10:36 +0100)
committerPaolo Abeni <pabeni@redhat.com>
Thu, 22 Jan 2026 14:41:33 +0000 (15:41 +0100)
Add a regression test for the TX credit bounds fix. The test verifies
that a sender with a small local buffer size cannot queue excessive
data even when the peer advertises a large receive buffer.

The client:
  - Sets a small buffer size (64 KiB)
  - Connects to server (which advertises 2 MiB buffer)
  - Sends in non-blocking mode until EAGAIN
  - Verifies total queued data is bounded

This guards against the original vulnerability where a remote peer
could cause unbounded kernel memory allocation by advertising a large
buffer and reading slowly.

Suggested-by: Stefano Garzarella <sgarzare@redhat.com>
Signed-off-by: Melbin K Mathew <mlbnkm1@gmail.com>
[Stefano: use sock_buf_size to check the bytes sent + small fixes]
Signed-off-by: Stefano Garzarella <sgarzare@redhat.com>
Link: https://patch.msgid.link/20260121093628.9941-5-sgarzare@redhat.com
Acked-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
tools/testing/vsock/vsock_test.c

index 668fbe9eb3cc588442eb496d5bc162c349c50fe5..5bd20ccd9335caafe68e8b7a5d02a4deb3d2deec 100644 (file)
@@ -347,6 +347,7 @@ static void test_stream_msg_peek_server(const struct test_opts *opts)
 }
 
 #define SOCK_BUF_SIZE (2 * 1024 * 1024)
+#define SOCK_BUF_SIZE_SMALL (64 * 1024)
 #define MAX_MSG_PAGES 4
 
 static void test_seqpacket_msg_bounds_client(const struct test_opts *opts)
@@ -2230,6 +2231,101 @@ static void test_stream_accepted_setsockopt_server(const struct test_opts *opts)
        close(fd);
 }
 
+static void test_stream_tx_credit_bounds_client(const struct test_opts *opts)
+{
+       unsigned long long sock_buf_size;
+       size_t total = 0;
+       char buf[4096];
+       int fd;
+
+       memset(buf, 'A', sizeof(buf));
+
+       fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
+       if (fd < 0) {
+               perror("connect");
+               exit(EXIT_FAILURE);
+       }
+
+       sock_buf_size = SOCK_BUF_SIZE_SMALL;
+
+       setsockopt_ull_check(fd, AF_VSOCK, SO_VM_SOCKETS_BUFFER_MAX_SIZE,
+                            sock_buf_size,
+                            "setsockopt(SO_VM_SOCKETS_BUFFER_MAX_SIZE)");
+
+       setsockopt_ull_check(fd, AF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE,
+                            sock_buf_size,
+                            "setsockopt(SO_VM_SOCKETS_BUFFER_SIZE)");
+
+       if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) < 0) {
+               perror("fcntl(F_SETFL)");
+               exit(EXIT_FAILURE);
+       }
+
+       control_expectln("SRVREADY");
+
+       for (;;) {
+               ssize_t sent = send(fd, buf, sizeof(buf), 0);
+
+               if (sent == 0) {
+                       fprintf(stderr, "unexpected EOF while sending bytes\n");
+                       exit(EXIT_FAILURE);
+               }
+
+               if (sent < 0) {
+                       if (errno == EINTR)
+                               continue;
+
+                       if (errno == EAGAIN || errno == EWOULDBLOCK)
+                               break;
+
+                       perror("send");
+                       exit(EXIT_FAILURE);
+               }
+
+               total += sent;
+       }
+
+       control_writeln("CLIDONE");
+       close(fd);
+
+       /* We should not be able to send more bytes than the value set as
+        * local buffer size.
+        */
+       if (total > sock_buf_size) {
+               fprintf(stderr,
+                       "TX credit too large: queued %zu bytes (expected <= %llu)\n",
+                       total, sock_buf_size);
+               exit(EXIT_FAILURE);
+       }
+}
+
+static void test_stream_tx_credit_bounds_server(const struct test_opts *opts)
+{
+       unsigned long long sock_buf_size;
+       int fd;
+
+       fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);
+       if (fd < 0) {
+               perror("accept");
+               exit(EXIT_FAILURE);
+       }
+
+       sock_buf_size = SOCK_BUF_SIZE;
+
+       setsockopt_ull_check(fd, AF_VSOCK, SO_VM_SOCKETS_BUFFER_MAX_SIZE,
+                            sock_buf_size,
+                            "setsockopt(SO_VM_SOCKETS_BUFFER_MAX_SIZE)");
+
+       setsockopt_ull_check(fd, AF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE,
+                            sock_buf_size,
+                            "setsockopt(SO_VM_SOCKETS_BUFFER_SIZE)");
+
+       control_writeln("SRVREADY");
+       control_expectln("CLIDONE");
+
+       close(fd);
+}
+
 static struct test_case test_cases[] = {
        {
                .name = "SOCK_STREAM connection reset",
@@ -2419,6 +2515,11 @@ static struct test_case test_cases[] = {
                .run_client = test_stream_msgzcopy_mangle_client,
                .run_server = test_stream_msgzcopy_mangle_server,
        },
+       {
+               .name = "SOCK_STREAM TX credit bounds",
+               .run_client = test_stream_tx_credit_bounds_client,
+               .run_server = test_stream_tx_credit_bounds_server,
+       },
        {},
 };