Skip to content

Commit 5c18245

Browse files
herbertxdavem330
authored andcommitted
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 <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent fcaa406 commit 5c18245

File tree

3 files changed

+34
-17
lines changed

3 files changed

+34
-17
lines changed

include/linux/netlink.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ struct netlink_callback
220220
int (*dump)(struct sk_buff * skb, struct netlink_callback *cb);
221221
int (*done)(struct netlink_callback *cb);
222222
int family;
223-
long args[6];
223+
long args[7];
224224
};
225225

226226
struct netlink_notify

include/net/xfrm.h

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,6 +1246,8 @@ struct xfrm6_tunnel {
12461246
};
12471247

12481248
struct xfrm_state_walk {
1249+
struct list_head list;
1250+
unsigned long genid;
12491251
struct xfrm_state *state;
12501252
int count;
12511253
u8 proto;
@@ -1281,13 +1283,7 @@ static inline void xfrm6_fini(void)
12811283
extern int xfrm_proc_init(void);
12821284
#endif
12831285

1284-
static inline 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-
1286+
extern void xfrm_state_walk_init(struct xfrm_state_walk *walk, u8 proto);
12911287
extern int xfrm_state_walk(struct xfrm_state_walk *walk,
12921288
int (*func)(struct xfrm_state *, int, void*), void *);
12931289
extern void xfrm_state_walk_done(struct xfrm_state_walk *walk);

net/xfrm/xfrm_state.c

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ static unsigned long xfrm_state_walk_ongoing;
6464
/* Counter indicating walk completion, protected by xfrm_cfg_mutex. */
6565
static unsigned long xfrm_state_walk_completed;
6666

67+
/* List of outstanding state walks used to set the completed counter. */
68+
static LIST_HEAD(xfrm_state_walks);
69+
6770
static struct xfrm_state_afinfo *xfrm_state_get_afinfo(unsigned int family);
6871
static void xfrm_state_put_afinfo(struct xfrm_state_afinfo *afinfo);
6972

@@ -1584,7 +1587,6 @@ int xfrm_state_walk(struct xfrm_state_walk *walk,
15841587
if (err) {
15851588
xfrm_state_hold(last);
15861589
walk->state = last;
1587-
xfrm_state_walk_ongoing++;
15881590
goto out;
15891591
}
15901592
}
@@ -1599,25 +1601,44 @@ int xfrm_state_walk(struct xfrm_state_walk *walk,
15991601
err = func(last, 0, data);
16001602
out:
16011603
spin_unlock_bh(&xfrm_state_lock);
1602-
if (old != NULL) {
1604+
if (old != NULL)
16031605
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-
}
16081606
return err;
16091607
}
16101608
EXPORT_SYMBOL(xfrm_state_walk);
16111609

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+
16121620
void xfrm_state_walk_done(struct xfrm_state_walk *walk)
16131621
{
1622+
struct list_head *prev;
1623+
16141624
if (walk->state != NULL) {
16151625
xfrm_state_put(walk->state);
16161626
walk->state = NULL;
1617-
xfrm_state_walk_completed++;
1618-
if (!list_empty(&xfrm_state_gc_leftovers))
1619-
schedule_work(&xfrm_state_gc_work);
16201627
}
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);
16211642
}
16221643
EXPORT_SYMBOL(xfrm_state_walk_done);
16231644

0 commit comments

Comments
 (0)