From: Minghao Zhang The TCP header validation in both nf_reject_ip_tcphdr_get() and nf_reject_ip6_tcphdr_get() checks that the TCP segment is at least sizeof(struct tcphdr) long, but does not verify that the header length claimed via the data offset is actually present in the packet. A peer can send a TCP packet claiming a 60-byte header although only the fixed 20-byte header is present. The reset generation then computes the ACK number using the claimed header length, and the subtraction of the data offset from the TCP segment length underflows, producing a wrapped ACK value in the generated RST. For a 20-byte segment with doff = 15, ACK bit clear, sequence number 0, SYN and FIN clear, the RST carries ack_seq 0xffffffd8 instead of a meaningful value. Reject TCP headers whose data offset is smaller than the fixed header or exceeds the available TCP segment length. This prevents generating resets with an ACK number derived from an invalid TCP header length. Found during a systematic audit of TCP header validation in the netfilter reject paths. Tested in network namespaces with nftables "reject with tcp reset" rules: on both IPv4 and IPv6, a probe with doff = 15 produced an RST with a wrapped ack_seq; with the fix no RST is generated for such packets, while probes with well-formed headers still get a correct RST. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Fixes: 764d8a9f2407 ("[NETFILTER]: Add IPv6 REJECT target") Reported-by: Minghao Zhang Reported-by: Jianjun Chen Cc: stable@vger.kernel.org Signed-off-by: Minghao Zhang --- net/ipv4/netfilter/nf_reject_ipv4.c | 5 +++++ net/ipv6/netfilter/nf_reject_ipv6.c | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/net/ipv4/netfilter/nf_reject_ipv4.c b/net/ipv4/netfilter/nf_reject_ipv4.c index 59ec465a9..0e8823a7d 100644 --- a/net/ipv4/netfilter/nf_reject_ipv4.c +++ b/net/ipv4/netfilter/nf_reject_ipv4.c @@ -189,6 +189,11 @@ nf_reject_ip_tcphdr_get(struct sk_buff *oldskb, if (oth == NULL) return NULL; + /* Reject invalid or truncated TCP headers. */ + if (oth->doff < sizeof(struct tcphdr) / 4 || + oldskb->len - ip_hdrlen(oldskb) < (oth->doff << 2)) + return NULL; + /* No RST for RST. */ if (oth->rst) return NULL; diff --git a/net/ipv6/netfilter/nf_reject_ipv6.c b/net/ipv6/netfilter/nf_reject_ipv6.c index 07cdaa10d..9dc112e50 100644 --- a/net/ipv6/netfilter/nf_reject_ipv6.c +++ b/net/ipv6/netfilter/nf_reject_ipv6.c @@ -223,6 +223,11 @@ nf_reject_ip6_tcphdr_get(struct sk_buff *oldskb, if (otcph == NULL) return NULL; + /* Reject invalid or truncated TCP headers. */ + if (otcph->doff < sizeof(struct tcphdr) / 4 || + *otcplen < (otcph->doff << 2)) + return NULL; + /* No RST for RST. */ if (otcph->rst) { pr_debug("RST is set\n"); -- 2.34.1