mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-04-09 02:57:42 -04:00
Recent changes to make netlink socket memory accounting must have broken the implicit assumption of the netlink-dump test that we can fit exactly 64 dumps into the socket. Handle the failure mode properly, and increase the dump count to 80 to make sure we still run into the error condition if the default buffer size increases in the future. Link: https://patch.msgid.link/20250906211351.3192412-1-kuba@kernel.org Signed-off-by: Jakub Kicinski <kuba@kernel.org>
263 lines
5.6 KiB
C
263 lines
5.6 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include <linux/genetlink.h>
|
|
#include <linux/neighbour.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/netlink.h>
|
|
#include <linux/mqueue.h>
|
|
#include <linux/rtnetlink.h>
|
|
|
|
#include "../kselftest_harness.h"
|
|
|
|
#include <ynl.h>
|
|
|
|
struct ext_ack {
|
|
int err;
|
|
|
|
__u32 attr_offs;
|
|
__u32 miss_type;
|
|
__u32 miss_nest;
|
|
const char *str;
|
|
};
|
|
|
|
enum get_ea_ret {
|
|
ERROR = -1,
|
|
NO_CTRL = 0,
|
|
FOUND_DONE,
|
|
FOUND_ERR,
|
|
FOUND_EXTACK,
|
|
};
|
|
|
|
static enum get_ea_ret
|
|
nl_get_extack(char *buf, size_t n, struct ext_ack *ea)
|
|
{
|
|
enum get_ea_ret ret = NO_CTRL;
|
|
const struct nlmsghdr *nlh;
|
|
const struct nlattr *attr;
|
|
ssize_t rem;
|
|
|
|
for (rem = n; rem > 0; NLMSG_NEXT(nlh, rem)) {
|
|
nlh = (struct nlmsghdr *)&buf[n - rem];
|
|
if (!NLMSG_OK(nlh, rem))
|
|
return ERROR;
|
|
|
|
if (nlh->nlmsg_type == NLMSG_ERROR)
|
|
ret = FOUND_ERR;
|
|
else if (nlh->nlmsg_type == NLMSG_DONE)
|
|
ret = FOUND_DONE;
|
|
else
|
|
continue;
|
|
|
|
ea->err = -*(int *)NLMSG_DATA(nlh);
|
|
|
|
if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS))
|
|
return ret;
|
|
|
|
ynl_attr_for_each(attr, nlh, sizeof(int)) {
|
|
switch (ynl_attr_type(attr)) {
|
|
case NLMSGERR_ATTR_OFFS:
|
|
ea->attr_offs = ynl_attr_get_u32(attr);
|
|
break;
|
|
case NLMSGERR_ATTR_MISS_TYPE:
|
|
ea->miss_type = ynl_attr_get_u32(attr);
|
|
break;
|
|
case NLMSGERR_ATTR_MISS_NEST:
|
|
ea->miss_nest = ynl_attr_get_u32(attr);
|
|
break;
|
|
case NLMSGERR_ATTR_MSG:
|
|
ea->str = ynl_attr_get_str(attr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return FOUND_EXTACK;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct {
|
|
struct nlmsghdr nlhdr;
|
|
struct ndmsg ndm;
|
|
struct nlattr ahdr;
|
|
__u32 val;
|
|
} dump_neigh_bad = {
|
|
.nlhdr = {
|
|
.nlmsg_len = sizeof(dump_neigh_bad),
|
|
.nlmsg_type = RTM_GETNEIGH,
|
|
.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP,
|
|
.nlmsg_seq = 1,
|
|
},
|
|
.ndm = {
|
|
.ndm_family = 123,
|
|
},
|
|
.ahdr = {
|
|
.nla_len = 4 + 4,
|
|
.nla_type = NDA_FLAGS_EXT,
|
|
},
|
|
.val = -1, // should fail MASK validation
|
|
};
|
|
|
|
TEST(dump_extack)
|
|
{
|
|
int netlink_sock;
|
|
int i, cnt, ret;
|
|
char buf[8192];
|
|
int one = 1;
|
|
ssize_t n;
|
|
|
|
netlink_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
|
|
ASSERT_GE(netlink_sock, 0);
|
|
|
|
n = setsockopt(netlink_sock, SOL_NETLINK, NETLINK_CAP_ACK,
|
|
&one, sizeof(one));
|
|
ASSERT_EQ(n, 0);
|
|
n = setsockopt(netlink_sock, SOL_NETLINK, NETLINK_EXT_ACK,
|
|
&one, sizeof(one));
|
|
ASSERT_EQ(n, 0);
|
|
n = setsockopt(netlink_sock, SOL_NETLINK, NETLINK_GET_STRICT_CHK,
|
|
&one, sizeof(one));
|
|
ASSERT_EQ(n, 0);
|
|
|
|
/* Dump so many times we fill up the buffer */
|
|
cnt = 80;
|
|
for (i = 0; i < cnt; i++) {
|
|
n = send(netlink_sock, &dump_neigh_bad,
|
|
sizeof(dump_neigh_bad), 0);
|
|
ASSERT_EQ(n, sizeof(dump_neigh_bad));
|
|
}
|
|
|
|
/* Read out the ENOBUFS */
|
|
n = recv(netlink_sock, buf, sizeof(buf), MSG_DONTWAIT);
|
|
EXPECT_EQ(n, -1);
|
|
EXPECT_EQ(errno, ENOBUFS);
|
|
|
|
for (i = 0; i < cnt; i++) {
|
|
struct ext_ack ea = {};
|
|
|
|
n = recv(netlink_sock, buf, sizeof(buf), MSG_DONTWAIT);
|
|
if (n < 0) {
|
|
ASSERT_GE(i, 10);
|
|
break;
|
|
}
|
|
ASSERT_GE(n, (ssize_t)sizeof(struct nlmsghdr));
|
|
|
|
ret = nl_get_extack(buf, n, &ea);
|
|
/* Once we fill the buffer we'll see one ENOBUFS followed
|
|
* by a number of EBUSYs. Then the last recv() will finally
|
|
* trigger and complete the dump.
|
|
*/
|
|
if (ret == FOUND_ERR && (ea.err == ENOBUFS || ea.err == EBUSY))
|
|
continue;
|
|
EXPECT_EQ(ret, FOUND_EXTACK);
|
|
EXPECT_EQ(ea.err, EINVAL);
|
|
EXPECT_EQ(ea.attr_offs,
|
|
sizeof(struct nlmsghdr) + sizeof(struct ndmsg));
|
|
}
|
|
/* Make sure last message was a full DONE+extack */
|
|
EXPECT_EQ(ret, FOUND_EXTACK);
|
|
}
|
|
|
|
static const struct {
|
|
struct nlmsghdr nlhdr;
|
|
struct genlmsghdr genlhdr;
|
|
struct nlattr ahdr;
|
|
__u16 val;
|
|
__u16 pad;
|
|
} dump_policies = {
|
|
.nlhdr = {
|
|
.nlmsg_len = sizeof(dump_policies),
|
|
.nlmsg_type = GENL_ID_CTRL,
|
|
.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP,
|
|
.nlmsg_seq = 1,
|
|
},
|
|
.genlhdr = {
|
|
.cmd = CTRL_CMD_GETPOLICY,
|
|
.version = 2,
|
|
},
|
|
.ahdr = {
|
|
.nla_len = 6,
|
|
.nla_type = CTRL_ATTR_FAMILY_ID,
|
|
},
|
|
.val = GENL_ID_CTRL,
|
|
.pad = 0,
|
|
};
|
|
|
|
// Sanity check for the test itself, make sure the dump doesn't fit in one msg
|
|
TEST(test_sanity)
|
|
{
|
|
int netlink_sock;
|
|
char buf[8192];
|
|
ssize_t n;
|
|
|
|
netlink_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
|
|
ASSERT_GE(netlink_sock, 0);
|
|
|
|
n = send(netlink_sock, &dump_policies, sizeof(dump_policies), 0);
|
|
ASSERT_EQ(n, sizeof(dump_policies));
|
|
|
|
n = recv(netlink_sock, buf, sizeof(buf), MSG_DONTWAIT);
|
|
ASSERT_GE(n, (ssize_t)sizeof(struct nlmsghdr));
|
|
|
|
n = recv(netlink_sock, buf, sizeof(buf), MSG_DONTWAIT);
|
|
ASSERT_GE(n, (ssize_t)sizeof(struct nlmsghdr));
|
|
|
|
close(netlink_sock);
|
|
}
|
|
|
|
TEST(close_in_progress)
|
|
{
|
|
int netlink_sock;
|
|
ssize_t n;
|
|
|
|
netlink_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
|
|
ASSERT_GE(netlink_sock, 0);
|
|
|
|
n = send(netlink_sock, &dump_policies, sizeof(dump_policies), 0);
|
|
ASSERT_EQ(n, sizeof(dump_policies));
|
|
|
|
close(netlink_sock);
|
|
}
|
|
|
|
TEST(close_with_ref)
|
|
{
|
|
char cookie[NOTIFY_COOKIE_LEN] = {};
|
|
int netlink_sock, mq_fd;
|
|
struct sigevent sigev;
|
|
ssize_t n;
|
|
|
|
netlink_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
|
|
ASSERT_GE(netlink_sock, 0);
|
|
|
|
n = send(netlink_sock, &dump_policies, sizeof(dump_policies), 0);
|
|
ASSERT_EQ(n, sizeof(dump_policies));
|
|
|
|
mq_fd = syscall(__NR_mq_open, "sed", O_CREAT | O_WRONLY, 0600, 0);
|
|
ASSERT_GE(mq_fd, 0);
|
|
|
|
memset(&sigev, 0, sizeof(sigev));
|
|
sigev.sigev_notify = SIGEV_THREAD;
|
|
sigev.sigev_value.sival_ptr = cookie;
|
|
sigev.sigev_signo = netlink_sock;
|
|
|
|
syscall(__NR_mq_notify, mq_fd, &sigev);
|
|
|
|
close(netlink_sock);
|
|
|
|
// give mqueue time to fire
|
|
usleep(100 * 1000);
|
|
}
|
|
|
|
TEST_HARNESS_MAIN
|