[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