New: net_device refcnt bug when NFQUEUEing bridged packets
Subsystems (4)
A message that is not in the archive (sent off-list or not yet received): [email protected]/
Quoted 40 lines · On Tue, 15 Jan 2008 15:28:31 -0800 (PST) [email protected] wrote:
http://bugzilla.kernel.org/show_bug.cgi?id=9758
Summary: net_device refcnt bug when NFQUEUEing bridged packets Product: Networking Version: 2.5 KernelVersion: 2.6.24-rc7 Platform: All OS/Version: Linux Tree: Mainline Status: NEW Severity: normal Priority: P1 Component: Netfilter/Iptables AssignedTo: [email protected] ReportedBy: [email protected]The bug is probably around since the combination bridge+NFQUEUE is possible, and does not depend on distro or environment:
Packets that are to be sent out over a bridge device are skb_clone()d in
br_loop()before traversing the appropriate (FORWARD/OUTPUT) NF chain. The copies made byskb_clone()share theirnf_bridgemetadata with the original, which is no problem usually.
If however one or more packets of abr_loop()run end up in a NFQUEUE, their sharednf_bridgemetadata causes trouble when they are about to be
reinjected:nf_reinject()decrements thenet_devicerefcounts that were previously upped when queueing the packet in__nf_queue(), but asskb->nf_bridge->physoutdevpoints to the same device for all these packets, most (if not all) of them will affect the wrong refcnt.(I originally encountered the bug on a Xen host because the hypervisor refused to shutdown a virtual device with non-zero refcount... but it is perfectly reproducible with a standard kernel, too, although it was a bit more tedious to create a test scenario, involving a couple of UMLs.)
I'd suggest to make a real copy of the
nf_bridgemember inbr_loop()ifCONFIG_BRIDGE_NETFILTERis defined, remedying the entanglement. I'd go ahead and create a patch, but I'm unsure as to where that logic should be implemented.
Quoted 24 lines · Andrew Morton
Quoted 21 lines · On Tue, 15 Jan 2008 15:28:31 -0800 (PST) [email protected] wrote:
http://bugzilla.kernel.org/show_bug.cgi?id=9758
The bug is probably around since the combination bridge+NFQUEUE is possible, and does not depend on distro or environment:
Packets that are to be sent out over a bridge device are skb_clone()d in
br_loop()before traversing the appropriate (FORWARD/OUTPUT) NF chain. The copies made byskb_clone()share theirnf_bridgemetadata with the original, which is no problem usually.
If however one or more packets of abr_loop()run end up in a NFQUEUE, their sharednf_bridgemetadata causes trouble when they are about to be
reinjected:nf_reinject()decrements thenet_devicerefcounts that were previously upped when queueing the packet in__nf_queue(), but asskb->nf_bridge->physoutdevpoints to the same device for all these packets, most (if not all) of them will affect the wrong refcnt.(I originally encountered the bug on a Xen host because the hypervisor refused to shutdown a virtual device with non-zero refcount... but it is perfectly reproducible with a standard kernel, too, although it was a bit more tedious to create a test scenario, involving a couple of UMLs.)
Quoted 4 lines
Quoted 4 lines
I'd suggest to make a real copy of the
nf_bridgemember inbr_loop()ifCONFIG_BRIDGE_NETFILTERis defined, remedying the entanglement. I'd go ahead and create a patch, but I'm unsure as to where that logic should be implemented.
Very nice catch, that explains quite a few bug reports about refcnt leaks. Your patch looks correct and performs the copying in the logically correct place, it would be nicer to keep this crap limited to bridge netfilter however.
What should work is to perform the copying in br_netfilter.c at the spots where phsyoutdev is assigned. As an optimization we should be able to avoid the copying in most cases by checking that the bridge info has a refcount above 1.
Could you test whether this patch also fixes the problem?
net/bridge/br_netfilter.c +26 -0
@@ -142,6 +142,22 @@ static inline struct nf_bridge_info *nf_bridge_alloc(struct sk_buff *skb)return skb->nf_bridge; } +static inline struct nf_bridge_info *nf_bridge_unshare (struct sk_buff *skb) +{ +struct nf_bridge_info *nf_bridge = skb->nf_bridge; + +if (atomic_read (&nf_bridge->use) >1 ) { +struct nf_bridge_info *tmp =nf_bridge_alloc (skb); + +if (tmp) { +memcpy (tmp, nf_bridge,sizeof (struct nf_bridge_info )); +nf_bridge_put (nf_bridge); + } + nf_bridge = tmp; + } +return nf_bridge; +} +static inline void nf_bridge_push_encap_header (struct sk_buff *skb) {unsigned int len =nf_bridge_encap_header_len (skb);
@@ -637,6 +653,11 @@ static unsigned int br_nf_forward_ip(unsigned int hook, struct sk_buff *skb,if (!skb->nf_bridge)return NF_ACCEPT ; +/* Need exclusive nf_bridge_info since we might have multiple +* different physoutdevs. */ +if (!nf_bridge_unshare (skb)) +return NF_DROP ; + parent =bridge_parent (out);if (!parent)return NF_DROP ;
@@ -718,6 +739,11 @@ static unsigned int br_nf_local_out(unsigned int hook, struct sk_buff *skb,if (!skb->nf_bridge)return NF_ACCEPT ; +/* Need exclusive nf_bridge_info since we might have multiple +* different physoutdevs. */ +if (!nf_bridge_unshare (skb)) +return NF_DROP ; + nf_bridge = skb->nf_bridge;if (!(nf_bridge->mask &BRNF_BRIDGED_DNAT ))return NF_ACCEPT ;
Quoted 11 lines · Patrick McHardy
Very nice catch, that explains quite a few bug reports about refcnt leaks. Your patch looks correct and performs the copying in the logically correct place, it would be nicer to keep this crap limited to bridge netfilter however.
What should work is to perform the copying in
br_netfilter.cat the spots where phsyoutdev is assigned. As an optimization we should be able to avoid the copying in most cases by checking that the bridge info has a refcount above 1.Could you test whether this patch also fixes the problem?
That patch had a bug, we need to set the refcount of the new bridge info to 1 after performing the copy.
net/bridge/br_netfilter.c +27 -0
@@ -142,6 +142,23 @@ static inline struct nf_bridge_info *nf_bridge_alloc(struct sk_buff *skb)return skb->nf_bridge; } +static inline struct nf_bridge_info *nf_bridge_unshare (struct sk_buff *skb) +{ +struct nf_bridge_info *nf_bridge = skb->nf_bridge; + +if (atomic_read (&nf_bridge->use) >1 ) { +struct nf_bridge_info *tmp =nf_bridge_alloc (skb); + +if (tmp) { +memcpy (tmp, nf_bridge,sizeof (struct nf_bridge_info )); +atomic_set (&tmp->use,1 ); +nf_bridge_put (nf_bridge); + } + nf_bridge = tmp; + } +return nf_bridge; +} +static inline void nf_bridge_push_encap_header (struct sk_buff *skb) {unsigned int len =nf_bridge_encap_header_len (skb);
@@ -637,6 +654,11 @@ static unsigned int br_nf_forward_ip(unsigned int hook, struct sk_buff *skb,if (!skb->nf_bridge)return NF_ACCEPT ; +/* Need exclusive nf_bridge_info since we might have multiple +* different physoutdevs. */ +if (!nf_bridge_unshare (skb)) +return NF_DROP ; + parent =bridge_parent (out);if (!parent)return NF_DROP ;
@@ -718,6 +740,11 @@ static unsigned int br_nf_local_out(unsigned int hook, struct sk_buff *skb,if (!skb->nf_bridge)return NF_ACCEPT ; +/* Need exclusive nf_bridge_info since we might have multiple +* different physoutdevs. */ +if (!nf_bridge_unshare (skb)) +return NF_DROP ; + nf_bridge = skb->nf_bridge;if (!(nf_bridge->mask &BRNF_BRIDGED_DNAT ))return NF_ACCEPT ;
Quoted 11 lines · On Wed, 16 Jan 2008 05:59:21 +0100 Patrick McHardy <[email protected]> wrote:
Quoted 10 lines · Patrick McHardy
Very nice catch, that explains quite a few bug reports about refcnt leaks. Your patch looks correct and performs the copying in the logically correct place, it would be nicer to keep this crap limited to bridge netfilter however.
What should work is to perform the copying in
br_netfilter.cat the spots where phsyoutdev is assigned. As an optimization we should be able to avoid the copying in most cases by checking that the bridge info has a refcount above 1.
Quoted 6 lines
Quoted 1 line
Could you test whether this patch also fixes the problem?
That patch had a bug, we need to set the refcount of the new bridge info to 1 after performing the copy.
This looks good, but you could use a structure assignment rather memcpy (just a personal style preference because assignment is typed and memcpy is not).
Quoted 3 lines · Stephen Hemminger
This looks good, but you could use a structure assignment rather memcpy (just a personal style preference because assignment is typed and memcpy is not).
That doesn't work in this case since nf_bridge_info contains
unsigned long data[32 / sizeof(unsigned long)];
and I don't want to assign each array element seperately.