Commit 71ad00c5 authored by Florian Westphal's avatar Florian Westphal Committed by Pablo Neira Ayuso

netfilter: nf_tables: fix module unload race

We must first remove the nfnetlink protocol handler when nf_tables module
is unloaded -- we don't want userspace to submit new change requests once
we've started to tear down nft state.

Furthermore, nfnetlink must not call any subsystem function after
call_batch returned -EAGAIN.

EAGAIN means the subsys mutex was dropped, so its unlikely but possible that
nf_tables subsystem was removed due to 'rmmod nf_tables' on another cpu.

Therefore, we must abort batch completely and not move on to next part of
the batch.

Last, we can't invoke ->abort unless we've checked that the subsystem is
still registered.

Change netns exit path of nf_tables to make sure any incompleted
transaction gets removed on exit.
Signed-off-by: default avatarFlorian Westphal <fw@strlen.de>
Signed-off-by: default avatarPablo Neira Ayuso <pablo@netfilter.org>
parent 215a31f1
...@@ -6439,7 +6439,7 @@ static void nf_tables_abort_release(struct nft_trans *trans) ...@@ -6439,7 +6439,7 @@ static void nf_tables_abort_release(struct nft_trans *trans)
kfree(trans); kfree(trans);
} }
static int nf_tables_abort(struct net *net, struct sk_buff *skb) static int __nf_tables_abort(struct net *net)
{ {
struct nft_trans *trans, *next; struct nft_trans *trans, *next;
struct nft_trans_elem *te; struct nft_trans_elem *te;
...@@ -6555,6 +6555,11 @@ static void nf_tables_cleanup(struct net *net) ...@@ -6555,6 +6555,11 @@ static void nf_tables_cleanup(struct net *net)
nft_validate_state_update(net, NFT_VALIDATE_SKIP); nft_validate_state_update(net, NFT_VALIDATE_SKIP);
} }
static int nf_tables_abort(struct net *net, struct sk_buff *skb)
{
return __nf_tables_abort(net);
}
static bool nf_tables_valid_genid(struct net *net, u32 genid) static bool nf_tables_valid_genid(struct net *net, u32 genid)
{ {
return net->nft.base_seq == genid; return net->nft.base_seq == genid;
...@@ -7149,9 +7154,10 @@ static int __net_init nf_tables_init_net(struct net *net) ...@@ -7149,9 +7154,10 @@ static int __net_init nf_tables_init_net(struct net *net)
static void __net_exit nf_tables_exit_net(struct net *net) static void __net_exit nf_tables_exit_net(struct net *net)
{ {
if (!list_empty(&net->nft.commit_list))
__nf_tables_abort(net);
__nft_release_tables(net); __nft_release_tables(net);
WARN_ON_ONCE(!list_empty(&net->nft.tables)); WARN_ON_ONCE(!list_empty(&net->nft.tables));
WARN_ON_ONCE(!list_empty(&net->nft.commit_list));
} }
static struct pernet_operations nf_tables_net_ops = { static struct pernet_operations nf_tables_net_ops = {
...@@ -7193,9 +7199,9 @@ static int __init nf_tables_module_init(void) ...@@ -7193,9 +7199,9 @@ static int __init nf_tables_module_init(void)
static void __exit nf_tables_module_exit(void) static void __exit nf_tables_module_exit(void)
{ {
unregister_pernet_subsys(&nf_tables_net_ops);
nfnetlink_subsys_unregister(&nf_tables_subsys); nfnetlink_subsys_unregister(&nf_tables_subsys);
unregister_netdevice_notifier(&nf_tables_flowtable_notifier); unregister_netdevice_notifier(&nf_tables_flowtable_notifier);
unregister_pernet_subsys(&nf_tables_net_ops);
rcu_barrier(); rcu_barrier();
nf_tables_core_module_exit(); nf_tables_core_module_exit();
kfree(info); kfree(info);
......
...@@ -429,7 +429,7 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -429,7 +429,7 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh,
*/ */
if (err == -EAGAIN) { if (err == -EAGAIN) {
status |= NFNL_BATCH_REPLAY; status |= NFNL_BATCH_REPLAY;
goto next; goto done;
} }
} }
ack: ack:
...@@ -456,7 +456,7 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -456,7 +456,7 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh,
if (err) if (err)
status |= NFNL_BATCH_FAILURE; status |= NFNL_BATCH_FAILURE;
} }
next:
msglen = NLMSG_ALIGN(nlh->nlmsg_len); msglen = NLMSG_ALIGN(nlh->nlmsg_len);
if (msglen > skb->len) if (msglen > skb->len)
msglen = skb->len; msglen = skb->len;
...@@ -464,7 +464,11 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -464,7 +464,11 @@ static void nfnetlink_rcv_batch(struct sk_buff *skb, struct nlmsghdr *nlh,
} }
done: done:
if (status & NFNL_BATCH_REPLAY) { if (status & NFNL_BATCH_REPLAY) {
ss->abort(net, oskb); const struct nfnetlink_subsystem *ss2;
ss2 = nfnl_dereference_protected(subsys_id);
if (ss2 == ss)
ss->abort(net, oskb);
nfnl_err_reset(&err_list); nfnl_err_reset(&err_list);
nfnl_unlock(subsys_id); nfnl_unlock(subsys_id);
kfree_skb(skb); kfree_skb(skb);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment