From mboxrd@z Thu Jan 1 00:00:00 1970 From: Florian Westphal Subject: [PATCH] bridge: netfilter: work around shared nfct struct Date: Tue, 30 Aug 2011 12:57:07 +0200 Message-ID: <1314701827-21702-1-git-send-email-fw@strlen.de> Cc: netdev@vger.kernel.org, Florian Westphal To: netfilter-devel@vger.kernel.org Return-path: Sender: netfilter-devel-owner@vger.kernel.org List-Id: netdev.vger.kernel.org When incoking iptables hooks from bridge netfilter, the assumption that non-confirmed skb->nfct is never shared does no longer hold, as bridge code clones skbs when e.g. forwarding packets to multiple bridge ports. When NFQUEUE is used, we can BUG because nf_nat_setup_info can be invoked simultaneously for the same conntrack: [ 3196.798768] kernel BUG at net/ipv4/netfilter/nf_nat_core.c:300! [..] [ 3196.798768] [] ? nf_hook_slow+0x21a/0x282 [ 3196.798768] [] ? br_handle_frame_finish+0x0/0x13b [bridge] [ 3196.798768] [] ? alloc_null_binding+0x47/0x4c [iptable_nat] [ 3196.798768] [] ? nf_nat_fn+0x193/0x1fb [iptable_nat] [ 3196.798768] [] ? nf_iterate+0x40/0x9f [ 3196.798768] [] ? nf_hook_slow+0x21a/0x282 [ 3196.798768] [] ? ip_local_deliver_finish+0x0/0x1f1 [ 3196.798768] [] ? ip_local_deliver_finish+0x0/0x1f1 [ 3196.798768] [] ? nf_hook_slow+0x21a/0x282 [ 3196.798768] [] ? ip_rcv_finish+0x0/0x340 [ 3196.798768] [] ? ip_local_deliver+0x52/0x6c [ 3196.798768] [] ? ip_rcv_finish+0x326/0x340 [ 3196.798768] [] ? ip_rcv+0x273/0x2b8 [ 3196.798768] [] ? process_backlog+0x8d/0xc6 [ 3196.798768] [] ? net_rx_action+0xa2/0x1cf [ 3196.798768] [] ? __do_softirq+0x8b/0x10b [ 3196.798768] [] ? call_softirq+0x1c/0x28 [ 3196.798768] [] ? do_softirq+0x31/0x66 [ 3196.798768] [] ? irq_exit+0x36/0x78 [ 3196.798768] [] ? do_IRQ+0xa0/0xb6 [ 3196.798768] [] ? ret_from_intr+0x0/0xa [..] [ 3196.798768] Code: be 2b 01 00 00 48 c7 c7 e8 cd 29 a0 e8 e8 d7 d9 e0 45 85 ff 49 8b 45 78 75 06 48 c1 e8 07 eb 04 48 c1 e8 08 83 e0 01 85 c0 74 04 <0f> 0b eb fe 49 8d 75 50 48 8d bc 24 80 00 00 00 e8 83 38 f7 ff [ 3196.798768] RIP [] nf_nat_setup_info+0x8a/0x564 [nf_nat] [ 3196.798768] RSP Fix this by changing ->nfct of all clones to untracked. This should be OK, because if we do a full copy of ->nfct we'd end up trying to confirm the same tuples multiple times, which results in NF_DROP for the cloned skbs. Also, we only need to do this if the conntrack is unconfirmed. Signed-off-by: Florian Westphal --- net/bridge/br_netfilter.c | 34 ++++++++++++++++++++++++++++++++++ 1 files changed, 34 insertions(+), 0 deletions(-) I have one alternate patch that changes nf_nat_setup_info to detect conflicts by forcing serialization via ct->lock spinlock. But it is silly to do this for the sake of bridge netfilter only... Any other ideas? diff --git a/net/bridge/br_netfilter.c b/net/bridge/br_netfilter.c index 3fa1231..7d47f34 100644 --- a/net/bridge/br_netfilter.c +++ b/net/bridge/br_netfilter.c @@ -42,6 +42,10 @@ #include #endif +#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE) +#include +#endif + #define skb_origaddr(skb) (((struct bridge_skb_cb *) \ (skb->nf_bridge->data))->daddr.ipv4) #define store_orig_dstaddr(skb) (skb_origaddr(skb) = ip_hdr(skb)->daddr) @@ -158,10 +162,40 @@ static inline struct nf_bridge_info *nf_bridge_alloc(struct sk_buff *skb) return skb->nf_bridge; } + +/* conntrack assumes exclusive ownership of skb->nfct + * if conntrack has not yet been confirmed. + * + * Without this, we may BUG because we might try to set up + * NAT bindings for the same conntrack struct simultaneously. + * + * Work around this by forcing untracked state. + */ +static inline void nf_bridge_unshare_nfct(struct sk_buff *skb) +{ +#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE) + struct nf_conn *ct, *ct_orig = (void *) skb->nfct; + + if (!ct_orig || nf_ct_is_untracked(ct_orig)) + return; + + if (likely(nf_ct_is_confirmed(ct_orig)) || + atomic_read(&ct_orig->ct_general.use) == 1) + return; + + ct = nf_ct_untracked_get(); + atomic_inc(&ct->ct_general.use); + nf_conntrack_put(skb->nfct); + skb->nfct = &ct->ct_general; +#endif +} + static inline struct nf_bridge_info *nf_bridge_unshare(struct sk_buff *skb) { struct nf_bridge_info *nf_bridge = skb->nf_bridge; + nf_bridge_unshare_nfct(skb); + if (atomic_read(&nf_bridge->use) > 1) { struct nf_bridge_info *tmp = nf_bridge_alloc(skb); -- 1.7.3.4