Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

netfilter: nf_tables: can't assume lock is acquired when dumping set elems

When dumping the elements related to a specified set, we may invoke the
nf_tables_dump_set with the NFNL_SUBSYS_NFTABLES lock not acquired. So
we should use the proper rcu operation to avoid race condition, just
like other nft dump operations.

Signed-off-by: Liping Zhang <zlpnobody@gmail.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

authored by

Liping Zhang and committed by
Pablo Neira Ayuso
fa803605 87e94dbc

+57 -23
+56 -22
net/netfilter/nf_tables_api.c
··· 3367 3367 return nf_tables_fill_setelem(args->skb, set, elem); 3368 3368 } 3369 3369 3370 + struct nft_set_dump_ctx { 3371 + const struct nft_set *set; 3372 + struct nft_ctx ctx; 3373 + }; 3374 + 3370 3375 static int nf_tables_dump_set(struct sk_buff *skb, struct netlink_callback *cb) 3371 3376 { 3377 + struct nft_set_dump_ctx *dump_ctx = cb->data; 3372 3378 struct net *net = sock_net(skb->sk); 3373 - u8 genmask = nft_genmask_cur(net); 3379 + struct nft_af_info *afi; 3380 + struct nft_table *table; 3374 3381 struct nft_set *set; 3375 3382 struct nft_set_dump_args args; 3376 - struct nft_ctx ctx; 3377 - struct nlattr *nla[NFTA_SET_ELEM_LIST_MAX + 1]; 3383 + bool set_found = false; 3378 3384 struct nfgenmsg *nfmsg; 3379 3385 struct nlmsghdr *nlh; 3380 3386 struct nlattr *nest; 3381 3387 u32 portid, seq; 3382 - int event, err; 3388 + int event; 3383 3389 3384 - err = nlmsg_parse(cb->nlh, sizeof(struct nfgenmsg), nla, 3385 - NFTA_SET_ELEM_LIST_MAX, nft_set_elem_list_policy, 3386 - NULL); 3387 - if (err < 0) 3388 - return err; 3390 + rcu_read_lock(); 3391 + list_for_each_entry_rcu(afi, &net->nft.af_info, list) { 3392 + if (afi != dump_ctx->ctx.afi) 3393 + continue; 3389 3394 3390 - err = nft_ctx_init_from_elemattr(&ctx, net, cb->skb, cb->nlh, 3391 - (void *)nla, genmask); 3392 - if (err < 0) 3393 - return err; 3395 + list_for_each_entry_rcu(table, &afi->tables, list) { 3396 + if (table != dump_ctx->ctx.table) 3397 + continue; 3394 3398 3395 - set = nf_tables_set_lookup(ctx.table, nla[NFTA_SET_ELEM_LIST_SET], 3396 - genmask); 3397 - if (IS_ERR(set)) 3398 - return PTR_ERR(set); 3399 + list_for_each_entry_rcu(set, &table->sets, list) { 3400 + if (set == dump_ctx->set) { 3401 + set_found = true; 3402 + break; 3403 + } 3404 + } 3405 + break; 3406 + } 3407 + break; 3408 + } 3409 + 3410 + if (!set_found) { 3411 + rcu_read_unlock(); 3412 + return -ENOENT; 3413 + } 3399 3414 3400 3415 event = nfnl_msg_type(NFNL_SUBSYS_NFTABLES, NFT_MSG_NEWSETELEM); 3401 3416 portid = NETLINK_CB(cb->skb).portid; ··· 3422 3407 goto nla_put_failure; 3423 3408 3424 3409 nfmsg = nlmsg_data(nlh); 3425 - nfmsg->nfgen_family = ctx.afi->family; 3410 + nfmsg->nfgen_family = afi->family; 3426 3411 nfmsg->version = NFNETLINK_V0; 3427 - nfmsg->res_id = htons(ctx.net->nft.base_seq & 0xffff); 3412 + nfmsg->res_id = htons(net->nft.base_seq & 0xffff); 3428 3413 3429 - if (nla_put_string(skb, NFTA_SET_ELEM_LIST_TABLE, ctx.table->name)) 3414 + if (nla_put_string(skb, NFTA_SET_ELEM_LIST_TABLE, table->name)) 3430 3415 goto nla_put_failure; 3431 3416 if (nla_put_string(skb, NFTA_SET_ELEM_LIST_SET, set->name)) 3432 3417 goto nla_put_failure; ··· 3437 3422 3438 3423 args.cb = cb; 3439 3424 args.skb = skb; 3440 - args.iter.genmask = nft_genmask_cur(ctx.net); 3425 + args.iter.genmask = nft_genmask_cur(net); 3441 3426 args.iter.skip = cb->args[0]; 3442 3427 args.iter.count = 0; 3443 3428 args.iter.err = 0; 3444 3429 args.iter.fn = nf_tables_dump_setelem; 3445 - set->ops->walk(&ctx, set, &args.iter); 3430 + set->ops->walk(&dump_ctx->ctx, set, &args.iter); 3431 + rcu_read_unlock(); 3446 3432 3447 3433 nla_nest_end(skb, nest); 3448 3434 nlmsg_end(skb, nlh); ··· 3457 3441 return skb->len; 3458 3442 3459 3443 nla_put_failure: 3444 + rcu_read_unlock(); 3460 3445 return -ENOSPC; 3446 + } 3447 + 3448 + static int nf_tables_dump_set_done(struct netlink_callback *cb) 3449 + { 3450 + kfree(cb->data); 3451 + return 0; 3461 3452 } 3462 3453 3463 3454 static int nf_tables_getsetelem(struct net *net, struct sock *nlsk, ··· 3488 3465 if (nlh->nlmsg_flags & NLM_F_DUMP) { 3489 3466 struct netlink_dump_control c = { 3490 3467 .dump = nf_tables_dump_set, 3468 + .done = nf_tables_dump_set_done, 3491 3469 }; 3470 + struct nft_set_dump_ctx *dump_ctx; 3471 + 3472 + dump_ctx = kmalloc(sizeof(*dump_ctx), GFP_KERNEL); 3473 + if (!dump_ctx) 3474 + return -ENOMEM; 3475 + 3476 + dump_ctx->set = set; 3477 + dump_ctx->ctx = ctx; 3478 + 3479 + c.data = dump_ctx; 3492 3480 return netlink_dump_start(nlsk, skb, nlh, &c); 3493 3481 } 3494 3482 return -EOPNOTSUPP;
+1 -1
net/netfilter/nft_set_hash.c
··· 222 222 struct nft_set_elem elem; 223 223 int err; 224 224 225 - err = rhashtable_walk_init(&priv->ht, &hti, GFP_KERNEL); 225 + err = rhashtable_walk_init(&priv->ht, &hti, GFP_ATOMIC); 226 226 iter->err = err; 227 227 if (err) 228 228 return;