[PATCH nf 2/2] netfilter: nft_compat: protect lists between select_ops and init

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

 



nft_{match/target}_select_ops() find xt modules in the list. if the
module is found, it is just used without any holding module or
increasing reference counts. and the reference count is increased in
nft_{match/target}_init() function.
But, xt modules can be removed anytime if reference count is 0.

CPU0					CPU1
nft_{match/target}_select_ops
					nft_{match/target}_destroy
nft_{match/target}_init

The scenario is like this:
1. ->select_ops() found a module in the list and keeps an element of it
without increasing reference count.
2. ->destroy() frees an element of list.
3. nft_{match/target}_init try to use the pointer of element, but this is
already freed.

Test commands:
   while :
   do
	iptables-nft -t nat -I PREROUTING -m string --string ap --algo \
		kmp -j MASQUERADE &
	nft flush ruleset &
   done

Result:
[  682.340431] BUG: KASAN: use-after-free in nft_target_init+0xb96/0xbe0 [nft_compat]
[  682.340431] Read of size 4 at addr ffff8881079a73a0 by task iptables-nft/8136
[  682.340431]
[  682.340431] CPU: 1 PID: 8136 Comm: iptables-nft Not tainted 4.20.0+ #59
[  682.373297] Call Trace:
[  682.373297]  dump_stack+0xc9/0x16b
[  682.373297]  ? show_regs_print_info+0x5/0x5
[  682.373297]  ? kmsg_dump_rewind_nolock+0xd9/0xd9
[  682.402587]  ? nft_target_init+0xb96/0xbe0 [nft_compat]
[  682.402587]  print_address_description+0x6a/0x270
[  682.402587]  ? nft_target_init+0xb96/0xbe0 [nft_compat]
[  682.402587]  ? nft_target_init+0xb96/0xbe0 [nft_compat]
[  682.402587]  kasan_report+0x12a/0x16f
[  682.429087]  ? nft_target_init+0xb96/0xbe0 [nft_compat]
[  682.433653]  nft_target_init+0xb96/0xbe0 [nft_compat]
[  682.433653]  ? target_compat_from_user.isra.5+0x80/0x80 [nft_compat]
[  682.433653]  ? nf_tables_newrule+0xe46/0x2570 [nf_tables]
[  682.433653]  ? ksm_do_scan+0x2bc0/0x3300
[  682.433653]  ? hlock_class+0x140/0x140
[  682.433653]  ? nf_tables_newrule+0xe46/0x2570 [nf_tables]
[  682.465582]  ? memcpy+0x39/0x50
[  682.465582]  ? __kmalloc+0x134/0x2e0
[  682.465582]  ? nf_tables_newrule+0xe46/0x2570 [nf_tables]
[  682.465582]  nf_tables_newrule+0x10a4/0x2570 [nf_tables]
[ ... ]
[  682.828711]
[  682.828711] Allocated by task 8134:
[  682.828711]  kasan_kmalloc+0xa5/0xd0
[  682.828711]  kmem_cache_alloc_trace+0x117/0x290
[  682.828711]  nft_target_select_ops+0x481/0x8e0 [nft_compat]
[  682.828711]  nf_tables_expr_parse+0x2a1/0x510 [nf_tables]
[  682.828711]  nf_tables_newrule+0xd0c/0x2570 [nf_tables]
[  682.828711]  nfnetlink_rcv_batch+0xd2e/0x1550 [nfnetlink]
[  682.828711]  nfnetlink_rcv+0x2ee/0x350 [nfnetlink]
[  682.828711]  netlink_unicast+0x414/0x5e0
[  682.828711]  netlink_sendmsg+0x7b8/0xcf0
[  682.937632]  ___sys_sendmsg+0x689/0x990
[  682.937632]  __sys_sendmsg+0xd2/0x210
[  682.937632]  do_syscall_64+0x138/0x560
[  682.937632]  entry_SYSCALL_64_after_hwframe+0x49/0xbe
[  682.937632]
[  682.937632] Freed by task 8140:
[  682.937632]  __kasan_slab_free+0x135/0x180
[  682.937632]  kfree+0xdb/0x280
[  682.937632]  rcu_process_callbacks+0x947/0x24d0
[  682.937632]  __do_softirq+0x2a5/0xa3b
[  682.937632]

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

diff --git a/net/netfilter/nft_compat.c b/net/netfilter/nft_compat.c
index 83adabdec900..8f3114871d20 100644
--- a/net/netfilter/nft_compat.c
+++ b/net/netfilter/nft_compat.c
@@ -29,6 +29,7 @@ struct nft_xt {
 	struct list_head	head;
 	struct nft_expr_ops	ops;
 	unsigned int		refcnt;
+	bool			use;
 
 	/* Unlike other expressions, ops doesn't have static storage duration.
 	 * nft core assumes they do.  We use kfree_rcu so that nft core can
@@ -48,7 +49,7 @@ struct nft_xt_match_priv {
 static bool nft_xt_put(struct nft_xt *xt)
 {
 	mutex_lock(&nft_xt_lock);
-	if (--xt->refcnt == 0) {
+	if (--xt->refcnt == 0 && !xt->use) {
 		list_del(&xt->head);
 		kfree_rcu(xt, rcu_head);
 		mutex_unlock(&nft_xt_lock);
@@ -280,6 +281,7 @@ nft_target_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
 	mutex_lock(&nft_xt_lock);
 	nft_xt = container_of(expr->ops, struct nft_xt, ops);
 	nft_xt->refcnt++;
+	nft_xt->use = false;
 	mutex_unlock(&nft_xt_lock);
 	return 0;
 }
@@ -495,6 +497,7 @@ __nft_match_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
 	mutex_lock(&nft_xt_lock);
 	nft_xt = container_of(expr->ops, struct nft_xt, ops);
 	nft_xt->refcnt++;
+	nft_xt->use = false;
 	mutex_unlock(&nft_xt_lock);
 	return 0;
 }
@@ -780,6 +783,7 @@ nft_match_select_ops(const struct nft_ctx *ctx,
 		struct xt_match *match = nft_match->ops.data;
 
 		if (nft_match_cmp(match, mt_name, rev, family)) {
+			nft_match->use = true;
 			mutex_unlock(&nft_xt_lock);
 			return &nft_match->ops;
 		}
@@ -803,6 +807,7 @@ nft_match_select_ops(const struct nft_ctx *ctx,
 	}
 
 	nft_match->refcnt = 0;
+	nft_match->use = true;
 	nft_match->ops.type = &nft_match_type;
 	nft_match->ops.eval = nft_match_eval;
 	nft_match->ops.init = nft_match_init;
@@ -885,6 +890,7 @@ nft_target_select_ops(const struct nft_ctx *ctx,
 			continue;
 
 		if (nft_target_cmp(target, tg_name, rev, family)) {
+			nft_target->use = true;
 			mutex_unlock(&nft_xt_lock);
 			return &nft_target->ops;
 		}
@@ -913,6 +919,7 @@ nft_target_select_ops(const struct nft_ctx *ctx,
 	}
 
 	nft_target->refcnt = 0;
+	nft_target->use = true;
 	nft_target->ops.type = &nft_target_type;
 	nft_target->ops.size = NFT_EXPR_SIZE(XT_ALIGN(target->targetsize));
 	nft_target->ops.init = nft_target_init;
-- 
2.17.1




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

  Powered by Linux