aboutsummaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorYOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>2008-01-28 18:46:02 -0500
committerDavid S. Miller <davem@davemloft.net>2008-01-28 18:46:02 -0500
commit85040bcb4643cba578839e953f25e2d1965d83d0 (patch)
treed43d6a0d92e2957be260312214084131cf659833 /net
parent3c582b30bc2592081e9b23e253ca098fa7d57dc2 (diff)
[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)
106 kfree(p); 106 kfree(p);
107} 107}
108 108
109static void ip6addrlbl_free_rcu(struct rcu_head *h)
110{
111 ip6addrlbl_free(container_of(h, struct ip6addrlbl_entry, rcu));
112}
113
109static inline int ip6addrlbl_hold(struct ip6addrlbl_entry *p) 114static inline int ip6addrlbl_hold(struct ip6addrlbl_entry *p)
110{ 115{
111 return atomic_inc_not_zero(&p->refcnt); 116 return atomic_inc_not_zero(&p->refcnt);
@@ -114,12 +119,7 @@ static inline int ip6addrlbl_hold(struct ip6addrlbl_entry *p)
114static inline void ip6addrlbl_put(struct ip6addrlbl_entry *p) 119static inline void ip6addrlbl_put(struct ip6addrlbl_entry *p)
115{ 120{
116 if (atomic_dec_and_test(&p->refcnt)) 121 if (atomic_dec_and_test(&p->refcnt))
117 ip6addrlbl_free(p); 122 call_rcu(&p->rcu, ip6addrlbl_free_rcu);
118}
119
120static void ip6addrlbl_free_rcu(struct rcu_head *h)
121{
122 ip6addrlbl_free(container_of(h, struct ip6addrlbl_entry, rcu));
123} 123}
124 124
125/* Find label */ 125/* Find label */
@@ -240,7 +240,6 @@ static int __ip6addrlbl_add(struct ip6addrlbl_entry *newp, int replace)
240 } 240 }
241 hlist_replace_rcu(&p->list, &newp->list); 241 hlist_replace_rcu(&p->list, &newp->list);
242 ip6addrlbl_put(p); 242 ip6addrlbl_put(p);
243 call_rcu(&p->rcu, ip6addrlbl_free_rcu);
244 goto out; 243 goto out;
245 } else if ((p->prefixlen == newp->prefixlen && !p->ifindex) || 244 } else if ((p->prefixlen == newp->prefixlen && !p->ifindex) ||
246 (p->prefixlen < newp->prefixlen)) { 245 (p->prefixlen < newp->prefixlen)) {
@@ -300,7 +299,6 @@ static int __ip6addrlbl_del(const struct in6_addr *prefix, int prefixlen,
300 ipv6_addr_equal(&p->prefix, prefix)) { 299 ipv6_addr_equal(&p->prefix, prefix)) {
301 hlist_del_rcu(&p->list); 300 hlist_del_rcu(&p->list);
302 ip6addrlbl_put(p); 301 ip6addrlbl_put(p);
303 call_rcu(&p->rcu, ip6addrlbl_free_rcu);
304 ret = 0; 302 ret = 0;
305 break; 303 break;
306 } 304 }