VYPR
Unrated severityNVD Advisory· Published Jun 9, 2026

CVE-2026-46324

CVE-2026-46324

Description

In the Linux kernel, the following vulnerability has been resolved:

netfilter: nf_tables: use list_del_rcu for netlink hooks

nft_netdev_unregister_hooks and __nft_unregister_flowtable_net_hooks need to use list_del_rcu(), this list can be walked by concurrent dumpers.

Add a new helper and use it consistently.

Affected products

1

Patches

6
0f33e8ad6ac5

netfilter: nf_tables: use list_del_rcu for netlink hooks

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitFlorian WestphalApr 16, 2026Fixed in 7.0.10via kernel-cna
1 file changed · +18 27
  • net/netfilter/nf_tables_api.c+18 27 modified
    diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
    index 8c42247a176c7..090d4d688a333 100644
    --- a/net/netfilter/nf_tables_api.c
    +++ b/net/netfilter/nf_tables_api.c
    @@ -374,6 +374,12 @@ static void nft_netdev_hook_free_rcu(struct nft_hook *hook)
     	call_rcu(&hook->rcu, __nft_netdev_hook_free_rcu);
     }
     
    +static void nft_netdev_hook_unlink_free_rcu(struct nft_hook *hook)
    +{
    +	list_del_rcu(&hook->list);
    +	nft_netdev_hook_free_rcu(hook);
    +}
    +
     static void nft_netdev_unregister_hooks(struct net *net,
     					struct list_head *hook_list,
     					bool release_netdev)
    @@ -384,10 +390,8 @@ static void nft_netdev_unregister_hooks(struct net *net,
     	list_for_each_entry_safe(hook, next, hook_list, list) {
     		list_for_each_entry(ops, &hook->ops_list, list)
     			nf_unregister_net_hook(net, ops);
    -		if (release_netdev) {
    -			list_del(&hook->list);
    -			nft_netdev_hook_free_rcu(hook);
    -		}
    +		if (release_netdev)
    +			nft_netdev_hook_unlink_free_rcu(hook);
     	}
     }
     
    @@ -2323,10 +2327,8 @@ void nf_tables_chain_destroy(struct nft_chain *chain)
     
     		if (nft_base_chain_netdev(table->family, basechain->ops.hooknum)) {
     			list_for_each_entry_safe(hook, next,
    -						 &basechain->hook_list, list) {
    -				list_del_rcu(&hook->list);
    -				nft_netdev_hook_free_rcu(hook);
    -			}
    +						 &basechain->hook_list, list)
    +				nft_netdev_hook_unlink_free_rcu(hook);
     		}
     		module_put(basechain->type->owner);
     		if (rcu_access_pointer(basechain->stats)) {
    @@ -3026,6 +3028,7 @@ err_hooks:
     				list_for_each_entry(ops, &h->ops_list, list)
     					nf_unregister_net_hook(ctx->net, ops);
     			}
    +			/* hook.list is on stack, no need for list_del_rcu() */
     			list_del(&h->list);
     			nft_netdev_hook_free_rcu(h);
     		}
    @@ -8903,10 +8906,8 @@ static void __nft_unregister_flowtable_net_hooks(struct net *net,
     	list_for_each_entry_safe(hook, next, hook_list, list) {
     		list_for_each_entry(ops, &hook->ops_list, list)
     			nft_unregister_flowtable_ops(net, flowtable, ops);
    -		if (release_netdev) {
    -			list_del(&hook->list);
    -			nft_netdev_hook_free_rcu(hook);
    -		}
    +		if (release_netdev)
    +			nft_netdev_hook_unlink_free_rcu(hook);
     	}
     }
     
    @@ -8977,8 +8978,7 @@ err_unregister_net_hooks:
     
     			nft_unregister_flowtable_ops(net, flowtable, ops);
     		}
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    +		nft_netdev_hook_unlink_free_rcu(hook);
     	}
     
     	return err;
    @@ -8988,10 +8988,8 @@ static void nft_hooks_destroy(struct list_head *hook_list)
     {
     	struct nft_hook *hook, *next;
     
    -	list_for_each_entry_safe(hook, next, hook_list, list) {
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    -	}
    +	list_for_each_entry_safe(hook, next, hook_list, list)
    +		nft_netdev_hook_unlink_free_rcu(hook);
     }
     
     static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh,
    @@ -9079,8 +9077,7 @@ err_flowtable_update_hook:
     				nft_unregister_flowtable_ops(ctx->net,
     							     flowtable, ops);
     		}
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    +		nft_netdev_hook_unlink_free_rcu(hook);
     	}
     
     	return err;
    @@ -9586,13 +9583,8 @@ err:
     
     static void nf_tables_flowtable_destroy(struct nft_flowtable *flowtable)
     {
    -	struct nft_hook *hook, *next;
    -
     	flowtable->data.type->free(&flowtable->data);
    -	list_for_each_entry_safe(hook, next, &flowtable->hook_list, list) {
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    -	}
    +	nft_hooks_destroy(&flowtable->hook_list);
     	kfree(flowtable->name);
     	module_put(flowtable->data.type->owner);
     	kfree(flowtable);
    -- 
    cgit 1.3-korg
    
    
    
0bd93ce4f3c3

netfilter: nf_tables: use list_del_rcu for netlink hooks

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitFlorian WestphalApr 16, 2026Fixed in 6.18.33via kernel-cna
1 file changed · +18 27
  • net/netfilter/nf_tables_api.c+18 27 modified
    diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
    index 5b25b032e285d..64e6a95439abd 100644
    --- a/net/netfilter/nf_tables_api.c
    +++ b/net/netfilter/nf_tables_api.c
    @@ -373,6 +373,12 @@ static void nft_netdev_hook_free_rcu(struct nft_hook *hook)
     	call_rcu(&hook->rcu, __nft_netdev_hook_free_rcu);
     }
     
    +static void nft_netdev_hook_unlink_free_rcu(struct nft_hook *hook)
    +{
    +	list_del_rcu(&hook->list);
    +	nft_netdev_hook_free_rcu(hook);
    +}
    +
     static void nft_netdev_unregister_hooks(struct net *net,
     					struct list_head *hook_list,
     					bool release_netdev)
    @@ -383,10 +389,8 @@ static void nft_netdev_unregister_hooks(struct net *net,
     	list_for_each_entry_safe(hook, next, hook_list, list) {
     		list_for_each_entry(ops, &hook->ops_list, list)
     			nf_unregister_net_hook(net, ops);
    -		if (release_netdev) {
    -			list_del(&hook->list);
    -			nft_netdev_hook_free_rcu(hook);
    -		}
    +		if (release_netdev)
    +			nft_netdev_hook_unlink_free_rcu(hook);
     	}
     }
     
    @@ -2322,10 +2326,8 @@ void nf_tables_chain_destroy(struct nft_chain *chain)
     
     		if (nft_base_chain_netdev(table->family, basechain->ops.hooknum)) {
     			list_for_each_entry_safe(hook, next,
    -						 &basechain->hook_list, list) {
    -				list_del_rcu(&hook->list);
    -				nft_netdev_hook_free_rcu(hook);
    -			}
    +						 &basechain->hook_list, list)
    +				nft_netdev_hook_unlink_free_rcu(hook);
     		}
     		module_put(basechain->type->owner);
     		if (rcu_access_pointer(basechain->stats)) {
    @@ -3025,6 +3027,7 @@ err_hooks:
     				list_for_each_entry(ops, &h->ops_list, list)
     					nf_unregister_net_hook(ctx->net, ops);
     			}
    +			/* hook.list is on stack, no need for list_del_rcu() */
     			list_del(&h->list);
     			nft_netdev_hook_free_rcu(h);
     		}
    @@ -9069,10 +9072,8 @@ static void __nft_unregister_flowtable_net_hooks(struct net *net,
     	list_for_each_entry_safe(hook, next, hook_list, list) {
     		list_for_each_entry(ops, &hook->ops_list, list)
     			nft_unregister_flowtable_ops(net, flowtable, ops);
    -		if (release_netdev) {
    -			list_del(&hook->list);
    -			nft_netdev_hook_free_rcu(hook);
    -		}
    +		if (release_netdev)
    +			nft_netdev_hook_unlink_free_rcu(hook);
     	}
     }
     
    @@ -9143,8 +9144,7 @@ err_unregister_net_hooks:
     
     			nft_unregister_flowtable_ops(net, flowtable, ops);
     		}
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    +		nft_netdev_hook_unlink_free_rcu(hook);
     	}
     
     	return err;
    @@ -9154,10 +9154,8 @@ static void nft_hooks_destroy(struct list_head *hook_list)
     {
     	struct nft_hook *hook, *next;
     
    -	list_for_each_entry_safe(hook, next, hook_list, list) {
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    -	}
    +	list_for_each_entry_safe(hook, next, hook_list, list)
    +		nft_netdev_hook_unlink_free_rcu(hook);
     }
     
     static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh,
    @@ -9245,8 +9243,7 @@ err_flowtable_update_hook:
     				nft_unregister_flowtable_ops(ctx->net,
     							     flowtable, ops);
     		}
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    +		nft_netdev_hook_unlink_free_rcu(hook);
     	}
     
     	return err;
    @@ -9752,13 +9749,8 @@ err:
     
     static void nf_tables_flowtable_destroy(struct nft_flowtable *flowtable)
     {
    -	struct nft_hook *hook, *next;
    -
     	flowtable->data.type->free(&flowtable->data);
    -	list_for_each_entry_safe(hook, next, &flowtable->hook_list, list) {
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    -	}
    +	nft_hooks_destroy(&flowtable->hook_list);
     	kfree(flowtable->name);
     	module_put(flowtable->data.type->owner);
     	kfree(flowtable);
    -- 
    cgit 1.3-korg
    
    
    
f3224ee463f8

netfilter: nf_tables: use list_del_rcu for netlink hooks

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitFlorian WestphalApr 16, 2026Fixed in 7.1-rc2via kernel-cna
1 file changed · +18 27
  • net/netfilter/nf_tables_api.c+18 27 modified
    diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
    index 8537b94653d37..07e1512457650 100644
    --- a/net/netfilter/nf_tables_api.c
    +++ b/net/netfilter/nf_tables_api.c
    @@ -374,6 +374,12 @@ static void nft_netdev_hook_free_rcu(struct nft_hook *hook)
     	call_rcu(&hook->rcu, __nft_netdev_hook_free_rcu);
     }
     
    +static void nft_netdev_hook_unlink_free_rcu(struct nft_hook *hook)
    +{
    +	list_del_rcu(&hook->list);
    +	nft_netdev_hook_free_rcu(hook);
    +}
    +
     static void nft_netdev_unregister_hooks(struct net *net,
     					struct list_head *hook_list,
     					bool release_netdev)
    @@ -384,10 +390,8 @@ static void nft_netdev_unregister_hooks(struct net *net,
     	list_for_each_entry_safe(hook, next, hook_list, list) {
     		list_for_each_entry(ops, &hook->ops_list, list)
     			nf_unregister_net_hook(net, ops);
    -		if (release_netdev) {
    -			list_del(&hook->list);
    -			nft_netdev_hook_free_rcu(hook);
    -		}
    +		if (release_netdev)
    +			nft_netdev_hook_unlink_free_rcu(hook);
     	}
     }
     
    @@ -2271,10 +2275,8 @@ void nf_tables_chain_destroy(struct nft_chain *chain)
     
     		if (nft_base_chain_netdev(table->family, basechain->ops.hooknum)) {
     			list_for_each_entry_safe(hook, next,
    -						 &basechain->hook_list, list) {
    -				list_del_rcu(&hook->list);
    -				nft_netdev_hook_free_rcu(hook);
    -			}
    +						 &basechain->hook_list, list)
    +				nft_netdev_hook_unlink_free_rcu(hook);
     		}
     		module_put(basechain->type->owner);
     		if (rcu_access_pointer(basechain->stats)) {
    @@ -2974,6 +2976,7 @@ err_hooks:
     				list_for_each_entry(ops, &h->ops_list, list)
     					nf_unregister_net_hook(ctx->net, ops);
     			}
    +			/* hook.list is on stack, no need for list_del_rcu() */
     			list_del(&h->list);
     			nft_netdev_hook_free_rcu(h);
     		}
    @@ -8852,10 +8855,8 @@ static void __nft_unregister_flowtable_net_hooks(struct net *net,
     	list_for_each_entry_safe(hook, next, hook_list, list) {
     		list_for_each_entry(ops, &hook->ops_list, list)
     			nft_unregister_flowtable_ops(net, flowtable, ops);
    -		if (release_netdev) {
    -			list_del(&hook->list);
    -			nft_netdev_hook_free_rcu(hook);
    -		}
    +		if (release_netdev)
    +			nft_netdev_hook_unlink_free_rcu(hook);
     	}
     }
     
    @@ -8926,8 +8927,7 @@ err_unregister_net_hooks:
     
     			nft_unregister_flowtable_ops(net, flowtable, ops);
     		}
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    +		nft_netdev_hook_unlink_free_rcu(hook);
     	}
     
     	return err;
    @@ -8937,10 +8937,8 @@ static void nft_hooks_destroy(struct list_head *hook_list)
     {
     	struct nft_hook *hook, *next;
     
    -	list_for_each_entry_safe(hook, next, hook_list, list) {
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    -	}
    +	list_for_each_entry_safe(hook, next, hook_list, list)
    +		nft_netdev_hook_unlink_free_rcu(hook);
     }
     
     static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh,
    @@ -9028,8 +9026,7 @@ err_flowtable_update_hook:
     				nft_unregister_flowtable_ops(ctx->net,
     							     flowtable, ops);
     		}
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    +		nft_netdev_hook_unlink_free_rcu(hook);
     	}
     
     	return err;
    @@ -9535,13 +9532,8 @@ err:
     
     static void nf_tables_flowtable_destroy(struct nft_flowtable *flowtable)
     {
    -	struct nft_hook *hook, *next;
    -
     	flowtable->data.type->free(&flowtable->data);
    -	list_for_each_entry_safe(hook, next, &flowtable->hook_list, list) {
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    -	}
    +	nft_hooks_destroy(&flowtable->hook_list);
     	kfree(flowtable->name);
     	module_put(flowtable->data.type->owner);
     	kfree(flowtable);
    -- 
    cgit 1.3-korg
    
    
    
0bd93ce4f3c3

netfilter: nf_tables: use list_del_rcu for netlink hooks

1 file changed · +18 27
  • net/netfilter/nf_tables_api.c+18 27 modified
    diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
    index 5b25b032e285d..64e6a95439abd 100644
    --- a/net/netfilter/nf_tables_api.c
    +++ b/net/netfilter/nf_tables_api.c
    @@ -373,6 +373,12 @@ static void nft_netdev_hook_free_rcu(struct nft_hook *hook)
     	call_rcu(&hook->rcu, __nft_netdev_hook_free_rcu);
     }
     
    +static void nft_netdev_hook_unlink_free_rcu(struct nft_hook *hook)
    +{
    +	list_del_rcu(&hook->list);
    +	nft_netdev_hook_free_rcu(hook);
    +}
    +
     static void nft_netdev_unregister_hooks(struct net *net,
     					struct list_head *hook_list,
     					bool release_netdev)
    @@ -383,10 +389,8 @@ static void nft_netdev_unregister_hooks(struct net *net,
     	list_for_each_entry_safe(hook, next, hook_list, list) {
     		list_for_each_entry(ops, &hook->ops_list, list)
     			nf_unregister_net_hook(net, ops);
    -		if (release_netdev) {
    -			list_del(&hook->list);
    -			nft_netdev_hook_free_rcu(hook);
    -		}
    +		if (release_netdev)
    +			nft_netdev_hook_unlink_free_rcu(hook);
     	}
     }
     
    @@ -2322,10 +2326,8 @@ void nf_tables_chain_destroy(struct nft_chain *chain)
     
     		if (nft_base_chain_netdev(table->family, basechain->ops.hooknum)) {
     			list_for_each_entry_safe(hook, next,
    -						 &basechain->hook_list, list) {
    -				list_del_rcu(&hook->list);
    -				nft_netdev_hook_free_rcu(hook);
    -			}
    +						 &basechain->hook_list, list)
    +				nft_netdev_hook_unlink_free_rcu(hook);
     		}
     		module_put(basechain->type->owner);
     		if (rcu_access_pointer(basechain->stats)) {
    @@ -3025,6 +3027,7 @@ err_hooks:
     				list_for_each_entry(ops, &h->ops_list, list)
     					nf_unregister_net_hook(ctx->net, ops);
     			}
    +			/* hook.list is on stack, no need for list_del_rcu() */
     			list_del(&h->list);
     			nft_netdev_hook_free_rcu(h);
     		}
    @@ -9069,10 +9072,8 @@ static void __nft_unregister_flowtable_net_hooks(struct net *net,
     	list_for_each_entry_safe(hook, next, hook_list, list) {
     		list_for_each_entry(ops, &hook->ops_list, list)
     			nft_unregister_flowtable_ops(net, flowtable, ops);
    -		if (release_netdev) {
    -			list_del(&hook->list);
    -			nft_netdev_hook_free_rcu(hook);
    -		}
    +		if (release_netdev)
    +			nft_netdev_hook_unlink_free_rcu(hook);
     	}
     }
     
    @@ -9143,8 +9144,7 @@ err_unregister_net_hooks:
     
     			nft_unregister_flowtable_ops(net, flowtable, ops);
     		}
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    +		nft_netdev_hook_unlink_free_rcu(hook);
     	}
     
     	return err;
    @@ -9154,10 +9154,8 @@ static void nft_hooks_destroy(struct list_head *hook_list)
     {
     	struct nft_hook *hook, *next;
     
    -	list_for_each_entry_safe(hook, next, hook_list, list) {
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    -	}
    +	list_for_each_entry_safe(hook, next, hook_list, list)
    +		nft_netdev_hook_unlink_free_rcu(hook);
     }
     
     static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh,
    @@ -9245,8 +9243,7 @@ err_flowtable_update_hook:
     				nft_unregister_flowtable_ops(ctx->net,
     							     flowtable, ops);
     		}
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    +		nft_netdev_hook_unlink_free_rcu(hook);
     	}
     
     	return err;
    @@ -9752,13 +9749,8 @@ err:
     
     static void nf_tables_flowtable_destroy(struct nft_flowtable *flowtable)
     {
    -	struct nft_hook *hook, *next;
    -
     	flowtable->data.type->free(&flowtable->data);
    -	list_for_each_entry_safe(hook, next, &flowtable->hook_list, list) {
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    -	}
    +	nft_hooks_destroy(&flowtable->hook_list);
     	kfree(flowtable->name);
     	module_put(flowtable->data.type->owner);
     	kfree(flowtable);
    -- 
    cgit 1.3-korg
    
    
    
0f33e8ad6ac5

netfilter: nf_tables: use list_del_rcu for netlink hooks

1 file changed · +18 27
  • net/netfilter/nf_tables_api.c+18 27 modified
    diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
    index 8c42247a176c7..090d4d688a333 100644
    --- a/net/netfilter/nf_tables_api.c
    +++ b/net/netfilter/nf_tables_api.c
    @@ -374,6 +374,12 @@ static void nft_netdev_hook_free_rcu(struct nft_hook *hook)
     	call_rcu(&hook->rcu, __nft_netdev_hook_free_rcu);
     }
     
    +static void nft_netdev_hook_unlink_free_rcu(struct nft_hook *hook)
    +{
    +	list_del_rcu(&hook->list);
    +	nft_netdev_hook_free_rcu(hook);
    +}
    +
     static void nft_netdev_unregister_hooks(struct net *net,
     					struct list_head *hook_list,
     					bool release_netdev)
    @@ -384,10 +390,8 @@ static void nft_netdev_unregister_hooks(struct net *net,
     	list_for_each_entry_safe(hook, next, hook_list, list) {
     		list_for_each_entry(ops, &hook->ops_list, list)
     			nf_unregister_net_hook(net, ops);
    -		if (release_netdev) {
    -			list_del(&hook->list);
    -			nft_netdev_hook_free_rcu(hook);
    -		}
    +		if (release_netdev)
    +			nft_netdev_hook_unlink_free_rcu(hook);
     	}
     }
     
    @@ -2323,10 +2327,8 @@ void nf_tables_chain_destroy(struct nft_chain *chain)
     
     		if (nft_base_chain_netdev(table->family, basechain->ops.hooknum)) {
     			list_for_each_entry_safe(hook, next,
    -						 &basechain->hook_list, list) {
    -				list_del_rcu(&hook->list);
    -				nft_netdev_hook_free_rcu(hook);
    -			}
    +						 &basechain->hook_list, list)
    +				nft_netdev_hook_unlink_free_rcu(hook);
     		}
     		module_put(basechain->type->owner);
     		if (rcu_access_pointer(basechain->stats)) {
    @@ -3026,6 +3028,7 @@ err_hooks:
     				list_for_each_entry(ops, &h->ops_list, list)
     					nf_unregister_net_hook(ctx->net, ops);
     			}
    +			/* hook.list is on stack, no need for list_del_rcu() */
     			list_del(&h->list);
     			nft_netdev_hook_free_rcu(h);
     		}
    @@ -8903,10 +8906,8 @@ static void __nft_unregister_flowtable_net_hooks(struct net *net,
     	list_for_each_entry_safe(hook, next, hook_list, list) {
     		list_for_each_entry(ops, &hook->ops_list, list)
     			nft_unregister_flowtable_ops(net, flowtable, ops);
    -		if (release_netdev) {
    -			list_del(&hook->list);
    -			nft_netdev_hook_free_rcu(hook);
    -		}
    +		if (release_netdev)
    +			nft_netdev_hook_unlink_free_rcu(hook);
     	}
     }
     
    @@ -8977,8 +8978,7 @@ err_unregister_net_hooks:
     
     			nft_unregister_flowtable_ops(net, flowtable, ops);
     		}
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    +		nft_netdev_hook_unlink_free_rcu(hook);
     	}
     
     	return err;
    @@ -8988,10 +8988,8 @@ static void nft_hooks_destroy(struct list_head *hook_list)
     {
     	struct nft_hook *hook, *next;
     
    -	list_for_each_entry_safe(hook, next, hook_list, list) {
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    -	}
    +	list_for_each_entry_safe(hook, next, hook_list, list)
    +		nft_netdev_hook_unlink_free_rcu(hook);
     }
     
     static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh,
    @@ -9079,8 +9077,7 @@ err_flowtable_update_hook:
     				nft_unregister_flowtable_ops(ctx->net,
     							     flowtable, ops);
     		}
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    +		nft_netdev_hook_unlink_free_rcu(hook);
     	}
     
     	return err;
    @@ -9586,13 +9583,8 @@ err:
     
     static void nf_tables_flowtable_destroy(struct nft_flowtable *flowtable)
     {
    -	struct nft_hook *hook, *next;
    -
     	flowtable->data.type->free(&flowtable->data);
    -	list_for_each_entry_safe(hook, next, &flowtable->hook_list, list) {
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    -	}
    +	nft_hooks_destroy(&flowtable->hook_list);
     	kfree(flowtable->name);
     	module_put(flowtable->data.type->owner);
     	kfree(flowtable);
    -- 
    cgit 1.3-korg
    
    
    
f3224ee463f8

netfilter: nf_tables: use list_del_rcu for netlink hooks

1 file changed · +18 27
  • net/netfilter/nf_tables_api.c+18 27 modified
    diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
    index 8537b94653d37..07e1512457650 100644
    --- a/net/netfilter/nf_tables_api.c
    +++ b/net/netfilter/nf_tables_api.c
    @@ -374,6 +374,12 @@ static void nft_netdev_hook_free_rcu(struct nft_hook *hook)
     	call_rcu(&hook->rcu, __nft_netdev_hook_free_rcu);
     }
     
    +static void nft_netdev_hook_unlink_free_rcu(struct nft_hook *hook)
    +{
    +	list_del_rcu(&hook->list);
    +	nft_netdev_hook_free_rcu(hook);
    +}
    +
     static void nft_netdev_unregister_hooks(struct net *net,
     					struct list_head *hook_list,
     					bool release_netdev)
    @@ -384,10 +390,8 @@ static void nft_netdev_unregister_hooks(struct net *net,
     	list_for_each_entry_safe(hook, next, hook_list, list) {
     		list_for_each_entry(ops, &hook->ops_list, list)
     			nf_unregister_net_hook(net, ops);
    -		if (release_netdev) {
    -			list_del(&hook->list);
    -			nft_netdev_hook_free_rcu(hook);
    -		}
    +		if (release_netdev)
    +			nft_netdev_hook_unlink_free_rcu(hook);
     	}
     }
     
    @@ -2271,10 +2275,8 @@ void nf_tables_chain_destroy(struct nft_chain *chain)
     
     		if (nft_base_chain_netdev(table->family, basechain->ops.hooknum)) {
     			list_for_each_entry_safe(hook, next,
    -						 &basechain->hook_list, list) {
    -				list_del_rcu(&hook->list);
    -				nft_netdev_hook_free_rcu(hook);
    -			}
    +						 &basechain->hook_list, list)
    +				nft_netdev_hook_unlink_free_rcu(hook);
     		}
     		module_put(basechain->type->owner);
     		if (rcu_access_pointer(basechain->stats)) {
    @@ -2974,6 +2976,7 @@ err_hooks:
     				list_for_each_entry(ops, &h->ops_list, list)
     					nf_unregister_net_hook(ctx->net, ops);
     			}
    +			/* hook.list is on stack, no need for list_del_rcu() */
     			list_del(&h->list);
     			nft_netdev_hook_free_rcu(h);
     		}
    @@ -8852,10 +8855,8 @@ static void __nft_unregister_flowtable_net_hooks(struct net *net,
     	list_for_each_entry_safe(hook, next, hook_list, list) {
     		list_for_each_entry(ops, &hook->ops_list, list)
     			nft_unregister_flowtable_ops(net, flowtable, ops);
    -		if (release_netdev) {
    -			list_del(&hook->list);
    -			nft_netdev_hook_free_rcu(hook);
    -		}
    +		if (release_netdev)
    +			nft_netdev_hook_unlink_free_rcu(hook);
     	}
     }
     
    @@ -8926,8 +8927,7 @@ err_unregister_net_hooks:
     
     			nft_unregister_flowtable_ops(net, flowtable, ops);
     		}
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    +		nft_netdev_hook_unlink_free_rcu(hook);
     	}
     
     	return err;
    @@ -8937,10 +8937,8 @@ static void nft_hooks_destroy(struct list_head *hook_list)
     {
     	struct nft_hook *hook, *next;
     
    -	list_for_each_entry_safe(hook, next, hook_list, list) {
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    -	}
    +	list_for_each_entry_safe(hook, next, hook_list, list)
    +		nft_netdev_hook_unlink_free_rcu(hook);
     }
     
     static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh,
    @@ -9028,8 +9026,7 @@ err_flowtable_update_hook:
     				nft_unregister_flowtable_ops(ctx->net,
     							     flowtable, ops);
     		}
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    +		nft_netdev_hook_unlink_free_rcu(hook);
     	}
     
     	return err;
    @@ -9535,13 +9532,8 @@ err:
     
     static void nf_tables_flowtable_destroy(struct nft_flowtable *flowtable)
     {
    -	struct nft_hook *hook, *next;
    -
     	flowtable->data.type->free(&flowtable->data);
    -	list_for_each_entry_safe(hook, next, &flowtable->hook_list, list) {
    -		list_del_rcu(&hook->list);
    -		nft_netdev_hook_free_rcu(hook);
    -	}
    +	nft_hooks_destroy(&flowtable->hook_list);
     	kfree(flowtable->name);
     	module_put(flowtable->data.type->owner);
     	kfree(flowtable);
    -- 
    cgit 1.3-korg
    
    
    

Vulnerability mechanics

Root cause

"The nf_tables module incorrectly used list_del() instead of list_del_rcu() when unregistering netlink hooks, leading to potential race conditions."

Attack vector

An attacker could trigger this vulnerability by performing operations that involve the unregistration of netlink hooks within the nf_tables module. This could occur during concurrent dump operations or when managing network namespaces. The vulnerability arises from a race condition where the list of hooks can be walked by dumpers while being modified by unregistration functions that do not use RCU-safe list deletion.

Affected code

The vulnerability resides in the netfilter subsystem of the Linux kernel, specifically within the nf_tables module. The affected functions include nft_netdev_unregister_hooks, __nft_unregister_flowtable_net_hooks, nf_tables_chain_destroy, and nf_tables_flowtable_destroy, all located in the file net/netfilter/nf_tables_api.c. These functions were found to be using list_del() instead of the RCU-safe list_del_rcu() when unregistering hooks.

What the fix does

The patch introduces a new helper function, nft_netdev_hook_unlink_free_rcu, which correctly uses list_del_rcu() for removing hooks from the list. This change is applied consistently across various unregistration functions, including nft_netdev_unregister_hooks, __nft_unregister_flowtable_net_hooks, and within nf_tables_chain_destroy and nf_tables_flowtable_destroy. By using list_del_rcu(), the code ensures that concurrent dumpers accessing the hook list do not encounter issues when hooks are being removed, thus resolving the race condition [patch_id=5354492].

Generated on Jun 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

3

News mentions

1