* [PATCH iproute2 0/5] netlink receive path hardening
@ 2026-11-09 1:43 Stephen Hemminger
2026-11-09 1:43 ` [PATCH iproute2 1/5] netlink: use a counter for sequence numbers Stephen Hemminger
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Stephen Hemminger @ 2026-11-09 1:43 UTC (permalink / raw)
To: netdev; +Cc: dsahern, Stephen Hemminger
Six scanner reports all landed on the same code, so they are better
fixed together than one at a time. The time() seeding was parallel
discovery: Florian reported it on netdev independently of the two
scanner findings that flagged it.
The common thread is that iproute2 trusted whatever came back from its
own socket. The sender went unchecked until after the framing checks,
and in the ss -D case until after the datagram had been written to the
dump file. The framing walk assumed every message in a datagram is
padded. Any process with CAP_NET_ADMIN over the namespace could use
that to inject events into a monitor, or to keep a dump from ever
completing.
One behavior change: paths that used to exit(1) on a short or malformed
datagram now skip it and carry on, since a datagram from a non-kernel
sender is no reason to abort.
Note: rdma monitor is not fixed by these changes. It opens its socket with a
bare mnl_socket_open(), so the connect() does not reach it.
Worth exercising:
- ip monitor all, ip -r monitor, devlink monitor, rdma monitor.
- ss -D dump recording, and replay with ss -F.
- ss -E, which sets local.nl_pid = 0 and dump = 0 to match multicast
notifications. Should be unaffected: it relies on the
nlmsg_pid/nlmsg_seq comparison, unchanged here, and the
nladdr.nl_pid requirement it already met is now enforced earlier in
rtnl_recvmsg().
- rdma stat and rdma dev, now going through mnlu_socket_recvfrom()
and mnlu_next_seq().
Stephen Hemminger (5):
netlink: use a counter for sequence numbers
netlink: ignore messages not sent by the kernel
netlink: connect sockets to the kernel
netlink: handle unpadded last message in receive loops
libnetlink: do not read past datagram when recording dump
include/mnl_utils.h | 2 +
lib/libnetlink.c | 131 +++++++++++++++++++++-----------------------
lib/mnl_utils.c | 54 +++++++++++++++++-
rdma/stat.c | 2 +-
rdma/utils.c | 2 +-
5 files changed, 117 insertions(+), 74 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH iproute2 1/5] netlink: use a counter for sequence numbers
2026-11-09 1:43 [PATCH iproute2 0/5] netlink receive path hardening Stephen Hemminger
@ 2026-11-09 1:43 ` Stephen Hemminger
2026-11-09 1:43 ` [PATCH iproute2 2/5] netlink: ignore messages not sent by the kernel Stephen Hemminger
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Stephen Hemminger @ 2026-11-09 1:43 UTC (permalink / raw)
To: netdev; +Cc: dsahern, Stephen Hemminger, Florian Fainelli
Seeding the sequence number from time() breaks with a 32-bit time_t
after 2038: time() returns -1, the first request gets sequence zero,
and the range check in __rtnl_talk_iov() underflows, leaving ip
blocked in recvmsg(). The seed adds nothing since replies are already
matched by port id.
Drop the seed so libnetlink starts at 1, make the range check
wrap-safe, and replace the per-message time() in the mnl helpers with
a counter that starts at 1 and skips zero.
Reported-by: Florian Fainelli <f.fainelli@gmail.com>
Reported-by: Anthropic OSS scanner (ANT-2026-E6IHJA62)
Reported-by: Anthropic OSS scanner (ANT-2026-1UC2GBCD)
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
include/mnl_utils.h | 1 +
lib/libnetlink.c | 4 +---
lib/mnl_utils.c | 13 +++++++++++--
rdma/utils.c | 2 +-
4 files changed, 14 insertions(+), 6 deletions(-)
diff --git a/include/mnl_utils.h b/include/mnl_utils.h
index 4df9e398..d809383c 100644
--- a/include/mnl_utils.h
+++ b/include/mnl_utils.h
@@ -25,6 +25,7 @@ int mnlu_gen_socket_sndrcv(struct mnlu_gen_socket *nlg, const struct nlmsghdr *n
struct mnl_socket *mnlu_socket_open(int bus);
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);
int mnlu_socket_recv_run(struct mnl_socket *nl, unsigned int seq, void *buf, size_t buf_size,
diff --git a/lib/libnetlink.c b/lib/libnetlink.c
index edf3a8ba..6ff9cace 100644
--- a/lib/libnetlink.c
+++ b/lib/libnetlink.c
@@ -15,7 +15,6 @@
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
-#include <time.h>
#include <sys/uio.h>
#include <linux/fib_rules.h>
#include <linux/if_addrlabel.h>
@@ -246,7 +245,6 @@ int rtnl_open_byproto(struct rtnl_handle *rth, unsigned int subscriptions,
rth->local.nl_family);
goto err;
}
- rth->seq = time(NULL);
return 0;
err:
rtnl_close(rth);
@@ -1092,7 +1090,7 @@ next:
if (nladdr.nl_pid != 0 ||
h->nlmsg_pid != rtnl->local.nl_pid ||
- h->nlmsg_seq > seq || h->nlmsg_seq < seq - iovlen) {
+ (unsigned int)(seq - h->nlmsg_seq) >= iovlen) {
/* Don't forget to skip that message. */
status -= NLMSG_ALIGN(len);
h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
diff --git a/lib/mnl_utils.c b/lib/mnl_utils.c
index 43b4cd1a..44e54aa8 100644
--- a/lib/mnl_utils.c
+++ b/lib/mnl_utils.c
@@ -5,7 +5,6 @@
#include <errno.h>
#include <string.h>
-#include <time.h>
#include <libmnl/libmnl.h>
#include <linux/genetlink.h>
@@ -40,6 +39,16 @@ int mnl_add_nl_group(struct mnl_socket *nl, unsigned int group)
return mnl_socket_bind(nl, group, MNL_SOCKET_AUTOPID);
}
+/* Sequence number for next request, zero is reserved for kernel events */
+uint32_t mnlu_next_seq(void)
+{
+ static uint32_t seq;
+
+ if (++seq == 0)
+ seq = 1;
+ return seq;
+}
+
struct nlmsghdr *mnlu_msg_prepare(void *buf, uint32_t nlmsg_type, uint16_t flags,
void *extra_header, size_t extra_header_size)
{
@@ -49,7 +58,7 @@ struct nlmsghdr *mnlu_msg_prepare(void *buf, uint32_t nlmsg_type, uint16_t flags
nlh = mnl_nlmsg_put_header(buf);
nlh->nlmsg_type = nlmsg_type;
nlh->nlmsg_flags = flags;
- nlh->nlmsg_seq = time(NULL);
+ nlh->nlmsg_seq = mnlu_next_seq();
eh = mnl_nlmsg_put_extra_header(nlh, extra_header_size);
memcpy(eh, extra_header, extra_header_size);
diff --git a/rdma/utils.c b/rdma/utils.c
index f6605eef..a85e8422 100644
--- a/rdma/utils.c
+++ b/rdma/utils.c
@@ -685,7 +685,7 @@ int rd_exec_cmd(struct rd *rd, const struct rd_cmd *cmds, const char *str)
void rd_prepare_msg(struct rd *rd, uint32_t cmd, uint32_t *seq, uint16_t flags)
{
- *seq = time(NULL);
+ *seq = mnlu_next_seq();
rd->nlh = mnl_nlmsg_put_header(rd->buff);
rd->nlh->nlmsg_type = RDMA_NL_GET_TYPE(RDMA_NL_NLDEV, cmd);
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH iproute2 2/5] netlink: ignore messages not sent by the kernel
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
2026-11-09 1:44 ` [PATCH iproute2 3/5] netlink: connect sockets to " Stephen Hemminger
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Stephen Hemminger @ 2026-11-09 1:43 UTC (permalink / raw)
To: netdev; +Cc: dsahern, Stephen Hemminger
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
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH iproute2 3/5] netlink: connect sockets to the kernel
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 ` [PATCH iproute2 2/5] netlink: ignore messages not sent by the kernel Stephen Hemminger
@ 2026-11-09 1:44 ` Stephen Hemminger
2026-11-09 1:44 ` [PATCH iproute2 4/5] netlink: handle unpadded last message in receive loops Stephen Hemminger
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Stephen Hemminger @ 2026-11-09 1:44 UTC (permalink / raw)
To: netdev; +Cc: dsahern, Stephen Hemminger
The netlink sockets were bound but not connected, so any process with
CAP_NET_ADMIN over the namespace can queue unicast datagrams to them.
Even though they are discarded, a steady stream fills the receive queue
and keeps a dump from ever completing.
Connect the socket to the kernel (port id 0) after bind, so the kernel
refuses unicast from other senders. Multicast is still delivered and
is filtered on receive.
Reported-by: Anthropic OSS scanner (ANT-2026-WUWQ5598)
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
lib/libnetlink.c | 8 ++++++++
lib/mnl_utils.c | 6 ++++++
2 files changed, 14 insertions(+)
diff --git a/lib/libnetlink.c b/lib/libnetlink.c
index b923ba02..6432d537 100644
--- a/lib/libnetlink.c
+++ b/lib/libnetlink.c
@@ -192,6 +192,7 @@ void rtnl_close(struct rtnl_handle *rth)
int rtnl_open_byproto(struct rtnl_handle *rth, unsigned int subscriptions,
int protocol)
{
+ const struct sockaddr_nl kernel_addr = { .nl_family = AF_NETLINK };
socklen_t addr_len;
int sndbuf = 32768;
int one = 1;
@@ -230,6 +231,13 @@ int rtnl_open_byproto(struct rtnl_handle *rth, unsigned int subscriptions,
perror("Cannot bind netlink socket");
goto err;
}
+
+ /* Refuse unicast from other processes, only the kernel may send */
+ if (connect(rth->fd, (struct sockaddr *)&kernel_addr,
+ sizeof(kernel_addr)) < 0) {
+ perror("Cannot connect netlink socket");
+ goto err;
+ }
addr_len = sizeof(rth->local);
if (getsockname(rth->fd, (struct sockaddr *)&rth->local,
&addr_len) < 0) {
diff --git a/lib/mnl_utils.c b/lib/mnl_utils.c
index 8ddf2b90..c49afed7 100644
--- a/lib/mnl_utils.c
+++ b/lib/mnl_utils.c
@@ -15,6 +15,7 @@
struct mnl_socket *mnlu_socket_open(int bus)
{
+ const struct sockaddr_nl kernel_addr = { .nl_family = AF_NETLINK };
struct mnl_socket *nl;
int one = 1;
@@ -28,6 +29,11 @@ struct mnl_socket *mnlu_socket_open(int bus)
if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
goto err_bind;
+ /* Refuse unicast from other processes, only the kernel may send */
+ if (connect(mnl_socket_get_fd(nl), (struct sockaddr *)&kernel_addr,
+ sizeof(kernel_addr)) < 0)
+ goto err_bind;
+
return nl;
err_bind:
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH iproute2 4/5] netlink: handle unpadded last message in receive loops
2026-11-09 1:43 [PATCH iproute2 0/5] netlink receive path hardening Stephen Hemminger
` (2 preceding siblings ...)
2026-11-09 1:44 ` [PATCH iproute2 3/5] netlink: connect sockets to " Stephen Hemminger
@ 2026-11-09 1:44 ` 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
5 siblings, 0 replies; 7+ messages in thread
From: Stephen Hemminger @ 2026-11-09 1:44 UTC (permalink / raw)
To: netdev; +Cc: dsahern, Stephen Hemminger
The kernel may send a datagram whose last message has no trailing
padding, for example Open vSwitch upcalls with OVS_DP_F_UNALIGNED.
Subtracting the aligned length then leaves the byte count at -1..-3.
__rtnl_talk_iov() and rtnl_listen() compare it as unsigned, read one
more header past the datagram and exit with "malformed message";
rtnl_dump_filter_l() exits with "Remnant of size -3".
Walk the datagram with NLMSG_OK()/NLMSG_NEXT() everywhere, and treat
only a positive rest as a remnant.
Reported-by: Anthropic OSS scanner (ANT-2026-S4ZHKVIW)
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
lib/libnetlink.c | 76 +++++++++++++++++-------------------------------
1 file changed, 27 insertions(+), 49 deletions(-)
diff --git a/lib/libnetlink.c b/lib/libnetlink.c
index 6432d537..de0681ac 100644
--- a/lib/libnetlink.c
+++ b/lib/libnetlink.c
@@ -994,7 +994,8 @@ skip_it:
fprintf(stderr, "Message truncated\n");
continue;
}
- if (msglen) {
+ /* negative if the last message had no padding */
+ if (msglen > 0) {
fprintf(stderr, "!!!Remnant of size %d\n", msglen);
exit(1);
}
@@ -1088,29 +1089,13 @@ next:
if (status < 0)
return status;
- for (h = (struct nlmsghdr *)buf; status >= sizeof(*h); ) {
- int len = h->nlmsg_len;
- int l = len - sizeof(*h);
-
- if (l < 0 || len > status) {
- if (msg.msg_flags & MSG_TRUNC) {
- fprintf(stderr, "Truncated message\n");
- free(buf);
- return -1;
- }
- fprintf(stderr,
- "!!!malformed message: len=%d\n",
- len);
- exit(1);
- }
+ for (h = (struct nlmsghdr *)buf; NLMSG_OK(h, status);
+ h = NLMSG_NEXT(h, status)) {
+ int l = h->nlmsg_len - sizeof(*h);
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);
- h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
+ (unsigned int)(seq - h->nlmsg_seq) >= iovlen)
continue;
- }
if (h->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(h);
@@ -1156,21 +1141,23 @@ next:
}
fprintf(stderr, "Unexpected reply!!!\n");
-
- status -= NLMSG_ALIGN(len);
- h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
}
free(buf);
+ /* negative if the last message had no padding */
+ if (status > 0) {
+ if (msg.msg_flags & MSG_TRUNC) {
+ fprintf(stderr, "Truncated message\n");
+ return -1;
+ }
+ fprintf(stderr, "!!!Remnant of size %d\n", status);
+ exit(1);
+ }
+
if (msg.msg_flags & MSG_TRUNC) {
fprintf(stderr, "Message truncated\n");
continue;
}
-
- if (status) {
- fprintf(stderr, "!!!Remnant of size %d\n", status);
- exit(1);
- }
}
}
@@ -1294,38 +1281,29 @@ int rtnl_listen(struct rtnl_handle *rtnl,
}
}
- for (h = (struct nlmsghdr *)buf; status >= sizeof(*h); ) {
+ for (h = (struct nlmsghdr *)buf; NLMSG_OK(h, status);
+ h = NLMSG_NEXT(h, status)) {
int err;
- int len = h->nlmsg_len;
- int l = len - sizeof(*h);
-
- if (l < 0 || len > status) {
- if (msg.msg_flags & MSG_TRUNC) {
- fprintf(stderr, "Truncated message\n");
- return -1;
- }
- fprintf(stderr,
- "!!!malformed message: len=%d\n",
- len);
- exit(1);
- }
err = handler(&ctrl, h, jarg);
fflush(stdout);
if (err < 0)
return err;
+ }
- status -= NLMSG_ALIGN(len);
- h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
+ /* negative if the last message had no padding */
+ if (status > 0) {
+ if (msg.msg_flags & MSG_TRUNC) {
+ fprintf(stderr, "Truncated message\n");
+ return -1;
+ }
+ fprintf(stderr, "!!!Remnant of size %d\n", status);
+ exit(1);
}
if (msg.msg_flags & MSG_TRUNC) {
fprintf(stderr, "Message truncated\n");
continue;
}
- if (status) {
- fprintf(stderr, "!!!Remnant of size %d\n", status);
- exit(1);
- }
}
}
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH iproute2 5/5] libnetlink: do not read past datagram when recording dump
2026-11-09 1:43 [PATCH iproute2 0/5] netlink receive path hardening Stephen Hemminger
` (3 preceding siblings ...)
2026-11-09 1:44 ` [PATCH iproute2 4/5] netlink: handle unpadded last message in receive loops Stephen Hemminger
@ 2026-11-09 1:44 ` Stephen Hemminger
2026-11-11 17:30 ` [PATCH iproute2 0/5] netlink receive path hardening patchwork-bot+netdevbpf
5 siblings, 0 replies; 7+ messages in thread
From: Stephen Hemminger @ 2026-11-09 1:44 UTC (permalink / raw)
To: netdev; +Cc: dsahern, Stephen Hemminger
With a dump file (ss -D), rtnl_dump_filter_l() wrote NLMSG_ALIGN(status)
bytes, reading up to 3 bytes past the received data, and past the
buffer when the datagram is larger than 32K.
Write the datagram, then explicit zero padding, since rtnl_from_file()
expects padded messages.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
lib/libnetlink.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/lib/libnetlink.c b/lib/libnetlink.c
index de0681ac..f67f2b48 100644
--- a/lib/libnetlink.c
+++ b/lib/libnetlink.c
@@ -928,8 +928,13 @@ static int rtnl_dump_filter_l(struct rtnl_handle *rth,
if (status < 0)
return status;
- if (rth->dump_fp)
- fwrite(buf, 1, NLMSG_ALIGN(status), rth->dump_fp);
+ if (rth->dump_fp) {
+ static const char pad[NLMSG_ALIGNTO];
+
+ /* rtnl_from_file() expects padded messages */
+ fwrite(buf, 1, status, rth->dump_fp);
+ fwrite(pad, 1, NLMSG_ALIGN(status) - status, rth->dump_fp);
+ }
for (a = arg; a->filter; a++) {
struct nlmsghdr *h = (struct nlmsghdr *)buf;
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH iproute2 0/5] netlink receive path hardening
2026-11-09 1:43 [PATCH iproute2 0/5] netlink receive path hardening Stephen Hemminger
` (4 preceding siblings ...)
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 ` patchwork-bot+netdevbpf
5 siblings, 0 replies; 7+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-11-11 17:30 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: netdev, dsahern
Hello:
This series was applied to iproute2/iproute2.git (main)
by Stephen Hemminger <stephen@networkplumber.org>:
On Sun, 8 Nov 2026 17:43:57 -0800 you wrote:
> Six scanner reports all landed on the same code, so they are better
> fixed together than one at a time. The time() seeding was parallel
> discovery: Florian reported it on netdev independently of the two
> scanner findings that flagged it.
>
> The common thread is that iproute2 trusted whatever came back from its
> own socket. The sender went unchecked until after the framing checks,
> and in the ss -D case until after the datagram had been written to the
> dump file. The framing walk assumed every message in a datagram is
> padded. Any process with CAP_NET_ADMIN over the namespace could use
> that to inject events into a monitor, or to keep a dump from ever
> completing.
>
> [...]
Here is the summary with links:
- [iproute2,1/5] netlink: use a counter for sequence numbers
https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/commit/?id=23df79c6c71c
- [iproute2,2/5] netlink: ignore messages not sent by the kernel
https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/commit/?id=29548251e33f
- [iproute2,3/5] netlink: connect sockets to the kernel
https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/commit/?id=31232dbd684f
- [iproute2,4/5] netlink: handle unpadded last message in receive loops
https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/commit/?id=51d90a2d062f
- [iproute2,5/5] libnetlink: do not read past datagram when recording dump
https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/commit/?id=575421242b2e
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-11-11 17:30 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [PATCH iproute2 2/5] netlink: ignore messages not sent by the kernel Stephen Hemminger
2026-11-09 1:44 ` [PATCH iproute2 3/5] netlink: connect sockets to " 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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).