Skip to content

Commit d127ac8

Browse files
mmhalkuba-moo
authored andcommitted
vsock/test: Add test for MSG_ZEROCOPY completion memory leak
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 60cf620 ("virtio/vsock: Improve MSG_ZEROCOPY error handling"). Reviewed-by: Stefano Garzarella <[email protected]> Signed-off-by: Michal Luczaj <[email protected]> Link: https://patch.msgid.link/[email protected] Signed-off-by: Jakub Kicinski <[email protected]>
1 parent ec50efe commit d127ac8

File tree

1 file changed

+152
-0
lines changed

1 file changed

+152
-0
lines changed

tools/testing/vsock/vsock_test.c

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1561,6 +1561,153 @@ static void test_stream_msgzcopy_leak_errq_server(const struct test_opts *opts)
15611561
close(fd);
15621562
}
15631563

1564+
/* Test msgzcopy_leak_zcskb is meant to exercise sendmsg() error handling path,
1565+
* that might leak an skb. The idea is to fail virtio_transport_init_zcopy_skb()
1566+
* by hitting net.core.optmem_max limit in sock_omalloc(), specifically
1567+
*
1568+
* vsock_connectible_sendmsg
1569+
* virtio_transport_stream_enqueue
1570+
* virtio_transport_send_pkt_info
1571+
* virtio_transport_init_zcopy_skb
1572+
* . msg_zerocopy_realloc
1573+
* . msg_zerocopy_alloc
1574+
* . sock_omalloc
1575+
* . sk_omem_alloc + size > sysctl_optmem_max
1576+
* return -ENOMEM
1577+
*
1578+
* We abuse the implementation detail of net/socket.c:____sys_sendmsg().
1579+
* sk_omem_alloc can be precisely bumped by sock_kmalloc(), as it is used to
1580+
* fetch user-provided control data.
1581+
*
1582+
* While this approach works for now, it relies on assumptions regarding the
1583+
* implementation and configuration (for example, order of net.core.optmem_max
1584+
* can not exceed MAX_PAGE_ORDER), which may not hold in the future. A more
1585+
* resilient testing could be implemented by leveraging the Fault injection
1586+
* framework (CONFIG_FAULT_INJECTION), e.g.
1587+
*
1588+
* client# echo N > /sys/kernel/debug/failslab/ignore-gfp-wait
1589+
* client# echo 0 > /sys/kernel/debug/failslab/verbose
1590+
*
1591+
* void client(const struct test_opts *opts)
1592+
* {
1593+
* char buf[16];
1594+
* int f, s, i;
1595+
*
1596+
* f = open("/proc/self/fail-nth", O_WRONLY);
1597+
*
1598+
* for (i = 1; i < 32; i++) {
1599+
* control_writeulong(CONTROL_CONTINUE);
1600+
*
1601+
* s = vsock_stream_connect(opts->peer_cid, opts->peer_port);
1602+
* enable_so_zerocopy_check(s);
1603+
*
1604+
* sprintf(buf, "%d", i);
1605+
* write(f, buf, strlen(buf));
1606+
*
1607+
* send(s, &(char){ 0 }, 1, MSG_ZEROCOPY);
1608+
*
1609+
* write(f, "0", 1);
1610+
* close(s);
1611+
* }
1612+
*
1613+
* control_writeulong(CONTROL_DONE);
1614+
* close(f);
1615+
* }
1616+
*
1617+
* void server(const struct test_opts *opts)
1618+
* {
1619+
* int fd;
1620+
*
1621+
* while (control_readulong() == CONTROL_CONTINUE) {
1622+
* fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);
1623+
* vsock_wait_remote_close(fd);
1624+
* close(fd);
1625+
* }
1626+
* }
1627+
*
1628+
* Refer to Documentation/fault-injection/fault-injection.rst.
1629+
*/
1630+
#define MAX_PAGE_ORDER 10 /* usually */
1631+
#define PAGE_SIZE 4096
1632+
1633+
/* Test for a memory leak. User is expected to run kmemleak scan, see README. */
1634+
static void test_stream_msgzcopy_leak_zcskb_client(const struct test_opts *opts)
1635+
{
1636+
size_t optmem_max, ctl_len, chunk_size;
1637+
struct msghdr msg = { 0 };
1638+
struct iovec iov;
1639+
char *chunk;
1640+
int fd, res;
1641+
FILE *f;
1642+
1643+
f = fopen("/proc/sys/net/core/optmem_max", "r");
1644+
if (!f) {
1645+
perror("fopen(optmem_max)");
1646+
exit(EXIT_FAILURE);
1647+
}
1648+
1649+
if (fscanf(f, "%zu", &optmem_max) != 1) {
1650+
fprintf(stderr, "fscanf(optmem_max) failed\n");
1651+
exit(EXIT_FAILURE);
1652+
}
1653+
1654+
fclose(f);
1655+
1656+
fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
1657+
if (fd < 0) {
1658+
perror("connect");
1659+
exit(EXIT_FAILURE);
1660+
}
1661+
1662+
enable_so_zerocopy_check(fd);
1663+
1664+
ctl_len = optmem_max - 1;
1665+
if (ctl_len > PAGE_SIZE << MAX_PAGE_ORDER) {
1666+
fprintf(stderr, "Try with net.core.optmem_max = 100000\n");
1667+
exit(EXIT_FAILURE);
1668+
}
1669+
1670+
chunk_size = CMSG_SPACE(ctl_len);
1671+
chunk = malloc(chunk_size);
1672+
if (!chunk) {
1673+
perror("malloc");
1674+
exit(EXIT_FAILURE);
1675+
}
1676+
memset(chunk, 0, chunk_size);
1677+
1678+
iov.iov_base = &(char){ 0 };
1679+
iov.iov_len = 1;
1680+
1681+
msg.msg_iov = &iov;
1682+
msg.msg_iovlen = 1;
1683+
msg.msg_control = chunk;
1684+
msg.msg_controllen = ctl_len;
1685+
1686+
errno = 0;
1687+
res = sendmsg(fd, &msg, MSG_ZEROCOPY);
1688+
if (res >= 0 || errno != ENOMEM) {
1689+
fprintf(stderr, "Expected ENOMEM, got errno=%d res=%d\n",
1690+
errno, res);
1691+
exit(EXIT_FAILURE);
1692+
}
1693+
1694+
close(fd);
1695+
}
1696+
1697+
static void test_stream_msgzcopy_leak_zcskb_server(const struct test_opts *opts)
1698+
{
1699+
int fd;
1700+
1701+
fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);
1702+
if (fd < 0) {
1703+
perror("accept");
1704+
exit(EXIT_FAILURE);
1705+
}
1706+
1707+
vsock_wait_remote_close(fd);
1708+
close(fd);
1709+
}
1710+
15641711
static struct test_case test_cases[] = {
15651712
{
15661713
.name = "SOCK_STREAM connection reset",
@@ -1701,6 +1848,11 @@ static struct test_case test_cases[] = {
17011848
.run_client = test_stream_msgzcopy_leak_errq_client,
17021849
.run_server = test_stream_msgzcopy_leak_errq_server,
17031850
},
1851+
{
1852+
.name = "SOCK_STREAM MSG_ZEROCOPY leak completion skb",
1853+
.run_client = test_stream_msgzcopy_leak_zcskb_client,
1854+
.run_server = test_stream_msgzcopy_leak_zcskb_server,
1855+
},
17041856
{},
17051857
};
17061858

0 commit comments

Comments
 (0)