aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHerbert Xu <herbert@gondor.apana.org.au>2008-09-22 22:48:19 -0400
committerDavid S. Miller <davem@davemloft.net>2008-09-22 22:48:19 -0400
commit5c1824587f0797373c95719a196f6098f7c6d20c (patch)
treec3a5af01afc01d88e111c7e1821b03bf404566f6
parentfcaa40669cd798ca2ac0d15441e8a1d1145f2b16 (diff)
ipsec: Fix xfrm_state_walk race
As discovered by Timo Teräs, the currently xfrm_state_walk scheme is racy because if a second dump finishes before the first, we may free xfrm states that the first dump would walk over later. This patch fixes this by storing the dumps in a list in order to calculate the correct completion counter which cures this problem. I've expanded netlink_cb in order to accomodate the extra state related to this. It shouldn't be a big deal since netlink_cb is kmalloced for each dump and we're just increasing it by 4 or 8 bytes. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r--include/linux/netlink.h2
-rw-r--r--include/net/xfrm.h10
-rw-r--r--net/xfrm/xfrm_state.c39
3 files changed, 34 insertions, 17 deletions
diff --git a/include/linux/netlink.h b/include/linux/netlink.h
index 9ff1b54908f3..cbba7760545b 100644
--- a/include/linux/netlink.h
+++ b/include/linux/netlink.h
@@ -220,7 +220,7 @@ struct netlink_callback
220 int (*dump)(struct sk_buff * skb, struct netlink_callback *cb); 220 int (*dump)(struct sk_buff * skb, struct netlink_callback *cb);
221 int (*done)(struct netlink_callback *cb); 221 int (*done)(struct netlink_callback *cb);
222 int family; 222 int family;
223 long args[6]; 223 long args[7];
224}; 224};
225 225
226struct netlink_notify 226struct netlink_notify
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 4bb94992b5fa..48630b266593 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -1246,6 +1246,8 @@ struct xfrm6_tunnel {
1246}; 1246};
1247 1247
1248struct xfrm_state_walk { 1248struct xfrm_state_walk {
1249 struct list_head list;
1250 unsigned long genid;
1249 struct xfrm_state *state; 1251 struct xfrm_state *state;
1250 int count; 1252 int count;
1251 u8 proto; 1253 u8 proto;
@@ -1281,13 +1283,7 @@ static inline void xfrm6_fini(void)
1281extern int xfrm_proc_init(void); 1283extern int xfrm_proc_init(void);
1282#endif 1284#endif
1283 1285
1284static inline void xfrm_state_walk_init(struct xfrm_state_walk *walk, u8 proto) 1286extern void xfrm_state_walk_init(struct xfrm_state_walk *walk, u8 proto);
1285{
1286 walk->proto = proto;
1287 walk->state = NULL;
1288 walk->count = 0;
1289}
1290
1291extern int xfrm_state_walk(struct xfrm_state_walk *walk, 1287extern int xfrm_state_walk(struct xfrm_state_walk *walk,
1292 int (*func)(struct xfrm_state *, int, void*), void *); 1288 int (*func)(struct xfrm_state *, int, void*), void *);
1293extern void xfrm_state_walk_done(struct xfrm_state_walk *walk); 1289extern void xfrm_state_walk_done(struct xfrm_state_walk *walk);
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index abbe2702c400..053970e8765d 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -64,6 +64,9 @@ static unsigned long xfrm_state_walk_ongoing;
64/* Counter indicating walk completion, protected by xfrm_cfg_mutex. */ 64/* Counter indicating walk completion, protected by xfrm_cfg_mutex. */
65static unsigned long xfrm_state_walk_completed; 65static unsigned long xfrm_state_walk_completed;
66 66
67/* List of outstanding state walks used to set the completed counter. */
68static LIST_HEAD(xfrm_state_walks);
69
67static struct xfrm_state_afinfo *xfrm_state_get_afinfo(unsigned int family); 70static struct xfrm_state_afinfo *xfrm_state_get_afinfo(unsigned int family);
68static void xfrm_state_put_afinfo(struct xfrm_state_afinfo *afinfo); 71static void xfrm_state_put_afinfo(struct xfrm_state_afinfo *afinfo);
69 72
@@ -1584,7 +1587,6 @@ int xfrm_state_walk(struct xfrm_state_walk *walk,
1584 if (err) { 1587 if (err) {
1585 xfrm_state_hold(last); 1588 xfrm_state_hold(last);
1586 walk->state = last; 1589 walk->state = last;
1587 xfrm_state_walk_ongoing++;
1588 goto out; 1590 goto out;
1589 } 1591 }
1590 } 1592 }
@@ -1599,25 +1601,44 @@ int xfrm_state_walk(struct xfrm_state_walk *walk,
1599 err = func(last, 0, data); 1601 err = func(last, 0, data);
1600out: 1602out:
1601 spin_unlock_bh(&xfrm_state_lock); 1603 spin_unlock_bh(&xfrm_state_lock);
1602 if (old != NULL) { 1604 if (old != NULL)
1603 xfrm_state_put(old); 1605 xfrm_state_put(old);
1604 xfrm_state_walk_completed++;
1605 if (!list_empty(&xfrm_state_gc_leftovers))
1606 schedule_work(&xfrm_state_gc_work);
1607 }
1608 return err; 1606 return err;
1609} 1607}
1610EXPORT_SYMBOL(xfrm_state_walk); 1608EXPORT_SYMBOL(xfrm_state_walk);
1611 1609
1610void xfrm_state_walk_init(struct xfrm_state_walk *walk, u8 proto)
1611{
1612 walk->proto = proto;
1613 walk->state = NULL;
1614 walk->count = 0;
1615 list_add_tail(&walk->list, &xfrm_state_walks);
1616 walk->genid = ++xfrm_state_walk_ongoing;
1617}
1618EXPORT_SYMBOL(xfrm_state_walk_init);
1619
1612void xfrm_state_walk_done(struct xfrm_state_walk *walk) 1620void xfrm_state_walk_done(struct xfrm_state_walk *walk)
1613{ 1621{
1622 struct list_head *prev;
1623
1614 if (walk->state != NULL) { 1624 if (walk->state != NULL) {
1615 xfrm_state_put(walk->state); 1625 xfrm_state_put(walk->state);
1616 walk->state = NULL; 1626 walk->state = NULL;
1617 xfrm_state_walk_completed++;
1618 if (!list_empty(&xfrm_state_gc_leftovers))
1619 schedule_work(&xfrm_state_gc_work);
1620 } 1627 }
1628
1629 prev = walk->list.prev;
1630 list_del(&walk->list);
1631
1632 if (prev != &xfrm_state_walks) {
1633 list_entry(prev, struct xfrm_state_walk, list)->genid =
1634 walk->genid;
1635 return;
1636 }
1637
1638 xfrm_state_walk_completed = walk->genid;
1639
1640 if (!list_empty(&xfrm_state_gc_leftovers))
1641 schedule_work(&xfrm_state_gc_work);
1621} 1642}
1622EXPORT_SYMBOL(xfrm_state_walk_done); 1643EXPORT_SYMBOL(xfrm_state_walk_done);
1623 1644