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 /net/xfrm/xfrm_state.c | |
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>
Diffstat (limited to 'net/xfrm/xfrm_state.c')
-rw-r--r-- | net/xfrm/xfrm_state.c | 39 |
1 files changed, 30 insertions, 9 deletions
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 | ||