aboutsummaryrefslogtreecommitdiffstats
path: root/net/tipc/socket.c
diff options
context:
space:
mode:
authorCong Wang <xiyou.wangcong@gmail.com>2018-08-24 15:28:06 -0400
committerDavid S. Miller <davem@davemloft.net>2018-08-29 21:04:54 -0400
commit9a07efa9aea2f4a59f35da0785a4e6a6b5a96192 (patch)
treef557853b702648e5497483065b96bc332e9d68de /net/tipc/socket.c
parente5133f2f1261f8ab412e7fc5e3694c9f84328f89 (diff)
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: syzbot+b9c8f3ab2994b7cd1625@syzkaller.appspotmail.com Cc: Jon Maloy <jon.maloy@ericsson.com> Cc: Ying Xue <ying.xue@windriver.com> Signed-off-by: Cong Wang <xiyou.wangcong@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/tipc/socket.c')
-rw-r--r--net/tipc/socket.c76
1 files changed, 50 insertions, 26 deletions
diff --git a/net/tipc/socket.c b/net/tipc/socket.c
index c9a50b62c738..ab7a2a7178f7 100644
--- a/net/tipc/socket.c
+++ b/net/tipc/socket.c
@@ -3229,45 +3229,69 @@ int tipc_nl_sk_walk(struct sk_buff *skb, struct netlink_callback *cb,
3229 struct netlink_callback *cb, 3229 struct netlink_callback *cb,
3230 struct tipc_sock *tsk)) 3230 struct tipc_sock *tsk))
3231{ 3231{
3232 struct net *net = sock_net(skb->sk); 3232 struct rhashtable_iter *iter = (void *)cb->args[0];
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;
3238 struct tipc_sock *tsk; 3233 struct tipc_sock *tsk;
3239 int err; 3234 int err;
3240 3235
3241 rcu_read_lock(); 3236 rhashtable_walk_start(iter);
3242 tbl = rht_dereference_rcu((&tn->sk_rht)->tbl, &tn->sk_rht); 3237 while ((tsk = rhashtable_walk_next(iter)) != NULL) {
3243 for (; tbl_id < tbl->size; tbl_id++) { 3238 if (IS_ERR(tsk)) {
3244 rht_for_each_entry_rcu(tsk, pos, tbl, tbl_id, node) { 3239 err = PTR_ERR(tsk);
3245 spin_lock_bh(&tsk->sk.sk_lock.slock); 3240 if (err == -EAGAIN) {
3246 if (prev_portid && prev_portid != tsk->portid) { 3241 err = 0;
3247 spin_unlock_bh(&tsk->sk.sk_lock.slock);
3248 continue; 3242 continue;
3249 } 3243 }
3244 break;
3245 }
3250 3246
3251 err = skb_handler(skb, cb, tsk); 3247 sock_hold(&tsk->sk);
3252 if (err) { 3248 rhashtable_walk_stop(iter);
3253 prev_portid = tsk->portid; 3249 lock_sock(&tsk->sk);
3254 spin_unlock_bh(&tsk->sk.sk_lock.slock); 3250 err = skb_handler(skb, cb, tsk);
3255 goto out; 3251 if (err) {
3256 } 3252 release_sock(&tsk->sk);
3257 3253 sock_put(&tsk->sk);
3258 prev_portid = 0; 3254 goto out;
3259 spin_unlock_bh(&tsk->sk.sk_lock.slock);
3260 } 3255 }
3256 release_sock(&tsk->sk);
3257 rhashtable_walk_start(iter);
3258 sock_put(&tsk->sk);
3261 } 3259 }
3260 rhashtable_walk_stop(iter);
3262out: 3261out:
3263 rcu_read_unlock();
3264 cb->args[0] = tbl_id;
3265 cb->args[1] = prev_portid;
3266
3267 return skb->len; 3262 return skb->len;
3268} 3263}
3269EXPORT_SYMBOL(tipc_nl_sk_walk); 3264EXPORT_SYMBOL(tipc_nl_sk_walk);
3270 3265
3266int 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}
3283EXPORT_SYMBOL(tipc_dump_start);
3284
3285int 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}
3293EXPORT_SYMBOL(tipc_dump_done);
3294
3271int tipc_sk_fill_sock_diag(struct sk_buff *skb, struct netlink_callback *cb, 3295int tipc_sk_fill_sock_diag(struct sk_buff *skb, struct netlink_callback *cb,
3272 struct tipc_sock *tsk, u32 sk_filter_state, 3296 struct tipc_sock *tsk, u32 sk_filter_state,
3273 u64 (*tipc_diag_gen_cookie)(struct sock *sk)) 3297 u64 (*tipc_diag_gen_cookie)(struct sock *sk))