summaryrefslogtreecommitdiff
path: root/net
diff options
context:
space:
mode:
authorYOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>2008-01-29 02:46:02 +0300
committerDavid S. Miller <davem@davemloft.net>2008-01-29 02:46:02 +0300
commit85040bcb4643cba578839e953f25e2d1965d83d0 (patch)
treed43d6a0d92e2957be260312214084131cf659833 /net
parent3c582b30bc2592081e9b23e253ca098fa7d57dc2 (diff)
downloadlinux-85040bcb4643cba578839e953f25e2d1965d83d0.tar.xz
[IPV6] ADDRLABEL: Fix double free on label deletion.
If an entry is being deleted because it has only one reference, we immediately delete it and blindly register the rcu handler for it, This results in oops by double freeing that object. This patch fixes it by consolidating the code paths for the deletion; let its rcu handler delete the object if it has no more reference. Bug was found by Mitsuru Chinen <mitch@linux.vnet.ibm.com> Signed-off-by: YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net')
-rw-r--r--net/ipv6/addrlabel.c14
1 files changed, 6 insertions, 8 deletions
diff --git a/net/ipv6/addrlabel.c b/net/ipv6/addrlabel.c
index 38674121ae5f..a3c5a72218fd 100644
--- a/net/ipv6/addrlabel.c
+++ b/net/ipv6/addrlabel.c
@@ -106,6 +106,11 @@ static inline void ip6addrlbl_free(struct ip6addrlbl_entry *p)
kfree(p);
}
+static void ip6addrlbl_free_rcu(struct rcu_head *h)
+{
+ ip6addrlbl_free(container_of(h, struct ip6addrlbl_entry, rcu));
+}
+
static inline int ip6addrlbl_hold(struct ip6addrlbl_entry *p)
{
return atomic_inc_not_zero(&p->refcnt);
@@ -114,12 +119,7 @@ static inline int ip6addrlbl_hold(struct ip6addrlbl_entry *p)
static inline void ip6addrlbl_put(struct ip6addrlbl_entry *p)
{
if (atomic_dec_and_test(&p->refcnt))
- ip6addrlbl_free(p);
-}
-
-static void ip6addrlbl_free_rcu(struct rcu_head *h)
-{
- ip6addrlbl_free(container_of(h, struct ip6addrlbl_entry, rcu));
+ call_rcu(&p->rcu, ip6addrlbl_free_rcu);
}
/* Find label */
@@ -240,7 +240,6 @@ static int __ip6addrlbl_add(struct ip6addrlbl_entry *newp, int replace)
}
hlist_replace_rcu(&p->list, &newp->list);
ip6addrlbl_put(p);
- call_rcu(&p->rcu, ip6addrlbl_free_rcu);
goto out;
} else if ((p->prefixlen == newp->prefixlen && !p->ifindex) ||
(p->prefixlen < newp->prefixlen)) {
@@ -300,7 +299,6 @@ static int __ip6addrlbl_del(const struct in6_addr *prefix, int prefixlen,
ipv6_addr_equal(&p->prefix, prefix)) {
hlist_del_rcu(&p->list);
ip6addrlbl_put(p);
- call_rcu(&p->rcu, ip6addrlbl_free_rcu);
ret = 0;
break;
}