Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

netfilter: conntrack: make conntrack userspace helpers work again

Florian Westphal says:

"Problem is that after the helper hook was merged back into the confirm
one, the queueing itself occurs from the confirm hook, i.e. we queue
from the last netfilter callback in the hook-list.

Therefore, on return, the packet bypasses the confirm action and the
connection is never committed to the main conntrack table.

To fix this there are several ways:
1. revert the 'Fixes' commit and have a extra helper hook again.
Works, but has the drawback of adding another indirect call for
everyone.

2. Special case this: split the hooks only when userspace helper
gets added, so queueing occurs at a lower priority again,
and normal enqueue reinject would eventually call the last hook.

3. Extend the existing nf_queue ct update hook to allow a forced
confirmation (plus run the seqadj code).

This goes for 3)."

Fixes: 827318feb69cb ("netfilter: conntrack: remove helper hook again")
Reviewed-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

+72 -6
+72 -6
net/netfilter/nf_conntrack_core.c
··· 2016 2016 nf_conntrack_get(skb_nfct(nskb)); 2017 2017 } 2018 2018 2019 - static int nf_conntrack_update(struct net *net, struct sk_buff *skb) 2019 + static int __nf_conntrack_update(struct net *net, struct sk_buff *skb, 2020 + struct nf_conn *ct) 2020 2021 { 2021 2022 struct nf_conntrack_tuple_hash *h; 2022 2023 struct nf_conntrack_tuple tuple; 2023 2024 enum ip_conntrack_info ctinfo; 2024 2025 struct nf_nat_hook *nat_hook; 2025 2026 unsigned int status; 2026 - struct nf_conn *ct; 2027 2027 int dataoff; 2028 2028 u16 l3num; 2029 2029 u8 l4num; 2030 - 2031 - ct = nf_ct_get(skb, &ctinfo); 2032 - if (!ct || nf_ct_is_confirmed(ct)) 2033 - return 0; 2034 2030 2035 2031 l3num = nf_ct_l3num(ct); 2036 2032 ··· 2082 2086 return -1; 2083 2087 2084 2088 return 0; 2089 + } 2090 + 2091 + /* This packet is coming from userspace via nf_queue, complete the packet 2092 + * processing after the helper invocation in nf_confirm(). 2093 + */ 2094 + static int nf_confirm_cthelper(struct sk_buff *skb, struct nf_conn *ct, 2095 + enum ip_conntrack_info ctinfo) 2096 + { 2097 + const struct nf_conntrack_helper *helper; 2098 + const struct nf_conn_help *help; 2099 + unsigned int protoff; 2100 + 2101 + help = nfct_help(ct); 2102 + if (!help) 2103 + return 0; 2104 + 2105 + helper = rcu_dereference(help->helper); 2106 + if (!(helper->flags & NF_CT_HELPER_F_USERSPACE)) 2107 + return 0; 2108 + 2109 + switch (nf_ct_l3num(ct)) { 2110 + case NFPROTO_IPV4: 2111 + protoff = skb_network_offset(skb) + ip_hdrlen(skb); 2112 + break; 2113 + #if IS_ENABLED(CONFIG_IPV6) 2114 + case NFPROTO_IPV6: { 2115 + __be16 frag_off; 2116 + u8 pnum; 2117 + 2118 + pnum = ipv6_hdr(skb)->nexthdr; 2119 + protoff = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &pnum, 2120 + &frag_off); 2121 + if (protoff < 0 || (frag_off & htons(~0x7)) != 0) 2122 + return 0; 2123 + break; 2124 + } 2125 + #endif 2126 + default: 2127 + return 0; 2128 + } 2129 + 2130 + if (test_bit(IPS_SEQ_ADJUST_BIT, &ct->status) && 2131 + !nf_is_loopback_packet(skb)) { 2132 + if (!nf_ct_seq_adjust(skb, ct, ctinfo, protoff)) { 2133 + NF_CT_STAT_INC_ATOMIC(nf_ct_net(ct), drop); 2134 + return -1; 2135 + } 2136 + } 2137 + 2138 + /* We've seen it coming out the other side: confirm it */ 2139 + return nf_conntrack_confirm(skb) == NF_DROP ? - 1 : 0; 2140 + } 2141 + 2142 + static int nf_conntrack_update(struct net *net, struct sk_buff *skb) 2143 + { 2144 + enum ip_conntrack_info ctinfo; 2145 + struct nf_conn *ct; 2146 + int err; 2147 + 2148 + ct = nf_ct_get(skb, &ctinfo); 2149 + if (!ct) 2150 + return 0; 2151 + 2152 + if (!nf_ct_is_confirmed(ct)) { 2153 + err = __nf_conntrack_update(net, skb, ct); 2154 + if (err < 0) 2155 + return err; 2156 + } 2157 + 2158 + return nf_confirm_cthelper(skb, ct, ctinfo); 2085 2159 } 2086 2160 2087 2161 static bool nf_conntrack_get_tuple_skb(struct nf_conntrack_tuple *dst_tuple,