[PATCH nf 1/2] netfilter: nft_compat: fix a race condition in match/target list

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The nft_compat module manages match/target lists which are
nft_{match/target}_list.
These lists are used in several functions and these functions can be
executed concurrently because the rule destroying function is called
by workqueue.
So, in order to protect these lists, a lock should be used.
A new nft_xt_lock is added and this protects both lists and the refcnt
of the struct nft_xt.

Test commands:
   while :
   do
	iptables-nft -I FORWARD -m string --string ap --algo kmp &
	nft flush ruleset &
   done

Result:
[  488.337545] kernel BUG at lib/list_debug.c:50!
[  488.346922] invalid opcode: 0000 [#1] SMP DEBUG_PAGEALLOC KASAN PTI
[  488.347881] CPU: 0 PID: 183 Comm: kworker/0:2 Tainted: G    B             4.20.0+ #59
[  488.347881] Workqueue: events nf_tables_trans_destroy_work [nf_tables]
[  488.347881] RIP: 0010:__list_del_entry_valid+0xd8/0x150
[  488.347881] Code: 39 48 83 c4 08 b8 01 00 00 00 5b 5d c3 48 89 ea 48 c7 c7 00 d4 5b 93 e8 0f 55 41 ff 0f 0b 48 c7 c7 60 d4 5b 93 e8 01 55 41 ff <0f> 0b 48 c7 c7 c0 d4 5b 93 e8 f3 54 41 ff 0f 0b 48 c7 c7 20 d5 5b
[  488.347881] RSP: 0018:ffff8881122073b0 EFLAGS: 00010286
[  488.409630] RAX: 000000000000004e RBX: dead000000000200 RCX: 0000000000000000
[  488.409630] RDX: 000000000000004e RSI: 0000000000000008 RDI: ffffed1022440e6c
[  488.409630] RBP: ffff88810b800ee8 R08: ffffed10235fee91 R09: ffffed10235fee91
[  488.409630] R10: 00000000dc1f97e1 R11: ffffed10235fee90 R12: ffff88810ef2a890
[  488.409630] R13: dffffc0000000000 R14: ffff8881120c7a40 R15: 0000000000000002
[  488.409630] FS:  0000000000000000(0000) GS:ffff88811ae00000(0000) knlGS:0000000000000000
[  488.462588] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[  488.462588] CR2: 00007fa67de0f000 CR3: 0000000108dd6000 CR4: 00000000001006f0
[  488.473606] Call Trace:
[  488.473606]  nft_xt_put.part.7+0x63/0x200 [nft_compat]
[  488.481996]  ? nft_match_init+0x10/0x10 [nft_compat]
[  488.481996]  ? __kasan_slab_free+0x14a/0x180
[  488.481996]  ? __nft_match_destroy.isra.8+0x1c6/0x360 [nft_compat]
[  488.502312]  __nft_match_destroy.isra.8+0x23d/0x360 [nft_compat]
[  488.502312]  ? nft_target_destroy+0x2e0/0x2e0 [nft_compat]
[  488.502312]  nf_tables_expr_destroy+0x61/0x100 [nf_tables]
[  488.502312]  nf_tables_rule_destroy+0xd8/0x170 [nf_tables]
[  488.502312]  nf_tables_trans_destroy_work+0x5ac/0x840 [nf_tables]
[  488.502312]  ? sched_clock_cpu+0x126/0x170

Fixes: 0935d5588400 ("netfilter: nf_tables: asynchronous release")
Signed-off-by: Taehee Yoo <ap420073@xxxxxxxxx>
---
 net/netfilter/nft_compat.c | 28 ++++++++++++++++++++++++++--
 1 file changed, 26 insertions(+), 2 deletions(-)

diff --git a/net/netfilter/nft_compat.c b/net/netfilter/nft_compat.c
index 7334e0b80a5e..83adabdec900 100644
--- a/net/netfilter/nft_compat.c
+++ b/net/netfilter/nft_compat.c
@@ -23,6 +23,8 @@
 #include <linux/netfilter_arp/arp_tables.h>
 #include <net/netfilter/nf_tables.h>
 
+static DEFINE_MUTEX(nft_xt_lock);
+
 struct nft_xt {
 	struct list_head	head;
 	struct nft_expr_ops	ops;
@@ -45,12 +47,15 @@ struct nft_xt_match_priv {
 
 static bool nft_xt_put(struct nft_xt *xt)
 {
+	mutex_lock(&nft_xt_lock);
 	if (--xt->refcnt == 0) {
 		list_del(&xt->head);
 		kfree_rcu(xt, rcu_head);
+		mutex_unlock(&nft_xt_lock);
 		return true;
 	}
 
+	mutex_unlock(&nft_xt_lock);
 	return false;
 }
 
@@ -272,8 +277,10 @@ nft_target_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
 	if (!target->target)
 		return -EINVAL;
 
+	mutex_lock(&nft_xt_lock);
 	nft_xt = container_of(expr->ops, struct nft_xt, ops);
 	nft_xt->refcnt++;
+	mutex_unlock(&nft_xt_lock);
 	return 0;
 }
 
@@ -485,8 +492,10 @@ __nft_match_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
 	if (ret < 0)
 		return ret;
 
+	mutex_lock(&nft_xt_lock);
 	nft_xt = container_of(expr->ops, struct nft_xt, ops);
 	nft_xt->refcnt++;
+	mutex_unlock(&nft_xt_lock);
 	return 0;
 }
 
@@ -766,12 +775,16 @@ nft_match_select_ops(const struct nft_ctx *ctx,
 	family = ctx->family;
 
 	/* Re-use the existing match if it's already loaded. */
+	mutex_lock(&nft_xt_lock);
 	list_for_each_entry(nft_match, &nft_match_list, head) {
 		struct xt_match *match = nft_match->ops.data;
 
-		if (nft_match_cmp(match, mt_name, rev, family))
+		if (nft_match_cmp(match, mt_name, rev, family)) {
+			mutex_unlock(&nft_xt_lock);
 			return &nft_match->ops;
+		}
 	}
+	mutex_unlock(&nft_xt_lock);
 
 	match = xt_request_find_match(family, mt_name, rev);
 	if (IS_ERR(match))
@@ -810,7 +823,9 @@ nft_match_select_ops(const struct nft_ctx *ctx,
 
 	nft_match->ops.size = matchsize;
 
+	mutex_lock(&nft_xt_lock);
 	list_add(&nft_match->head, &nft_match_list);
+	mutex_unlock(&nft_xt_lock);
 
 	return &nft_match->ops;
 err:
@@ -862,15 +877,19 @@ nft_target_select_ops(const struct nft_ctx *ctx,
 		return ERR_PTR(-EINVAL);
 
 	/* Re-use the existing target if it's already loaded. */
+	mutex_lock(&nft_xt_lock);
 	list_for_each_entry(nft_target, &nft_target_list, head) {
 		struct xt_target *target = nft_target->ops.data;
 
 		if (!target->target)
 			continue;
 
-		if (nft_target_cmp(target, tg_name, rev, family))
+		if (nft_target_cmp(target, tg_name, rev, family)) {
+			mutex_unlock(&nft_xt_lock);
 			return &nft_target->ops;
+		}
 	}
+	mutex_unlock(&nft_xt_lock);
 
 	target = xt_request_find_target(family, tg_name, rev);
 	if (IS_ERR(target))
@@ -907,7 +926,9 @@ nft_target_select_ops(const struct nft_ctx *ctx,
 	else
 		nft_target->ops.eval = nft_target_eval_xt;
 
+	mutex_lock(&nft_xt_lock);
 	list_add(&nft_target->head, &nft_target_list);
+	mutex_unlock(&nft_xt_lock);
 
 	return &nft_target->ops;
 err:
@@ -961,6 +982,7 @@ static void __exit nft_compat_module_exit(void)
 	 * In this case, the lists contain 0-refcount entries that still
 	 * hold module reference.
 	 */
+	mutex_lock(&nft_xt_lock);
 	list_for_each_entry_safe(xt, next, &nft_target_list, head) {
 		struct xt_target *target = xt->ops.data;
 
@@ -975,9 +997,11 @@ static void __exit nft_compat_module_exit(void)
 
 		if (WARN_ON_ONCE(xt->refcnt))
 			continue;
+
 		module_put(match->me);
 		kfree(xt);
 	}
+	mutex_unlock(&nft_xt_lock);
 	nfnetlink_subsys_unregister(&nfnl_compat_subsys);
 	nft_unregister_expr(&nft_target_type);
 	nft_unregister_expr(&nft_match_type);
-- 
2.17.1




[Index of Archives]     [Netfitler Users]     [LARTC]     [Bugtraq]     [Yosemite Forum]

  Powered by Linux