diff options
author | Herbert Xu <herbert@gondor.apana.org.au> | 2008-09-22 22:48:19 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2008-09-22 22:48:19 -0400 |
commit | 5c1824587f0797373c95719a196f6098f7c6d20c (patch) | |
tree | c3a5af01afc01d88e111c7e1821b03bf404566f6 | |
parent | fcaa40669cd798ca2ac0d15441e8a1d1145f2b16 (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.h | 2 | ||||
-rw-r--r-- | include/net/xfrm.h | 10 | ||||
-rw-r--r-- | net/xfrm/xfrm_state.c | 39 |
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 | ||
226 | struct netlink_notify | 226 | struct 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 | ||
1248 | struct xfrm_state_walk { | 1248 | struct 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) | |||
1281 | extern int xfrm_proc_init(void); | 1283 | extern int xfrm_proc_init(void); |
1282 | #endif | 1284 | #endif |
1283 | 1285 | ||
1284 | static inline void xfrm_state_walk_init(struct xfrm_state_walk *walk, u8 proto) | 1286 | extern 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 | |||
1291 | extern int xfrm_state_walk(struct xfrm_state_walk *walk, | 1287 | extern 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 *); |
1293 | extern void xfrm_state_walk_done(struct xfrm_state_walk *walk); | 1289 | extern 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. */ |
65 | static unsigned long xfrm_state_walk_completed; | 65 | static unsigned long xfrm_state_walk_completed; |
66 | 66 | ||
67 | /* List of outstanding state walks used to set the completed counter. */ | ||
68 | static LIST_HEAD(xfrm_state_walks); | ||
69 | |||
67 | static struct xfrm_state_afinfo *xfrm_state_get_afinfo(unsigned int family); | 70 | static struct xfrm_state_afinfo *xfrm_state_get_afinfo(unsigned int family); |
68 | static void xfrm_state_put_afinfo(struct xfrm_state_afinfo *afinfo); | 71 | static 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); |
1600 | out: | 1602 | out: |
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 | } |
1610 | EXPORT_SYMBOL(xfrm_state_walk); | 1608 | EXPORT_SYMBOL(xfrm_state_walk); |
1611 | 1609 | ||
1610 | void 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 | } | ||
1618 | EXPORT_SYMBOL(xfrm_state_walk_init); | ||
1619 | |||
1612 | void xfrm_state_walk_done(struct xfrm_state_walk *walk) | 1620 | void 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 | } |
1622 | EXPORT_SYMBOL(xfrm_state_walk_done); | 1643 | EXPORT_SYMBOL(xfrm_state_walk_done); |
1623 | 1644 | ||