Commit 2dc2f207 authored by Patrick McHardy's avatar Patrick McHardy Committed by David S. Miller

[NETFILTER]: bridge-netfilter: fix net_device refcnt leaks

When packets are flood-forwarded to multiple output devices, the
bridge-netfilter code reuses skb->nf_bridge for each clone to store
the bridge port. When queueing packets using NFQUEUE netfilter takes
a reference to skb->nf_bridge->physoutdev, which is overwritten
when the packet is forwarded to the second port. This causes
refcount unterflows for the first device and refcount leaks for all
others. Additionally this provides incorrect data to the iptables
physdev match.

Unshare skb->nf_bridge by copying it if it is shared before assigning
the physoutdev device.

Reported, tested and based on initial patch by
Jan Christoph Nordholz <hesso@pool.math.tu-berlin.de>.
Signed-off-by: default avatarPatrick McHardy <kaber@trash.net>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 398bcbeb
...@@ -142,6 +142,23 @@ static inline struct nf_bridge_info *nf_bridge_alloc(struct sk_buff *skb) ...@@ -142,6 +142,23 @@ static inline struct nf_bridge_info *nf_bridge_alloc(struct sk_buff *skb)
return skb->nf_bridge; 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) static inline void nf_bridge_push_encap_header(struct sk_buff *skb)
{ {
unsigned int len = nf_bridge_encap_header_len(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, ...@@ -637,6 +654,11 @@ static unsigned int br_nf_forward_ip(unsigned int hook, struct sk_buff *skb,
if (!skb->nf_bridge) if (!skb->nf_bridge)
return NF_ACCEPT; 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); parent = bridge_parent(out);
if (!parent) if (!parent)
return NF_DROP; return NF_DROP;
...@@ -718,6 +740,11 @@ static unsigned int br_nf_local_out(unsigned int hook, struct sk_buff *skb, ...@@ -718,6 +740,11 @@ static unsigned int br_nf_local_out(unsigned int hook, struct sk_buff *skb,
if (!skb->nf_bridge) if (!skb->nf_bridge)
return NF_ACCEPT; 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; nf_bridge = skb->nf_bridge;
if (!(nf_bridge->mask & BRNF_BRIDGED_DNAT)) if (!(nf_bridge->mask & BRNF_BRIDGED_DNAT))
return NF_ACCEPT; return NF_ACCEPT;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment