Thread (7 messages) flat view 7 messages, 3 authors, 4d ago

Re: [PATCH] net: make sure final 'struct net' free in net_complete_free() is always deferred

From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Date: 2026-08-25 13:21:46
Subsystem: networking [general], the rest · Maintainers: "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds

On 2026/08/17 23:00, Eric Dumazet wrote:
However, the observation regarding the copy_net_ns() error path is valid:
because failed namespaces never go through __put_net(), net_cleanup_work
is not queued, so a failed net structure can stay on defer_free_list until
another namespace is destroyed.

The proper fix for that benign issue would be to directly free the
struct net in the
copy_net_ns() error path (since it was never active and does not need deferred
freeing), rather than modifying cleanup_net().
Now that "net: fix a resource leak in copy_net_ns() error handling path"
handed over to net_passive_dec() (instead of calling kmem_cache_free() from
copy_net_ns()), it is time to fix the last problem which sashiko repeatedly mentions
due to commit 0f6ede9fbc74 ("net: defer final 'struct net' free in netns dismantle").

My understanding is that we need to wait for RCU grace period before
calling kmem_cache_free(). Then, I think we can simplify like shown below (either
pattern A or pattern B). Which pattern do you like?



Pattern A:

 net/core/net_namespace.c | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index da5f881fbd3b..421398149bc2 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -511,19 +511,20 @@ static struct net *net_alloc(void)
 
 static LLIST_HEAD(defer_free_list);
 
-static void net_complete_free(void)
+static void net_complete_free(struct work_struct *work)
 {
 	struct llist_node *kill_list;
 	struct net *net, *next;
 
-	/* Get the list of namespaces to free from last round. */
 	kill_list = llist_del_all(&defer_free_list);
-
+	/* Wait for an extra rcu_barrier() before final free. */
+	rcu_barrier();
 	llist_for_each_entry_safe(net, next, kill_list, defer_free_list)
 		kmem_cache_free(net_cachep, net);
-
 }
 
+static DECLARE_WORK(net_complete_free_work, net_complete_free);
+
 void net_passive_dec(struct net *net)
 {
 	if (refcount_dec_and_test(&net->passive)) {
@@ -536,8 +537,8 @@ void net_passive_dec(struct net *net)
 			ref_tracker_dir_exit(&net->refcnt_tracker);
 #endif
 
-		/* Wait for an extra rcu_barrier() before final free. */
 		llist_add(&net->defer_free_list, &defer_free_list);
+		queue_work(system_long_wq, &net_complete_free_work);
 	}
 }
 
@@ -712,8 +713,6 @@ static void cleanup_net(struct work_struct *work)
 	 */
 	rcu_barrier();
 
-	net_complete_free();
-
 	/* Finally it is safe to free my network namespace structure */
 	list_for_each_entry_safe(net, tmp, &net_exit_list, exit_list) {
 		list_del_init(&net->exit_list);
-- 



Pattern B:

 include/net/net_namespace.h |  2 +-
 net/core/net_namespace.c    | 18 ++++--------------
 2 files changed, 5 insertions(+), 15 deletions(-)

diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h
index e5ee673b9fcf..9419a198faf7 100644
--- a/include/net/net_namespace.h
+++ b/include/net/net_namespace.h
@@ -81,7 +81,7 @@ struct net {
 						 * or to unregister pernet ops
 						 * (pernet_ops_rwsem write locked).
 						 */
-	struct llist_node	defer_free_list;
+	struct rcu_head		complete_free_rcu;
 	struct llist_node	cleanup_list;	/* namespaces on death row */
 
 	struct list_head ptype_all;
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index da5f881fbd3b..2ce0f281922d 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -509,18 +509,11 @@ static struct net *net_alloc(void)
 	goto out;
 }
 
-static LLIST_HEAD(defer_free_list);
-
-static void net_complete_free(void)
+static void net_complete_free(struct rcu_head *rcu)
 {
-	struct llist_node *kill_list;
-	struct net *net, *next;
-
-	/* Get the list of namespaces to free from last round. */
-	kill_list = llist_del_all(&defer_free_list);
+	struct net *net = container_of(rcu, struct net, complete_free_rcu);
 
-	llist_for_each_entry_safe(net, next, kill_list, defer_free_list)
-		kmem_cache_free(net_cachep, net);
+	kmem_cache_free(net_cachep, net);
 
 }
 
@@ -536,8 +529,7 @@ void net_passive_dec(struct net *net)
 			ref_tracker_dir_exit(&net->refcnt_tracker);
 #endif
 
-		/* Wait for an extra rcu_barrier() before final free. */
-		llist_add(&net->defer_free_list, &defer_free_list);
+		call_rcu(&net->complete_free_rcu, net_complete_free);
 	}
 }
 
@@ -712,8 +704,6 @@ static void cleanup_net(struct work_struct *work)
 	 */
 	rcu_barrier();
 
-	net_complete_free();
-
 	/* Finally it is safe to free my network namespace structure */
 	list_for_each_entry_safe(net, tmp, &net_exit_list, exit_list) {
 		list_del_init(&net->exit_list);
-- 
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help