tcp_v4_conn_request() drops a SYN sent to a broadcast or multicast address. The SYNACK route has no such check. A SYN with a multicast source address can reach a listener when it is looped back with its dst attached: ip_rcv_finish_core() then skips ip_route_input_noref() and its martian source check. A raw IP_HDRINCL socket sending to a local address does this, as does re-injection by nft dup or the iptables TEE target. If the SYN destination is an address on a non-loopback device and the source is a group joined there (such as 224.0.0.1), the SYNACK route has RTCF_MULTICAST and RTCF_LOCAL set, and ip_build_and_send_pkt() sends it through ip_mc_output(). skb->sk of a SYNACK is the request socket, so sk_mc_loop() reads inet_flags past the end of it: BUG: KASAN: slab-out-of-bounds in sk_mc_loop+0x111/0x170 Read of size 8 at addr ffff88800f2b7f10 by task repro-raw/470 Call Trace: sk_mc_loop+0x111/0x170 ip_mc_output+0x355/0x930 ip_build_and_send_pkt+0xb87/0xc50 tcp_v4_send_synack+0x500/0x6f0 tcp_conn_request+0x2135/0x2d90 tcp_v4_conn_request+0xa5/0x210 tcp_rcv_state_process+0x136e/0x6920 tcp_v4_do_rcv+0x339/0xb10 tcp_v4_rcv+0x34ab/0x3ab0 ip_protocol_deliver_rcu+0x6e/0x3e0 ip_local_deliver_finish+0x34d/0x510 ip_local_deliver+0x1bc/0x310 ip_rcv+0x390/0x410 __netif_receive_skb_one_core+0x199/0x1e0 process_backlog+0x239/0x680 __napi_poll+0xb5/0x650 net_rx_action+0x980/0xd60 handle_softirqs+0x17f/0x590 do_softirq+0x3f/0x60 __local_bh_enable_ip+0x66/0x80 __dev_queue_xmit+0xa65/0x3520 ip_finish_output2+0xb00/0x16f0 ip_output+0x2ad/0x4a0 raw_sendmsg+0x245b/0x28e0 inet_sendmsg+0x121/0x150 __sys_sendto+0x450/0x4e0 do_syscall_64+0xf6/0x500 Allocated by task 470: inet_reqsk_alloc+0x97/0x6f0 tcp_conn_request+0x4c6/0x2d90 tcp_v4_conn_request+0xa5/0x210 The buggy address belongs to the object at ffff88800f2b7d60 which belongs to the cache request_sock_TCP of size 312 The buggy address is located 120 bytes to the right of allocated 312-byte region [ffff88800f2b7d60, ffff88800f2b7e98) Apply the same check to the SYNACK route in inet_csk_route_req(). Fixes: ca6fb0651883 ("tcp: attach SYNACK messages to request sockets instead of listener") Suggested-by: Eric Dumazet Signed-off-by: Theodor Arsenij Larionov Trichkine --- v2: - Reject broadcast/multicast SYNACK routes in inet_csk_route_req() (Eric Dumazet). - Describe how the SYN reaches the listener; add KASAN splat and repro. v1: https://lore.kernel.org/netdev/20261008100423.1256884-1-theodorlarionov@gmail.com/ Reproducer (unprivileged, user + network namespace): // gcc -O2 -static -o repro repro.c && ./repro #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #define LOCAL_ADDR "10.0.0.1" /* address of dummy0 */ #define MCAST_SRC "224.0.0.1" /* all-hosts, joined on every interface */ #define PORT 20000 static void die(const char *m) { perror(m); exit(1); } static void wr(const char *path, const char *buf) { int fd = open(path, O_WRONLY); if (fd < 0 || write(fd, buf, strlen(buf)) < 0) die(path); close(fd); } static void run(const char *cmd) { if (system(cmd)) fprintf(stderr, "failed: %s\n", cmd); } static uint16_t csum(const uint8_t *p, int len) { uint32_t s = 0; int i; for (i = 0; i + 1 < len; i += 2) s += (p[i] << 8) | p[i + 1]; if (i < len) s += p[i] << 8; while (s >> 16) s = (s & 0xffff) + (s >> 16); return ~s; } int main(void) { char map[64]; int uid = getuid(), gid = getgid(); if (unshare(CLONE_NEWUSER | CLONE_NEWNET)) die("unshare"); wr("/proc/self/setgroups", "deny"); snprintf(map, sizeof(map), "0 %d 1", uid); wr("/proc/self/uid_map", map); snprintf(map, sizeof(map), "0 %d 1", gid); wr("/proc/self/gid_map", map); run("ip link set lo up"); run("ip link add dummy0 type dummy"); run("ip addr add " LOCAL_ADDR "/24 dev dummy0"); run("ip link set dummy0 up"); int l = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in a = { .sin_family = AF_INET, .sin_port = htons(PORT), }; if (l < 0 || bind(l, (struct sockaddr *)&a, sizeof(a)) || listen(l, 128)) die("listen"); int raw = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (raw < 0) die("raw socket"); uint8_t pkt[40] = { 0 }, ph[32]; uint32_t saddr = inet_addr(MCAST_SRC), daddr = inet_addr(LOCAL_ADDR); pkt[0] = 0x45; /* IPv4, ihl 5 */ pkt[3] = sizeof(pkt); /* tot_len */ pkt[8] = 64; /* ttl */ pkt[9] = IPPROTO_TCP; memcpy(pkt + 12, &saddr, 4); memcpy(pkt + 16, &daddr, 4); pkt[22] = PORT >> 8; /* dport */ pkt[23] = PORT & 0xff; pkt[32] = 5 << 4; /* doff */ pkt[33] = 0x02; /* SYN */ pkt[34] = 0x40; /* window */ for (int i = 0; i < 100; i++) { uint16_t c, sport = 10000 + i; struct sockaddr_in to = { .sin_family = AF_INET, .sin_addr.s_addr = daddr, }; pkt[20] = sport >> 8; pkt[21] = sport & 0xff; pkt[36] = pkt[37] = 0; memcpy(ph, pkt + 12, 8); /* pseudo header */ ph[8] = 0; ph[9] = IPPROTO_TCP; ph[10] = 0; ph[11] = 20; memcpy(ph + 12, pkt + 20, 20); c = csum(ph, sizeof(ph)); pkt[36] = c >> 8; pkt[37] = c & 0xff; if (sendto(raw, pkt, sizeof(pkt), 0, (struct sockaddr *)&to, sizeof(to)) < 0) die("sendto"); } sleep(1); return 0; } net/ipv4/inet_connection_sock.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c index 6a30f1138454..aa928015cd14 100644 --- a/net/ipv4/inet_connection_sock.c +++ b/net/ipv4/inet_connection_sock.c @@ -779,6 +779,9 @@ struct dst_entry *inet_csk_route_req(const struct sock *sk, goto no_route; if (opt && opt->opt.is_strictroute && rt->rt_uses_gateway) goto route_err; + /* Never send a SYNACK to a broadcast or multicast destination. */ + if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST)) + goto route_err; rcu_read_unlock(); return &rt->dst; base-commit: 6d25ffca055a77787c21a36b66c253f76239411b -- 2.34.1