[PATCH net v2] tcp: do not send a SYNACK to a broadcast or multicast address
From: Theodor Arsenij Larionov Trichkine
Date: Thu Oct 08 2026 - 07:51:51 EST
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:
<IRQ>
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
</IRQ>
<TASK>
__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
</TASK>
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 <edumazet@xxxxxxxxxx>
Signed-off-by: Theodor Arsenij Larionov Trichkine <theodorlarionov@xxxxxxxxx>
---
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@xxxxxxxxx/
Reproducer (unprivileged, user + network namespace):
// gcc -O2 -static -o repro repro.c && ./repro
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#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