Skip to content

Commit b8e9dc1

Browse files
Florian Westphalummakynes
authored andcommitted
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 <[email protected]> Reported-by: Taehee Yoo <[email protected]> Signed-off-by: Florian Westphal <[email protected]> Signed-off-by: Pablo Neira Ayuso <[email protected]>
1 parent a499568 commit b8e9dc1

File tree

1 file changed

+58
-34
lines changed

1 file changed

+58
-34
lines changed

net/netfilter/nft_compat.c

Lines changed: 58 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,24 @@ struct nft_xt {
2727
struct list_head head;
2828
struct nft_expr_ops ops;
2929
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;
3037
};
3138

32-
static void nft_xt_put(struct nft_xt *xt)
39+
static bool nft_xt_put(struct nft_xt *xt)
3340
{
3441
if (--xt->refcnt == 0) {
3542
list_del(&xt->head);
36-
kfree(xt);
43+
kfree_rcu(xt, rcu_head);
44+
return true;
3745
}
46+
47+
return false;
3848
}
3949

4050
static 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,
226236
struct xt_target *target = expr->ops->data;
227237
struct xt_tgchk_param par;
228238
size_t size = XT_ALIGN(nla_len(tb[NFTA_TARGET_INFO]));
239+
struct nft_xt *nft_xt;
229240
u16 proto = 0;
230241
bool inv = false;
231242
union nft_entry e = {};
@@ -236,25 +247,22 @@ nft_target_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
236247
if (ctx->nla[NFTA_RULE_COMPAT]) {
237248
ret = nft_parse_compat(ctx->nla[NFTA_RULE_COMPAT], &proto, &inv);
238249
if (ret < 0)
239-
goto err;
250+
return ret;
240251
}
241252

242253
nft_target_set_tgchk_param(&par, ctx, target, info, &e, proto, inv);
243254

244255
ret = xt_check_target(&par, size, proto, inv);
245256
if (ret < 0)
246-
goto err;
257+
return ret;
247258

248259
/* The standard target cannot be used */
249-
if (target->target == NULL) {
250-
ret = -EINVAL;
251-
goto err;
252-
}
260+
if (!target->target)
261+
return -EINVAL;
253262

263+
nft_xt = container_of(expr->ops, struct nft_xt, ops);
264+
nft_xt->refcnt++;
254265
return 0;
255-
err:
256-
module_put(target->me);
257-
return ret;
258266
}
259267

260268
static void
@@ -271,8 +279,8 @@ nft_target_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
271279
if (par.target->destroy != NULL)
272280
par.target->destroy(&par);
273281

274-
nft_xt_put(container_of(expr->ops, struct nft_xt, ops));
275-
module_put(target->me);
282+
if (nft_xt_put(container_of(expr->ops, struct nft_xt, ops)))
283+
module_put(target->me);
276284
}
277285

278286
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, const struct nft_expr *expr,
411419
struct xt_match *match = expr->ops->data;
412420
struct xt_mtchk_param par;
413421
size_t size = XT_ALIGN(nla_len(tb[NFTA_MATCH_INFO]));
422+
struct nft_xt *nft_xt;
414423
u16 proto = 0;
415424
bool inv = false;
416425
union nft_entry e = {};
@@ -421,19 +430,18 @@ nft_match_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
421430
if (ctx->nla[NFTA_RULE_COMPAT]) {
422431
ret = nft_parse_compat(ctx->nla[NFTA_RULE_COMPAT], &proto, &inv);
423432
if (ret < 0)
424-
goto err;
433+
return ret;
425434
}
426435

427436
nft_match_set_mtchk_param(&par, ctx, match, info, &e, proto, inv);
428437

429438
ret = xt_check_match(&par, size, proto, inv);
430439
if (ret < 0)
431-
goto err;
440+
return ret;
432441

442+
nft_xt = container_of(expr->ops, struct nft_xt, ops);
443+
nft_xt->refcnt++;
433444
return 0;
434-
err:
435-
module_put(match->me);
436-
return ret;
437445
}
438446

439447
static void
@@ -450,8 +458,8 @@ nft_match_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
450458
if (par.match->destroy != NULL)
451459
par.match->destroy(&par);
452460

453-
nft_xt_put(container_of(expr->ops, struct nft_xt, ops));
454-
module_put(match->me);
461+
if (nft_xt_put(container_of(expr->ops, struct nft_xt, ops)))
462+
module_put(match->me);
455463
}
456464

457465
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_ctx *ctx,
654662
list_for_each_entry(nft_match, &nft_match_list, head) {
655663
struct xt_match *match = nft_match->ops.data;
656664

657-
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++;
665+
if (nft_match_cmp(match, mt_name, rev, family))
662666
return &nft_match->ops;
663-
}
664667
}
665668

666669
match = xt_request_find_match(family, mt_name, rev);
@@ -679,7 +682,7 @@ nft_match_select_ops(const struct nft_ctx *ctx,
679682
goto err;
680683
}
681684

682-
nft_match->refcnt = 1;
685+
nft_match->refcnt = 0;
683686
nft_match->ops.type = &nft_match_type;
684687
nft_match->ops.size = NFT_EXPR_SIZE(XT_ALIGN(match->matchsize));
685688
nft_match->ops.eval = nft_match_eval;
@@ -739,13 +742,8 @@ nft_target_select_ops(const struct nft_ctx *ctx,
739742
list_for_each_entry(nft_target, &nft_target_list, head) {
740743
struct xt_target *target = nft_target->ops.data;
741744

742-
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++;
745+
if (nft_target_cmp(target, tg_name, rev, family))
747746
return &nft_target->ops;
748-
}
749747
}
750748

751749
target = xt_request_find_target(family, tg_name, rev);
@@ -764,7 +762,7 @@ nft_target_select_ops(const struct nft_ctx *ctx,
764762
goto err;
765763
}
766764

767-
nft_target->refcnt = 1;
765+
nft_target->refcnt = 0;
768766
nft_target->ops.type = &nft_target_type;
769767
nft_target->ops.size = NFT_EXPR_SIZE(XT_ALIGN(target->targetsize));
770768
nft_target->ops.init = nft_target_init;
@@ -823,6 +821,32 @@ static int __init nft_compat_module_init(void)
823821

824822
static void __exit nft_compat_module_exit(void)
825823
{
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+
}
826850
nfnetlink_subsys_unregister(&nfnl_compat_subsys);
827851
nft_unregister_expr(&nft_target_type);
828852
nft_unregister_expr(&nft_match_type);

0 commit comments

Comments
 (0)