Skip to content

Commit 1675202

Browse files
committed
netfilter: nf_tables: make destruction work queue pernet
jira VULN-44483 cve-bf CVE-2024-42109 commit-author Florian Westphal <fw@strlen.de> commit fb82865 upstream-diff Context conflicts only The call to flush_work before tearing down a table from the netlink notifier was supposed to make sure that all earlier updates (e.g. rule add) that might reference that table have been processed. Unfortunately, flush_work() waits for the last queued instance. This could be an instance that is different from the one that we must wait for. This is because transactions are protected with a pernet mutex, but the work item is global, so holding the transaction mutex doesn't prevent another netns from queueing more work. Make the work item pernet so that flush_work() will wait for all transactions queued from this netns. A welcome side effect is that we no longer need to wait for transaction objects from foreign netns. The gc work queue is still global. This seems to be ok because nft_set structures are reference counted and each container structure owns a reference on the net namespace. The destroy_list is still protected by a global spinlock rather than pernet one but the hold time is very short anyway. v2: call cancel_work_sync before reaping the remaining tables (Pablo). Fixes: 9f6958b ("netfilter: nf_tables: unconditionally flush pending work before notifier") Reported-by: syzbot+5d8c5789c8cb076b2c25@syzkaller.appspotmail.com Signed-off-by: Florian Westphal <fw@strlen.de> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> (cherry picked from commit fb82865) Signed-off-by: Marcin Wcisło <marcin.wcislo@conclusive.pl>
1 parent 95d7fea commit 1675202

3 files changed

Lines changed: 21 additions & 15 deletions

File tree

include/net/netfilter/nf_tables.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1654,7 +1654,7 @@ void nft_chain_filter_fini(void);
16541654
void __init nft_chain_route_init(void);
16551655
void nft_chain_route_fini(void);
16561656

1657-
void nf_tables_trans_destroy_flush_work(void);
1657+
void nf_tables_trans_destroy_flush_work(struct net *net);
16581658

16591659
int nf_msecs_to_jiffies64(const struct nlattr *nla, u64 *result);
16601660
__be64 nf_jiffies64_to_msecs(u64 input);
@@ -1668,6 +1668,7 @@ static inline int nft_request_module(struct net *net, const char *fmt, ...) { re
16681668
struct nftables_pernet {
16691669
struct list_head tables;
16701670
struct list_head commit_list;
1671+
struct list_head destroy_list;
16711672
struct list_head binding_list;
16721673
struct list_head module_list;
16731674
struct list_head notify_list;
@@ -1676,6 +1677,7 @@ struct nftables_pernet {
16761677
unsigned int base_seq;
16771678
unsigned int gc_seq;
16781679
u8 validate_state;
1680+
struct work_struct destroy_work;
16791681
};
16801682

16811683
extern unsigned int nf_tables_net_id;

net/netfilter/nf_tables_api.c

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ unsigned int nf_tables_net_id __read_mostly;
3030
static LIST_HEAD(nf_tables_expressions);
3131
static LIST_HEAD(nf_tables_objects);
3232
static LIST_HEAD(nf_tables_flowtables);
33-
static LIST_HEAD(nf_tables_destroy_list);
3433
static LIST_HEAD(nf_tables_gc_list);
3534
static DEFINE_SPINLOCK(nf_tables_destroy_list_lock);
3635
static DEFINE_SPINLOCK(nf_tables_gc_list_lock);
@@ -122,7 +121,6 @@ static void nft_validate_state_update(struct net *net, u8 new_validate_state)
122121
nft_net->validate_state = new_validate_state;
123122
}
124123
static void nf_tables_trans_destroy_work(struct work_struct *w);
125-
static DECLARE_WORK(trans_destroy_work, nf_tables_trans_destroy_work);
126124

127125
static void nft_trans_gc_work(struct work_struct *work);
128126
static DECLARE_WORK(trans_gc_work, nft_trans_gc_work);
@@ -8824,11 +8822,12 @@ static void nft_commit_release(struct nft_trans *trans)
88248822

88258823
static void nf_tables_trans_destroy_work(struct work_struct *w)
88268824
{
8825+
struct nftables_pernet *nft_net = container_of(w, struct nftables_pernet, destroy_work);
88278826
struct nft_trans *trans, *next;
88288827
LIST_HEAD(head);
88298828

88308829
spin_lock(&nf_tables_destroy_list_lock);
8831-
list_splice_init(&nf_tables_destroy_list, &head);
8830+
list_splice_init(&nft_net->destroy_list, &head);
88328831
spin_unlock(&nf_tables_destroy_list_lock);
88338832

88348833
if (list_empty(&head))
@@ -8842,9 +8841,11 @@ static void nf_tables_trans_destroy_work(struct work_struct *w)
88428841
}
88438842
}
88448843

8845-
void nf_tables_trans_destroy_flush_work(void)
8844+
void nf_tables_trans_destroy_flush_work(struct net *net)
88468845
{
8847-
flush_work(&trans_destroy_work);
8846+
struct nftables_pernet *nft_net = nft_pernet(net);
8847+
8848+
flush_work(&nft_net->destroy_work);
88488849
}
88498850
EXPORT_SYMBOL_GPL(nf_tables_trans_destroy_flush_work);
88508851

@@ -9263,11 +9264,11 @@ static void nf_tables_commit_release(struct net *net)
92639264

92649265
trans->put_net = true;
92659266
spin_lock(&nf_tables_destroy_list_lock);
9266-
list_splice_tail_init(&nft_net->commit_list, &nf_tables_destroy_list);
9267+
list_splice_tail_init(&nft_net->commit_list, &nft_net->destroy_list);
92679268
spin_unlock(&nf_tables_destroy_list_lock);
92689269

92699270
nf_tables_module_autoload_cleanup(net);
9270-
schedule_work(&trans_destroy_work);
9271+
schedule_work(&nft_net->destroy_work);
92719272

92729273
mutex_unlock(&nft_net->commit_mutex);
92739274
}
@@ -10677,7 +10678,7 @@ static int nft_rcv_nl_event(struct notifier_block *this, unsigned long event,
1067710678

1067810679
gc_seq = nft_gc_seq_begin(nft_net);
1067910680

10680-
nf_tables_trans_destroy_flush_work();
10681+
nf_tables_trans_destroy_flush_work(net);
1068110682
again:
1068210683
list_for_each_entry(table, &nft_net->tables, list) {
1068310684
if (nft_table_has_owner(table) &&
@@ -10715,13 +10716,15 @@ static int __net_init nf_tables_init_net(struct net *net)
1071510716

1071610717
INIT_LIST_HEAD(&nft_net->tables);
1071710718
INIT_LIST_HEAD(&nft_net->commit_list);
10719+
INIT_LIST_HEAD(&nft_net->destroy_list);
1071810720
INIT_LIST_HEAD(&nft_net->binding_list);
1071910721
INIT_LIST_HEAD(&nft_net->module_list);
1072010722
INIT_LIST_HEAD(&nft_net->notify_list);
1072110723
mutex_init(&nft_net->commit_mutex);
1072210724
nft_net->base_seq = 1;
1072310725
nft_net->gc_seq = 0;
1072410726
nft_net->validate_state = NFT_VALIDATE_SKIP;
10727+
INIT_WORK(&nft_net->destroy_work, nf_tables_trans_destroy_work);
1072510728

1072610729
return 0;
1072710730
}
@@ -10749,14 +10752,17 @@ static void __net_exit nf_tables_exit_net(struct net *net)
1074910752
if (!list_empty(&nft_net->module_list))
1075010753
nf_tables_module_autoload_cleanup(net);
1075110754

10755+
cancel_work_sync(&nft_net->destroy_work);
1075210756
__nft_release_tables(net);
1075310757

1075410758
nft_gc_seq_end(nft_net, gc_seq);
1075510759

1075610760
mutex_unlock(&nft_net->commit_mutex);
10761+
1075710762
WARN_ON_ONCE(!list_empty(&nft_net->tables));
1075810763
WARN_ON_ONCE(!list_empty(&nft_net->module_list));
1075910764
WARN_ON_ONCE(!list_empty(&nft_net->notify_list));
10765+
WARN_ON_ONCE(!list_empty(&nft_net->destroy_list));
1076010766
}
1076110767

1076210768
static void nf_tables_exit_batch(struct list_head *net_exit_list)
@@ -10839,10 +10845,8 @@ static void __exit nf_tables_module_exit(void)
1083910845
unregister_netdevice_notifier(&nf_tables_flowtable_notifier);
1084010846
nft_chain_filter_fini();
1084110847
nft_chain_route_fini();
10842-
nf_tables_trans_destroy_flush_work();
1084310848
unregister_pernet_subsys(&nf_tables_net_ops);
1084410849
cancel_work_sync(&trans_gc_work);
10845-
cancel_work_sync(&trans_destroy_work);
1084610850
rcu_barrier();
1084710851
rhltable_destroy(&nft_objname_ht);
1084810852
nf_tables_core_module_exit();

net/netfilter/nft_compat.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,15 +223,15 @@ static int nft_parse_compat(const struct nlattr *attr, u16 *proto, bool *inv)
223223
return 0;
224224
}
225225

226-
static void nft_compat_wait_for_destructors(void)
226+
static void nft_compat_wait_for_destructors(struct net *net)
227227
{
228228
/* xtables matches or targets can have side effects, e.g.
229229
* creation/destruction of /proc files.
230230
* The xt ->destroy functions are run asynchronously from
231231
* work queue. If we have pending invocations we thus
232232
* need to wait for those to finish.
233233
*/
234-
nf_tables_trans_destroy_flush_work();
234+
nf_tables_trans_destroy_flush_work(net);
235235
}
236236

237237
static int
@@ -257,7 +257,7 @@ nft_target_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
257257

258258
nft_target_set_tgchk_param(&par, ctx, target, info, &e, proto, inv);
259259

260-
nft_compat_wait_for_destructors();
260+
nft_compat_wait_for_destructors(ctx->net);
261261

262262
ret = xt_check_target(&par, size, proto, inv);
263263
if (ret < 0) {
@@ -494,7 +494,7 @@ __nft_match_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
494494

495495
nft_match_set_mtchk_param(&par, ctx, match, info, &e, proto, inv);
496496

497-
nft_compat_wait_for_destructors();
497+
nft_compat_wait_for_destructors(ctx->net);
498498

499499
return xt_check_match(&par, size, proto, inv);
500500
}

0 commit comments

Comments
 (0)