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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.