diff options
| author | Herbert Xu <herbert@gondor.apana.org.au> | 2008-09-22 19:48:19 -0700 | 
|---|---|---|
| committer | David S. Miller <davem@davemloft.net> | 2008-09-22 19:48:19 -0700 | 
| 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;  /* Counter indicating walk completion, protected by xfrm_cfg_mutex. */  static unsigned long xfrm_state_walk_completed; +/* List of outstanding state walks used to set the completed counter.  */ +static LIST_HEAD(xfrm_state_walks); +  static struct xfrm_state_afinfo *xfrm_state_get_afinfo(unsigned int family);  static void xfrm_state_put_afinfo(struct xfrm_state_afinfo *afinfo); @@ -1584,7 +1587,6 @@ int xfrm_state_walk(struct xfrm_state_walk *walk,  			if (err) {  				xfrm_state_hold(last);  				walk->state = last; -				xfrm_state_walk_ongoing++;  				goto out;  			}  		} @@ -1599,25 +1601,44 @@ int xfrm_state_walk(struct xfrm_state_walk *walk,  		err = func(last, 0, data);  out:  	spin_unlock_bh(&xfrm_state_lock); -	if (old != NULL) { +	if (old != NULL)  		xfrm_state_put(old); -		xfrm_state_walk_completed++; -		if (!list_empty(&xfrm_state_gc_leftovers)) -			schedule_work(&xfrm_state_gc_work); -	}  	return err;  }  EXPORT_SYMBOL(xfrm_state_walk); +void xfrm_state_walk_init(struct xfrm_state_walk *walk, u8 proto) +{ +	walk->proto = proto; +	walk->state = NULL; +	walk->count = 0; +	list_add_tail(&walk->list, &xfrm_state_walks); +	walk->genid = ++xfrm_state_walk_ongoing; +} +EXPORT_SYMBOL(xfrm_state_walk_init); +  void xfrm_state_walk_done(struct xfrm_state_walk *walk)  { +	struct list_head *prev; +  	if (walk->state != NULL) {  		xfrm_state_put(walk->state);  		walk->state = NULL; -		xfrm_state_walk_completed++; -		if (!list_empty(&xfrm_state_gc_leftovers)) -			schedule_work(&xfrm_state_gc_work);  	} + +	prev = walk->list.prev; +	list_del(&walk->list); + +	if (prev != &xfrm_state_walks) { +		list_entry(prev, struct xfrm_state_walk, list)->genid = +			walk->genid; +		return; +	} + +	xfrm_state_walk_completed = walk->genid; + +	if (!list_empty(&xfrm_state_gc_leftovers)) +		schedule_work(&xfrm_state_gc_work);  }  EXPORT_SYMBOL(xfrm_state_walk_done); | 
