From: Stephen Hemminger <stephen@networkplumber.org>
To: netdev@vger.kernel.org
Cc: dsahern@kernel.org, Stephen Hemminger <stephen@networkplumber.org>
Subject: [PATCH iproute2 2/5] netlink: ignore messages not sent by the kernel
Date: Sun, 8 Nov 2026 17:43:59 -0800 [thread overview]
Message-ID: <20261109014434.115922-3-stephen@networkplumber.org> (raw)
In-Reply-To: <20261109014434.115922-1-stephen@networkplumber.org>
A process with CAP_NET_ADMIN over the namespace can send unicast or
multicast netlink datagrams to iproute2 tools. rtnl_listen() and
mnlu_socket_recv_run() never checked the sender, so monitors print
forged events and exit or overflow on malformed ones. The dump and talk
loops checked nl_pid only after framing checks and after ss -D had
recorded the datagram.
Drop any datagram whose sender is not port id 0 right after recvmsg(),
in rtnl_recvmsg(), rtnl_listen() and a new mnlu_socket_recvfrom().
Reported-by: Anthropic OSS scanner (ANT-2026-ISEFARJF)
Reported-by: Anthropic OSS scanner (ANT-2026-GYQAKSVI)
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
include/mnl_utils.h | 1 +
lib/libnetlink.c | 36 ++++++++++++++++++++----------------
lib/mnl_utils.c | 35 ++++++++++++++++++++++++++++++++++-
rdma/stat.c | 2 +-
4 files changed, 56 insertions(+), 18 deletions(-)
diff --git a/include/mnl_utils.h b/include/mnl_utils.h
index d809383c..19a49de6 100644
--- a/include/mnl_utils.h
+++ b/include/mnl_utils.h
@@ -28,6 +28,7 @@ int mnl_add_nl_group(struct mnl_socket *nl, unsigned int group);
uint32_t mnlu_next_seq(void);
struct nlmsghdr *mnlu_msg_prepare(void *buf, uint32_t nlmsg_type, uint16_t flags,
void *extra_header, size_t extra_header_size);
+ssize_t mnlu_socket_recvfrom(const struct mnl_socket *nl, void *buf, size_t buf_size);
int mnlu_socket_recv_run(struct mnl_socket *nl, unsigned int seq, void *buf, size_t buf_size,
mnl_cb_t cb, void *data);
int mnlu_gen_socket_recv_run(struct mnlu_gen_socket *nlg, mnl_cb_t cb,
diff --git a/lib/libnetlink.c b/lib/libnetlink.c
index 6ff9cace..b923ba02 100644
--- a/lib/libnetlink.c
+++ b/lib/libnetlink.c
@@ -843,12 +843,22 @@ static int __rtnl_recvmsg(int fd, struct msghdr *msg, int flags)
return len;
}
+/* Netlink datagrams from other processes are discarded */
+static bool rtnl_from_kernel(const struct msghdr *msg)
+{
+ const struct sockaddr_nl *nladdr = msg->msg_name;
+
+ return msg->msg_namelen == sizeof(*nladdr) && nladdr->nl_pid == 0;
+}
+
static int rtnl_recvmsg(int fd, struct msghdr *msg, char **answer)
{
struct iovec *iov = msg->msg_iov;
char *buf;
int len;
+again:
+ msg->msg_namelen = sizeof(struct sockaddr_nl);
iov->iov_base = NULL;
iov->iov_len = 0;
@@ -873,6 +883,11 @@ static int rtnl_recvmsg(int fd, struct msghdr *msg, char **answer)
return len;
}
+ if (!rtnl_from_kernel(msg)) {
+ free(buf);
+ goto again;
+ }
+
if (answer)
*answer = buf;
else
@@ -918,8 +933,7 @@ static int rtnl_dump_filter_l(struct rtnl_handle *rth,
h->nlmsg_flags &= ~a->nc_flags;
- if (nladdr.nl_pid != 0 ||
- h->nlmsg_pid != rth->local.nl_pid ||
+ if (h->nlmsg_pid != rth->local.nl_pid ||
h->nlmsg_seq != rth->dump)
goto skip_it;
@@ -1066,12 +1080,6 @@ next:
if (status < 0)
return status;
- if (msg.msg_namelen != sizeof(nladdr)) {
- fprintf(stderr,
- "sender address length == %d\n",
- msg.msg_namelen);
- exit(1);
- }
for (h = (struct nlmsghdr *)buf; status >= sizeof(*h); ) {
int len = h->nlmsg_len;
int l = len - sizeof(*h);
@@ -1088,8 +1096,7 @@ next:
exit(1);
}
- if (nladdr.nl_pid != 0 ||
- h->nlmsg_pid != rtnl->local.nl_pid ||
+ if (h->nlmsg_pid != rtnl->local.nl_pid ||
(unsigned int)(seq - h->nlmsg_seq) >= iovlen) {
/* Don't forget to skip that message. */
status -= NLMSG_ALIGN(len);
@@ -1246,6 +1253,7 @@ int rtnl_listen(struct rtnl_handle *rtnl,
}
iov.iov_len = sizeof(buf);
+ msg.msg_namelen = sizeof(nladdr);
status = recvmsg(rtnl->fd, &msg, 0);
if (status < 0) {
@@ -1261,12 +1269,8 @@ int rtnl_listen(struct rtnl_handle *rtnl,
fprintf(stderr, "EOF on netlink\n");
return -1;
}
- if (msg.msg_namelen != sizeof(nladdr)) {
- fprintf(stderr,
- "Sender address length == %d\n",
- msg.msg_namelen);
- exit(1);
- }
+ if (!rtnl_from_kernel(&msg))
+ continue;
if (rtnl->flags & RTNL_HANDLE_F_LISTEN_ALL_NSID) {
memset(&ctrl, 0, sizeof(ctrl));
diff --git a/lib/mnl_utils.c b/lib/mnl_utils.c
index 44e54aa8..8ddf2b90 100644
--- a/lib/mnl_utils.c
+++ b/lib/mnl_utils.c
@@ -5,6 +5,7 @@
#include <errno.h>
#include <string.h>
+#include <sys/socket.h>
#include <libmnl/libmnl.h>
#include <linux/genetlink.h>
@@ -111,6 +112,38 @@ static mnl_cb_t mnlu_cb_array[NLMSG_MIN_TYPE] = {
[NLMSG_OVERRUN] = mnlu_cb_noop,
};
+/*
+ * Like mnl_socket_recvfrom(), but discards datagrams that were
+ * not sent by the kernel.
+ */
+ssize_t mnlu_socket_recvfrom(const struct mnl_socket *nl, void *buf, size_t buf_size)
+{
+ struct sockaddr_nl addr;
+ struct iovec iov = {
+ .iov_base = buf,
+ .iov_len = buf_size,
+ };
+ struct msghdr msg = {
+ .msg_name = &addr,
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+ ssize_t ret;
+
+ do {
+ msg.msg_namelen = sizeof(addr);
+ ret = recvmsg(mnl_socket_get_fd(nl), &msg, 0);
+ if (ret < 0)
+ return ret;
+ } while (msg.msg_namelen != sizeof(addr) || addr.nl_pid != 0);
+
+ if (msg.msg_flags & MSG_TRUNC) {
+ errno = ENOSPC;
+ return -1;
+ }
+ return ret;
+}
+
int mnlu_socket_recv_run(struct mnl_socket *nl, unsigned int seq, void *buf, size_t buf_size,
mnl_cb_t cb, void *data)
{
@@ -118,7 +151,7 @@ int mnlu_socket_recv_run(struct mnl_socket *nl, unsigned int seq, void *buf, siz
int err;
do {
- err = mnl_socket_recvfrom(nl, buf, buf_size);
+ err = mnlu_socket_recvfrom(nl, buf, buf_size);
if (err <= 0)
break;
err = mnl_cb_run2(buf, err, seq, portid,
diff --git a/rdma/stat.c b/rdma/stat.c
index f2c2d173..01cc171b 100644
--- a/rdma/stat.c
+++ b/rdma/stat.c
@@ -735,7 +735,7 @@ static int stat_one_qp_unbind(struct rd *rd)
* then rd_recv_msg() always return -1 here
*/
portid = mnl_socket_get_portid(rd->nl);
- ret = mnl_socket_recvfrom(rd->nl, buf, sizeof(buf));
+ ret = mnlu_socket_recvfrom(rd->nl, buf, sizeof(buf));
if (ret <= 0)
return ret;
--
2.53.0
next prev parent reply other threads:[~2026-11-09 1:44 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-09 1:43 [PATCH iproute2 0/5] netlink receive path hardening Stephen Hemminger
2026-11-09 1:43 ` [PATCH iproute2 1/5] netlink: use a counter for sequence numbers Stephen Hemminger
2026-11-09 1:43 ` Stephen Hemminger [this message]
2026-11-09 1:44 ` [PATCH iproute2 3/5] netlink: connect sockets to the kernel Stephen Hemminger
2026-11-09 1:44 ` [PATCH iproute2 4/5] netlink: handle unpadded last message in receive loops Stephen Hemminger
2026-11-09 1:44 ` [PATCH iproute2 5/5] libnetlink: do not read past datagram when recording dump Stephen Hemminger
2026-11-11 17:30 ` [PATCH iproute2 0/5] netlink receive path hardening patchwork-bot+netdevbpf
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261109014434.115922-3-stephen@networkplumber.org \
--to=stephen@networkplumber.org \
--cc=dsahern@kernel.org \
--cc=netdev@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox