Complete hardening of the connection tracking helper invocation sequence initiated in the previous refactoring commits. 1. Tuple Verification: Extracts the *current* tuple from the packet and verify it matches the connection tracking entry's tuple. This prevents helpers from being invoked on packets that may have been modified after the initial connection lookup, e.g. via act_ct -> pedit. This also prevents the helper from operating on ICMP(v6) PMTU errors. 2. TCP Header Sanity: For TCP traffic, the patch introduces a check to ensure a full and sane TCP header is present. Also adds missing 'nhoff' to ipv6_skip_exthdr(), this would be required for conntrack over bridges. Fixes: 9fb9cbb1082d ("[NETFILTER]: Add nf_conntrack subsystem.") Signed-off-by: Florian Westphal --- v3: also add nhoff in ipv6. LLM reports this even though its unrelated and we never got bug reports about this. Also add nf_ct_invert_tuple return value check and refuse huge protoff sooner. net/netfilter/nf_conntrack_proto.c | 43 ++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/net/netfilter/nf_conntrack_proto.c b/net/netfilter/nf_conntrack_proto.c index 18125aa29e0d..43bf3a71dbbf 100644 --- a/net/netfilter/nf_conntrack_proto.c +++ b/net/netfilter/nf_conntrack_proto.c @@ -136,6 +136,9 @@ static bool nf_confirm_get_protoff(struct sk_buff *skb, struct net *net, enum ip_conntrack_info ctinfo, unsigned int *protoffp, u8 *pnum) { + unsigned int nhoff = skb_network_offset(skb); + struct nf_conntrack_tuple tuple, invert; + enum ip_conntrack_dir dir; unsigned int protoff; __be16 frag_off; int start; @@ -144,12 +147,13 @@ static bool nf_confirm_get_protoff(struct sk_buff *skb, struct net *net, case NFPROTO_IPV4: if (ip_is_fragment(ip_hdr(skb))) return false; - protoff = skb_network_offset(skb) + ip_hdrlen(skb); + protoff = nhoff + ip_hdrlen(skb); *pnum = ip_hdr(skb)->protocol; break; case NFPROTO_IPV6: *pnum = ipv6_hdr(skb)->nexthdr; - start = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), pnum, &frag_off); + start = ipv6_skip_exthdr(skb, nhoff + sizeof(struct ipv6hdr), + pnum, &frag_off); if (start < 0 || frag_off) return false; @@ -160,6 +164,41 @@ static bool nf_confirm_get_protoff(struct sk_buff *skb, struct net *net, return false; } + if (protoff > skb->len) + return false; + + if (!nf_ct_get_tuplepr(skb, nhoff, nf_ct_l3num(ct), net, &tuple)) + return false; + + dir = CTINFO2DIR(ctinfo); + if (!nf_ct_invert_tuple(&invert, &tuple)) + return false; + + /* This is called after L3/L4 headers have been mangled by NAT: + * Packet in original direction has been subject to SNAT, i.e. + * inverted reply dir. + * Packet in reply direction has been subject to DNAT, i.e. + * inverted original direction. + */ + if (!nf_ct_tuple_equal(&invert, nf_ct_tuple(ct, !dir))) + return false; + + /* Validate that a full, sane TCP header (including options) is + * present at protoff before helpers/seqadj are allowed to touch it. + */ + if (tuple.dst.protonum == IPPROTO_TCP) { + unsigned int tcplen = skb->len - protoff; + const struct tcphdr *th; + struct tcphdr _tcph; + + th = skb_header_pointer(skb, protoff, sizeof(_tcph), &_tcph); + if (!th) + return false; + + if (th->doff * 4 < sizeof(*th) || tcplen < th->doff * 4) + return false; + } + *protoffp = protoff; return true; } -- 2.55.0