Skip to content

Commit eb014de

Browse files
committed
netfilter: nf_tables: autoload modules from the abort path
This patch introduces a list of pending module requests. This new module list is composed of nft_module_request objects that contain the module name and one status field that tells if the module has been already loaded (the 'done' field). In the first pass, from the preparation phase, the netlink command finds that a module is missing on this list. Then, a module request is allocated and added to this list and nft_request_module() returns -EAGAIN. This triggers the abort path with the autoload parameter set on from nfnetlink, request_module() is called and the module request enters the 'done' state. Since the mutex is released when loading modules from the abort phase, the module list is zapped so this is iteration occurs over a local list. Therefore, the request_module() calls happen when object lists are in consistent state (after fulling aborting the transaction) and the commit list is empty. On the second pass, the netlink command will find that it already tried to load the module, so it does not request it again and nft_request_module() returns 0. Then, there is a look up to find the object that the command was missing. If the module was successfully loaded, the command proceeds normally since it finds the missing object in place, otherwise -ENOENT is reported to userspace. This patch also updates nfnetlink to include the reason to enter the abort phase, which is required for this new autoload module rationale. Fixes: ec7470b ("netfilter: nf_tables: store transaction list locally while requesting module") Reported-by: [email protected] Signed-off-by: Pablo Neira Ayuso <[email protected]>
1 parent 8260354 commit eb014de

File tree

4 files changed

+91
-44
lines changed

4 files changed

+91
-44
lines changed

include/linux/netfilter/nfnetlink.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ struct nfnetlink_subsystem {
3131
const struct nfnl_callback *cb; /* callback for individual types */
3232
struct module *owner;
3333
int (*commit)(struct net *net, struct sk_buff *skb);
34-
int (*abort)(struct net *net, struct sk_buff *skb);
34+
int (*abort)(struct net *net, struct sk_buff *skb, bool autoload);
3535
void (*cleanup)(struct net *net);
3636
bool (*valid_genid)(struct net *net, u32 genid);
3737
};

include/net/netns/nftables.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
struct netns_nftables {
88
struct list_head tables;
99
struct list_head commit_list;
10+
struct list_head module_list;
1011
struct mutex commit_mutex;
1112
unsigned int base_seq;
1213
u8 gencursor;

net/netfilter/nf_tables_api.c

Lines changed: 86 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -578,35 +578,45 @@ __nf_tables_chain_type_lookup(const struct nlattr *nla, u8 family)
578578
return NULL;
579579
}
580580

581-
/*
582-
* Loading a module requires dropping mutex that guards the transaction.
583-
* A different client might race to start a new transaction meanwhile. Zap the
584-
* list of pending transaction and then restore it once the mutex is grabbed
585-
* again. Users of this function return EAGAIN which implicitly triggers the
586-
* transaction abort path to clean up the list of pending transactions.
587-
*/
581+
struct nft_module_request {
582+
struct list_head list;
583+
char module[MODULE_NAME_LEN];
584+
bool done;
585+
};
586+
588587
#ifdef CONFIG_MODULES
589-
static void nft_request_module(struct net *net, const char *fmt, ...)
588+
static int nft_request_module(struct net *net, const char *fmt, ...)
590589
{
591590
char module_name[MODULE_NAME_LEN];
592-
LIST_HEAD(commit_list);
591+
struct nft_module_request *req;
593592
va_list args;
594593
int ret;
595594

596-
list_splice_init(&net->nft.commit_list, &commit_list);
597-
598595
va_start(args, fmt);
599596
ret = vsnprintf(module_name, MODULE_NAME_LEN, fmt, args);
600597
va_end(args);
601598
if (ret >= MODULE_NAME_LEN)
602-
return;
599+
return 0;
603600

604-
mutex_unlock(&net->nft.commit_mutex);
605-
request_module("%s", module_name);
606-
mutex_lock(&net->nft.commit_mutex);
601+
list_for_each_entry(req, &net->nft.module_list, list) {
602+
if (!strcmp(req->module, module_name)) {
603+
if (req->done)
604+
return 0;
607605

608-
WARN_ON_ONCE(!list_empty(&net->nft.commit_list));
609-
list_splice(&commit_list, &net->nft.commit_list);
606+
/* A request to load this module already exists. */
607+
return -EAGAIN;
608+
}
609+
}
610+
611+
req = kmalloc(sizeof(*req), GFP_KERNEL);
612+
if (!req)
613+
return -ENOMEM;
614+
615+
req->done = false;
616+
strlcpy(req->module, module_name, MODULE_NAME_LEN);
617+
list_add_tail(&req->list, &net->nft.module_list);
618+
619+
return -EAGAIN;
610620
}
611621
#endif
612622

@@ -630,10 +640,9 @@ nf_tables_chain_type_lookup(struct net *net, const struct nlattr *nla,
630640
lockdep_nfnl_nft_mutex_not_held();
631641
#ifdef CONFIG_MODULES
632642
if (autoload) {
633-
nft_request_module(net, "nft-chain-%u-%.*s", family,
634-
nla_len(nla), (const char *)nla_data(nla));
635-
type = __nf_tables_chain_type_lookup(nla, family);
636-
if (type != NULL)
643+
if (nft_request_module(net, "nft-chain-%u-%.*s", family,
644+
nla_len(nla),
645+
(const char *)nla_data(nla)) == -EAGAIN)
637646
return ERR_PTR(-EAGAIN);
638647
}
639648
#endif
@@ -2341,9 +2350,8 @@ static const struct nft_expr_type *__nft_expr_type_get(u8 family,
23412350
static int nft_expr_type_request_module(struct net *net, u8 family,
23422351
struct nlattr *nla)
23432352
{
2344-
nft_request_module(net, "nft-expr-%u-%.*s", family,
2345-
nla_len(nla), (char *)nla_data(nla));
2346-
if (__nft_expr_type_get(family, nla))
2353+
if (nft_request_module(net, "nft-expr-%u-%.*s", family,
2354+
nla_len(nla), (char *)nla_data(nla)) == -EAGAIN)
23472355
return -EAGAIN;
23482356

23492357
return 0;
@@ -2369,9 +2377,9 @@ static const struct nft_expr_type *nft_expr_type_get(struct net *net,
23692377
if (nft_expr_type_request_module(net, family, nla) == -EAGAIN)
23702378
return ERR_PTR(-EAGAIN);
23712379

2372-
nft_request_module(net, "nft-expr-%.*s",
2373-
nla_len(nla), (char *)nla_data(nla));
2374-
if (__nft_expr_type_get(family, nla))
2380+
if (nft_request_module(net, "nft-expr-%.*s",
2381+
nla_len(nla),
2382+
(char *)nla_data(nla)) == -EAGAIN)
23752383
return ERR_PTR(-EAGAIN);
23762384
}
23772385
#endif
@@ -2462,9 +2470,10 @@ static int nf_tables_expr_parse(const struct nft_ctx *ctx,
24622470
err = PTR_ERR(ops);
24632471
#ifdef CONFIG_MODULES
24642472
if (err == -EAGAIN)
2465-
nft_expr_type_request_module(ctx->net,
2466-
ctx->family,
2467-
tb[NFTA_EXPR_NAME]);
2473+
if (nft_expr_type_request_module(ctx->net,
2474+
ctx->family,
2475+
tb[NFTA_EXPR_NAME]) != -EAGAIN)
2476+
err = -ENOENT;
24682477
#endif
24692478
goto err1;
24702479
}
@@ -3301,8 +3310,7 @@ nft_select_set_ops(const struct nft_ctx *ctx,
33013310
lockdep_nfnl_nft_mutex_not_held();
33023311
#ifdef CONFIG_MODULES
33033312
if (list_empty(&nf_tables_set_types)) {
3304-
nft_request_module(ctx->net, "nft-set");
3305-
if (!list_empty(&nf_tables_set_types))
3313+
if (nft_request_module(ctx->net, "nft-set") == -EAGAIN)
33063314
return ERR_PTR(-EAGAIN);
33073315
}
33083316
#endif
@@ -5428,8 +5436,7 @@ nft_obj_type_get(struct net *net, u32 objtype)
54285436
lockdep_nfnl_nft_mutex_not_held();
54295437
#ifdef CONFIG_MODULES
54305438
if (type == NULL) {
5431-
nft_request_module(net, "nft-obj-%u", objtype);
5432-
if (__nft_obj_type_get(objtype))
5439+
if (nft_request_module(net, "nft-obj-%u", objtype) == -EAGAIN)
54335440
return ERR_PTR(-EAGAIN);
54345441
}
54355442
#endif
@@ -6002,8 +6009,7 @@ nft_flowtable_type_get(struct net *net, u8 family)
60026009
lockdep_nfnl_nft_mutex_not_held();
60036010
#ifdef CONFIG_MODULES
60046011
if (type == NULL) {
6005-
nft_request_module(net, "nf-flowtable-%u", family);
6006-
if (__nft_flowtable_type_get(family))
6012+
if (nft_request_module(net, "nf-flowtable-%u", family) == -EAGAIN)
60076013
return ERR_PTR(-EAGAIN);
60086014
}
60096015
#endif
@@ -7005,6 +7011,18 @@ static void nft_chain_del(struct nft_chain *chain)
70057011
list_del_rcu(&chain->list);
70067012
}
70077013

7014+
static void nf_tables_module_autoload_cleanup(struct net *net)
7015+
{
7016+
struct nft_module_request *req, *next;
7017+
7018+
WARN_ON_ONCE(!list_empty(&net->nft.commit_list));
7019+
list_for_each_entry_safe(req, next, &net->nft.module_list, list) {
7020+
WARN_ON_ONCE(!req->done);
7021+
list_del(&req->list);
7022+
kfree(req);
7023+
}
7024+
}
7025+
70087026
static void nf_tables_commit_release(struct net *net)
70097027
{
70107028
struct nft_trans *trans;
@@ -7017,6 +7035,7 @@ static void nf_tables_commit_release(struct net *net)
70177035
* to prevent expensive synchronize_rcu() in commit phase.
70187036
*/
70197037
if (list_empty(&net->nft.commit_list)) {
7038+
nf_tables_module_autoload_cleanup(net);
70207039
mutex_unlock(&net->nft.commit_mutex);
70217040
return;
70227041
}
@@ -7031,6 +7050,7 @@ static void nf_tables_commit_release(struct net *net)
70317050
list_splice_tail_init(&net->nft.commit_list, &nf_tables_destroy_list);
70327051
spin_unlock(&nf_tables_destroy_list_lock);
70337052

7053+
nf_tables_module_autoload_cleanup(net);
70347054
mutex_unlock(&net->nft.commit_mutex);
70357055

70367056
schedule_work(&trans_destroy_work);
@@ -7222,6 +7242,26 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
72227242
return 0;
72237243
}
72247244

7245+
static void nf_tables_module_autoload(struct net *net)
7246+
{
7247+
struct nft_module_request *req, *next;
7248+
LIST_HEAD(module_list);
7249+
7250+
list_splice_init(&net->nft.module_list, &module_list);
7251+
mutex_unlock(&net->nft.commit_mutex);
7252+
list_for_each_entry_safe(req, next, &module_list, list) {
7253+
if (req->done) {
7254+
list_del(&req->list);
7255+
kfree(req);
7256+
} else {
7257+
request_module("%s", req->module);
7258+
req->done = true;
7259+
}
7260+
}
7261+
mutex_lock(&net->nft.commit_mutex);
7262+
list_splice(&module_list, &net->nft.module_list);
7263+
}
7264+
72257265
static void nf_tables_abort_release(struct nft_trans *trans)
72267266
{
72277267
switch (trans->msg_type) {
@@ -7251,7 +7291,7 @@ static void nf_tables_abort_release(struct nft_trans *trans)
72517291
kfree(trans);
72527292
}
72537293

7254-
static int __nf_tables_abort(struct net *net)
7294+
static int __nf_tables_abort(struct net *net, bool autoload)
72557295
{
72567296
struct nft_trans *trans, *next;
72577297
struct nft_trans_elem *te;
@@ -7373,6 +7413,11 @@ static int __nf_tables_abort(struct net *net)
73737413
nf_tables_abort_release(trans);
73747414
}
73757415

7416+
if (autoload)
7417+
nf_tables_module_autoload(net);
7418+
else
7419+
nf_tables_module_autoload_cleanup(net);
7420+
73767421
return 0;
73777422
}
73787423

@@ -7381,9 +7426,9 @@ static void nf_tables_cleanup(struct net *net)
73817426
nft_validate_state_update(net, NFT_VALIDATE_SKIP);
73827427
}
73837428

7384-
static int nf_tables_abort(struct net *net, struct sk_buff *skb)
7429+
static int nf_tables_abort(struct net *net, struct sk_buff *skb, bool autoload)
73857430
{
7386-
int ret = __nf_tables_abort(net);
7431+
int ret = __nf_tables_abort(net, autoload);
73877432

73887433
mutex_unlock(&net->nft.commit_mutex);
73897434

@@ -7978,6 +8023,7 @@ static int __net_init nf_tables_init_net(struct net *net)
79788023
{
79798024
INIT_LIST_HEAD(&net->nft.tables);
79808025
INIT_LIST_HEAD(&net->nft.commit_list);
8026+
INIT_LIST_HEAD(&net->nft.module_list);
79818027
mutex_init(&net->nft.commit_mutex);
79828028
net->nft.base_seq = 1;
79838029
net->nft.validate_state = NFT_VALIDATE_SKIP;
@@ -7989,7 +8035,7 @@ static void __net_exit nf_tables_exit_net(struct net *net)
79898035
{
79908036
mutex_lock(&net->nft.commit_mutex);
79918037
if (!list_empty(&net->nft.commit_list))
7992-
__nf_tables_abort(net);
8038+
__nf_tables_abort(net, false);
79938039
__nft_release_tables(net);
79948040
mutex_unlock(&net->nft.commit_mutex);
79958041
WARN_ON_ONCE(!list_empty(&net->nft.tables));

net/netfilter/nfnetlink.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh,
476476
}
477477
done:
478478
if (status & NFNL_BATCH_REPLAY) {
479-
ss->abort(net, oskb);
479+
ss->abort(net, oskb, true);
480480
nfnl_err_reset(&err_list);
481481
kfree_skb(skb);
482482
module_put(ss->owner);
@@ -487,11 +487,11 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh,
487487
status |= NFNL_BATCH_REPLAY;
488488
goto done;
489489
} else if (err) {
490-
ss->abort(net, oskb);
490+
ss->abort(net, oskb, false);
491491
netlink_ack(oskb, nlmsg_hdr(oskb), err, NULL);
492492
}
493493
} else {
494-
ss->abort(net, oskb);
494+
ss->abort(net, oskb, false);
495495
}
496496
if (ss->cleanup)
497497
ss->cleanup(net);

0 commit comments

Comments
 (0)