Commit 85040bcb4643cba578839e953f25e2d1965d83d0

Authored by YOSHIFUJI Hideaki
Committed by David S. Miller
1 parent 3c582b30bc

[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>

Showing 1 changed file with 6 additions and 8 deletions Side-by-side Diff

net/ipv6/addrlabel.c
... ... @@ -106,6 +106,11 @@
106 106 kfree(p);
107 107 }
108 108  
  109 +static void ip6addrlbl_free_rcu(struct rcu_head *h)
  110 +{
  111 + ip6addrlbl_free(container_of(h, struct ip6addrlbl_entry, rcu));
  112 +}
  113 +
109 114 static inline int ip6addrlbl_hold(struct ip6addrlbl_entry *p)
110 115 {
111 116 return atomic_inc_not_zero(&p->refcnt);
112 117  
... ... @@ -114,14 +119,9 @@
114 119 static inline void ip6addrlbl_put(struct ip6addrlbl_entry *p)
115 120 {
116 121 if (atomic_dec_and_test(&p->refcnt))
117   - ip6addrlbl_free(p);
  122 + call_rcu(&p->rcu, ip6addrlbl_free_rcu);
118 123 }
119 124  
120   -static void ip6addrlbl_free_rcu(struct rcu_head *h)
121   -{
122   - ip6addrlbl_free(container_of(h, struct ip6addrlbl_entry, rcu));
123   -}
124   -
125 125 /* Find label */
126 126 static int __ip6addrlbl_match(struct ip6addrlbl_entry *p,
127 127 const struct in6_addr *addr,
... ... @@ -240,7 +240,6 @@
240 240 }
241 241 hlist_replace_rcu(&p->list, &newp->list);
242 242 ip6addrlbl_put(p);
243   - call_rcu(&p->rcu, ip6addrlbl_free_rcu);
244 243 goto out;
245 244 } else if ((p->prefixlen == newp->prefixlen && !p->ifindex) ||
246 245 (p->prefixlen < newp->prefixlen)) {
... ... @@ -300,7 +299,6 @@
300 299 ipv6_addr_equal(&p->prefix, prefix)) {
301 300 hlist_del_rcu(&p->list);
302 301 ip6addrlbl_put(p);
303   - call_rcu(&p->rcu, ip6addrlbl_free_rcu);
304 302 ret = 0;
305 303 break;
306 304 }