[Devel] [PATCH RHEL10 COMMIT] ve/net: thread owning ve through copy_net_ns

Konstantin Khorenko khorenko at virtuozzo.com
Thu May 14 18:51:42 MSK 2026


The commit is pushed to "branch-rh10-6.12.0-55.52.1.5.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-55.52.1.5.24.vz10
------>
commit a122689a3dbab2c11f2f7f7972ecfaa339bd5e7a
Author: Pavel Tikhomirov <ptikhomirov at virtuozzo.com>
Date:   Wed Apr 29 15:41:38 2026 +0200

    ve/net: thread owning ve through copy_net_ns
    
    Add new_ve parameter to copy_net_ns(). NULL preserves the existing
    behaviour of taking current ve via get_exec_env().
    
    This will be used to derive correct ownership of newly created net
    namespace in case of simultaneous creation of new ve namespace and net
    namespace, where we would like new net namespace having the new ve as an
    owner.
    
    While on it, move ve reference count handling from setup_net() to
    copy_net_ns(), since we already handle dec/inc_netns_avail() up there.
    This way we don't need to thread it also to setup_net(). That is
    also consistent with how user_ns is threaded.
    
    The init_net.owner_ve is now set explicitly to &ve0 in net_ns_init()
    since setup_net() no longer does it for the boot-time call.
    
    There is no change in behaviour yet: create_new_namespaces() still uses
    NULL.
    
    https://virtuozzo.atlassian.net/browse/VSTOR-129744
    Signed-off-by: Pavel Tikhomirov <ptikhomirov at virtuozzo.com>
    Reviewed-by: Vasileios Almpanis <vasileios.almpanis at virtuozzo.com>
    
    Feature: ve: ve generic structures
    ======
    Patchset description:
    ve: fix owner_ve of net/mnt namespaces created together with CLONE_NEWVE
    
    When CLONE_NEWVE is combined with CLONE_NEWNET and/or CLONE_NEWNS in a
    single clone3() or unshare(), copy_net_ns() and copy_mnt_ns() resolve
    the owning ve via get_exec_env(), which still points at the parent ve
    at that point. The freshly created net/mnt namespaces end up wired to
    the wrong ve, and unshare(CLONE_NEWVE | CLONE_NEW{NS,NET}) is rejected
    outright by check_unshare_flags().
    
    Fix it by threading the new ve from copy_namespaces() and
    unshare_nsproxy_namespaces() down into copy_net_ns() and copy_mnt_ns(),
    so the correct ve is charged for the new netns and for every mount in
    the new mntns.
    
    Patches 1-4 are pure plumbing (signature changes, no behaviour change).
    Patch 5 is the actual fix that forwards the new ve. Patch 6 drops the
    now-redundant CLONE_NEWVE-alone restriction in check_unshare_flags().
    Patch 7 exposes ve.mnt_nr via cgroupfs to make per-ve mount accounting
    observable from userspace. Patch 8 adds a selftest covering both the
    clone3() and unshare() paths.
    
    Verified with crash on a vzctl-started container: task_ve,
    nsproxy->net_ns->owner_ve, nsproxy->mnt_ns->ve_owner and
    nsproxy->mnt_ns->root.ve_owner all resolve to the new ve.
    The new selftest passes both cases.
---
 include/net/net_namespace.h |  7 +++++--
 kernel/nsproxy.c            |  3 ++-
 net/core/net_namespace.c    | 33 +++++++++++++++++++++++----------
 3 files changed, 30 insertions(+), 13 deletions(-)

diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h
index ed077747f710f..dd0edb96f0eea 100644
--- a/include/net/net_namespace.h
+++ b/include/net/net_namespace.h
@@ -203,9 +203,11 @@ struct net {
 /* Init's network namespace */
 extern struct net init_net;
 
+struct ve_struct;
+
 #ifdef CONFIG_NET_NS
 struct net *copy_net_ns(unsigned long flags, struct user_namespace *user_ns,
-			struct net *old_net);
+			struct net *old_net, struct ve_struct *new_ve);
 
 void net_ns_get_ownership(const struct net *net, kuid_t *uid, kgid_t *gid);
 
@@ -218,7 +220,8 @@ struct net *get_net_ns_by_fd(int fd);
 #include <linux/sched.h>
 #include <linux/nsproxy.h>
 static inline struct net *copy_net_ns(unsigned long flags,
-	struct user_namespace *user_ns, struct net *old_net)
+	struct user_namespace *user_ns, struct net *old_net,
+	struct ve_struct *new_ve)
 {
 	if (flags & CLONE_NEWNET)
 		return ERR_PTR(-EINVAL);
diff --git a/kernel/nsproxy.c b/kernel/nsproxy.c
index 12de23ec80bf5..1d0e8f9d98a37 100644
--- a/kernel/nsproxy.c
+++ b/kernel/nsproxy.c
@@ -109,7 +109,8 @@ static struct nsproxy *create_new_namespaces(unsigned long flags,
 		goto out_cgroup;
 	}
 
-	new_nsp->net_ns = copy_net_ns(flags, user_ns, tsk->nsproxy->net_ns);
+	new_nsp->net_ns = copy_net_ns(flags, user_ns, tsk->nsproxy->net_ns,
+				      NULL);
 	if (IS_ERR(new_nsp->net_ns)) {
 		err = PTR_ERR(new_nsp->net_ns);
 		goto out_net;
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index 40e74d956bc20..3738e5274cd9a 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -352,10 +352,6 @@ static __net_init int setup_net(struct net *net)
 	LIST_HEAD(dev_kill_list);
 	int error = 0;
 
-#ifdef CONFIG_VE
-	net->owner_ve = get_ve(get_exec_env());
-#endif
-
 	preempt_disable();
 	net->net_cookie = gen_cookie_next(&net_cookie);
 	preempt_enable();
@@ -400,9 +396,6 @@ static __net_init int setup_net(struct net *net)
 		ops_free_list(ops, &net_exit_list);
 
 	rcu_barrier();
-#ifdef CONFIG_VE
-	put_ve(net->owner_ve);
-#endif
 	goto out;
 }
 
@@ -505,9 +498,10 @@ static void inc_netns_avail(struct ve_struct *ve)
 #endif
 
 struct net *copy_net_ns(unsigned long flags,
-			struct user_namespace *user_ns, struct net *old_net)
+			struct user_namespace *user_ns, struct net *old_net,
+			struct ve_struct *new_ve)
 {
-	struct ve_struct *ve = get_exec_env();
+	struct ve_struct *ve;
 	struct ucounts *ucounts;
 	struct net *net;
 	int rv;
@@ -515,6 +509,14 @@ struct net *copy_net_ns(unsigned long flags,
 	if (!(flags & CLONE_NEWNET))
 		return get_net(old_net);
 
+	/*
+	 * The ve that should own the new netns. When called from
+	 * copy_namespaces()/unshare_nsproxy_namespaces() with a freshly
+	 * created CLONE_NEWVE in flight, @new_ve is supplied by the
+	 * caller because get_exec_env() still resolves to the parent ve.
+	 */
+	ve = new_ve ?: get_exec_env();
+
 	ucounts = inc_net_namespaces(user_ns);
 	if (!ucounts)
 		return ERR_PTR(-ENOSPC);
@@ -538,16 +540,23 @@ struct net *copy_net_ns(unsigned long flags,
 	preinit_net(net, user_ns);
 	net->ucounts = ucounts;
 	get_user_ns(user_ns);
+#ifdef CONFIG_VE
+	net->owner_ve = get_ve(ve);
+#endif
 
 	rv = down_read_killable(&pernet_ops_rwsem);
 	if (rv < 0)
-		goto put_userns;
+		goto put_owner_ve;
 
 	rv = setup_net(net);
 
 	up_read(&pernet_ops_rwsem);
 
 	if (rv < 0) {
+put_owner_ve:
+#ifdef CONFIG_VE
+		put_ve(net->owner_ve);
+#endif
 put_userns:
 #ifdef CONFIG_KEYS
 		key_remove_domain(net->key_domain);
@@ -1261,6 +1270,10 @@ void __init net_ns_init(void)
 #endif
 	preinit_net(&init_net, &init_user_ns);
 
+#ifdef CONFIG_VE
+	init_net.owner_ve = get_ve(&ve0);
+#endif
+
 	down_write(&pernet_ops_rwsem);
 	if (setup_net(&init_net))
 		panic("Could not setup the initial network namespace");


More information about the Devel mailing list