summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Kicinski <kuba@kernel.org>2026-09-14 17:06:52 -0700
committerJakub Kicinski <kuba@kernel.org>2026-09-14 17:06:52 -0700
commit6c21ebc81338dd8abe021dec3d01acebd789ea2c (patch)
tree2a00925dd85ee70571bc7105721b629c3692dce0
parente6b6078ea1731b05b3b552497b3bce4bf8b014ae (diff)
parente75a9fa1d44bcbd66ea02e8781bcca6ea4076e0d (diff)
downloadlinux-next-6c21ebc81338dd8abe021dec3d01acebd789ea2c.tar.gz
linux-next-6c21ebc81338dd8abe021dec3d01acebd789ea2c.zip
Merge tag 'nf-26-09-11' of git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf
Pablo Neira Ayuso says: ==================== Netfilter fixes for net 1) Fix KMSAN reports an uninit-value in nf_nat_setup_info() for netmap, from Theodor Arsenij Larionov Trichkine. 2) Restrict deletion of netdevice in basechain and flowtable to exact matching only, from Fernando F. Mancera. 3) Fix nf_nat_register_fn() error path allowing for a memleak. 4) Hold reference on ct until flow is released to address, otherwise access to release ct->ext or different ct due to typesafe RCU semantics. * tag 'nf-26-09-11' of git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf: netfilter: flowtable: hold reference on ct until flow is released netfilter: nf_nat: unregister and release hooks on error netfilter: nf_tables: fix device name and prefix match in hook lookup netfilter: nft_nat: fully initialise new_addr in netmap setup ==================== Link: https://patch.msgid.link/20260913205447.1889203-1-pablo@netfilter.org Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-rw-r--r--net/netfilter/nf_flow_table_core.c12
-rw-r--r--net/netfilter/nf_nat_core.c46
-rw-r--r--net/netfilter/nf_tables_api.c22
-rw-r--r--net/netfilter/nft_nat.c2
4 files changed, 54 insertions, 28 deletions
diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c
index 03241d4bfd5e..934c6151f558 100644
--- a/net/netfilter/nf_flow_table_core.c
+++ b/net/netfilter/nf_flow_table_core.c
@@ -258,6 +258,14 @@ static void flow_offload_route_release(struct flow_offload *flow)
nft_flow_dst_release(flow, FLOW_OFFLOAD_DIR_REPLY);
}
+static void flow_offload_free_rcu(struct rcu_head *rcu_head)
+{
+ struct flow_offload *flow = container_of(rcu_head, struct flow_offload, rcu_head);
+
+ nf_ct_put(flow->ct);
+ kfree(flow);
+}
+
void flow_offload_free(struct flow_offload *flow)
{
switch (flow->type) {
@@ -267,8 +275,7 @@ void flow_offload_free(struct flow_offload *flow)
default:
break;
}
- nf_ct_put(flow->ct);
- kfree_rcu(flow, rcu_head);
+ call_rcu(&flow->rcu_head, flow_offload_free_rcu);
}
EXPORT_SYMBOL_GPL(flow_offload_free);
@@ -854,6 +861,7 @@ out_pernet:
static void __exit nf_flow_table_module_exit(void)
{
+ rcu_barrier();
nf_flow_table_offload_exit();
unregister_pernet_subsys(&nf_flow_table_net_ops);
kmem_cache_destroy(flow_offload_cachep);
diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c
index 8ac326e1eb5b..a4858c2b2d65 100644
--- a/net/netfilter/nf_nat_core.c
+++ b/net/netfilter/nf_nat_core.c
@@ -1224,31 +1224,45 @@ int nf_nat_register_fn(struct net *net, u8 pf, const struct nf_hook_ops *ops,
}
ret = nf_register_net_hooks(net, nat_ops, ops_count);
- if (ret < 0) {
- mutex_unlock(&nf_nat_proto_mutex);
- for (i = 0; i < ops_count; i++) {
- priv = nat_ops[i].priv;
- kfree_rcu(priv, rcu_head);
- }
- kfree_rcu(nat_ops, rcu);
- return ret;
- }
-
- nat_proto_net->nat_hook_ops = nat_ops;
+ if (ret < 0)
+ goto err_free_hooks;
+ } else {
+ nat_ops = nat_proto_net->nat_hook_ops;
}
- nat_ops = nat_proto_net->nat_hook_ops;
priv = nat_ops[hooknum].priv;
if (WARN_ON_ONCE(!priv)) {
- mutex_unlock(&nf_nat_proto_mutex);
- return -EOPNOTSUPP;
+ ret = -EOPNOTSUPP;
+ goto err_unregister_hooks;
}
ret = nf_hook_entries_insert_raw(&priv->entries, ops);
- if (ret == 0)
- nat_proto_net->users++;
+ if (ret)
+ goto err_unregister_hooks;
+
+ if (!nat_proto_net->nat_hook_ops)
+ nat_proto_net->nat_hook_ops = nat_ops;
+
+ nat_proto_net->users++;
mutex_unlock(&nf_nat_proto_mutex);
+
+ return 0;
+
+err_unregister_hooks:
+ if (nat_proto_net->nat_hook_ops) {
+ mutex_unlock(&nf_nat_proto_mutex);
+ return ret;
+ }
+ nf_unregister_net_hooks(net, nat_ops, ops_count);
+err_free_hooks:
+ mutex_unlock(&nf_nat_proto_mutex);
+ for (i = 0; i < ops_count; i++) {
+ priv = nat_ops[i].priv;
+ kfree_rcu(priv, rcu_head);
+ }
+ kfree_rcu(nat_ops, rcu);
+
return ret;
}
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index 31fbd5a28937..c0b754a2d45b 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -2440,11 +2440,14 @@ err_hook_free:
}
static struct nft_hook *nft_hook_list_find(struct list_head *hook_list,
- const struct nft_hook *this)
+ const struct nft_hook *this,
+ bool strict)
{
struct nft_hook *hook;
list_for_each_entry(hook, hook_list, list) {
+ if (strict && hook->ifnamelen != this->ifnamelen)
+ continue;
if (!strncmp(hook->ifname, this->ifname,
min(hook->ifnamelen, this->ifnamelen))) {
if (hook->flags & NFT_HOOK_REMOVE)
@@ -2486,7 +2489,7 @@ static int nf_tables_parse_netdev_hooks(struct net *net,
err = PTR_ERR(hook);
goto err_hook;
}
- if (nft_hook_list_find(hook_list, hook)) {
+ if (nft_hook_list_find(hook_list, hook, false)) {
NL_SET_BAD_ATTR(extack, tmp);
nft_netdev_hook_free(hook);
err = -EEXIST;
@@ -2943,7 +2946,7 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy,
ops->hook = basechain->ops.hook;
}
- if (nft_hook_list_find(&basechain->hook_list, h)) {
+ if (nft_hook_list_find(&basechain->hook_list, h, false)) {
list_del(&h->list);
nft_netdev_hook_free(h);
continue;
@@ -2956,7 +2959,8 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy,
!nft_trans_chain_update(trans))
continue;
- if (nft_hook_list_find(&nft_trans_chain_hooks(trans), h)) {
+ if (nft_hook_list_find(&nft_trans_chain_hooks(trans),
+ h, false)) {
nft_chain_release_hook(&hook);
return -EEXIST;
}
@@ -3257,7 +3261,7 @@ static int nft_delchain_hook(struct nft_ctx *ctx,
return err;
list_for_each_entry(this, &chain_hook.list, list) {
- hook = nft_hook_list_find(&basechain->hook_list, this);
+ hook = nft_hook_list_find(&basechain->hook_list, this, true);
if (!hook) {
err = -ENOENT;
goto err_chain_del_hook;
@@ -9053,7 +9057,7 @@ static int nft_register_flowtable_net_hooks(struct net *net,
if (!nft_is_active_next(net, ft))
continue;
- if (nft_hook_list_find(&ft->hook_list, hook)) {
+ if (nft_hook_list_find(&ft->hook_list, hook, false)) {
err = -EEXIST;
goto err_unregister_net_hooks;
}
@@ -9130,7 +9134,7 @@ static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh,
return err;
list_for_each_entry_safe(hook, next, &flowtable_hook.list, list) {
- if (nft_hook_list_find(&flowtable->hook_list, hook)) {
+ if (nft_hook_list_find(&flowtable->hook_list, hook, false)) {
list_del(&hook->list);
nft_netdev_hook_free(hook);
continue;
@@ -9143,7 +9147,7 @@ static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh,
!nft_trans_flowtable_update(trans))
continue;
- if (nft_hook_list_find(&nft_trans_flowtable_hooks(trans), hook)) {
+ if (nft_hook_list_find(&nft_trans_flowtable_hooks(trans), hook, false)) {
err = -EEXIST;
goto err_flowtable_update_hook;
}
@@ -9363,7 +9367,7 @@ static int nft_delflowtable_hook(struct nft_ctx *ctx,
return err;
list_for_each_entry(this, &flowtable_hook.list, list) {
- hook = nft_hook_list_find(&flowtable->hook_list, this);
+ hook = nft_hook_list_find(&flowtable->hook_list, this, true);
if (!hook) {
err = -ENOENT;
goto err_flowtable_del_hook;
diff --git a/net/netfilter/nft_nat.c b/net/netfilter/nft_nat.c
index e32cd9fbc7c2..cdbd800cac96 100644
--- a/net/netfilter/nft_nat.c
+++ b/net/netfilter/nft_nat.c
@@ -64,8 +64,8 @@ static void nft_nat_setup_netmap(struct nf_nat_range2 *range,
const struct nft_pktinfo *pkt,
const struct nft_nat *priv)
{
+ union nf_inet_addr new_addr = {};
struct sk_buff *skb = pkt->skb;
- union nf_inet_addr new_addr;
__be32 netmask;
int i, len = 0;