stable.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
To: linux-kernel@vger.kernel.org
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	stable@vger.kernel.org, Taehee Yoo <ap420073@gmail.com>,
	Florian Westphal <fw@strlen.de>,
	Pablo Neira Ayuso <pablo@netfilter.org>
Subject: [PATCH 4.14 23/61] netfilter: nf_tables: nft_compat: fix refcount leak on xt module
Date: Fri,  6 Jul 2018 07:46:47 +0200	[thread overview]
Message-ID: <20180706054713.227711060@linuxfoundation.org> (raw)
In-Reply-To: <20180706054712.332416244@linuxfoundation.org>

4.14-stable review patch.  If anyone has any objections, please let me know.

------------------

From: Florian Westphal <fw@strlen.de>

commit b8e9dc1c75714ceb53615743e1036f76e00f5a17 upstream.

Taehee Yoo reported following bug:
    iptables-compat -I OUTPUT -m cpu --cpu 0
    iptables-compat -F
    lsmod |grep xt_cpu
    xt_cpu                 16384  1

Quote:
"When above command is given, a netlink message has two expressions that
are the cpu compat and the nft_counter.
The nft_expr_type_get() in the nf_tables_expr_parse() successes
first expression then, calls select_ops callback.
(allocates memory and holds module)
But, second nft_expr_type_get() in the nf_tables_expr_parse()
returns -EAGAIN because of request_module().
In that point, by the 'goto err1',
the 'module_put(info[i].ops->type->owner)' is called.
There is no release routine."

The core problem is that unlike all other expression,
nft_compat select_ops has side effects.

1. it allocates dynamic memory which holds an nft ops struct.
   In all other expressions, ops has static storage duration.
2. It grabs references to the xt module that it is supposed to
   invoke.

Depending on where things go wrong, error unwinding doesn't
always do the right thing.

In the above scenario, a new nft_compat_expr is created and
xt_cpu module gets loaded with a refcount of 1.

Due to to -EAGAIN, the netlink messages get re-parsed.
When that happens, nft_compat finds that xt_cpu is already present
and increments module refcount again.

This fixes the problem by making select_ops to have no visible
side effects and removes all extra module_get/put.

When select_ops creates a new nft_compat expression, the new
expression has a refcount of 0, and the xt module gets its refcount
incremented.

When error happens, the next call finds existing entry, but will no
longer increase the reference count -- the presence of existing
nft_xt means we already hold a module reference.

Because nft_xt_put is only called from nft_compat destroy hook,
it will never see the initial zero reference count.
->destroy can only be called after ->init(), and that will increase the
refcount.

Lastly, we now free nft_xt struct with kfree_rcu.
Else, we get use-after free in nf_tables_rule_destroy:

  while (expr != nft_expr_last(rule) && expr->ops) {
    nf_tables_expr_destroy(ctx, expr);
    expr = nft_expr_next(expr); // here

nft_expr_next() dereferences expr->ops. This is safe
for all users, as ops have static storage duration.
In nft_compat case however, its ->destroy callback can
free the memory that hold the ops structure.

Tested-by: Taehee Yoo <ap420073@gmail.com>
Reported-by: Taehee Yoo <ap420073@gmail.com>
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

---
 net/netfilter/nft_compat.c |   92 ++++++++++++++++++++++++++++-----------------
 1 file changed, 58 insertions(+), 34 deletions(-)

--- a/net/netfilter/nft_compat.c
+++ b/net/netfilter/nft_compat.c
@@ -27,14 +27,24 @@ struct nft_xt {
 	struct list_head	head;
 	struct nft_expr_ops	ops;
 	unsigned int		refcnt;
+
+	/* Unlike other expressions, ops doesn't have static storage duration.
+	 * nft core assumes they do.  We use kfree_rcu so that nft core can
+	 * can check expr->ops->size even after nft_compat->destroy() frees
+	 * the nft_xt struct that holds the ops structure.
+	 */
+	struct rcu_head		rcu_head;
 };
 
-static void nft_xt_put(struct nft_xt *xt)
+static bool nft_xt_put(struct nft_xt *xt)
 {
 	if (--xt->refcnt == 0) {
 		list_del(&xt->head);
-		kfree(xt);
+		kfree_rcu(xt, rcu_head);
+		return true;
 	}
+
+	return false;
 }
 
 static int nft_compat_chain_validate_dependency(const char *tablename,
@@ -226,6 +236,7 @@ nft_target_init(const struct nft_ctx *ct
 	struct xt_target *target = expr->ops->data;
 	struct xt_tgchk_param par;
 	size_t size = XT_ALIGN(nla_len(tb[NFTA_TARGET_INFO]));
+	struct nft_xt *nft_xt;
 	u16 proto = 0;
 	bool inv = false;
 	union nft_entry e = {};
@@ -236,25 +247,22 @@ nft_target_init(const struct nft_ctx *ct
 	if (ctx->nla[NFTA_RULE_COMPAT]) {
 		ret = nft_parse_compat(ctx->nla[NFTA_RULE_COMPAT], &proto, &inv);
 		if (ret < 0)
-			goto err;
+			return ret;
 	}
 
 	nft_target_set_tgchk_param(&par, ctx, target, info, &e, proto, inv);
 
 	ret = xt_check_target(&par, size, proto, inv);
 	if (ret < 0)
-		goto err;
+		return ret;
 
 	/* The standard target cannot be used */
-	if (target->target == NULL) {
-		ret = -EINVAL;
-		goto err;
-	}
+	if (!target->target)
+		return -EINVAL;
 
+	nft_xt = container_of(expr->ops, struct nft_xt, ops);
+	nft_xt->refcnt++;
 	return 0;
-err:
-	module_put(target->me);
-	return ret;
 }
 
 static void
@@ -271,8 +279,8 @@ nft_target_destroy(const struct nft_ctx
 	if (par.target->destroy != NULL)
 		par.target->destroy(&par);
 
-	nft_xt_put(container_of(expr->ops, struct nft_xt, ops));
-	module_put(target->me);
+	if (nft_xt_put(container_of(expr->ops, struct nft_xt, ops)))
+		module_put(target->me);
 }
 
 static int nft_target_dump(struct sk_buff *skb, const struct nft_expr *expr)
@@ -411,6 +419,7 @@ nft_match_init(const struct nft_ctx *ctx
 	struct xt_match *match = expr->ops->data;
 	struct xt_mtchk_param par;
 	size_t size = XT_ALIGN(nla_len(tb[NFTA_MATCH_INFO]));
+	struct nft_xt *nft_xt;
 	u16 proto = 0;
 	bool inv = false;
 	union nft_entry e = {};
@@ -421,19 +430,18 @@ nft_match_init(const struct nft_ctx *ctx
 	if (ctx->nla[NFTA_RULE_COMPAT]) {
 		ret = nft_parse_compat(ctx->nla[NFTA_RULE_COMPAT], &proto, &inv);
 		if (ret < 0)
-			goto err;
+			return ret;
 	}
 
 	nft_match_set_mtchk_param(&par, ctx, match, info, &e, proto, inv);
 
 	ret = xt_check_match(&par, size, proto, inv);
 	if (ret < 0)
-		goto err;
+		return ret;
 
+	nft_xt = container_of(expr->ops, struct nft_xt, ops);
+	nft_xt->refcnt++;
 	return 0;
-err:
-	module_put(match->me);
-	return ret;
 }
 
 static void
@@ -450,8 +458,8 @@ nft_match_destroy(const struct nft_ctx *
 	if (par.match->destroy != NULL)
 		par.match->destroy(&par);
 
-	nft_xt_put(container_of(expr->ops, struct nft_xt, ops));
-	module_put(match->me);
+	if (nft_xt_put(container_of(expr->ops, struct nft_xt, ops)))
+		module_put(match->me);
 }
 
 static int nft_match_dump(struct sk_buff *skb, const struct nft_expr *expr)
@@ -654,13 +662,8 @@ nft_match_select_ops(const struct nft_ct
 	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 (!try_module_get(match->me))
-				return ERR_PTR(-ENOENT);
-
-			nft_match->refcnt++;
+		if (nft_match_cmp(match, mt_name, rev, family))
 			return &nft_match->ops;
-		}
 	}
 
 	match = xt_request_find_match(family, mt_name, rev);
@@ -679,7 +682,7 @@ nft_match_select_ops(const struct nft_ct
 		goto err;
 	}
 
-	nft_match->refcnt = 1;
+	nft_match->refcnt = 0;
 	nft_match->ops.type = &nft_match_type;
 	nft_match->ops.size = NFT_EXPR_SIZE(XT_ALIGN(match->matchsize));
 	nft_match->ops.eval = nft_match_eval;
@@ -739,13 +742,8 @@ nft_target_select_ops(const struct nft_c
 	list_for_each_entry(nft_target, &nft_target_list, head) {
 		struct xt_target *target = nft_target->ops.data;
 
-		if (nft_target_cmp(target, tg_name, rev, family)) {
-			if (!try_module_get(target->me))
-				return ERR_PTR(-ENOENT);
-
-			nft_target->refcnt++;
+		if (nft_target_cmp(target, tg_name, rev, family))
 			return &nft_target->ops;
-		}
 	}
 
 	target = xt_request_find_target(family, tg_name, rev);
@@ -764,7 +762,7 @@ nft_target_select_ops(const struct nft_c
 		goto err;
 	}
 
-	nft_target->refcnt = 1;
+	nft_target->refcnt = 0;
 	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;
@@ -825,6 +823,32 @@ err_match:
 
 static void __exit nft_compat_module_exit(void)
 {
+	struct nft_xt *xt, *next;
+
+	/* list should be empty here, it can be non-empty only in case there
+	 * was an error that caused nft_xt expr to not be initialized fully
+	 * and noone else requested the same expression later.
+	 *
+	 * In this case, the lists contain 0-refcount entries that still
+	 * hold module reference.
+	 */
+	list_for_each_entry_safe(xt, next, &nft_target_list, head) {
+		struct xt_target *target = xt->ops.data;
+
+		if (WARN_ON_ONCE(xt->refcnt))
+			continue;
+		module_put(target->me);
+		kfree(xt);
+	}
+
+	list_for_each_entry_safe(xt, next, &nft_match_list, head) {
+		struct xt_match *match = xt->ops.data;
+
+		if (WARN_ON_ONCE(xt->refcnt))
+			continue;
+		module_put(match->me);
+		kfree(xt);
+	}
 	nfnetlink_subsys_unregister(&nfnl_compat_subsys);
 	nft_unregister_expr(&nft_target_type);
 	nft_unregister_expr(&nft_match_type);

  parent reply	other threads:[~2018-07-06  5:50 UTC|newest]

Thread overview: 58+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-07-06  5:46 [PATCH 4.14 00/61] 4.14.54-stable review Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 01/61] usb: cdc_acm: Add quirk for Uniden UBC125 scanner Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 02/61] USB: serial: cp210x: add CESINEL device ids Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 03/61] USB: serial: cp210x: add Silicon Labs IDs for Windows Update Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 04/61] usb: dwc2: fix the incorrect bitmaps for the ports of multi_tt hub Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 05/61] acpi: Add helper for deactivating memory region Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 06/61] usb: typec: ucsi: acpi: Workaround for cache mode issue Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 07/61] usb: typec: ucsi: Fix for incorrect status data issue Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 08/61] xhci: Fix kernel oops in trace_xhci_free_virt_device Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 09/61] n_tty: Fix stall at n_tty_receive_char_special() Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 10/61] n_tty: Access echo_* variables carefully Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 11/61] staging: android: ion: Return an ERR_PTR in ion_map_kernel Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 12/61] serial: 8250_pci: Remove stalled entries in blacklist Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 13/61] serdev: fix memleak on module unload Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 14/61] vt: prevent leaking uninitialized data to userspace via /dev/vcs* Greg Kroah-Hartman
2018-07-06  5:52   ` syzbot
2018-07-06  5:46 ` [PATCH 4.14 18/61] drm/qxl: Call qxl_bo_unref outside atomic context Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 19/61] drm/atmel-hlcdc: check stride values in the first plane Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 22/61] drm/i915: Enable provoking vertex fix on Gen9 systems Greg Kroah-Hartman
2018-07-06  5:46 ` Greg Kroah-Hartman [this message]
2018-07-06  5:46 ` [PATCH 4.14 24/61] netfilter: nft_compat: prepare for indirect info storage Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 25/61] netfilter: nft_compat: fix handling of large matchinfo size Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 26/61] netfilter: nf_tables: dont assume chain stats are set when jumplabel is set Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 27/61] netfilter: nf_tables: bogus EBUSY in chain deletions Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 28/61] netfilter: nft_meta: fix wrong value dereference in nft_meta_set_eval Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 29/61] netfilter: nf_tables: disable preemption in nft_update_chain_stats() Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 30/61] netfilter: nf_tables: increase nft_counters_enabled in nft_chain_stats_replace() Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 31/61] netfilter: nf_tables: fix memory leak on error exit return Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 32/61] netfilter: nf_tables: add missing netlink attrs to policies Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 33/61] netfilter: nf_tables: fix NULL-ptr in nf_tables_dump_obj() Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 34/61] md: always hold reconfig_mutex when calling mddev_suspend() Greg Kroah-Hartman
2018-07-06  5:46 ` [PATCH 4.14 35/61] md: dont call bitmap_create() while array is quiesced Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 36/61] md: move suspend_hi/lo handling into core md code Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 37/61] md: use mddev_suspend/resume instead of ->quiesce() Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 38/61] md: allow metadata update while suspending Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 39/61] md: remove special meaning of ->quiesce(.., 2) Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 40/61] netfilter: dont set F_IFACE on ipv6 fib lookups Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 41/61] netfilter: ip6t_rpfilter: provide input interface for route lookup Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 42/61] netfilter: nf_tables: use WARN_ON_ONCE instead of BUG_ON in nft_do_chain() Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 43/61] ARM: dts: imx6q: Use correct SDMA script for SPI5 core Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 44/61] mtd: rawnand: fix return value check for bad block status Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 46/61] afs: Fix directory permissions check Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 47/61] netfilter: ebtables: handle string from userspace with care Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 48/61] s390/dasd: use blk_mq_rq_from_pdu for per request data Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 49/61] netfilter: nft_limit: fix packet ratelimiting Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 50/61] ipvs: fix buffer overflow with sync daemon and service Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 51/61] iwlwifi: pcie: compare with number of IRQs requested for, not number of CPUs Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 52/61] atm: zatm: fix memcmp casting Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 54/61] perf test: "Session topology" dumps core on s390 Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 55/61] perf bpf: Fix NULL return handling in bpf__prepare_load() Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 56/61] fs: clear writeback errors in inode_init_always Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 57/61] sched/core: Fix rules for running on online && !active CPUs Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 58/61] sched/core: Require cpu_active() in select_task_rq(), for user tasks Greg Kroah-Hartman
2018-07-06  5:47 ` [PATCH 4.14 60/61] net/sonic: Use dma_mapping_error() Greg Kroah-Hartman
2018-07-06 17:54 ` [PATCH 4.14 00/61] 4.14.54-stable review Dan Rue
2018-07-07 21:39 ` Guenter Roeck
2018-07-08 13:29   ` Greg Kroah-Hartman
2018-07-09 13:28     ` Guenter Roeck

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20180706054713.227711060@linuxfoundation.org \
    --to=gregkh@linuxfoundation.org \
    --cc=ap420073@gmail.com \
    --cc=fw@strlen.de \
    --cc=linux-kernel@vger.kernel.org \
    --cc=pablo@netfilter.org \
    --cc=stable@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).