aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFlorian Westphal <fw@strlen.de>2018-05-02 08:07:42 -0400
committerPablo Neira Ayuso <pablo@netfilter.org>2018-05-08 08:08:21 -0400
commitb8e9dc1c75714ceb53615743e1036f76e00f5a17 (patch)
treed873fa63ae6d0dd9a3dbf9f04be0251ba2eb7416
parenta4995684a949cc1d28fbf09900c47c34b9427ecf (diff)
netfilter: nf_tables: nft_compat: fix refcount leak on xt module
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>
-rw-r--r--net/netfilter/nft_compat.c92
1 files changed, 58 insertions, 34 deletions
diff --git a/net/netfilter/nft_compat.c b/net/netfilter/nft_compat.c
index 8e23726b9081..870d8c29dae9 100644
--- a/net/netfilter/nft_compat.c
+++ b/net/netfilter/nft_compat.c
@@ -27,14 +27,24 @@ struct nft_xt {
27 struct list_head head; 27 struct list_head head;
28 struct nft_expr_ops ops; 28 struct nft_expr_ops ops;
29 unsigned int refcnt; 29 unsigned int refcnt;
30
31 /* Unlike other expressions, ops doesn't have static storage duration.
32 * nft core assumes they do. We use kfree_rcu so that nft core can
33 * can check expr->ops->size even after nft_compat->destroy() frees
34 * the nft_xt struct that holds the ops structure.
35 */
36 struct rcu_head rcu_head;
30}; 37};
31 38
32static void nft_xt_put(struct nft_xt *xt) 39static bool nft_xt_put(struct nft_xt *xt)
33{ 40{
34 if (--xt->refcnt == 0) { 41 if (--xt->refcnt == 0) {
35 list_del(&xt->head); 42 list_del(&xt->head);
36 kfree(xt); 43 kfree_rcu(xt, rcu_head);
44 return true;
37 } 45 }
46
47 return false;
38} 48}
39 49
40static int nft_compat_chain_validate_dependency(const char *tablename, 50static int nft_compat_chain_validate_dependency(const char *tablename,
@@ -226,6 +236,7 @@ nft_target_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
226 struct xt_target *target = expr->ops->data; 236 struct xt_target *target = expr->ops->data;
227 struct xt_tgchk_param par; 237 struct xt_tgchk_param par;
228 size_t size = XT_ALIGN(nla_len(tb[NFTA_TARGET_INFO])); 238 size_t size = XT_ALIGN(nla_len(tb[NFTA_TARGET_INFO]));
239 struct nft_xt *nft_xt;
229 u16 proto = 0; 240 u16 proto = 0;
230 bool inv = false; 241 bool inv = false;
231 union nft_entry e = {}; 242 union nft_entry e = {};
@@ -236,25 +247,22 @@ nft_target_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
236 if (ctx->nla[NFTA_RULE_COMPAT]) { 247 if (ctx->nla[NFTA_RULE_COMPAT]) {
237 ret = nft_parse_compat(ctx->nla[NFTA_RULE_COMPAT], &proto, &inv); 248 ret = nft_parse_compat(ctx->nla[NFTA_RULE_COMPAT], &proto, &inv);
238 if (ret < 0) 249 if (ret < 0)
239 goto err; 250 return ret;
240 } 251 }
241 252
242 nft_target_set_tgchk_param(&par, ctx, target, info, &e, proto, inv); 253 nft_target_set_tgchk_param(&par, ctx, target, info, &e, proto, inv);
243 254
244 ret = xt_check_target(&par, size, proto, inv); 255 ret = xt_check_target(&par, size, proto, inv);
245 if (ret < 0) 256 if (ret < 0)
246 goto err; 257 return ret;
247 258
248 /* The standard target cannot be used */ 259 /* The standard target cannot be used */
249 if (target->target == NULL) { 260 if (!target->target)
250 ret = -EINVAL; 261 return -EINVAL;
251 goto err;
252 }
253 262
263 nft_xt = container_of(expr->ops, struct nft_xt, ops);
264 nft_xt->refcnt++;
254 return 0; 265 return 0;
255err:
256 module_put(target->me);
257 return ret;
258} 266}
259 267
260static void 268static void
@@ -271,8 +279,8 @@ nft_target_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
271 if (par.target->destroy != NULL) 279 if (par.target->destroy != NULL)
272 par.target->destroy(&par); 280 par.target->destroy(&par);
273 281
274 nft_xt_put(container_of(expr->ops, struct nft_xt, ops)); 282 if (nft_xt_put(container_of(expr->ops, struct nft_xt, ops)))
275 module_put(target->me); 283 module_put(target->me);
276} 284}
277 285
278static int nft_target_dump(struct sk_buff *skb, const struct nft_expr *expr) 286static 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, const struct nft_expr *expr,
411 struct xt_match *match = expr->ops->data; 419 struct xt_match *match = expr->ops->data;
412 struct xt_mtchk_param par; 420 struct xt_mtchk_param par;
413 size_t size = XT_ALIGN(nla_len(tb[NFTA_MATCH_INFO])); 421 size_t size = XT_ALIGN(nla_len(tb[NFTA_MATCH_INFO]));
422 struct nft_xt *nft_xt;
414 u16 proto = 0; 423 u16 proto = 0;
415 bool inv = false; 424 bool inv = false;
416 union nft_entry e = {}; 425 union nft_entry e = {};
@@ -421,19 +430,18 @@ nft_match_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
421 if (ctx->nla[NFTA_RULE_COMPAT]) { 430 if (ctx->nla[NFTA_RULE_COMPAT]) {
422 ret = nft_parse_compat(ctx->nla[NFTA_RULE_COMPAT], &proto, &inv); 431 ret = nft_parse_compat(ctx->nla[NFTA_RULE_COMPAT], &proto, &inv);
423 if (ret < 0) 432 if (ret < 0)
424 goto err; 433 return ret;
425 } 434 }
426 435
427 nft_match_set_mtchk_param(&par, ctx, match, info, &e, proto, inv); 436 nft_match_set_mtchk_param(&par, ctx, match, info, &e, proto, inv);
428 437
429 ret = xt_check_match(&par, size, proto, inv); 438 ret = xt_check_match(&par, size, proto, inv);
430 if (ret < 0) 439 if (ret < 0)
431 goto err; 440 return ret;
432 441
442 nft_xt = container_of(expr->ops, struct nft_xt, ops);
443 nft_xt->refcnt++;
433 return 0; 444 return 0;
434err:
435 module_put(match->me);
436 return ret;
437} 445}
438 446
439static void 447static void
@@ -450,8 +458,8 @@ nft_match_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
450 if (par.match->destroy != NULL) 458 if (par.match->destroy != NULL)
451 par.match->destroy(&par); 459 par.match->destroy(&par);
452 460
453 nft_xt_put(container_of(expr->ops, struct nft_xt, ops)); 461 if (nft_xt_put(container_of(expr->ops, struct nft_xt, ops)))
454 module_put(match->me); 462 module_put(match->me);
455} 463}
456 464
457static int nft_match_dump(struct sk_buff *skb, const struct nft_expr *expr) 465static int nft_match_dump(struct sk_buff *skb, const struct nft_expr *expr)
@@ -654,13 +662,8 @@ nft_match_select_ops(const struct nft_ctx *ctx,
654 list_for_each_entry(nft_match, &nft_match_list, head) { 662 list_for_each_entry(nft_match, &nft_match_list, head) {
655 struct xt_match *match = nft_match->ops.data; 663 struct xt_match *match = nft_match->ops.data;
656 664
657 if (nft_match_cmp(match, mt_name, rev, family)) { 665 if (nft_match_cmp(match, mt_name, rev, family))
658 if (!try_module_get(match->me))
659 return ERR_PTR(-ENOENT);
660
661 nft_match->refcnt++;
662 return &nft_match->ops; 666 return &nft_match->ops;
663 }
664 } 667 }
665 668
666 match = xt_request_find_match(family, mt_name, rev); 669 match = xt_request_find_match(family, mt_name, rev);
@@ -679,7 +682,7 @@ nft_match_select_ops(const struct nft_ctx *ctx,
679 goto err; 682 goto err;
680 } 683 }
681 684
682 nft_match->refcnt = 1; 685 nft_match->refcnt = 0;
683 nft_match->ops.type = &nft_match_type; 686 nft_match->ops.type = &nft_match_type;
684 nft_match->ops.size = NFT_EXPR_SIZE(XT_ALIGN(match->matchsize)); 687 nft_match->ops.size = NFT_EXPR_SIZE(XT_ALIGN(match->matchsize));
685 nft_match->ops.eval = nft_match_eval; 688 nft_match->ops.eval = nft_match_eval;
@@ -739,13 +742,8 @@ nft_target_select_ops(const struct nft_ctx *ctx,
739 list_for_each_entry(nft_target, &nft_target_list, head) { 742 list_for_each_entry(nft_target, &nft_target_list, head) {
740 struct xt_target *target = nft_target->ops.data; 743 struct xt_target *target = nft_target->ops.data;
741 744
742 if (nft_target_cmp(target, tg_name, rev, family)) { 745 if (nft_target_cmp(target, tg_name, rev, family))
743 if (!try_module_get(target->me))
744 return ERR_PTR(-ENOENT);
745
746 nft_target->refcnt++;
747 return &nft_target->ops; 746 return &nft_target->ops;
748 }
749 } 747 }
750 748
751 target = xt_request_find_target(family, tg_name, rev); 749 target = xt_request_find_target(family, tg_name, rev);
@@ -764,7 +762,7 @@ nft_target_select_ops(const struct nft_ctx *ctx,
764 goto err; 762 goto err;
765 } 763 }
766 764
767 nft_target->refcnt = 1; 765 nft_target->refcnt = 0;
768 nft_target->ops.type = &nft_target_type; 766 nft_target->ops.type = &nft_target_type;
769 nft_target->ops.size = NFT_EXPR_SIZE(XT_ALIGN(target->targetsize)); 767 nft_target->ops.size = NFT_EXPR_SIZE(XT_ALIGN(target->targetsize));
770 nft_target->ops.init = nft_target_init; 768 nft_target->ops.init = nft_target_init;
@@ -823,6 +821,32 @@ err_match:
823 821
824static void __exit nft_compat_module_exit(void) 822static void __exit nft_compat_module_exit(void)
825{ 823{
824 struct nft_xt *xt, *next;
825
826 /* list should be empty here, it can be non-empty only in case there
827 * was an error that caused nft_xt expr to not be initialized fully
828 * and noone else requested the same expression later.
829 *
830 * In this case, the lists contain 0-refcount entries that still
831 * hold module reference.
832 */
833 list_for_each_entry_safe(xt, next, &nft_target_list, head) {
834 struct xt_target *target = xt->ops.data;
835
836 if (WARN_ON_ONCE(xt->refcnt))
837 continue;
838 module_put(target->me);
839 kfree(xt);
840 }
841
842 list_for_each_entry_safe(xt, next, &nft_match_list, head) {
843 struct xt_match *match = xt->ops.data;
844
845 if (WARN_ON_ONCE(xt->refcnt))
846 continue;
847 module_put(match->me);
848 kfree(xt);
849 }
826 nfnetlink_subsys_unregister(&nfnl_compat_subsys); 850 nfnetlink_subsys_unregister(&nfnl_compat_subsys);
827 nft_unregister_expr(&nft_target_type); 851 nft_unregister_expr(&nft_target_type);
828 nft_unregister_expr(&nft_match_type); 852 nft_unregister_expr(&nft_match_type);