Skip to content

Commit cf52572

Browse files
Florian Westphalummakynes
authored andcommitted
netfilter: nft_compat: make lists per netns
There are two problems with nft_compat since the netlink config plane uses a per-netns mutex: 1. Concurrent add/del accesses to the same list 2. accesses to a list element after it has been free'd already. This patch fixes the first problem. Freeing occurs from a work queue, after transaction mutexes have been released, i.e., it still possible for a new transaction (even from same net ns) to find the to-be-deleted expression in the list. The ->destroy functions are not allowed to have any such side effects, i.e. the list_del() in the destroy function is not allowed. This part of the problem is solved in the next patch. I tried to make this work by serializing list access via mutex and by moving list_del() to a deactivate callback, but Taehee spotted following race on this approach: NET #0 NET #1 >select_ops() ->init() ->select_ops() ->deactivate() ->destroy() nft_xt_put() kfree_rcu(xt, rcu_head); ->init() <-- use-after-free occurred. Unfortunately, we can't increment reference count in select_ops(), because we can't undo the refcount increase in case a different expression fails in the same batch. (The destroy hook will only be called in case the expression was initialized successfully). Fixes: f102d66 ("netfilter: nf_tables: use dedicated mutex to guard transactions") Reported-by: Taehee Yoo <[email protected]> Signed-off-by: Florian Westphal <[email protected]> Signed-off-by: Pablo Neira Ayuso <[email protected]>
1 parent 12c44ab commit cf52572

File tree

1 file changed

+89
-40
lines changed

1 file changed

+89
-40
lines changed

net/netfilter/nft_compat.c

Lines changed: 89 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <linux/netfilter_bridge/ebtables.h>
2323
#include <linux/netfilter_arp/arp_tables.h>
2424
#include <net/netfilter/nf_tables.h>
25+
#include <net/netns/generic.h>
2526

2627
struct nft_xt {
2728
struct list_head head;
@@ -43,6 +44,20 @@ struct nft_xt_match_priv {
4344
void *info;
4445
};
4546

47+
struct nft_compat_net {
48+
struct list_head nft_target_list;
49+
struct list_head nft_match_list;
50+
};
51+
52+
static unsigned int nft_compat_net_id __read_mostly;
53+
static struct nft_expr_type nft_match_type;
54+
static struct nft_expr_type nft_target_type;
55+
56+
static struct nft_compat_net *nft_compat_pernet(struct net *net)
57+
{
58+
return net_generic(net, nft_compat_net_id);
59+
}
60+
4661
static bool nft_xt_put(struct nft_xt *xt)
4762
{
4863
if (refcount_dec_and_test(&xt->refcnt)) {
@@ -734,10 +749,6 @@ static const struct nfnetlink_subsystem nfnl_compat_subsys = {
734749
.cb = nfnl_nft_compat_cb,
735750
};
736751

737-
static LIST_HEAD(nft_match_list);
738-
739-
static struct nft_expr_type nft_match_type;
740-
741752
static bool nft_match_cmp(const struct xt_match *match,
742753
const char *name, u32 rev, u32 family)
743754
{
@@ -749,6 +760,7 @@ static const struct nft_expr_ops *
749760
nft_match_select_ops(const struct nft_ctx *ctx,
750761
const struct nlattr * const tb[])
751762
{
763+
struct nft_compat_net *cn;
752764
struct nft_xt *nft_match;
753765
struct xt_match *match;
754766
unsigned int matchsize;
@@ -765,8 +777,10 @@ nft_match_select_ops(const struct nft_ctx *ctx,
765777
rev = ntohl(nla_get_be32(tb[NFTA_MATCH_REV]));
766778
family = ctx->family;
767779

780+
cn = nft_compat_pernet(ctx->net);
781+
768782
/* Re-use the existing match if it's already loaded. */
769-
list_for_each_entry(nft_match, &nft_match_list, head) {
783+
list_for_each_entry(nft_match, &cn->nft_match_list, head) {
770784
struct xt_match *match = nft_match->ops.data;
771785

772786
if (nft_match_cmp(match, mt_name, rev, family))
@@ -810,7 +824,7 @@ nft_match_select_ops(const struct nft_ctx *ctx,
810824

811825
nft_match->ops.size = matchsize;
812826

813-
list_add(&nft_match->head, &nft_match_list);
827+
list_add(&nft_match->head, &cn->nft_match_list);
814828

815829
return &nft_match->ops;
816830
err:
@@ -826,10 +840,6 @@ static struct nft_expr_type nft_match_type __read_mostly = {
826840
.owner = THIS_MODULE,
827841
};
828842

829-
static LIST_HEAD(nft_target_list);
830-
831-
static struct nft_expr_type nft_target_type;
832-
833843
static bool nft_target_cmp(const struct xt_target *tg,
834844
const char *name, u32 rev, u32 family)
835845
{
@@ -841,6 +851,7 @@ static const struct nft_expr_ops *
841851
nft_target_select_ops(const struct nft_ctx *ctx,
842852
const struct nlattr * const tb[])
843853
{
854+
struct nft_compat_net *cn;
844855
struct nft_xt *nft_target;
845856
struct xt_target *target;
846857
char *tg_name;
@@ -861,8 +872,9 @@ nft_target_select_ops(const struct nft_ctx *ctx,
861872
strcmp(tg_name, "standard") == 0)
862873
return ERR_PTR(-EINVAL);
863874

875+
cn = nft_compat_pernet(ctx->net);
864876
/* Re-use the existing target if it's already loaded. */
865-
list_for_each_entry(nft_target, &nft_target_list, head) {
877+
list_for_each_entry(nft_target, &cn->nft_target_list, head) {
866878
struct xt_target *target = nft_target->ops.data;
867879

868880
if (!target->target)
@@ -907,7 +919,7 @@ nft_target_select_ops(const struct nft_ctx *ctx,
907919
else
908920
nft_target->ops.eval = nft_target_eval_xt;
909921

910-
list_add(&nft_target->head, &nft_target_list);
922+
list_add(&nft_target->head, &cn->nft_target_list);
911923

912924
return &nft_target->ops;
913925
err:
@@ -923,13 +935,74 @@ static struct nft_expr_type nft_target_type __read_mostly = {
923935
.owner = THIS_MODULE,
924936
};
925937

938+
static int __net_init nft_compat_init_net(struct net *net)
939+
{
940+
struct nft_compat_net *cn = nft_compat_pernet(net);
941+
942+
INIT_LIST_HEAD(&cn->nft_target_list);
943+
INIT_LIST_HEAD(&cn->nft_match_list);
944+
945+
return 0;
946+
}
947+
948+
static void __net_exit nft_compat_exit_net(struct net *net)
949+
{
950+
struct nft_compat_net *cn = nft_compat_pernet(net);
951+
struct nft_xt *xt, *next;
952+
953+
if (list_empty(&cn->nft_match_list) &&
954+
list_empty(&cn->nft_target_list))
955+
return;
956+
957+
/* If there was an error that caused nft_xt expr to not be initialized
958+
* fully and noone else requested the same expression later, the lists
959+
* contain 0-refcount entries that still hold module reference.
960+
*
961+
* Clean them here.
962+
*/
963+
mutex_lock(&net->nft.commit_mutex);
964+
list_for_each_entry_safe(xt, next, &cn->nft_target_list, head) {
965+
struct xt_target *target = xt->ops.data;
966+
967+
list_del_init(&xt->head);
968+
969+
if (refcount_read(&xt->refcnt))
970+
continue;
971+
module_put(target->me);
972+
kfree(xt);
973+
}
974+
975+
list_for_each_entry_safe(xt, next, &cn->nft_match_list, head) {
976+
struct xt_match *match = xt->ops.data;
977+
978+
list_del_init(&xt->head);
979+
980+
if (refcount_read(&xt->refcnt))
981+
continue;
982+
module_put(match->me);
983+
kfree(xt);
984+
}
985+
mutex_unlock(&net->nft.commit_mutex);
986+
}
987+
988+
static struct pernet_operations nft_compat_net_ops = {
989+
.init = nft_compat_init_net,
990+
.exit = nft_compat_exit_net,
991+
.id = &nft_compat_net_id,
992+
.size = sizeof(struct nft_compat_net),
993+
};
994+
926995
static int __init nft_compat_module_init(void)
927996
{
928997
int ret;
929998

999+
ret = register_pernet_subsys(&nft_compat_net_ops);
1000+
if (ret < 0)
1001+
goto err_target;
1002+
9301003
ret = nft_register_expr(&nft_match_type);
9311004
if (ret < 0)
932-
return ret;
1005+
goto err_pernet;
9331006

9341007
ret = nft_register_expr(&nft_target_type);
9351008
if (ret < 0)
@@ -942,45 +1015,21 @@ static int __init nft_compat_module_init(void)
9421015
}
9431016

9441017
return ret;
945-
9461018
err_target:
9471019
nft_unregister_expr(&nft_target_type);
9481020
err_match:
9491021
nft_unregister_expr(&nft_match_type);
1022+
err_pernet:
1023+
unregister_pernet_subsys(&nft_compat_net_ops);
9501024
return ret;
9511025
}
9521026

9531027
static void __exit nft_compat_module_exit(void)
9541028
{
955-
struct nft_xt *xt, *next;
956-
957-
/* list should be empty here, it can be non-empty only in case there
958-
* was an error that caused nft_xt expr to not be initialized fully
959-
* and noone else requested the same expression later.
960-
*
961-
* In this case, the lists contain 0-refcount entries that still
962-
* hold module reference.
963-
*/
964-
list_for_each_entry_safe(xt, next, &nft_target_list, head) {
965-
struct xt_target *target = xt->ops.data;
966-
967-
if (WARN_ON_ONCE(refcount_read(&xt->refcnt)))
968-
continue;
969-
module_put(target->me);
970-
kfree(xt);
971-
}
972-
973-
list_for_each_entry_safe(xt, next, &nft_match_list, head) {
974-
struct xt_match *match = xt->ops.data;
975-
976-
if (WARN_ON_ONCE(refcount_read(&xt->refcnt)))
977-
continue;
978-
module_put(match->me);
979-
kfree(xt);
980-
}
9811029
nfnetlink_subsys_unregister(&nfnl_compat_subsys);
9821030
nft_unregister_expr(&nft_target_type);
9831031
nft_unregister_expr(&nft_match_type);
1032+
unregister_pernet_subsys(&nft_compat_net_ops);
9841033
}
9851034

9861035
MODULE_ALIAS_NFNL_SUBSYS(NFNL_SUBSYS_NFT_COMPAT);

0 commit comments

Comments
 (0)