Skip to content

Commit 9a07efa

Browse files
congwangdavem330
authored andcommitted
tipc: switch to rhashtable iterator
syzbot reported a use-after-free in tipc_group_fill_sock_diag(), where tipc_group_fill_sock_diag() still reads tsk->group meanwhile tipc_group_delete() just deletes it in tipc_release(). tipc_nl_sk_walk() aims to lock this sock when walking each sock in the hash table to close race conditions with sock changes like this one, by acquiring tsk->sk.sk_lock.slock spinlock, unfortunately this doesn't work at all. All non-BH call path should take lock_sock() instead to make it work. tipc_nl_sk_walk() brutally iterates with raw rht_for_each_entry_rcu() where RCU read lock is required, this is the reason why lock_sock() can't be taken on this path. This could be resolved by switching to rhashtable iterator API's, where taking a sleepable lock is possible. Also, the iterator API's are friendly for restartable calls like diag dump, the last position is remembered behind the scence, all we need to do here is saving the iterator into cb->args[]. I tested this with parallel tipc diag dump and thousands of tipc socket creation and release, no crash or memory leak. Reported-by: [email protected] Cc: Jon Maloy <[email protected]> Cc: Ying Xue <[email protected]> Signed-off-by: Cong Wang <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent e5133f2 commit 9a07efa

File tree

4 files changed

+56
-26
lines changed

4 files changed

+56
-26
lines changed

net/tipc/diag.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ static int tipc_sock_diag_handler_dump(struct sk_buff *skb,
8484

8585
if (h->nlmsg_flags & NLM_F_DUMP) {
8686
struct netlink_dump_control c = {
87+
.start = tipc_dump_start,
8788
.dump = tipc_diag_dump,
89+
.done = tipc_dump_done,
8890
};
8991
netlink_dump_start(net->diag_nlsk, skb, h, &c);
9092
return 0;

net/tipc/netlink.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,9 @@ static const struct genl_ops tipc_genl_v2_ops[] = {
167167
},
168168
{
169169
.cmd = TIPC_NL_SOCK_GET,
170+
.start = tipc_dump_start,
170171
.dumpit = tipc_nl_sk_dump,
172+
.done = tipc_dump_done,
171173
.policy = tipc_nl_policy,
172174
},
173175
{

net/tipc/socket.c

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3229,45 +3229,69 @@ int tipc_nl_sk_walk(struct sk_buff *skb, struct netlink_callback *cb,
32293229
struct netlink_callback *cb,
32303230
struct tipc_sock *tsk))
32313231
{
3232-
struct net *net = sock_net(skb->sk);
3233-
struct tipc_net *tn = tipc_net(net);
3234-
const struct bucket_table *tbl;
3235-
u32 prev_portid = cb->args[1];
3236-
u32 tbl_id = cb->args[0];
3237-
struct rhash_head *pos;
3232+
struct rhashtable_iter *iter = (void *)cb->args[0];
32383233
struct tipc_sock *tsk;
32393234
int err;
32403235

3241-
rcu_read_lock();
3242-
tbl = rht_dereference_rcu((&tn->sk_rht)->tbl, &tn->sk_rht);
3243-
for (; tbl_id < tbl->size; tbl_id++) {
3244-
rht_for_each_entry_rcu(tsk, pos, tbl, tbl_id, node) {
3245-
spin_lock_bh(&tsk->sk.sk_lock.slock);
3246-
if (prev_portid && prev_portid != tsk->portid) {
3247-
spin_unlock_bh(&tsk->sk.sk_lock.slock);
3236+
rhashtable_walk_start(iter);
3237+
while ((tsk = rhashtable_walk_next(iter)) != NULL) {
3238+
if (IS_ERR(tsk)) {
3239+
err = PTR_ERR(tsk);
3240+
if (err == -EAGAIN) {
3241+
err = 0;
32483242
continue;
32493243
}
3244+
break;
3245+
}
32503246

3251-
err = skb_handler(skb, cb, tsk);
3252-
if (err) {
3253-
prev_portid = tsk->portid;
3254-
spin_unlock_bh(&tsk->sk.sk_lock.slock);
3255-
goto out;
3256-
}
3257-
3258-
prev_portid = 0;
3259-
spin_unlock_bh(&tsk->sk.sk_lock.slock);
3247+
sock_hold(&tsk->sk);
3248+
rhashtable_walk_stop(iter);
3249+
lock_sock(&tsk->sk);
3250+
err = skb_handler(skb, cb, tsk);
3251+
if (err) {
3252+
release_sock(&tsk->sk);
3253+
sock_put(&tsk->sk);
3254+
goto out;
32603255
}
3256+
release_sock(&tsk->sk);
3257+
rhashtable_walk_start(iter);
3258+
sock_put(&tsk->sk);
32613259
}
3260+
rhashtable_walk_stop(iter);
32623261
out:
3263-
rcu_read_unlock();
3264-
cb->args[0] = tbl_id;
3265-
cb->args[1] = prev_portid;
3266-
32673262
return skb->len;
32683263
}
32693264
EXPORT_SYMBOL(tipc_nl_sk_walk);
32703265

3266+
int tipc_dump_start(struct netlink_callback *cb)
3267+
{
3268+
struct rhashtable_iter *iter = (void *)cb->args[0];
3269+
struct net *net = sock_net(cb->skb->sk);
3270+
struct tipc_net *tn = tipc_net(net);
3271+
3272+
if (!iter) {
3273+
iter = kmalloc(sizeof(*iter), GFP_KERNEL);
3274+
if (!iter)
3275+
return -ENOMEM;
3276+
3277+
cb->args[0] = (long)iter;
3278+
}
3279+
3280+
rhashtable_walk_enter(&tn->sk_rht, iter);
3281+
return 0;
3282+
}
3283+
EXPORT_SYMBOL(tipc_dump_start);
3284+
3285+
int tipc_dump_done(struct netlink_callback *cb)
3286+
{
3287+
struct rhashtable_iter *hti = (void *)cb->args[0];
3288+
3289+
rhashtable_walk_exit(hti);
3290+
kfree(hti);
3291+
return 0;
3292+
}
3293+
EXPORT_SYMBOL(tipc_dump_done);
3294+
32713295
int tipc_sk_fill_sock_diag(struct sk_buff *skb, struct netlink_callback *cb,
32723296
struct tipc_sock *tsk, u32 sk_filter_state,
32733297
u64 (*tipc_diag_gen_cookie)(struct sock *sk))

net/tipc/socket.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,6 @@ int tipc_nl_sk_walk(struct sk_buff *skb, struct netlink_callback *cb,
6868
int (*skb_handler)(struct sk_buff *skb,
6969
struct netlink_callback *cb,
7070
struct tipc_sock *tsk));
71+
int tipc_dump_start(struct netlink_callback *cb);
72+
int tipc_dump_done(struct netlink_callback *cb);
7173
#endif

0 commit comments

Comments
 (0)