All Virtuozzo development lists (kernel + QEMU)
 help / color / mirror / Atom feed
Search results ordered by [date|relevance]  view[summary|nested|Atom feed]
thread overview below | download mbox.gz: |
* [Devel] [PATCH VZ10 v5 3/9] ve/fs: Rework per-ve mount count
  @ 2026-08-02 11:40  3% ` Vladimir Riabchun
  2026-08-02 11:40  9% ` [Devel] [PATCH VZ10 v5 7/9] ve: Introduce per-VE failcount Vladimir Riabchun
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 119+ results
From: Vladimir Riabchun @ 2026-08-02 11:40 UTC (permalink / raw)


Previous approach with current mounts counter had an issue:
there was a gap between ve_mount_allowed check and ve_mount_nr_inc,
which could allow CT to have more mounts than expected.

Fix this by tracking the number of available mounts instead
of current ones. This also makes resources accounting
more consistent - we are using ***_avail_nr approach more.

One more issue with inconsistent ve value is fixed:
ve_mount_allowed always used ve from get_exec_env, but
ve_mount_nr_inc operated with owner_ve.
Now actual ve value is calculated in the beginning of alloc_vfsmnt.

To avoid incorrect accounting when is_pseudosuper is changed,
update avail_nr count without > 0 check if VE is ve0 or pseudosuper.
This also simplifies ve_mount_put, since increment is
now unconditional.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Feature: per-ve failcounters
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
v4 -> v5:
 - Dropped created_as_pseudosuper, now all mounts are accounted,
   but for ve0 and pseudosuper mnt_avail_nr is allowed to go below 0.

 fs/namespace.c     | 71 +++++++++++++++++++++++++++-------------------
 include/linux/ve.h |  2 +-
 kernel/ve/ve.c     | 12 ++++----
 3 files changed, 49 insertions(+), 36 deletions(-)

diff --git a/fs/namespace.c b/fs/namespace.c
index cd6aa2127203..2550aeba2f1e 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -317,18 +317,21 @@ int mnt_get_count(struct mount *mnt)
 #endif
 }
 
-static inline int ve_mount_allowed(void);
-static inline void ve_mount_nr_inc(struct mount *mnt, struct ve_struct *ve);
-static inline void ve_mount_nr_dec(struct mount *mnt);
+static inline int ve_try_reserve_mount(struct ve_struct *ve);
+static inline void ve_mount_put(struct mount *mnt, struct ve_struct *ve);
 
 static struct mount *alloc_vfsmnt(const char *name, struct ve_struct *owner_ve)
 {
 	struct mount *mnt;
+	struct ve_struct *ve = owner_ve;
 
-	if (!ve_mount_allowed()) {
+	if (!ve)
+		ve = get_exec_env();
+
+	if (!ve_try_reserve_mount(ve)) {
 		pr_warn_ratelimited(
 			"CT#%s reached the limit on mounts.\n",
-			ve_name(get_exec_env()));
+			ve_name(ve));
 		return NULL;
 	}
 
@@ -336,6 +339,14 @@ static struct mount *alloc_vfsmnt(const char *name, struct ve_struct *owner_ve)
 	if (mnt) {
 		int err;
 
+#ifdef CONFIG_VE
+		/*
+		 * Got ve reference in ve_try_reserve_mount, set mnt ve data
+		 * here, so in case of error ve_mount_put sees correct info.
+		 */
+		mnt->ve_owner = ve;
+#endif
+
 		err = mnt_alloc_id(mnt);
 		if (err)
 			goto out_free_cache;
@@ -370,7 +381,8 @@ static struct mount *alloc_vfsmnt(const char *name, struct ve_struct *owner_ve)
 		INIT_LIST_HEAD(&mnt->mnt_umounting);
 		INIT_HLIST_HEAD(&mnt->mnt_stuck_children);
 		mnt->mnt.mnt_idmap = &nop_mnt_idmap;
-		ve_mount_nr_inc(mnt, owner_ve);
+	} else {
+		ve_mount_put(mnt, ve);
 	}
 	return mnt;
 
@@ -381,6 +393,8 @@ static struct mount *alloc_vfsmnt(const char *name, struct ve_struct *owner_ve)
 out_free_id:
 	mnt_free_id(mnt);
 out_free_cache:
+	/* Got ve reference in ve_try_reserve_mount */
+	ve_mount_put(mnt, ve);
 	kmem_cache_free(mnt_cache, mnt);
 	return NULL;
 }
@@ -750,7 +764,7 @@ int sb_prepare_remount_readonly(struct super_block *sb)
 static void free_vfsmnt(struct mount *mnt)
 {
 	mnt_idmap_put(mnt_idmap(&mnt->mnt));
-	ve_mount_nr_dec(mnt);
+	ve_mount_put(mnt, mnt->ve_owner);
 	kfree_const(mnt->mnt_devname);
 #ifdef CONFIG_SMP
 	free_percpu(mnt->mnt_pcp);
@@ -3205,7 +3219,7 @@ int ve_devmnt_process(struct ve_struct *ve, dev_t dev, void **data_pp, int remou
 		if (devmnt->dev == dev) {
 			err = ve_devmnt_check(data, devmnt->allowed_options);
 			/*
-			 * In case of @is_pseudouser set, ie restore procedure,
+			 * In case of @is_pseudosuper set, ie restore procedure,
 			 * we don't check for allowed options filtering, since
 			 * restore mode is special.
 			 */
@@ -3258,30 +3272,30 @@ int ve_devmnt_process(struct ve_struct *ve, dev_t dev, void **data_pp, int remou
 	return err;
 }
 
-static inline int ve_mount_allowed(void)
-{
-	struct ve_struct *ve = get_exec_env();
-
-	return ve_is_super(ve) || ve->is_pseudosuper ||
-		atomic_read(&ve->mnt_nr) < (int)sysctl_ve_mount_nr;
-}
-
-static inline void ve_mount_nr_inc(struct mount *mnt, struct ve_struct *ve)
+static inline int ve_try_reserve_mount(struct ve_struct *ve)
 {
-	if (!ve)
-		ve = get_exec_env();
+	int ret = ve_is_super(ve) || ve->is_pseudosuper;
+	/* Ignore limits in ve0 and pseudosuper cases, but still count. */
+	if (ret)
+		atomic_dec(&ve->mnt_avail_nr);
+	else
+		ret = atomic_dec_if_positive(&ve->mnt_avail_nr) >= 0;
 
-	mnt->ve_owner = get_ve(ve);
-	atomic_inc(&ve->mnt_nr);
+	if (ret)
+		get_ve(ve);
+	return ret;
 }
 
-static inline void ve_mount_nr_dec(struct mount *mnt)
+static inline void ve_mount_put(struct mount *mnt, struct ve_struct *ve)
 {
-	struct ve_struct *ve = mnt->ve_owner;
-
-	atomic_dec(&ve->mnt_nr);
+	/*
+	 * ve argument is needed to reuse this function in alloc_vfsmnt error path.
+	 * Other users should pass mnt->ve_owner value.
+	 */
+	atomic_inc(&ve->mnt_avail_nr);
 	put_ve(ve);
-	mnt->ve_owner = NULL;
+	if (mnt)
+		mnt->ve_owner = NULL;
 }
 
 bool is_sb_ve_accessible(struct ve_struct *ve, struct super_block *sb)
@@ -3303,9 +3317,8 @@ bool is_sb_ve_accessible(struct ve_struct *ve, struct super_block *sb)
 
 #else /* CONFIG_VE */
 
-static inline int ve_mount_allowed(void) { return 1; }
-static inline void ve_mount_nr_inc(struct mount *mnt, struct ve_struct *ve) { }
-static inline void ve_mount_nr_dec(struct mount *mnt) { }
+static inline int ve_try_reserve_mount(struct ve_struct *ve) { return 1; }
+static inline void ve_mount_put(struct mount *mnt, struct ve_struct *ve) { }
 #endif /* CONFIG_VE */
 
 static int ve_prepare_mount_options(struct fs_context *fc, void *data)
diff --git a/include/linux/ve.h b/include/linux/ve.h
index 3facbd1759df..cca0a2bc1aac 100644
--- a/include/linux/ve.h
+++ b/include/linux/ve.h
@@ -88,7 +88,7 @@ struct ve_struct {
 	atomic_t		nd_neigh_nr;
 	unsigned long		meminfo_val;
 
-	atomic_t		mnt_nr; /* number of present VE mounts */
+	atomic_t		mnt_avail_nr; /* number of available VE mounts */
 
 #ifdef CONFIG_COREDUMP
 	char			core_pattern[CORENAME_MAX_SIZE];
diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
index dddf2393326d..3f66144eeb7e 100644
--- a/kernel/ve/ve.c
+++ b/kernel/ve/ve.c
@@ -81,7 +81,7 @@ struct ve_struct ve0 = {
 
 	.arp_neigh_nr		= ATOMIC_INIT(0),
 	.nd_neigh_nr		= ATOMIC_INIT(0),
-	.mnt_nr			= ATOMIC_INIT(0),
+	.mnt_avail_nr		= ATOMIC_INIT(INT_MAX),
 	.meminfo_val		= VE_MEMINFO_SYSTEM,
 	.umh_running_helpers	= ATOMIC_INIT(0),
 	.umh_helpers_waitq	= __WAIT_QUEUE_HEAD_INITIALIZER(ve0.umh_helpers_waitq),
@@ -778,7 +778,7 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
 
 	atomic_set(&ve->arp_neigh_nr, 0);
 	atomic_set(&ve->nd_neigh_nr, 0);
-	atomic_set(&ve->mnt_nr, 0);
+	atomic_set(&ve->mnt_avail_nr, sysctl_ve_mount_nr);
 
 #ifdef CONFIG_COREDUMP
 	strcpy(ve->core_pattern, "core");
@@ -1055,9 +1055,9 @@ static u64 ve_netns_avail_nr_read(struct cgroup_subsys_state *css, struct cftype
 	return atomic_read(&css_to_ve(css)->netns_avail_nr);
 }
 
-static u64 ve_mnt_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
+static s64 ve_mnt_avail_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
 {
-	return atomic_read(&css_to_ve(css)->mnt_nr);
+	return atomic_read(&css_to_ve(css)->mnt_avail_nr);
 }
 
 static u64 ve_netif_max_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
@@ -1617,8 +1617,8 @@ static struct cftype ve_cftypes[] = {
 		.read_u64		= ve_netns_avail_nr_read,
 	},
 	{
-		.name			= "mnt_nr",
-		.read_u64		= ve_mnt_nr_read,
+		.name			= "mnt_avail_nr",
+		.read_s64		= ve_mnt_avail_nr_read,
 	},
 	{
 		.name			= "netif_max_nr",
-- 
2.47.1


^ permalink raw reply	[relevance 3%]

* [Devel] [PATCH VZ10 v5 7/9] ve: Introduce per-VE failcount
    2026-08-02 11:40  3% ` [Devel] [PATCH VZ10 v5 3/9] ve/fs: Rework per-ve mount count Vladimir Riabchun
@ 2026-08-02 11:40  9% ` Vladimir Riabchun
  2026-08-07  9:25  0%   ` Vasileios Almpanis
  2026-08-02 11:40  7% ` [Devel] [PATCH VZ10 v5 8/9] selftests/ve: Add more helpers Vladimir Riabchun
  2026-08-02 11:40  6% ` [Devel] [PATCH VZ10 v5 9/9] selftests/ve: Add mount accounting selftest Vladimir Riabchun
  3 siblings, 1 reply; 119+ results
From: Vladimir Riabchun @ 2026-08-02 11:40 UTC (permalink / raw)


It may be useful to have a history of resource limit hits for every VE,
this may simplify debugging and provide some information about the
resources usage.

This information is provided by ve.failcount file, any write to it
resets all failcounts.

To add a new failcounter we need to create a new atomic_t field
name_failcount in ve structure and add a new VE_FC_ENTRY in
ve_failcounts array.

One change, unrelated to failcounts: aio fields are now initialized
in ve0.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Feature: per-ve failcounters
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
 fs/aio.c                 |  1 +
 fs/namespace.c           |  2 ++
 include/linux/ve.h       |  6 ++++
 kernel/bpf/syscall.c     |  1 +
 kernel/ve/ve.c           | 67 ++++++++++++++++++++++++++++++++++++++++
 net/core/dev.c           |  2 ++
 net/core/neighbour.c     |  1 +
 net/core/net_namespace.c |  4 ++-
 8 files changed, 83 insertions(+), 1 deletion(-)

diff --git a/fs/aio.c b/fs/aio.c
index cb63416af135..3fa07cc626f8 100644
--- a/fs/aio.c
+++ b/fs/aio.c
@@ -814,6 +814,7 @@ static struct kioctx *ioctx_alloc(unsigned nr_events)
 	spin_lock(&ve->aio_nr_lock);
 	if (ve->aio_nr + ctx->max_reqs > ve->aio_max_nr ||
 	    ve->aio_nr + ctx->max_reqs < ve->aio_nr) {
+		atomic_inc(&ve->aio_failcount);
 		spin_unlock(&ve->aio_nr_lock);
 		err = -EAGAIN;
 		goto err_ctx;
diff --git a/fs/namespace.c b/fs/namespace.c
index 2550aeba2f1e..c4e2c7f7f725 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -3283,6 +3283,8 @@ static inline int ve_try_reserve_mount(struct ve_struct *ve)
 
 	if (ret)
 		get_ve(ve);
+	else
+		atomic_inc(&ve->mnt_failcount);
 	return ret;
 }
 
diff --git a/include/linux/ve.h b/include/linux/ve.h
index 5687faad46ff..9e73527e970e 100644
--- a/include/linux/ve.h
+++ b/include/linux/ve.h
@@ -72,12 +72,15 @@ struct ve_struct {
 	struct kmapset_key	proc_perms_key;
 
 	atomic_t		netns_avail_nr;
+	atomic_t		netns_failcount;
 	int			netns_max_nr;
 
 	atomic_t		netif_avail_nr;
+	atomic_t		netif_failcount;
 	int			netif_max_nr;
 
 	atomic_t		bpf_prog_avail_nr;
+	atomic_t		bpf_prog_failcount;
 	int			bpf_prog_max_nr;
 
 	atomic64_t		_uevent_seqnum;
@@ -86,6 +89,7 @@ struct ve_struct {
 
 	atomic_t		arp_neigh_nr;
 	atomic_t		nd_neigh_nr;
+	atomic_t		neigh_tbl_failcount;
 	unsigned long		meminfo_val;
 
 	/*
@@ -94,6 +98,7 @@ struct ve_struct {
 	 * other containers.
 	 */
 	atomic_t		mnt_avail_nr; /* number of available VE mounts */
+	atomic_t		mnt_failcount;
 	int			mnt_max_nr;
 
 #ifdef CONFIG_COREDUMP
@@ -121,6 +126,7 @@ struct ve_struct {
 	spinlock_t		aio_nr_lock;
 	unsigned long		aio_nr;
 	unsigned long		aio_max_nr;
+	atomic_t		aio_failcount;
 #endif
 	struct vfsmount		*devtmpfs_mnt;
 };
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index ff2a51c59f04..95e806fa19f4 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -2891,6 +2891,7 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
 	if (!bpf_cap && type == BPF_PROG_TYPE_CGROUP_DEVICE) {
 		load_ve = get_exec_env();
 		if (atomic_dec_if_positive(&load_ve->bpf_prog_avail_nr) < 0) {
+			atomic_inc(&load_ve->bpf_prog_failcount);
 			load_ve = NULL;
 			err = -ENOSPC;
 			goto put_token;
diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
index dc3faa0b1d76..a5c93be759ac 100644
--- a/kernel/ve/ve.c
+++ b/kernel/ve/ve.c
@@ -99,10 +99,13 @@ struct ve_struct ve0 = {
 	.features		= -1,
 	.sched_lat_ve.cur	= &ve0_lat_stats,
 	.netns_avail_nr		= ATOMIC_INIT(INT_MAX),
+	.netns_failcount	= ATOMIC_INIT(0),
 	.netns_max_nr		= INT_MAX,
 	.netif_avail_nr		= ATOMIC_INIT(INT_MAX),
+	.netif_failcount	= ATOMIC_INIT(0),
 	.netif_max_nr		= INT_MAX,
 	.bpf_prog_avail_nr	= ATOMIC_INIT(INT_MAX),
+	.bpf_prog_failcount	= ATOMIC_INIT(0),
 	.bpf_prog_max_nr	= INT_MAX,
 	.fsync_enable		= FSYNC_FILTERED,
 	._randomize_va_space	=
@@ -114,8 +117,16 @@ struct ve_struct ve0 = {
 
 	.arp_neigh_nr		= ATOMIC_INIT(0),
 	.nd_neigh_nr		= ATOMIC_INIT(0),
+	.neigh_tbl_failcount	= ATOMIC_INIT(0),
 	.mnt_avail_nr		= ATOMIC_INIT(INT_MAX),
 	.mnt_max_nr		= INT_MAX,
+	.mnt_failcount		= ATOMIC_INIT(0),
+#ifdef CONFIG_AIO
+	.aio_nr_lock		= __SPIN_LOCK_UNLOCKED(aio_nr_lock),
+	.aio_nr			= 0,
+	.aio_max_nr		= AIO_MAX_NR_DEFAULT,
+	.aio_failcount		= ATOMIC_INIT(0),
+#endif
 	.meminfo_val		= VE_MEMINFO_SYSTEM,
 	.umh_running_helpers	= ATOMIC_INIT(0),
 	.umh_helpers_waitq	= __WAIT_QUEUE_HEAD_INITIALIZER(ve0.umh_helpers_waitq),
@@ -780,12 +791,15 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
 	ve->fsync_enable = FSYNC_FILTERED;
 
 	atomic_set(&ve->netns_avail_nr, NETNS_MAX_NR_DEFAULT);
+	atomic_set(&ve->netns_failcount, 0);
 	ve->netns_max_nr = NETNS_MAX_NR_DEFAULT;
 
 	atomic_set(&ve->netif_avail_nr, NETIF_MAX_NR_DEFAULT);
+	atomic_set(&ve->netif_failcount, 0);
 	ve->netif_max_nr = NETIF_MAX_NR_DEFAULT;
 
 	atomic_set(&ve->bpf_prog_avail_nr, BPF_PROG_MAX_NR_DEFAULT);
+	atomic_set(&ve->bpf_prog_failcount, 0);
 	ve->bpf_prog_max_nr = BPF_PROG_MAX_NR_DEFAULT;
 
 	err = ve_log_init(ve);
@@ -812,7 +826,9 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
 
 	atomic_set(&ve->arp_neigh_nr, 0);
 	atomic_set(&ve->nd_neigh_nr, 0);
+	atomic_set(&ve->neigh_tbl_failcount, 0);
 	ve->mnt_max_nr = MNT_MAX_NR_DEFAULT;
+	atomic_set(&ve->mnt_failcount, 0);
 	atomic_set(&ve->mnt_avail_nr, MNT_MAX_NR_DEFAULT);
 
 #ifdef CONFIG_COREDUMP
@@ -825,6 +841,7 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
 	spin_lock_init(&ve->aio_nr_lock);
 	ve->aio_nr = 0;
 	ve->aio_max_nr = AIO_MAX_NR_DEFAULT;
+	atomic_set(&ve->aio_failcount, 0);
 #endif
 
 	return &ve->css;
@@ -1066,6 +1083,50 @@ VE_RESOURCE(mnt);
 VE_RESOURCE(netif);
 VE_RESOURCE(bpf_prog);
 
+static const struct ve_failcount_entry {
+	const char *name;
+	size_t offset;
+} ve_failcounts[] = {
+#define VE_FC_ENTRY(name) { #name, offsetof(struct ve_struct, name##_failcount) }
+	VE_FC_ENTRY(netns),
+	VE_FC_ENTRY(mnt),
+	VE_FC_ENTRY(netif),
+	VE_FC_ENTRY(bpf_prog),
+	VE_FC_ENTRY(neigh_tbl),
+#ifdef CONFIG_AIO
+	VE_FC_ENTRY(aio),
+#endif
+	{}
+};
+
+static int ve_failcount_read(struct seq_file *sf, void *v)
+{
+	struct ve_struct *ve = css_to_ve(seq_css(sf));
+	struct ve_failcount_entry *entry;
+	atomic_t *fc;
+
+	for (entry = ve_failcounts; entry->name; entry++) {
+		fc = (void *)ve + entry->offset;
+		seq_printf(sf, "%s: %d\n", entry->name, atomic_read(fc));
+	}
+	return 0;
+}
+
+static ssize_t ve_failcount_write(struct kernfs_open_file *of, char *buf,
+				  size_t nbytes, loff_t off)
+{
+	struct ve_struct *ve = css_to_ve(of_css(of));
+	struct ve_failcount_entry *entry;
+	atomic_t *fc;
+
+	for (entry = ve_failcounts; entry->name; entry++) {
+		fc = (void *)ve + entry->offset;
+		atomic_set(fc, 0);
+	}
+
+	return nbytes;
+}
+
 static int ve_os_release_read(struct seq_file *sf, void *v)
 {
 	struct cgroup_subsys_state *css = seq_css(sf);
@@ -1603,6 +1664,12 @@ static struct cftype ve_cftypes[] = {
 		.flags			= CFTYPE_NOT_ON_ROOT,
 		.write_u64		= ve_rpc_kill_write,
 	},
+	{
+		.name			= "failcount",
+		.flags			= CFTYPE_NOT_ON_ROOT,
+		.seq_show		= ve_failcount_read,
+		.write			= ve_failcount_write,
+	},
 	{ }
 };
 
diff --git a/net/core/dev.c b/net/core/dev.c
index c7dddb200489..05e0b9b6ba23 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -10997,6 +10997,7 @@ int register_netdevice(struct net_device *dev)
 
 	ret = -ENOMEM;
 	if (atomic_dec_if_positive(&net->owner_ve->netif_avail_nr) < 0) {
+		atomic_inc(&net->owner_ve->netif_failcount);
 		ve_pr_warn_ratelimited(VE_LOG_BOTH,
 			"CT%s: hits max number of network devices, "
 			"increase ve::netif_max_nr parameter\n",
@@ -12211,6 +12212,7 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net,
 
 	err = -ENOMEM;
 	if (atomic_dec_if_positive(&net->owner_ve->netif_avail_nr) < 0) {
+		atomic_inc(&net->owner_ve->netif_failcount);
 		ve_pr_warn_ratelimited(VE_LOG_BOTH,
 			"CT%s: hits max number of network devices, "
 			"increase ve::netif_max_nr parameter\n",
diff --git a/net/core/neighbour.c b/net/core/neighbour.c
index f90deb17fb25..57a49d9c98a7 100644
--- a/net/core/neighbour.c
+++ b/net/core/neighbour.c
@@ -520,6 +520,7 @@ static struct neighbour *neigh_alloc(struct neigh_table *tbl,
 	    (glob_entries >= READ_ONCE(tbl->gc_thresh2) &&
 	     time_after(now, READ_ONCE(tbl->last_flush) + 5 * HZ))) {
 		if (!neigh_forced_gc(tbl, ve) && entries >= gc_thresh3) {
+			atomic_inc(&ve->neigh_tbl_failcount);
 			net_info_ratelimited("%s: neighbor table overflow!\n",
 					     tbl->id);
 			NEIGH_CACHE_STAT_INC(tbl, table_fulls);
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index b3d54cad984a..9a3376d2682f 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -486,8 +486,10 @@ void net_drop_ns(void *p)
 #ifdef CONFIG_VE
 static int dec_netns_avail(struct ve_struct *ve)
 {
-	if (atomic_dec_if_positive(&ve->netns_avail_nr) < 0)
+	if (atomic_dec_if_positive(&ve->netns_avail_nr) < 0) {
+		atomic_inc(&ve->netns_failcount);
 		return -ENOSPC;
+	}
 	return 0;
 }
 
-- 
2.47.1


^ permalink raw reply	[relevance 9%]

* [Devel] [PATCH VZ10 v5 8/9] selftests/ve: Add more helpers
    2026-08-02 11:40  3% ` [Devel] [PATCH VZ10 v5 3/9] ve/fs: Rework per-ve mount count Vladimir Riabchun
  2026-08-02 11:40  9% ` [Devel] [PATCH VZ10 v5 7/9] ve: Introduce per-VE failcount Vladimir Riabchun
@ 2026-08-02 11:40  7% ` Vladimir Riabchun
  2026-08-02 11:40  6% ` [Devel] [PATCH VZ10 v5 9/9] selftests/ve: Add mount accounting selftest Vladimir Riabchun
  3 siblings, 0 replies; 119+ results
From: Vladimir Riabchun @ 2026-08-02 11:40 UTC (permalink / raw)


Some more read/write helpers may be useful.

Also, add a helper to execute functions in child process with
switched namespaces and cgroup.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Feature: per-ve failcounters
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
v4 -> v5:
 - In run_in_ve helper CLONE_NEWVE flag is added to unshare_flags
   unconditionally.

 tools/testing/selftests/ve/ve_selftest.h | 81 ++++++++++++++++++++++--
 1 file changed, 75 insertions(+), 6 deletions(-)

diff --git a/tools/testing/selftests/ve/ve_selftest.h b/tools/testing/selftests/ve/ve_selftest.h
index 69c0a52dd7ef..48bb7d1871bd 100644
--- a/tools/testing/selftests/ve/ve_selftest.h
+++ b/tools/testing/selftests/ve/ve_selftest.h
@@ -43,6 +43,14 @@ static inline int write_file_at(int dirfd, const char *path, const char *val)
 	return (ret == (int)len) ? 0 : -1;
 }
 
+static inline int write_u64_at(int dirfd, const char *path, unsigned long long val)
+{
+	char s[20];
+
+	snprintf(s, sizeof(s), "%llu", val);
+	return write_file_at(dirfd, path, s);
+}
+
 static inline int read_file_at(int dirfd, const char *path, char *buf,
 			       size_t buflen)
 {
@@ -73,19 +81,31 @@ static inline int read_u64_at(int dirfd, const char *path,
 			      unsigned long long *out)
 {
 	char buf[32] = {0}, *end;
-	int fd, ret;
+	int ret;
 
-	fd = openat(dirfd, path, O_RDONLY);
-	if (fd < 0)
+	ret = read_file_at(dirfd, path, buf, sizeof(buf));
+	if (ret <= 0)
 		return -1;
 
-	ret = read(fd, buf, sizeof(buf) - 1);
-	close(fd);
+	errno = 0;
+	*out = strtoull(buf, &end, 10);
+	if (errno || end == buf)
+		return -1;
+	return 0;
+}
+
+static inline int read_s32_at(int dirfd, const char *path,
+			      int *out)
+{
+	char buf[32] = {0}, *end;
+	int ret;
+
+	ret = read_file_at(dirfd, path, buf, sizeof(buf));
 	if (ret <= 0)
 		return -1;
 
 	errno = 0;
-	*out = strtoull(buf, &end, 10);
+	*out = strtol(buf, &end, 10);
 	if (errno || end == buf)
 		return -1;
 	return 0;
@@ -134,6 +154,55 @@ static inline int enter_cgroup(int cgv2_fd, int ctid)
 	return ret;
 }
 
+/*
+ * Run function in VE cgroup and new namespaces.
+ *
+ * Namespaces are provided via unshare_flags.
+ * CLONE_NEWVE flag is set by this function.
+ * Return values:
+ *  -  0 if function returns zero
+ *  - -1 if function returns negative value
+ *  -  1 if setup fails or function returns positive value
+ */
+static inline int run_in_ve(int cgv2_fd, int ctid, int unshare_flags,
+		int (*fn)(void *), void *arg)
+{
+	int status;
+	pid_t pid;
+
+	unshare_flags |= CLONE_NEWVE;
+	pid = fork();
+	if (pid < 0) {
+		fprintf(stderr, "%s: fork failed\n", __func__);
+		return 1;
+	}
+	if (pid == 0) {
+		int ret;
+
+		if (enter_cgroup(cgv2_fd, ctid) < 0) {
+			fprintf(stderr, "%s: enter_cgroup failed\n", __func__);
+			_exit(255);
+		}
+		if (unshare(unshare_flags) < 0) {
+			fprintf(stderr, "%s: unshare(%d) failed\n",
+				__func__, unshare_flags);
+			_exit(255);
+		}
+		ret = fn(arg);
+		if (ret < 0)
+			ret = 1;
+		else if (ret > 0)
+			ret = 255;
+		_exit(ret);
+	}
+	if (waitpid(pid, &status, 0) < 0 || !WIFEXITED(status) || WEXITSTATUS(status) == 255)
+		return 1;
+	if (WEXITSTATUS(status))
+		return -1;
+	return 0;
+
+}
+
 /*
  * Create a fresh VE cgroup at the first free id at or after @from and unhide
  * its ve.* control files. Return the new id, or -1.
-- 
2.47.1


^ permalink raw reply	[relevance 7%]

* [Devel] [PATCH VZ10 v5 9/9] selftests/ve: Add mount accounting selftest
                     ` (2 preceding siblings ...)
  2026-08-02 11:40  7% ` [Devel] [PATCH VZ10 v5 8/9] selftests/ve: Add more helpers Vladimir Riabchun
@ 2026-08-02 11:40  6% ` Vladimir Riabchun
  2026-08-07 10:01  0%   ` Vasileios Almpanis
  3 siblings, 1 reply; 119+ results
From: Vladimir Riabchun @ 2026-08-02 11:40 UTC (permalink / raw)


There are 6 test cases, covered in the new test:
1. Simple mount accouting correctness, just mount/umount.
2. Verification of correct limit hits and changes, including
   negative values.
3. Patial mounts test, when mount limit is hit in the middle
   of creation.
4. Test that enabled pseudosuper allows overuse.
5. Test that pseudosuper doesn't affect mount accoutning.
6. Failcount feature verification.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Feature: per-ve failcounters
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
v4 -> v5:
 - Splitted pseudosuper test into two.
 - Added new test to check that things go smoothly when
   we run out of mounts in the middle of a new mount.

 tools/testing/selftests/ve/.gitignore         |   1 +
 tools/testing/selftests/ve/Makefile           |   1 +
 .../selftests/ve/ve_mount_accounting_test.c   | 419 ++++++++++++++++++
 3 files changed, 421 insertions(+)
 create mode 100644 tools/testing/selftests/ve/ve_mount_accounting_test.c

diff --git a/tools/testing/selftests/ve/.gitignore b/tools/testing/selftests/ve/.gitignore
index afa4c568c2c9..3df4d05888dc 100644
--- a/tools/testing/selftests/ve/.gitignore
+++ b/tools/testing/selftests/ve/.gitignore
@@ -1,2 +1,3 @@
 ve_ns_owner_test
 ve_perms_test
+ve_mount_accounting_test
diff --git a/tools/testing/selftests/ve/Makefile b/tools/testing/selftests/ve/Makefile
index ec40cbc7b3a1..c6efe7c4b4fb 100644
--- a/tools/testing/selftests/ve/Makefile
+++ b/tools/testing/selftests/ve/Makefile
@@ -4,5 +4,6 @@ CFLAGS += -g -Wall -O2
 
 TEST_GEN_PROGS += ve_ns_owner_test
 TEST_GEN_PROGS += ve_perms_test
+TEST_GEN_PROGS += ve_mount_accounting_test
 
 include ../lib.mk
diff --git a/tools/testing/selftests/ve/ve_mount_accounting_test.c b/tools/testing/selftests/ve/ve_mount_accounting_test.c
new file mode 100644
index 000000000000..b295290ec6e8
--- /dev/null
+++ b/tools/testing/selftests/ve/ve_mount_accounting_test.c
@@ -0,0 +1,419 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ve_mount_accounting selftests
+ *
+ * Tests to check the correctness of mount accounting.
+ */
+#define _GNU_SOURCE
+#include <linux/sched.h>
+#include <linux/mount.h>
+#include <sched.h>
+#include <sys/wait.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+#include <asm/unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/mount.h>
+#include <linux/limits.h>
+#include <errno.h>
+
+#include "../kselftest_harness.h"
+#include "ve_selftest.h"
+
+#define TMP_DIR			"/ve-mnt-tmp/"
+#define VE_MOUNTS_MAX		128
+
+static int set_pseudosuper(int cgv2_fd, int ctid, int value)
+{
+	char path[64];
+
+	snprintf(path, sizeof(path), "%d/ve.pseudosuper", ctid);
+	return write_u64_at(cgv2_fd, path, value);
+}
+
+static int _create_mount(void *id_ptr)
+{
+	char path[PATH_MAX];
+	int id = *(int *)id_ptr, ret;
+
+	snprintf(path, sizeof(path), TMP_DIR "%d", id);
+
+	if (mkdir(path, 0755) < 0) {
+		fprintf(stderr, "Failed to create directory %s: %s\n", path, strerror(errno));
+		return -1;
+	}
+	ret = mount("tmpfs", path, "tmpfs", 0, "size=1M");
+	if (!ret)
+		return 0;
+	fprintf(stderr, "Failed to mount tmpfs to %s: %s\n", path, strerror(errno));
+
+	rmdir(path);
+	return ret;
+}
+
+static int create_mount(int cgv2_fd, int ctid, int id)
+{
+	int ret = run_in_ve(cgv2_fd, ctid, CLONE_NEWVE, _create_mount, &id);
+	/*
+	 * If mount fails, cleanup by free_vfsmnt will be called
+	 * via call_rcu, need to wait for update.
+	 */
+	sleep(1);
+	return ret;
+}
+
+static int _destroy_mount(void *id_ptr)
+{
+	char path[PATH_MAX];
+	struct stat st;
+	int id = *(int *)id_ptr;
+
+	snprintf(path, sizeof(path), TMP_DIR "%d", id);
+
+	if (stat(path, &st))
+		return 1;
+	if (umount(path)) {
+		fprintf(stderr, "failed to umount directory %s: %s\n", path, strerror(errno));
+		return -1;
+	}
+	if (rmdir(path)) {
+		fprintf(stderr, "failed to remove directory %s: %s\n", path, strerror(errno));
+		return -1;
+	}
+	return 0;
+}
+
+static int destroy_mount(int cgv2_fd, int ctid, int id)
+{
+	int ret = run_in_ve(cgv2_fd, ctid, CLONE_NEWVE, _destroy_mount, &id);
+	/* free_vfsmnt is called via call_rcu, need to wait for update */
+	sleep(1);
+	return ret;
+}
+
+#define MAX_MNT_ID 32
+
+static int get_free_mnt_id(void)
+{
+	int i;
+	struct stat st;
+	char path[PATH_MAX];
+
+	for (i = 0; i < MAX_MNT_ID; i++) {
+		snprintf(path, sizeof(path), TMP_DIR "%d", i);
+		if (stat(path, &st))
+			return i;
+	}
+	return -1;
+}
+
+static int get_mount_cost(int cgv2_fd, int ctid)
+{
+	int avail1, avail2, mnt_id;
+	char path[64];
+
+	mnt_id = get_free_mnt_id();
+
+	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", ctid);
+	if (mnt_id < 0 ||
+	    read_s32_at(cgv2_fd, path, &avail1) ||
+	    create_mount(cgv2_fd, ctid, mnt_id) ||
+	    read_s32_at(cgv2_fd, path, &avail2) ||
+	    destroy_mount(cgv2_fd, ctid, mnt_id))
+		return -1;
+
+	return avail1 - avail2;
+}
+
+/* Expect mount success and return new avail value */
+static int mount_and_get_avail(struct __test_metadata *_metadata,
+			int cgv2_fd, int ctid, int mnt_id)
+{
+	char path_avail[64];
+	int mnt_avail_nr;
+
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", ctid);
+
+	ASSERT_EQ(create_mount(cgv2_fd, ctid, mnt_id), 0);
+	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	return mnt_avail_nr;
+}
+
+/* Expect mount failure and ensure intact avail number */
+static void assert_mount_fails(struct __test_metadata *_metadata,
+			int cgv2_fd, int ctid, int mnt_id, int avail_count)
+{
+	char path_avail[64];
+	int mnt_avail_nr;
+
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", ctid);
+
+	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, avail_count);
+	ASSERT_LT(create_mount(cgv2_fd, ctid, mnt_id), 0);
+	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, avail_count);
+}
+
+FIXTURE(ve_mnt_acc)
+{
+	int cgv2_fd;
+	int ctid;
+};
+
+FIXTURE_SETUP(ve_mnt_acc)
+{
+	unsigned long long initial_mnt_avail_nr;
+	char path[64];
+
+	self->cgv2_fd = mount_cg2_fd();
+	ASSERT_GE(self->cgv2_fd, 0);
+	mkdir(TMP_DIR, 0755);
+
+	ASSERT_EQ(write_file_at(self->cgv2_fd, "cgroup.subtree_control",
+		  VE_CONTROLLERS), 0);
+
+	self->ctid = make_ve(self->cgv2_fd, CTID_MIN);
+	ASSERT_GE(self->ctid, 0);
+
+	snprintf(path, sizeof(path), "%d/ve.mnt_max_nr", self->ctid);
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path, VE_MOUNTS_MAX), 0);
+
+	/*
+	 * The new ve cgroup has not been entered by anything yet, so its
+	 * mnt_avail_nr counter should be VE_MOUNTS_MAX.
+	 */
+	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", self->ctid);
+	ASSERT_EQ(read_u64_at(self->cgv2_fd, path, &initial_mnt_avail_nr), 0);
+	ASSERT_EQ(initial_mnt_avail_nr, VE_MOUNTS_MAX);
+};
+
+FIXTURE_TEARDOWN(ve_mnt_acc)
+{
+	destroy_ve(self->cgv2_fd, self->ctid);
+	close(self->cgv2_fd);
+	rmdir(TMP_DIR);
+}
+
+/* Simple test to check mount/umount accounting correctness */
+TEST_F(ve_mnt_acc, mount_umount)
+{
+	int original_mnt_avail, mnt_avail_nr;
+	char path[64];
+
+	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", self->ctid);
+
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path, &original_mnt_avail), 0);
+
+	ASSERT_LT(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
+		  original_mnt_avail);
+
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, original_mnt_avail);
+}
+
+/* Test mount limit hits */
+TEST_F(ve_mnt_acc, hit_limits)
+{
+	int original_mnt_avail, mnt_avail_nr, mnt_cost;
+	int original_have_mnt;
+	char path_avail[64], path_max_nr[64];
+
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
+	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
+
+	mnt_cost = get_mount_cost(self->cgv2_fd, self->ctid);
+	ASSERT_GE(mnt_cost, 1);
+
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &original_mnt_avail), 0);
+	original_have_mnt = VE_MOUNTS_MAX - original_mnt_avail;
+
+	/* Step 1: reduce number of available mounts to mnt_cost */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 1 * mnt_cost), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, 1 * mnt_cost);
+
+	/* Step 2: do one mount, no mounts should be available */
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
+		  0);
+
+	/* Step 3: check that one more mount falils */
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 1, 0);
+
+	/* Step 4: increase mount limit a little bit, mount should still fail */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr,
+				original_have_mnt + 2 * mnt_cost - 1), 0);
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 1, mnt_cost - 1);
+
+	/* Step 5: increase by 1 and win now */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 2 * mnt_cost), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, mnt_cost);
+
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 1),
+		  0);
+
+	/* Step 6: reduce mnt_max_nr so we have more mounts than allowed */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 1 * mnt_cost), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, -1 * mnt_cost);
+
+	/* Step 7: try to do mount when avail < 0, ensure number is intact */
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, -1 * mnt_cost);
+
+	/* Step 8: remove one mount, check avail value update, mount should fail */
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, 0);
+
+	/* Step 9: remove one more mount and check that new mount succeeds */
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 1), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, 1 * mnt_cost);
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 2),
+		  0);
+
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 2), 0);
+}
+
+/*
+ * Mount propagation makes one mount cost more.
+ * This test check that if we run out or mounts in the middle of creating
+ * a new one, everything is restored smoothly and nothing leaks.
+ */
+TEST_F(ve_mnt_acc, partial_mounts)
+{
+	char path_avail[64], path_max_nr[64];
+	int mount_cost, i, orig_have, orig_mnt_avail;
+
+	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
+
+	mount_cost = get_mount_cost(self->cgv2_fd, self->ctid);
+	ASSERT_GE(mount_cost, 1);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &orig_mnt_avail), 0);
+	orig_have = VE_MOUNTS_MAX - orig_mnt_avail;
+
+	if (mount_cost == 1)
+		SKIP(return, "mount cost is 1, no partial mounts possible");
+
+	for (i = 0; i < mount_cost; i++) {
+		ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, orig_have + i), 0);
+		assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 0, i);
+	}
+}
+
+/* Test that pseudosuper allows negative avail with correct accounting. */
+TEST_F(ve_mnt_acc, pseudosuper_allows_overuse)
+{
+	int orig_mnt_avail, orig_have;
+	int mount_cost;
+	char path_avail[64], path_max_nr[64];
+
+	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &orig_mnt_avail), 0);
+	orig_have = VE_MOUNTS_MAX - orig_mnt_avail;
+
+	mount_cost = get_mount_cost(self->cgv2_fd, self->ctid);
+	ASSERT_GE(mount_cost, 1);
+
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, orig_have + mount_cost), 0);
+	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 1), 0);
+
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
+		  0);
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 1),
+		  -1 * mount_cost);
+
+	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 0), 0);
+
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, -1 * mount_cost);
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, 0);
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 1), 0);
+	ASSERT_EQ(get_mount_cost(self->cgv2_fd, self->ctid), mount_cost);
+}
+
+/* Test that pseudosuper doesn't disable accounting. */
+TEST_F(ve_mnt_acc, pseudosuper_continues_accounting)
+{
+	int orig_mnt_avail, mount_cost, mnt_avail_nr;
+	char path_avail[64];
+
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
+
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &orig_mnt_avail), 0);
+	mount_cost = get_mount_cost(self->cgv2_fd, self->ctid);
+	ASSERT_GE(mount_cost, 1);
+
+	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 0), 0);
+
+	/* mnt 0 - mounted without pseudosuper, umounted with it. */
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
+		  orig_mnt_avail - mount_cost);
+	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 1), 0);
+
+	/* Cost is the same when mount/umount happen under pseudosuper. */
+	ASSERT_EQ(get_mount_cost(self->cgv2_fd, self->ctid), mount_cost);
+
+	/* mnt 1 - mounted with pseudosuper, umounted without it. */
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 1),
+		  orig_mnt_avail - 2 * mount_cost);
+
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, orig_mnt_avail - mount_cost);
+
+	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 0), 0);
+
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 1), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, orig_mnt_avail);
+}
+
+/* Test failcount feature */
+TEST_F(ve_mnt_acc, failcount)
+{
+	char path_fc[64], failcount_str[512], path_max_nr[64];
+
+	snprintf(path_fc, sizeof(path_fc), "%d/ve.failcount", self->ctid);
+	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
+
+	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
+			       failcount_str, sizeof(failcount_str)), 0);
+	ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL);
+
+	/* Check successful mount doesn't affect failcount */
+	mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0);
+	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
+			       failcount_str, sizeof(failcount_str)), 0);
+	ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL);
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
+
+	/* Check failcount update when mount fails */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, 0), 0);
+	ASSERT_LT(create_mount(self->cgv2_fd, self->ctid, 1), 0);
+	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
+			       failcount_str, sizeof(failcount_str)), 0);
+	ASSERT_TRUE(strstr(failcount_str, "mnt: 1\n") != NULL);
+
+	/* Check failcount flush */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_fc, 0), 0);
+	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
+			       failcount_str, sizeof(failcount_str)), 0);
+	ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL);
+
+	/* Check failcount update when mount fails again */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, 0), 0);
+	ASSERT_LT(create_mount(self->cgv2_fd, self->ctid, 1), 0);
+	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
+			       failcount_str, sizeof(failcount_str)), 0);
+	ASSERT_TRUE(strstr(failcount_str, "mnt: 1\n") != NULL);
+}
+
+TEST_HARNESS_MAIN
-- 
2.47.1


^ permalink raw reply	[relevance 6%]

* Re: [Devel] [PATCH vz10 v2] ve/vtty: fix use-after-free on concurrent tty close and reopen
       [not found]     <20260630160639.3043321-1-eva.kurchatova@virtuozzo.com>
@ 2026-08-04 11:15  9% ` Vasileios Almpanis
  0 siblings, 0 replies; 119+ results
From: Vasileios Almpanis @ 2026-08-04 11:15 UTC (permalink / raw)



On 6/30/26 6:06 PM, Eva Kurchatova wrote:
> Two races in the vtty subsystem lead to use-after-free of tty_struct
> objects when vzctl console attach/detach cycles run concurrently with
> container-side tty open/close (e.g. SAK-triggered getty respawn):
>
> Race 1: double-final between concurrent vtty master and slave close.
>
> In tty_release(), the vttys (slave) side has o_tty == NULL because its
> driver subtype is PTY_TYPE_SLAVE, so the final-close check depends
> solely on !slave->count.  When master and slave close concurrently,
> both sides can independently determine final == true: the slave sees
> slave->count == 0, the master sees both counts == 0 (after the slave
> count underflows to -1 and gets reset).  Both then call
> tty_release_struct -> release_tty, and the second caller hits a
> use-after-free.
>
> Fix this by adding a vtty-specific check after computing final: for
> vttys closes, also verify that the peer vttym count is not positive.
> This is safe without holding the vttym lock because the vttym side of
> tty_release holds tty_lock_slave(vttys) while decrementing vttym->count,
> so while we hold tty_lock(vttys) the vttym cannot have decremented yet.
>
> Race 2: vtty_open_master reopens a dying tty pair.
>
> After tty_release() sets final == true and releases tty_lock, there is
> a window before release_tty() runs under tty_mutex.  During this window
> vtty_open_master() can find the old vttym in the vtty map with count == 0,
> pass the ">= 1" check (which was designed as a "one vttym at a time"
> guard, not a liveness check), re-increment the counts, and hand out a
> file descriptor pointing to a tty_struct that is about to be freed.
>
> Fix this by taking tty_lock(vttys) in vtty_open_master() to serialize
> with a concurrent tty_release() on the vttys side, so we read the slave
> count after any in-flight close has decremented it.  If vttys->count
> has reached zero the pair is dying, so return -EBUSY.  Incrementing
> vttys->count under the same tty_lock prevents a concurrent tty_release()
> from seeing zero and entering the final-close path.
>
> The lock nesting pattern of tty_mutex -> tty_lock is preserved, which
> prevents any kind of A->B, B->A circular locking dependency.
>
> Fixes: dfe187803cf9 ("ve/vtty: Don't close unread master peer if slave is nonzero")
> Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
> ---
>   drivers/tty/pty.c    | 31 ++++++++++++++++++++++++++-----
>   drivers/tty/tty_io.c | 17 +++++++++++++++++
>   include/linux/ve.h   |  1 +
>   3 files changed, 44 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c
> index f8610c77817a..2336204265ab 100644
> --- a/drivers/tty/pty.c
> +++ b/drivers/tty/pty.c
> @@ -628,6 +628,11 @@ bool vtty_is_master(struct tty_struct *tty)
>   	return tty->driver == vttym_driver;
>   }
>   
> +bool vtty_is_slave(struct tty_struct *tty)
> +{
> +	return tty->driver == vttys_driver;
> +}
> +
>   typedef struct {
>   	envid_t			veid;
>   	struct tty_struct	*vttys[MAX_NR_VTTY_CONSOLES];
> @@ -1082,15 +1087,31 @@ int vtty_open_master(envid_t veid, int idx)
>   		goto err_install;
>   	}
>   
> -	vtty_drop_context();
> -
>   	/*
> -	 * We're the master peer so increment
> -	 * slave counter as well.
> +	 * Serialize with a concurrent tty_release() on the vttys side.
> +	 * tty_lock guarantees we see the slave count after any in-flight
> +	 * close has decremented it.  If the slave has reached zero the
> +	 * pair is dying; return -EBUSY and let the caller retry; the
> +	 * old pair will be freed once we drop tty_mutex below, and the
> +	 * next attempt will create a fresh one.
> +	 *
> +	 * Incrementing slave->count under the same tty_lock prevents a
> +	 * concurrent tty_release() from seeing zero and starting the
> +	 * final-close path that would cause use-after-free.
>   	 */
> +	tty_lock(tty->link);
This check breaks attach to a console with no container-size opener. For 
fresh tty above you, decremented count to 0 and because of that opening 
with break with EBUSY.
> +	if (tty->link->count == 0) {
> +		tty_unlock(tty->link);
> +		ret = -EBUSY;
> +		goto err_install;
> +	}
> +	tty->link->count++;
> +	tty_unlock(tty->link);
> +
> +	vtty_drop_context();
> +
>   	tty_add_file(tty, file);
>   	tty->count++;
> -	tty->link->count++;
>   	fd_install(fd, file);
>   	vtty_open(tty, file);
>   
> diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
> index 797a9d0ddd1e..507da79a24fa 100644
> --- a/drivers/tty/tty_io.c
> +++ b/drivers/tty/tty_io.c
> @@ -1864,6 +1864,23 @@ int tty_release(struct inode *inode, struct file *filp)
>   	/* check whether both sides are closing ... */
>   	final = !tty->count && !(o_tty && o_tty->count);
>   
> +#ifdef CONFIG_VE
> +	/*
> +	 * vtty: prevent double-final between concurrent master and slave close.
> +	 *
> +	 * For vtty slaves o_tty is NULL (PTY_TYPE_SLAVE), so the standard final
> +	 * check only looks at slave->count.
> +	 *
> +	 * When the master is closing concurrently, it holds tty_lock_slave(slave)
> +	 * while decrementing master->count, so while we hold tty_lock(slave),
> +	 * the master cannot have decremented yet - master->count is still > 0.
> +	 * If master->count is already 0 here, the master already returned
> +	 * from tty_release with final = false, therefore we are closing it.
> +	 */
Can this really be reached now and be true? Since the ref increment and 
decrement happens under the same slave lock, can there be a case that 
slave->count == 0 and master->count > 0 were final was true? After 
adding locking in vtty_open_master this looks kinda impossible to me.

1) Slave closes first, master second:
 ? ?slave:? S 2->1, final = 0, check not reached
 ? ?master: S 1->0, M 1->0, final = 1, master frees the pair

2) Master closes first, slave second:
 ? ?master: S 2->1, M 1->0, final = 0
 ? ?slave:? S 1->0, final = 1, check reached: link->count == 0,
 ? ? ? ? ? ?condition false, slave frees the pair

3) Concurrent close, master's block takes tty_lock(slave) first:
 ? ?same as 2.

4) Concurrent close, slave's block takes the lock first:
 ? ?same as 1.

5) Several slave fds (M=1, S=N+1):
 ? ?slave closes can only bring S down to 1 while the master ref
 ? ?is present; S reaches 0 either inside the master's block
 ? ?(master frees) or in a slave's block after the master already
 ? ?closed (M == 0, condition false). Fallsback to cases 1-4.

6) No master attached (M=0, S=nfds):
 ? ?last slave close: S 1->0, final = 1, link->count == 0,
 ? ?condition false, slave frees the pair.

7) Master attached, no slave fd ever opened (M=1, S=1):
 ? ?detach: S 1->0, M 1->0, final = 1, master frees the pair.
 ? ?No slave release ever runs, check never executes.

8) Slave close racing vtty_open_master():
 ? ?attach order is: lock slave, check S != 0, S++, unlock, then
 ? ?M++. If the attach's locked section runs first, the closing
 ? ?slave sees S 2->1 and final = 0. If the close's block runs
 ? ?first, the attach cannot have done M++ yet (it comes strictly
 ? ?after its locked section, which is still waiting on the lock
 ? ?the closer holds), so the check reads M == 0, the slave frees
 ? ?the pair, and the attach then sees S == 0 and takes the
 ? ?dying-pair exit.

FYI: I asked AI to format these cases so they are more readable.


> +	if (final && !o_tty && tty->link && vtty_is_slave(tty) && tty->link->count > 0)
> +		final = 0;
> +#endif
> +
>   	tty_unlock_slave(o_tty);
>   	tty_unlock(tty);
>   
> diff --git a/include/linux/ve.h b/include/linux/ve.h
> index b037f60225bb..bf5d9acb964c 100644
> --- a/include/linux/ve.h
> +++ b/include/linux/ve.h
> @@ -236,6 +236,7 @@ extern int vtty_open_master(envid_t veid, int idx);
>   extern void vtty_release(struct tty_struct *tty, struct tty_struct *o_tty,
>   			int *tty_closing, int *o_tty_closing);
>   extern bool vtty_is_master(struct tty_struct *tty);
> +extern bool vtty_is_slave(struct tty_struct *tty);
>   extern void vtty_alloc_tty_struct(const struct tty_driver *driver,
>   				  struct tty_struct *o_tty);
>   #endif /* CONFIG_TTY */

-- 
Best regards, Vasileios Almpanis
Software Developer, Virtuozzo.


^ permalink raw reply	[relevance 9%]

* [Devel] [PATCH vz10 v8 1/1] fs: enforce container device-mount policy in the common mount path
@ 2026-08-04 12:53  3% Vasileios Almpanis
  2026-08-05 16:41  0% ` Pavel Tikhomirov
  2026-08-06 15:49  4% ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
  0 siblings, 2 replies; 119+ results
From: Vasileios Almpanis @ 2026-08-04 12:53 UTC (permalink / raw)


In a container the per-device ve_devmnt policy restricts which options
a device may be mounted with and force-inserts a set of hidden options.
The check used to run inside the option-string parser
(vfs_parse_monolithic_sep) and, for remount, in a separate helper. Two
things escaped it:

  - MS_* flags from the legacy mount(2)/fsconfig(2) API are folded into
    fc->sb_flags and never appear in the option string, so a container
    could set MS_RDONLY, MS_SYNCHRONOUS, MS_MANDLOCK, ... outside its
    allowed set.

  - The check sat in filesystem-selectable callbacks (->parse_monolithic,
    ->mount), so a filesystem not routing through them evaded the policy,
    and a skipped hidden-option insertion dropped a container's mandated
    options without error.

Enforce the policy in the fs-agnostic common mount path instead:
vfs_get_tree() for a new mount and reconfigure_super() for a remount.
The device is taken from the mounted superblock, so fc->source cannot be
raced to target another device, and fc->sb_flags is vetted alongside the
option string. On a new mount the forced options must also be present,
so a filesystem that skipped inserting them has its mount refused rather
than silently losing them.

The parse-time ve_devmnt_process() call is kept as a best-effort early
reject, so a disallowed device or option is refused before the
filesystem's fill_super() runs.

Fix a bug where a containers that use the legacy mount(2) syscall are able
to reconfigure a mount and change the superblock flags, for example
from RO to RW. Compute the effective superblock flags and emit rw incase
SB_RDONLY is missing in vfs_format_sb_flags.

Explicitly deny mounts for device backed legacy filesystems. So even
if FS_VIRTUALISED is added to them, mounting will not succeed since
legacy filesystems don't go through vfs_parse_monolithic_sep and the
policy is not applied to them. Keep this check until all filesystems
have migrated to the new mount API.

https://virtuozzo.atlassian.net/browse/VSTOR-132330
Fixes: 263467c864c5 ("ve/fs/devmnt: process mount options")
Signed-off-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
Co-developed-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
Signed-off-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>

Feature: ve: ve generic structures
---

Changes since v7:
- Add ve_devmnt_deny_legacy called by ve_devmnt_verify_fc only for
  mounts that required a block device and for non super VEs. With a
  comment explaining the rationale behind it, and for it to be removed
  in the future when all filesystem types have migrated to the new mount
  API namely when our kernels base becomes newer than v6.15. This will
  deny any mount in containers that are trying to use legacy filesystem
  types like ext2 even if some developer adds the FS_VIRTUALISED flag
  and recomplies the kernel with a warning.

Changes since v6:
 - Enforce sb-flag changes on remount: emit "rw" when SB_RDONLY is cleared
   and vet the effective flags,
   (sb->s_flags & ~fc->sb_flags_mask) | (fc->sb_flags & fc->sb_flags_mask),
   so a legacy mount(2) remount can no longer flip RO->RW unchecked.
 - Constrain only devices listed in ve.mount_opts: an unlisted device
   mounted with no userspace options is allowed rather than refused by the
   synthesized ro/rw token (tracked via have_user_opts).
 - Reset fc->ve_final_opts in vfs_dup_fs_context() to avoid a double-free.
 - Factor per-option emit into __vfs_emit_flag(); checkpatch/style fixes.

Changes since v5:
 - Fix use-after-free: do_new_mount() freed the merged page that
   legacy_get_tree()->mount() dereferences later; the fs now sees only
   the caller-owned original data.
 - Fix remount,dirsync failing with -EINVAL in containers: stop re-parsing
   the formatted flag names (SB_DIRSYNC is not in MS_RMT_MASK).
 - Check policy only: legacy fs parsers no longer get synthesized ro/sync
   tokens, and the flag check reads fc->sb_flags symmetrically for mount
   and fsconfig.
 - Keep ve_check_mount_options() only on the remount path (pinned sb
   device, covers legacy ->reconfigure); drop legacy_merge_mount_data()
   and ve_prepare_mount_options(); add Fixes: tag and rewrite the message.

Changes since v4:
 - Emit only the positive sb-flag names, not the clear names (rw/async/
   ...); on legacy remount sb_flags_mask is MS_RMT_MASK, so the clear
   names had wrongly rejected ordinary in-container remounts.
 - NUL-terminate the options page when data is empty and no flags emitted.
 - Fix __vfs_format_flags() comment (-E2BIG, not -ENOSPC).

Changes since v3:
 - Drop excess length check in legacy_merge_mount_data().

Changes since v2:
 - Remove the legacy_merge_mount_data guard in fs/internal.h.
 - Add __vfs_format_flags() helper, used by vfs_format_sb_flags().
 - Use -E2BIG (not -ENOSPC) for the buffer-too-small case.

Changes since v1:
 - Unify the comma-insert-copy pattern across call sites via an
   append_entry() helper.
 - Rework legacy_merge_mount_data() to allocate the page upfront and
   append sb flags via vfs_format_sb_flags(), dropping flags_buf[128]
   and the size arithmetic.
 - Use -ENOSPC (not -EINVAL) for buffer-too-small; comment
   FS_BINARY_MOUNTDATA; minor blank-line cleanups.

 fs/fs_context.c            | 193 +++++++++++++++++++++++++++++++++++++
 fs/internal.h              |   8 ++
 fs/namespace.c             | 110 ++++++++++++++++-----
 fs/super.c                 |  12 +++
 include/linux/fs_context.h |   2 +
 include/linux/mount.h      |   2 +
 6 files changed, 304 insertions(+), 23 deletions(-)

diff --git a/fs/fs_context.c b/fs/fs_context.c
index 76f34f3d468e..b2bd21a42083 100644
--- a/fs/fs_context.c
+++ b/fs/fs_context.c
@@ -81,6 +81,70 @@ static int vfs_parse_sb_flag(struct fs_context *fc, const char *key)
 	return -ENOPARAM;
 }
 
+/*
+ * Emit option @name into @buff at *@off, prefixed with ',' if the buffer
+ * already holds text. Advances *@off. Returns 0 or -E2BIG if @buff is full.
+ */
+static int __vfs_emit_flag(const char *name, char *buff,
+			   size_t size, size_t *off)
+{
+	ssize_t ret;
+
+	if (*off) {
+		if (*off + 1 >= size)
+			return -E2BIG;
+		buff[(*off)++] = ',';
+	}
+
+	ret = strscpy(buff + *off, name, size - *off);
+	if (ret < 0)
+		return -E2BIG;
+	*off += ret;
+	return 0;
+}
+
+static int __vfs_format_flags(const struct constant_table *p, unsigned int flags,
+			      char *buff, size_t size, size_t *off)
+{
+	for (; p->name; p++) {
+		int ret;
+
+		if (!(flags & p->value))
+			continue;
+		ret = __vfs_emit_flag(p->name, buff, size, off);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int vfs_format_sb_flags(char *buff, size_t size, size_t *off,
+			       unsigned int sb_flags)
+{
+	int err;
+
+	err = __vfs_format_flags(common_set_sb_flag, sb_flags, buff, size, off);
+	if (err)
+		return err;
+
+	/*
+	 * "rw" has no flag bit of its own - it is simply the absence of
+	 * SB_RDONLY. Emit it explicitly so the ve_devmnt policy can allow or
+	 * deny read-write access as a first-class option; otherwise a mount or
+	 * remount that leaves the superblock read-write carries no token and
+	 * slips past the "every option must be allowed" check.
+	 *
+	 * @sb_flags is the effective post-operation flag word, so this reflects
+	 * the state the superblock actually ends up in. A remount that only
+	 * touches an unrelated flag (e.g. "sync") keeps its current SB_RDONLY
+	 * and so does not emit "rw".
+	 */
+	if (!(sb_flags & SB_RDONLY))
+		return __vfs_emit_flag("rw", buff, size, off);
+
+	return 0;
+}
+
 /**
  * vfs_parse_fs_param_source - Handle setting "source" via parameter
  * @fc: The filesystem context to modify
@@ -224,6 +288,119 @@ static inline int fscontext_lookup_bdev(struct fs_context *fc, dev_t *s_dev)
 	return -ENODEV;
 }
 
+/*
+ * ve_devmnt_deny_legacy - refuse a device mount that escapes the policy
+ * @fc: the mount context
+ *
+ * A legacy context, one for a filesystem with no ->init_fs_context passes
+ * its mount data straight to ->mount()/->remount_fs() through
+ * legacy_parse_monolithic(), so vfs_parse_monolithic_sep() never runs on it.
+ * The container's forced options are then never inserted and fc->ve_final_opts
+ * stays empty, which would leave ve_devmnt_verify_fc() vetting an empty option
+ * string while the filesystem acts on the full one userspace passed.
+ *
+ * Returns 0 if the mount may go ahead, -EPERM if it must not.
+ */
+static int ve_devmnt_deny_legacy(struct fs_context *fc)
+{
+	struct ve_struct *ve = get_exec_env();
+
+	if (fc->ops != &legacy_fs_context_ops)
+		return 0;
+
+	ve_pr_warn_ratelimited(VE_LOG_BOTH,
+			       "VE%s: refusing to mount %s: filesystem has no fs_context support\n",
+			       ve_name(ve), fc->fs_type->name);
+	return -EPERM;
+}
+
+/*
+ * ve_devmnt_verify_fc - check a mount against the container device-mount policy
+ * @fc: the mount context, with fc->root set
+ * @new_mount: true at vfs_get_tree() (new mount), false at reconfigure_super()
+ *
+ * Vets the stashed userspace option string plus the synthesized SB_* flag
+ * names against the mounted superblock's device. A device absent from the
+ * policy that is mounted with no userspace options is allowed. Returns 0 when
+ * permitted (or no check applies), or a negative errno.
+ */
+int ve_devmnt_verify_fc(struct fs_context *fc, bool new_mount)
+{
+	struct ve_struct *ve = get_exec_env();
+	unsigned int sb_flags;
+	bool have_user_opts;
+	size_t off = 0;
+	char *page;
+	int err;
+
+	if (ve_is_super(ve))
+		return 0;
+
+	if (!fc->fs_type || !(fc->fs_type->fs_flags & FS_REQUIRES_DEV))
+		return 0;
+
+	/*
+	 * Keep this check until all filesystems have migrated to the new
+	 * mount API
+	 */
+	err = ve_devmnt_deny_legacy(fc);
+	if (err)
+		return err;
+
+	/*
+	 * Filesystems with binary mount data (e.g. btrfs) bypass option
+	 * string parsing entirely, so our checks cannot apply here.
+	 */
+	if (fc->fs_type->fs_flags & FS_BINARY_MOUNTDATA)
+		return 0;
+
+	if (WARN_ON_ONCE(!fc->root))
+		return -EINVAL;
+
+	page = (char *)__get_free_page(GFP_KERNEL);
+	if (!page)
+		return -ENOMEM;
+
+	/*
+	 * Track whether userspace actually supplied options. @page below also
+	 * gets the synthesized ro/rw flag token, so its length cannot answer
+	 * this; ve_final_opts holds only the userspace string.
+	 */
+	have_user_opts = fc->ve_final_opts && *fc->ve_final_opts;
+	if (have_user_opts) {
+		ssize_t ret = strscpy(page, fc->ve_final_opts, PAGE_SIZE);
+
+		if (ret < 0) {
+			err = -E2BIG;
+			goto out;
+		}
+		off = ret;
+	}
+
+	/*
+	 * On a remount fc->sb_flags holds only the bits being changed, so
+	 * combine them with the current superblock flags to get the state the
+	 * sb will actually have - the same value reconfigure_super() writes
+	 * back. On a new mount fc->sb_flags is already the full flag word.
+	 */
+	sb_flags = fc->sb_flags;
+	if (!new_mount)
+		sb_flags = (fc->root->d_sb->s_flags & ~fc->sb_flags_mask) |
+			   (fc->sb_flags & fc->sb_flags_mask);
+
+	err = vfs_format_sb_flags(page, PAGE_SIZE, &off, sb_flags);
+	if (err)
+		goto out;
+
+	page[off] = '\0';
+	err = ve_devmnt_verify(ve, fc->root->d_sb->s_dev, page, new_mount,
+			       have_user_opts);
+
+out:
+	free_page((unsigned long)page);
+	return err;
+}
+
 static int fscontext_init_lazy_opts(struct fs_context *fc)
 {
 	struct ve_struct *ve = get_exec_env();
@@ -389,10 +566,22 @@ int vfs_parse_monolithic_sep(struct fs_context *fc, void *data,
 			return -ENODEV;
 		}
 
+		/* Early reject and hidden-option insertion; verified for real later. */
 		ret = ve_devmnt_process(ve, bd_dev, (void **) &options,
 				fc->purpose == FS_CONTEXT_FOR_RECONFIGURE);
 		if (ret)
 			return ret;
+
+		/* Stash what the filesystem parses; checked in the common mount path. */
+		if (options) {
+			kfree(fc->ve_final_opts);
+			fc->ve_final_opts = kstrdup(options, GFP_KERNEL);
+			if (!fc->ve_final_opts) {
+				if (options != options_orig)
+					free_page((unsigned long)options);
+				return -ENOMEM;
+			}
+		}
 	}
 
 	/*
@@ -614,6 +803,7 @@ struct fs_context *vfs_dup_fs_context(struct fs_context *src_fc)
 	fc->s_fs_info	= NULL;
 	fc->source	= NULL;
 	fc->security	= NULL;
+	fc->ve_final_opts = NULL;
 	get_filesystem(fc->fs_type);
 	get_net(fc->net_ns);
 	get_user_ns(fc->user_ns);
@@ -742,6 +932,7 @@ void put_fs_context(struct fs_context *fc)
 	put_filesystem(fc->fs_type);
 	if (fc->lazy_opts)
 		free_page((unsigned long)fc->lazy_opts);
+	kfree(fc->ve_final_opts);
 	kfree(fc->source);
 	kfree(fc);
 }
@@ -962,6 +1153,8 @@ void vfs_clean_context(struct fs_context *fc)
 		free_page((unsigned long)fc->lazy_opts);
 		fc->lazy_opts = NULL;
 	}
+	kfree(fc->ve_final_opts);
+	fc->ve_final_opts = NULL;
 	kfree(fc->source);
 	fc->source = NULL;
 	fc->exclusive = false;
diff --git a/fs/internal.h b/fs/internal.h
index 3647ce69b2c7..f1892959db48 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -46,6 +46,14 @@ extern void __init chrdev_init(void);
  */
 extern const struct fs_context_operations legacy_fs_context_ops;
 extern int parse_monolithic_mount_data(struct fs_context *, void *);
+#ifdef CONFIG_VE
+extern int ve_devmnt_verify_fc(struct fs_context *fc, bool new_mount);
+#else
+static inline int ve_devmnt_verify_fc(struct fs_context *fc, bool new_mount)
+{
+	return 0;
+}
+#endif
 extern void vfs_clean_context(struct fs_context *fc);
 extern int finish_clean_context(struct fs_context *fc);
 
diff --git a/fs/namespace.c b/fs/namespace.c
index 43493f779c59..4d4dc5290350 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -3258,6 +3258,92 @@ int ve_devmnt_process(struct ve_struct *ve, dev_t dev, void **data_pp, int remou
 	return err;
 }
 
+/* Return 0 if every option in @options is listed in @a or @b, else -EPERM. */
+static int ve_devmnt_options_subset(char *options, char *a, char *b)
+{
+	char *copy, *cur, *p;
+	int err = 0;
+
+	if (!options || !*options)
+		return 0;
+	if (!a && !b)
+		return -EPERM;
+
+	copy = cur = kstrdup(options, GFP_KERNEL);
+	if (!copy)
+		return -ENOMEM;
+
+	while ((p = strsep(&cur, ",")) != NULL) {
+		if (!*p)
+			continue;
+		if ((!a || !strstr_separated(a, p, ',')) &&
+		    (!b || !strstr_separated(b, p, ','))) {
+			err = -EPERM;
+			break;
+		}
+	}
+
+	kfree(copy);
+	return err;
+}
+
+/*
+ * ve_devmnt_verify - enforce the container device-mount policy for @dev
+ * @ve: the container
+ * @dev: device taken from the mounted superblock (not from a raceable path)
+ * @opts: mount options plus the SB_* flag names to vet
+ * @new_mount: true for a new mount, false for a remount
+ * @have_user_opts: true if userspace supplied any mount options. @opts always
+ *	carries the kernel-synthesized ro/rw flag token, so it is never empty
+ *	and cannot answer this on its own.
+ *
+ * Every supplied option must be allowed or forced. On a new mount the forced
+ * ("hidden") options must also be present: a filesystem that skipped inserting
+ * them is refused rather than silently dropping a container's mandated option
+ */
+int ve_devmnt_verify(struct ve_struct *ve, dev_t dev, char *opts, bool new_mount,
+		     bool have_user_opts)
+{
+	struct ve_devmnt *devmnt;
+	char *allowed = NULL, *hidden = NULL;
+	bool found = false;
+	int err = 0;
+
+	if (ve->is_pseudosuper)
+		return 0;
+
+	mutex_lock(&ve->devmnt_mutex);
+	list_for_each_entry(devmnt, &ve->devmnt_list, link) {
+		if (devmnt->dev == dev) {
+			allowed = devmnt->allowed_options;
+			hidden = devmnt->hidden_options;
+			found = true;
+			break;
+		}
+	}
+
+	/*
+	 * Enforce for a listed device, or for any mount carrying userspace
+	 * options. An unlisted device with no userspace options is unconstrained
+	 * here, so the synthesized ro/rw token in @opts does not deny it.
+	 */
+	if (found || have_user_opts) {
+		/* every supplied option must be either allowed or forced */
+		err = ve_devmnt_options_subset(opts, allowed, hidden);
+
+		/* on a new mount every forced option must have reached the fs */
+		if (!err && new_mount)
+			err = ve_devmnt_options_subset(hidden, opts, NULL);
+	}
+	mutex_unlock(&ve->devmnt_mutex);
+
+	if (err == -EPERM)
+		ve_pr_warn_ratelimited(VE_LOG_BOTH,
+				       "VE%s: mount options not permitted for device %u:%u\n",
+				       ve_name(ve), MAJOR(dev), MINOR(dev));
+	return err;
+}
+
 static inline int ve_mount_allowed(void)
 {
 	struct ve_struct *ve = get_exec_env();
@@ -3308,23 +3394,6 @@ static inline void ve_mount_nr_inc(struct mount *mnt, struct ve_struct *ve) { }
 static inline void ve_mount_nr_dec(struct mount *mnt) { }
 #endif /* CONFIG_VE */
 
-static int ve_prepare_mount_options(struct fs_context *fc, void *data)
-{
-#ifdef CONFIG_VE
-	struct super_block *sb = fc->root->d_sb;
-	struct ve_struct *ve = get_exec_env();
-
-	if (sb->s_bdev && data && !ve_is_super(ve)) {
-		int err;
-
-		err = ve_devmnt_process(ve, sb->s_bdev->bd_dev, &data, 1);
-		if (err)
-			return err;
-	}
-#endif
-	return 0;
-}
-
 /*
  * change filesystem flags. dir should be a physical root of filesystem.
  * If you've mounted a non-root directory somewhere and want to do remount
@@ -3357,12 +3426,6 @@ static int do_remount(struct path *path, int ms_flags, int sb_flags,
 	 */
 	fc->oldapi = true;
 
-	err = ve_prepare_mount_options(fc, data);
-	if (err) {
-		put_fs_context(fc);
-		return err;
-	}
-
 	err = parse_monolithic_mount_data(fc, data);
 	if (!err) {
 		down_write(&sb->s_umount);
@@ -3816,6 +3879,7 @@ static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
 					  subtype, strlen(subtype));
 	if (!err && name)
 		err = vfs_parse_fs_string(fc, "source", name, strlen(name));
+	/* Container device-mount policy is enforced later, in vfs_get_tree(). */
 	if (!err)
 		err = parse_monolithic_mount_data(fc, data);
 	if (!err && !mount_capable(fc))
diff --git a/fs/super.c b/fs/super.c
index 1adebbf35803..c0c067eb2d8e 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -1085,6 +1085,11 @@ int reconfigure_super(struct fs_context *fc)
 	if (retval)
 		return retval;
 
+	/* Enforce the container device-mount policy on the remount options. */
+	retval = ve_devmnt_verify_fc(fc, false);
+	if (retval)
+		return retval;
+
 	if (fc->sb_flags_mask & SB_RDONLY) {
 #ifdef CONFIG_BLOCK
 		if (!(fc->sb_flags & SB_RDONLY) && sb->s_bdev &&
@@ -1924,6 +1929,13 @@ int vfs_get_tree(struct fs_context *fc)
 		return error;
 	}
 
+	/* Enforce the container device-mount policy against the real device. */
+	error = ve_devmnt_verify_fc(fc, true);
+	if (unlikely(error)) {
+		fc_drop_locked(fc);
+		return error;
+	}
+
 	/*
 	 * filesystems should never set s_maxbytes larger than MAX_LFS_FILESIZE
 	 * but s_maxbytes was an unsigned long long for many releases. Throw
diff --git a/include/linux/fs_context.h b/include/linux/fs_context.h
index 1801aed1da67..2ca586e2cc2a 100644
--- a/include/linux/fs_context.h
+++ b/include/linux/fs_context.h
@@ -93,6 +93,8 @@ struct fs_context {
 	struct file_system_type	*fs_type;
 	void			*fs_private;	/* The filesystem's context */
 	void			*lazy_opts;	/* mount options which can't be checked at fsconfig() time */
+	/* option string handed to the fs, for the ve_devmnt policy check */
+	char			*ve_final_opts;
 	void			*sget_key;
 	struct dentry		*root;		/* The root and superblock */
 	struct user_namespace	*user_ns;	/* The user namespace for this mount */
diff --git a/include/linux/mount.h b/include/linux/mount.h
index 0cbc6f6893c0..ab0ea4f7afc6 100644
--- a/include/linux/mount.h
+++ b/include/linux/mount.h
@@ -127,5 +127,7 @@ extern int cifs_root_data(char **dev, char **opts);
 
 struct ve_struct;
 extern int ve_devmnt_process(struct ve_struct *, dev_t, void **, int);
+extern int ve_devmnt_verify(struct ve_struct *ve, dev_t dev, char *opts,
+			    bool new_mount, bool have_user_opts);
 
 #endif /* _LINUX_MOUNT_H */
-- 
2.43.0


^ permalink raw reply	[relevance 3%]

* [Devel] [PATCH RHEL10 COMMIT] ms/qede: sync udp_tunnel ports outside qede_lock in the recovery path
       [not found]     <20260731155337.1209007-1-den@openvz.org>
@ 2026-08-05 11:20  4% ` Konstantin Khorenko
  0 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-05 11:20 UTC (permalink / raw)


The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.2.vz10
------>
commit b8673a452aa2f5ad79d424888140d11639ab0384
Author: Denis V. Lunev <den@openvz.org>
Date:   Fri Jul 31 17:53:37 2026 +0200

    ms/qede: sync udp_tunnel ports outside qede_lock in the recovery path
    
    A TX timeout on a qede NIC that has VXLAN/GENEVE tunnel ports
    configured wedges the rtnetlink control plane of the whole machine:
    
      NETDEV WATCHDOG: ens6f1 (qede): transmit queue 2 timed out 10226 ms
      [qede_tx_timeout:586(ens6f1)]TX timeout on queue 2!
      [qede_recovery_handler:2665(ens6f0)]Starting a recovery process
    
    The recovery path deadlocks on the driver's own mutex:
    
      qede_sp_task
       rtnl_lock()
       mutex_lock(&edev->qede_lock)        <- taken
       qede_recovery_handler
        qede_load
        udp_tunnel_nic_reset_ntf
         __udp_tunnel_nic_device_sync
          info->sync_table == qede_udp_tunnel_sync
           mutex_lock(&edev->qede_lock)    <- same task: deadlock
    
    The mutex is not recursive, so the kworker blocks on itself with
    rtnl_lock held, and neither lock is ever released. Every task that
    calls rtnl_lock() afterwards (ip, ovs-vswitchd, lldpad, IPv6
    addrconf, sshd) blocks forever while the node still answers ping.
    In a vmcore from an affected production node rtnl_mutex.owner
    decodes to the very kworker blocked at the innermost mutex_lock()
    above.
    
    Re-sync the tunnel ports from qede_sp_task() after the internal lock
    is dropped, still under rtnl_lock as the udp_tunnel API requires.
    This mirrors qede_open(), which calls udp_tunnel_nic_reset_ntf()
    under rtnl without the internal lock.
    
    qede_recovery_handler() now returns whether it has successfully
    reloaded an open device, and the caller re-syncs the ports only in
    that case. This keeps the old gating exactly: a device that was down
    or a failed recovery returns false, as those paths never reached the
    udp_tunnel_nic_reset_ntf() call before either.
    
    This was the only user of the qede_lock()/qede_unlock() helpers, so
    remove them.
    
    Fixes: 8cd160a29415 ("qede: convert to new udp_tunnel_nic infra")
    Signed-off-by: Denis V. Lunev <den@openvz.org>
    
    CC: Andrew Lunn <andrew+netdev@lunn.ch>
    CC: "David S. Miller" <davem@davemloft.net>
    CC: Eric Dumazet <edumazet@google.com>
    CC: Jakub Kicinski <kuba@kernel.org>
    CC: Paolo Abeni <pabeni@redhat.com>
    
    (cherry picked from commit 451c9075d6c53f2438d110addbeeeea6fac18567)
    https://virtuozzo.atlassian.net/browse/VSTOR-138358
    Feature: fix ms/drivers
---
 drivers/net/ethernet/qlogic/qede/qede_main.c | 44 ++++++++++++++--------------
 1 file changed, 22 insertions(+), 22 deletions(-)

diff --git a/drivers/net/ethernet/qlogic/qede/qede_main.c b/drivers/net/ethernet/qlogic/qede/qede_main.c
index 66ab1b9d65a1a..cdb20606048ae 100644
--- a/drivers/net/ethernet/qlogic/qede/qede_main.c
+++ b/drivers/net/ethernet/qlogic/qede/qede_main.c
@@ -107,7 +107,7 @@ static void qede_remove(struct pci_dev *pdev);
 static void qede_shutdown(struct pci_dev *pdev);
 static void qede_link_update(void *dev, struct qed_link_output *link);
 static void qede_schedule_recovery_handler(void *dev);
-static void qede_recovery_handler(struct qede_dev *edev);
+static bool qede_recovery_handler(struct qede_dev *edev);
 static void qede_schedule_hw_err_handler(void *dev,
 					 enum qed_hw_err_type err_type);
 static void qede_get_eth_tlv_data(void *edev, void *data);
@@ -1047,21 +1047,6 @@ void __qede_unlock(struct qede_dev *edev)
 	mutex_unlock(&edev->qede_lock);
 }
 
-/* This version of the lock should be used when acquiring the RTNL lock is also
- * needed in addition to the internal qede lock.
- */
-static void qede_lock(struct qede_dev *edev)
-{
-	rtnl_lock();
-	__qede_lock(edev);
-}
-
-static void qede_unlock(struct qede_dev *edev)
-{
-	__qede_unlock(edev);
-	rtnl_unlock();
-}
-
 static void qede_periodic_task(struct work_struct *work)
 {
 	struct qede_dev *edev = container_of(work, struct qede_dev,
@@ -1098,6 +1083,8 @@ static void qede_sp_task(struct work_struct *work)
 	 */
 
 	if (test_and_clear_bit(QEDE_SP_RECOVERY, &edev->sp_flags)) {
+		bool reloaded;
+
 		cancel_delayed_work_sync(&edev->periodic_task);
 #ifdef CONFIG_QED_SRIOV
 		/* SRIOV must be disabled outside the lock to avoid a deadlock.
@@ -1106,9 +1093,17 @@ static void qede_sp_task(struct work_struct *work)
 		if (pci_num_vf(edev->pdev))
 			qede_sriov_configure(edev->pdev, 0);
 #endif
-		qede_lock(edev);
-		qede_recovery_handler(edev);
-		qede_unlock(edev);
+		rtnl_lock();
+		__qede_lock(edev);
+		reloaded = qede_recovery_handler(edev);
+		__qede_unlock(edev);
+
+		/* The udp_tunnel core synchronously calls back into
+		 * qede_udp_tunnel_sync(), which takes the qede lock.
+		 */
+		if (reloaded)
+			udp_tunnel_nic_reset_ntf(edev->ndev);
+		rtnl_unlock();
 	}
 
 	__qede_lock(edev);
@@ -2649,9 +2644,13 @@ static void qede_recovery_failed(struct qede_dev *edev)
 		edev->ops->common->set_power_state(edev->cdev, PCI_D3hot);
 }
 
-static void qede_recovery_handler(struct qede_dev *edev)
+/* Returns true if an open device was successfully reloaded and its
+ * udp_tunnel ports need to be re-synced by the caller.
+ */
+static bool qede_recovery_handler(struct qede_dev *edev)
 {
 	u32 curr_state = edev->state;
+	bool reloaded = false;
 	int rc;
 
 	DP_NOTICE(edev, "Starting a recovery process\n");
@@ -2681,17 +2680,18 @@ static void qede_recovery_handler(struct qede_dev *edev)
 			goto err;
 
 		qede_config_rx_mode(edev->ndev);
-		udp_tunnel_nic_reset_ntf(edev->ndev);
+		reloaded = true;
 	}
 
 	edev->state = curr_state;
 
 	DP_NOTICE(edev, "Recovery handling is done\n");
 
-	return;
+	return reloaded;
 
 err:
 	qede_recovery_failed(edev);
+	return false;
 }
 
 static void qede_atomic_hw_err_handler(struct qede_dev *edev)

^ permalink raw reply	[relevance 4%]

* Re: [Devel] [PATCH vz10 v8 1/1] fs: enforce container device-mount policy in the common mount path
  2026-08-04 12:53  3% [Devel] [PATCH vz10 v8 1/1] fs: enforce container device-mount policy in the common mount path Vasileios Almpanis
@ 2026-08-05 16:41  0% ` Pavel Tikhomirov
  2026-08-06 15:49  4% ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
  1 sibling, 0 replies; 119+ results
From: Pavel Tikhomirov @ 2026-08-05 16:41 UTC (permalink / raw)


Reviewed-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>

On 8/4/26 14:53, Vasileios Almpanis wrote:
> In a container the per-device ve_devmnt policy restricts which options
> a device may be mounted with and force-inserts a set of hidden options.
> The check used to run inside the option-string parser
> (vfs_parse_monolithic_sep) and, for remount, in a separate helper. Two
> things escaped it:
> 
>   - MS_* flags from the legacy mount(2)/fsconfig(2) API are folded into
>     fc->sb_flags and never appear in the option string, so a container
>     could set MS_RDONLY, MS_SYNCHRONOUS, MS_MANDLOCK, ... outside its
>     allowed set.
> 
>   - The check sat in filesystem-selectable callbacks (->parse_monolithic,
>     ->mount), so a filesystem not routing through them evaded the policy,
>     and a skipped hidden-option insertion dropped a container's mandated
>     options without error.
> 
> Enforce the policy in the fs-agnostic common mount path instead:
> vfs_get_tree() for a new mount and reconfigure_super() for a remount.
> The device is taken from the mounted superblock, so fc->source cannot be
> raced to target another device, and fc->sb_flags is vetted alongside the
> option string. On a new mount the forced options must also be present,
> so a filesystem that skipped inserting them has its mount refused rather
> than silently losing them.
> 
> The parse-time ve_devmnt_process() call is kept as a best-effort early
> reject, so a disallowed device or option is refused before the
> filesystem's fill_super() runs.
> 
> Fix a bug where a containers that use the legacy mount(2) syscall are able
> to reconfigure a mount and change the superblock flags, for example
> from RO to RW. Compute the effective superblock flags and emit rw incase
> SB_RDONLY is missing in vfs_format_sb_flags.
> 
> Explicitly deny mounts for device backed legacy filesystems. So even
> if FS_VIRTUALISED is added to them, mounting will not succeed since
> legacy filesystems don't go through vfs_parse_monolithic_sep and the
> policy is not applied to them. Keep this check until all filesystems
> have migrated to the new mount API.
> 
> https://virtuozzo.atlassian.net/browse/VSTOR-132330
> Fixes: 263467c864c5 ("ve/fs/devmnt: process mount options")
> Signed-off-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
> Co-developed-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
> Signed-off-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
> 
> Feature: ve: ve generic structures
> ---
> 
> Changes since v7:
> - Add ve_devmnt_deny_legacy called by ve_devmnt_verify_fc only for
>   mounts that required a block device and for non super VEs. With a
>   comment explaining the rationale behind it, and for it to be removed
>   in the future when all filesystem types have migrated to the new mount
>   API namely when our kernels base becomes newer than v6.15. This will
>   deny any mount in containers that are trying to use legacy filesystem
>   types like ext2 even if some developer adds the FS_VIRTUALISED flag
>   and recomplies the kernel with a warning.
> 
> Changes since v6:
>  - Enforce sb-flag changes on remount: emit "rw" when SB_RDONLY is cleared
>    and vet the effective flags,
>    (sb->s_flags & ~fc->sb_flags_mask) | (fc->sb_flags & fc->sb_flags_mask),
>    so a legacy mount(2) remount can no longer flip RO->RW unchecked.
>  - Constrain only devices listed in ve.mount_opts: an unlisted device
>    mounted with no userspace options is allowed rather than refused by the
>    synthesized ro/rw token (tracked via have_user_opts).
>  - Reset fc->ve_final_opts in vfs_dup_fs_context() to avoid a double-free.
>  - Factor per-option emit into __vfs_emit_flag(); checkpatch/style fixes.
> 
> Changes since v5:
>  - Fix use-after-free: do_new_mount() freed the merged page that
>    legacy_get_tree()->mount() dereferences later; the fs now sees only
>    the caller-owned original data.
>  - Fix remount,dirsync failing with -EINVAL in containers: stop re-parsing
>    the formatted flag names (SB_DIRSYNC is not in MS_RMT_MASK).
>  - Check policy only: legacy fs parsers no longer get synthesized ro/sync
>    tokens, and the flag check reads fc->sb_flags symmetrically for mount
>    and fsconfig.
>  - Keep ve_check_mount_options() only on the remount path (pinned sb
>    device, covers legacy ->reconfigure); drop legacy_merge_mount_data()
>    and ve_prepare_mount_options(); add Fixes: tag and rewrite the message.
> 
> Changes since v4:
>  - Emit only the positive sb-flag names, not the clear names (rw/async/
>    ...); on legacy remount sb_flags_mask is MS_RMT_MASK, so the clear
>    names had wrongly rejected ordinary in-container remounts.
>  - NUL-terminate the options page when data is empty and no flags emitted.
>  - Fix __vfs_format_flags() comment (-E2BIG, not -ENOSPC).
> 
> Changes since v3:
>  - Drop excess length check in legacy_merge_mount_data().
> 
> Changes since v2:
>  - Remove the legacy_merge_mount_data guard in fs/internal.h.
>  - Add __vfs_format_flags() helper, used by vfs_format_sb_flags().
>  - Use -E2BIG (not -ENOSPC) for the buffer-too-small case.
> 
> Changes since v1:
>  - Unify the comma-insert-copy pattern across call sites via an
>    append_entry() helper.
>  - Rework legacy_merge_mount_data() to allocate the page upfront and
>    append sb flags via vfs_format_sb_flags(), dropping flags_buf[128]
>    and the size arithmetic.
>  - Use -ENOSPC (not -EINVAL) for buffer-too-small; comment
>    FS_BINARY_MOUNTDATA; minor blank-line cleanups.
> 
>  fs/fs_context.c            | 193 +++++++++++++++++++++++++++++++++++++
>  fs/internal.h              |   8 ++
>  fs/namespace.c             | 110 ++++++++++++++++-----
>  fs/super.c                 |  12 +++
>  include/linux/fs_context.h |   2 +
>  include/linux/mount.h      |   2 +
>  6 files changed, 304 insertions(+), 23 deletions(-)
> 
> diff --git a/fs/fs_context.c b/fs/fs_context.c
> index 76f34f3d468e..b2bd21a42083 100644
> --- a/fs/fs_context.c
> +++ b/fs/fs_context.c
> @@ -81,6 +81,70 @@ static int vfs_parse_sb_flag(struct fs_context *fc, const char *key)
>  	return -ENOPARAM;
>  }
>  
> +/*
> + * Emit option @name into @buff at *@off, prefixed with ',' if the buffer
> + * already holds text. Advances *@off. Returns 0 or -E2BIG if @buff is full.
> + */
> +static int __vfs_emit_flag(const char *name, char *buff,
> +			   size_t size, size_t *off)
> +{
> +	ssize_t ret;
> +
> +	if (*off) {
> +		if (*off + 1 >= size)
> +			return -E2BIG;
> +		buff[(*off)++] = ',';
> +	}
> +
> +	ret = strscpy(buff + *off, name, size - *off);
> +	if (ret < 0)
> +		return -E2BIG;
> +	*off += ret;
> +	return 0;
> +}
> +
> +static int __vfs_format_flags(const struct constant_table *p, unsigned int flags,
> +			      char *buff, size_t size, size_t *off)
> +{
> +	for (; p->name; p++) {
> +		int ret;
> +
> +		if (!(flags & p->value))
> +			continue;
> +		ret = __vfs_emit_flag(p->name, buff, size, off);
> +		if (ret)
> +			return ret;
> +	}
> +	return 0;
> +}
> +
> +static int vfs_format_sb_flags(char *buff, size_t size, size_t *off,
> +			       unsigned int sb_flags)
> +{
> +	int err;
> +
> +	err = __vfs_format_flags(common_set_sb_flag, sb_flags, buff, size, off);
> +	if (err)
> +		return err;
> +
> +	/*
> +	 * "rw" has no flag bit of its own - it is simply the absence of
> +	 * SB_RDONLY. Emit it explicitly so the ve_devmnt policy can allow or
> +	 * deny read-write access as a first-class option; otherwise a mount or
> +	 * remount that leaves the superblock read-write carries no token and
> +	 * slips past the "every option must be allowed" check.
> +	 *
> +	 * @sb_flags is the effective post-operation flag word, so this reflects
> +	 * the state the superblock actually ends up in. A remount that only
> +	 * touches an unrelated flag (e.g. "sync") keeps its current SB_RDONLY
> +	 * and so does not emit "rw".
> +	 */
> +	if (!(sb_flags & SB_RDONLY))
> +		return __vfs_emit_flag("rw", buff, size, off);
> +
> +	return 0;
> +}
> +
>  /**
>   * vfs_parse_fs_param_source - Handle setting "source" via parameter
>   * @fc: The filesystem context to modify
> @@ -224,6 +288,119 @@ static inline int fscontext_lookup_bdev(struct fs_context *fc, dev_t *s_dev)
>  	return -ENODEV;
>  }
>  
> +/*
> + * ve_devmnt_deny_legacy - refuse a device mount that escapes the policy
> + * @fc: the mount context
> + *
> + * A legacy context, one for a filesystem with no ->init_fs_context passes
> + * its mount data straight to ->mount()/->remount_fs() through
> + * legacy_parse_monolithic(), so vfs_parse_monolithic_sep() never runs on it.
> + * The container's forced options are then never inserted and fc->ve_final_opts
> + * stays empty, which would leave ve_devmnt_verify_fc() vetting an empty option
> + * string while the filesystem acts on the full one userspace passed.
> + *
> + * Returns 0 if the mount may go ahead, -EPERM if it must not.
> + */
> +static int ve_devmnt_deny_legacy(struct fs_context *fc)
> +{
> +	struct ve_struct *ve = get_exec_env();
> +
> +	if (fc->ops != &legacy_fs_context_ops)
> +		return 0;
> +
> +	ve_pr_warn_ratelimited(VE_LOG_BOTH,
> +			       "VE%s: refusing to mount %s: filesystem has no fs_context support\n",
> +			       ve_name(ve), fc->fs_type->name);
> +	return -EPERM;
> +}
> +
> +/*
> + * ve_devmnt_verify_fc - check a mount against the container device-mount policy
> + * @fc: the mount context, with fc->root set
> + * @new_mount: true at vfs_get_tree() (new mount), false at reconfigure_super()
> + *
> + * Vets the stashed userspace option string plus the synthesized SB_* flag
> + * names against the mounted superblock's device. A device absent from the
> + * policy that is mounted with no userspace options is allowed. Returns 0 when
> + * permitted (or no check applies), or a negative errno.
> + */
> +int ve_devmnt_verify_fc(struct fs_context *fc, bool new_mount)
> +{
> +	struct ve_struct *ve = get_exec_env();
> +	unsigned int sb_flags;
> +	bool have_user_opts;
> +	size_t off = 0;
> +	char *page;
> +	int err;
> +
> +	if (ve_is_super(ve))
> +		return 0;
> +
> +	if (!fc->fs_type || !(fc->fs_type->fs_flags & FS_REQUIRES_DEV))
> +		return 0;
> +
> +	/*
> +	 * Keep this check until all filesystems have migrated to the new
> +	 * mount API
> +	 */
> +	err = ve_devmnt_deny_legacy(fc);
> +	if (err)
> +		return err;
> +
> +	/*
> +	 * Filesystems with binary mount data (e.g. btrfs) bypass option
> +	 * string parsing entirely, so our checks cannot apply here.
> +	 */
> +	if (fc->fs_type->fs_flags & FS_BINARY_MOUNTDATA)
> +		return 0;
> +
> +	if (WARN_ON_ONCE(!fc->root))
> +		return -EINVAL;
> +
> +	page = (char *)__get_free_page(GFP_KERNEL);
> +	if (!page)
> +		return -ENOMEM;
> +
> +	/*
> +	 * Track whether userspace actually supplied options. @page below also
> +	 * gets the synthesized ro/rw flag token, so its length cannot answer
> +	 * this; ve_final_opts holds only the userspace string.
> +	 */
> +	have_user_opts = fc->ve_final_opts && *fc->ve_final_opts;
> +	if (have_user_opts) {
> +		ssize_t ret = strscpy(page, fc->ve_final_opts, PAGE_SIZE);
> +
> +		if (ret < 0) {
> +			err = -E2BIG;
> +			goto out;
> +		}
> +		off = ret;
> +	}
> +
> +	/*
> +	 * On a remount fc->sb_flags holds only the bits being changed, so
> +	 * combine them with the current superblock flags to get the state the
> +	 * sb will actually have - the same value reconfigure_super() writes
> +	 * back. On a new mount fc->sb_flags is already the full flag word.
> +	 */
> +	sb_flags = fc->sb_flags;
> +	if (!new_mount)
> +		sb_flags = (fc->root->d_sb->s_flags & ~fc->sb_flags_mask) |
> +			   (fc->sb_flags & fc->sb_flags_mask);
> +
> +	err = vfs_format_sb_flags(page, PAGE_SIZE, &off, sb_flags);
> +	if (err)
> +		goto out;
> +
> +	page[off] = '\0';
> +	err = ve_devmnt_verify(ve, fc->root->d_sb->s_dev, page, new_mount,
> +			       have_user_opts);
> +
> +out:
> +	free_page((unsigned long)page);
> +	return err;
> +}
> +
>  static int fscontext_init_lazy_opts(struct fs_context *fc)
>  {
>  	struct ve_struct *ve = get_exec_env();
> @@ -389,10 +566,22 @@ int vfs_parse_monolithic_sep(struct fs_context *fc, void *data,
>  			return -ENODEV;
>  		}
>  
> +		/* Early reject and hidden-option insertion; verified for real later. */
>  		ret = ve_devmnt_process(ve, bd_dev, (void **) &options,
>  				fc->purpose == FS_CONTEXT_FOR_RECONFIGURE);
>  		if (ret)
>  			return ret;
> +
> +		/* Stash what the filesystem parses; checked in the common mount path. */
> +		if (options) {
> +			kfree(fc->ve_final_opts);
> +			fc->ve_final_opts = kstrdup(options, GFP_KERNEL);
> +			if (!fc->ve_final_opts) {
> +				if (options != options_orig)
> +					free_page((unsigned long)options);
> +				return -ENOMEM;
> +			}
> +		}
>  	}
>  
>  	/*
> @@ -614,6 +803,7 @@ struct fs_context *vfs_dup_fs_context(struct fs_context *src_fc)
>  	fc->s_fs_info	= NULL;
>  	fc->source	= NULL;
>  	fc->security	= NULL;
> +	fc->ve_final_opts = NULL;
>  	get_filesystem(fc->fs_type);
>  	get_net(fc->net_ns);
>  	get_user_ns(fc->user_ns);
> @@ -742,6 +932,7 @@ void put_fs_context(struct fs_context *fc)
>  	put_filesystem(fc->fs_type);
>  	if (fc->lazy_opts)
>  		free_page((unsigned long)fc->lazy_opts);
> +	kfree(fc->ve_final_opts);
>  	kfree(fc->source);
>  	kfree(fc);
>  }
> @@ -962,6 +1153,8 @@ void vfs_clean_context(struct fs_context *fc)
>  		free_page((unsigned long)fc->lazy_opts);
>  		fc->lazy_opts = NULL;
>  	}
> +	kfree(fc->ve_final_opts);
> +	fc->ve_final_opts = NULL;
>  	kfree(fc->source);
>  	fc->source = NULL;
>  	fc->exclusive = false;
> diff --git a/fs/internal.h b/fs/internal.h
> index 3647ce69b2c7..f1892959db48 100644
> --- a/fs/internal.h
> +++ b/fs/internal.h
> @@ -46,6 +46,14 @@ extern void __init chrdev_init(void);
>   */
>  extern const struct fs_context_operations legacy_fs_context_ops;
>  extern int parse_monolithic_mount_data(struct fs_context *, void *);
> +#ifdef CONFIG_VE
> +extern int ve_devmnt_verify_fc(struct fs_context *fc, bool new_mount);
> +#else
> +static inline int ve_devmnt_verify_fc(struct fs_context *fc, bool new_mount)
> +{
> +	return 0;
> +}
> +#endif
>  extern void vfs_clean_context(struct fs_context *fc);
>  extern int finish_clean_context(struct fs_context *fc);
>  
> diff --git a/fs/namespace.c b/fs/namespace.c
> index 43493f779c59..4d4dc5290350 100644
> --- a/fs/namespace.c
> +++ b/fs/namespace.c
> @@ -3258,6 +3258,92 @@ int ve_devmnt_process(struct ve_struct *ve, dev_t dev, void **data_pp, int remou
>  	return err;
>  }
>  
> +/* Return 0 if every option in @options is listed in @a or @b, else -EPERM. */
> +static int ve_devmnt_options_subset(char *options, char *a, char *b)
> +{
> +	char *copy, *cur, *p;
> +	int err = 0;
> +
> +	if (!options || !*options)
> +		return 0;
> +	if (!a && !b)
> +		return -EPERM;
> +
> +	copy = cur = kstrdup(options, GFP_KERNEL);
> +	if (!copy)
> +		return -ENOMEM;
> +
> +	while ((p = strsep(&cur, ",")) != NULL) {
> +		if (!*p)
> +			continue;
> +		if ((!a || !strstr_separated(a, p, ',')) &&
> +		    (!b || !strstr_separated(b, p, ','))) {
> +			err = -EPERM;
> +			break;
> +		}
> +	}
> +
> +	kfree(copy);
> +	return err;
> +}
> +
> +/*
> + * ve_devmnt_verify - enforce the container device-mount policy for @dev
> + * @ve: the container
> + * @dev: device taken from the mounted superblock (not from a raceable path)
> + * @opts: mount options plus the SB_* flag names to vet
> + * @new_mount: true for a new mount, false for a remount
> + * @have_user_opts: true if userspace supplied any mount options. @opts always
> + *	carries the kernel-synthesized ro/rw flag token, so it is never empty
> + *	and cannot answer this on its own.
> + *
> + * Every supplied option must be allowed or forced. On a new mount the forced
> + * ("hidden") options must also be present: a filesystem that skipped inserting
> + * them is refused rather than silently dropping a container's mandated option
> + */
> +int ve_devmnt_verify(struct ve_struct *ve, dev_t dev, char *opts, bool new_mount,
> +		     bool have_user_opts)
> +{
> +	struct ve_devmnt *devmnt;
> +	char *allowed = NULL, *hidden = NULL;
> +	bool found = false;
> +	int err = 0;
> +
> +	if (ve->is_pseudosuper)
> +		return 0;
> +
> +	mutex_lock(&ve->devmnt_mutex);
> +	list_for_each_entry(devmnt, &ve->devmnt_list, link) {
> +		if (devmnt->dev == dev) {
> +			allowed = devmnt->allowed_options;
> +			hidden = devmnt->hidden_options;
> +			found = true;
> +			break;
> +		}
> +	}
> +
> +	/*
> +	 * Enforce for a listed device, or for any mount carrying userspace
> +	 * options. An unlisted device with no userspace options is unconstrained
> +	 * here, so the synthesized ro/rw token in @opts does not deny it.
> +	 */
> +	if (found || have_user_opts) {
> +		/* every supplied option must be either allowed or forced */
> +		err = ve_devmnt_options_subset(opts, allowed, hidden);
> +
> +		/* on a new mount every forced option must have reached the fs */
> +		if (!err && new_mount)
> +			err = ve_devmnt_options_subset(hidden, opts, NULL);
> +	}
> +	mutex_unlock(&ve->devmnt_mutex);
> +
> +	if (err == -EPERM)
> +		ve_pr_warn_ratelimited(VE_LOG_BOTH,
> +				       "VE%s: mount options not permitted for device %u:%u\n",
> +				       ve_name(ve), MAJOR(dev), MINOR(dev));
> +	return err;
> +}
> +
>  static inline int ve_mount_allowed(void)
>  {
>  	struct ve_struct *ve = get_exec_env();
> @@ -3308,23 +3394,6 @@ static inline void ve_mount_nr_inc(struct mount *mnt, struct ve_struct *ve) { }
>  static inline void ve_mount_nr_dec(struct mount *mnt) { }
>  #endif /* CONFIG_VE */
>  
> -static int ve_prepare_mount_options(struct fs_context *fc, void *data)
> -{
> -#ifdef CONFIG_VE
> -	struct super_block *sb = fc->root->d_sb;
> -	struct ve_struct *ve = get_exec_env();
> -
> -	if (sb->s_bdev && data && !ve_is_super(ve)) {
> -		int err;
> -
> -		err = ve_devmnt_process(ve, sb->s_bdev->bd_dev, &data, 1);
> -		if (err)
> -			return err;
> -	}
> -#endif
> -	return 0;
> -}
> -
>  /*
>   * change filesystem flags. dir should be a physical root of filesystem.
>   * If you've mounted a non-root directory somewhere and want to do remount
> @@ -3357,12 +3426,6 @@ static int do_remount(struct path *path, int ms_flags, int sb_flags,
>  	 */
>  	fc->oldapi = true;
>  
> -	err = ve_prepare_mount_options(fc, data);
> -	if (err) {
> -		put_fs_context(fc);
> -		return err;
> -	}
> -
>  	err = parse_monolithic_mount_data(fc, data);
>  	if (!err) {
>  		down_write(&sb->s_umount);
> @@ -3816,6 +3879,7 @@ static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
>  					  subtype, strlen(subtype));
>  	if (!err && name)
>  		err = vfs_parse_fs_string(fc, "source", name, strlen(name));
> +	/* Container device-mount policy is enforced later, in vfs_get_tree(). */
>  	if (!err)
>  		err = parse_monolithic_mount_data(fc, data);
>  	if (!err && !mount_capable(fc))
> diff --git a/fs/super.c b/fs/super.c
> index 1adebbf35803..c0c067eb2d8e 100644
> --- a/fs/super.c
> +++ b/fs/super.c
> @@ -1085,6 +1085,11 @@ int reconfigure_super(struct fs_context *fc)
>  	if (retval)
>  		return retval;
>  
> +	/* Enforce the container device-mount policy on the remount options. */
> +	retval = ve_devmnt_verify_fc(fc, false);
> +	if (retval)
> +		return retval;
> +
>  	if (fc->sb_flags_mask & SB_RDONLY) {
>  #ifdef CONFIG_BLOCK
>  		if (!(fc->sb_flags & SB_RDONLY) && sb->s_bdev &&
> @@ -1924,6 +1929,13 @@ int vfs_get_tree(struct fs_context *fc)
>  		return error;
>  	}
>  
> +	/* Enforce the container device-mount policy against the real device. */
> +	error = ve_devmnt_verify_fc(fc, true);
> +	if (unlikely(error)) {
> +		fc_drop_locked(fc);
> +		return error;
> +	}
> +
>  	/*
>  	 * filesystems should never set s_maxbytes larger than MAX_LFS_FILESIZE
>  	 * but s_maxbytes was an unsigned long long for many releases. Throw
> diff --git a/include/linux/fs_context.h b/include/linux/fs_context.h
> index 1801aed1da67..2ca586e2cc2a 100644
> --- a/include/linux/fs_context.h
> +++ b/include/linux/fs_context.h
> @@ -93,6 +93,8 @@ struct fs_context {
>  	struct file_system_type	*fs_type;
>  	void			*fs_private;	/* The filesystem's context */
>  	void			*lazy_opts;	/* mount options which can't be checked at fsconfig() time */
> +	/* option string handed to the fs, for the ve_devmnt policy check */
> +	char			*ve_final_opts;
>  	void			*sget_key;
>  	struct dentry		*root;		/* The root and superblock */
>  	struct user_namespace	*user_ns;	/* The user namespace for this mount */
> diff --git a/include/linux/mount.h b/include/linux/mount.h
> index 0cbc6f6893c0..ab0ea4f7afc6 100644
> --- a/include/linux/mount.h
> +++ b/include/linux/mount.h
> @@ -127,5 +127,7 @@ extern int cifs_root_data(char **dev, char **opts);
>  
>  struct ve_struct;
>  extern int ve_devmnt_process(struct ve_struct *, dev_t, void **, int);
> +extern int ve_devmnt_verify(struct ve_struct *ve, dev_t dev, char *opts,
> +			    bool new_mount, bool have_user_opts);
>  
>  #endif /* _LINUX_MOUNT_H */

-- 
Best regards, Pavel Tikhomirov
Senior Software Developer, Virtuozzo.


^ permalink raw reply	[relevance 0%]

* [Devel] [PATCH vz10 07/24] blk-cbt: don't WARN on a user-supplied ABI version mismatch
       [not found]       ` <970b9d91-59c9-4a6d-8494-2d58afcab4b8@virtuozzo.com>
@ 2026-08-05 19:26  0%     ` Konstantin Khorenko
  0 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-05 19:26 UTC (permalink / raw)


On 7/6/26 14:11, Andrey Zhadchenko wrote:
> I don't like that. Customers do not run with panic_on_warn. If this 
> fails in our test environment, that's actually great (it means something 
> went very wrong). pr_warn_ratelimited is worse than WARN_ONCE regarding 
> intentional spamming.

i agree, i will drop this patch.
It could definitely be useful in case there was WARN() - to change it to WARN_ONCE()
because this is triggerable from inside a Container, i have just checked that.

But as this is just a single WARN_ONCE, it's not a problem.

On the other hand mainstream fights with such user triggerable WARN_ONCE messages as well, like

commit 251a8fe1b9aedccd298b77bc28426d564c5a923f
Author: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Date:   Thu Jun 25 08:34:46 2026 +0900

    tracing/probes: Remove WARN_ON_ONCE from parse_btf_arg

    Sashiko found that user can cause this WARN_ON_ONCE() easily
    with adding a kprobe event based on a raw address with BTF
    parameter.

    Since this is not an unexpected condition, remove the
    WARN_ON_ONCE().

    Link: https://lore.kernel.org/all/178177265367.2059927.13789953014706792126.stgit at mhiramat.tok.corp.google.com/

    Link: https://sashiko.dev/#/patchset/178165816303.269421.7302603996990753309.stgit%40devnote2

    Reported-by: Sashiko <sashiko-bot@kernel.org>
    Fixes: b576e09701c7 ("tracing/probes: Support function parameters if BTF is available")
    Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>

diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
index fd1caa1f97233..98532c503d028 100644
--- a/kernel/trace/trace_probe.c
+++ b/kernel/trace/trace_probe.c
@@ -678,7 +678,7 @@ static int parse_btf_arg(char *varname,
        int i, is_ptr, ret;
        u32 tid;

-       if (WARN_ON_ONCE(!ctx->funcname && !(ctx->flags & TPARG_FL_TEVENT)))
+       if (!ctx->funcname && !(ctx->flags & TPARG_FL_TEVENT))
                return -EINVAL;

        is_ptr = split_next_field(varname, &field, ctx);

commit 40c88c429a598006f91ad7a2b89856cd50b3a008
Author: Andrii Nakryiko <andrii@kernel.org>
Date:   Tue May 16 11:04:09 2023 -0700

    bpf: drop unnecessary user-triggerable WARN_ONCE in verifierl log

    [ Upstream commit cff36398bd4c7d322d424433db437f3c3391c491 ]

    It's trivial for user to trigger "verifier log line truncated" warning,
    as verifier has a fixed-sized buffer of 1024 bytes (as of now), and there are at
    least two pieces of user-provided information that can be output through
    this buffer, and both can be arbitrarily sized by user:
      - BTF names;
      - BTF.ext source code lines strings.

    Verifier log buffer should be properly sized for typical verifier state
    output. But it's sort-of expected that this buffer won't be long enough
    in some circumstances. So let's drop the check. In any case code will
    work correctly, at worst truncating a part of a single line output.

    Reported-by: syzbot+8b2a08dfbd25fd933d75 at syzkaller.appspotmail.com
    Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
    Link: https://lore.kernel.org/r/20230516180409.3549088-1-andrii at kernel.org
    Signed-off-by: Alexei Starovoitov <ast@kernel.org>
    Signed-off-by: Sasha Levin <sashal@kernel.org>

diff --git a/kernel/bpf/log.c b/kernel/bpf/log.c
index 920061e38d2e1..cd1b7113fbfd0 100644
--- a/kernel/bpf/log.c
+++ b/kernel/bpf/log.c
@@ -22,9 +22,6 @@ void bpf_verifier_vlog(struct bpf_verifier_log *log, const char *fmt,

        n = vscnprintf(log->kbuf, BPF_VERIFIER_TMP_LOG_SIZE, fmt, args);

-       WARN_ONCE(n >= BPF_VERIFIER_TMP_LOG_SIZE - 1,
-                 "verifier log line truncated - local buffer too short\n");
-
        if (log->level == BPF_LOG_KERNEL) {
                bool newline = n > 0 && log->kbuf[n - 1] == '\n';


> On 7/6/26 12:59, Konstantin Khorenko wrote:
>> blk_cbt_ioctl() reads abi_version straight from the ioctl argument and
>> WARN_ONCE()s if it does not match CBT_ABI_VERSION. The value is fully
>> userspace-controlled, so any process issuing a BLKCBT* ioctl with a
>> stale/newer struct taints the kernel, dumps a backtrace, and can panic a
>> host that runs with panic_on_warn. Downgrade to pr_warn_ratelimited();
>> the -EOPNOTSUPP return is the actual contract.
>>
>> Fixes: 6e42f62a2c88 ("block/blk-cbt: introduce ABI versioning")
>> Feature: cbt: changed block tracking (for backup)
>> https://virtuozzo.atlassian.net/browse/VSTOR-137234
>> Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
>> ---
>>   block/blk-cbt.c | 4 ++--
>>   1 file changed, 2 insertions(+), 2 deletions(-)
>>
>> diff --git a/block/blk-cbt.c b/block/blk-cbt.c
>> index 90219f58f1ae..91a888770411 100644
>> --- a/block/blk-cbt.c
>> +++ b/block/blk-cbt.c
>> @@ -1093,8 +1093,8 @@ int blk_cbt_ioctl(struct block_device *bdev, unsigned cmd, char __user *arg)
>>   		return -EFAULT;
>>   
>>   	if (abi_version != CBT_ABI_VERSION) {
>> -		WARN_ONCE(1, "blk-cbt ABI mimatch: kernel has %d, userspace uses %d",
>> -			  CBT_ABI_VERSION, abi_version);
>> +		pr_warn_ratelimited("blk-cbt: ABI mismatch: kernel has %d, userspace uses %d\n",
>> +				    CBT_ABI_VERSION, abi_version);
>>   		return -EOPNOTSUPP;
>>   	}
>>   
> 


^ permalink raw reply	[relevance 0%]

* [Devel] [PATCH RHEL10 COMMIT] drivers/base/cpu: fix cpu/offline content inside a ve
       [not found]     <20260706110002.1024515-17-khorenko@virtuozzo.com>
@ 2026-08-05 20:05  7% ` Konstantin Khorenko
  0 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-05 20:05 UTC (permalink / raw)


The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.2.vz10
------>
commit fdfa48283061fcd77b59e4ba7d182d8af3e4198e
Author: Konstantin Khorenko <khorenko@virtuozzo.com>
Date:   Mon Jul 6 12:59:54 2026 +0200

    drivers/base/cpu: fix cpu/offline content inside a ve
    
    print_cpus_offline()'s non-super-ve early return passed len (declared
    0 at that point, used later only as the sysfs_emit_at() accumulator)
    as snprintf()'s size argument, so nothing was ever written to buf.
    Since sysfs zeroes the page before calling show(), reading
    /sys/devices/system/cpu/offline from inside a ve returned a single
    NUL byte instead of the intended empty line ("\n").
    
    Fixes: 44a8c49297f94 ("ve/cpu: handle sysfs attributes for CTs")
    Feature: sysfs: per-CT entries visibility and permissions configuration
    https://virtuozzo.atlassian.net/browse/VSTOR-137234
    Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
    Reviewed-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
---
 drivers/base/cpu.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c
index 1e992254b12c6..b7ddc8346603c 100644
--- a/drivers/base/cpu.c
+++ b/drivers/base/cpu.c
@@ -263,7 +263,7 @@ static ssize_t print_cpus_offline(struct device *dev,
 	cpumask_var_t offline;
 
 	if (!ve_is_super(get_exec_env()))
-		return snprintf(buf, len, "\n");
+		return sysfs_emit(buf, "\n");
 
 	/* display offline cpus < nr_cpu_ids */
 	if (!alloc_cpumask_var(&offline, GFP_KERNEL))

^ permalink raw reply	[relevance 7%]

* [Devel] [PATCH RHEL10 COMMIT] ve: fix NULL-deref / use-after-free in ve_create() error unwind
       [not found]     <20260706110002.1024515-19-khorenko@virtuozzo.com>
@ 2026-08-05 20:09  5% ` Konstantin Khorenko
  0 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-05 20:09 UTC (permalink / raw)


The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.2.vz10
------>
commit 7e884677eb9540f3dc2907fd9af753a5ef972a9a
Author: Konstantin Khorenko <khorenko@virtuozzo.com>
Date:   Mon Jul 6 12:59:56 2026 +0200

    ve: fix NULL-deref / use-after-free in ve_create() error unwind
    
    ve_create()'s error path ended with:
    
            err_lat:
                    kmem_cache_free(ve_cachep, ve);
            err_ve:
                    ve_set_state(ve, VE_STATE_STOPPED);
                    return ERR_PTR(err);
    
    ve_set_state() dereferences ve->state, but at err_ve @ve is either NULL
    (the kmem_cache_zalloc() failure jumps straight to err_ve) or already
    freed (the err_lat/err_log/err_vdso legs kmem_cache_free() @ve and then
    fall through to err_ve) - so every error exit of ve_create() is a NULL
    pointer dereference or a use-after-free. It only triggers when an
    allocation in ve_create() fails, which is why it stayed latent.
    
    The state assignment is meaningless on the failure path anyway: the css
    is never returned to cgroup and the ve is being freed. The success path
    already sets ve->state = VE_STATE_STARTING directly. Just drop the bogus
    ve_set_state() from the unwind.
    
    Fixes: 8a21e780ce29a ("ve/cgroups: rework is_running into an explicit VE state")
    Feature: ve: ve generic structures
    https://virtuozzo.atlassian.net/browse/VSTOR-137234
    Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
    Reviewed-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
---
 kernel/ve/ve.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
index d8ef28eedabd0..73d1c3b4873e5 100644
--- a/kernel/ve/ve.c
+++ b/kernel/ve/ve.c
@@ -802,7 +802,6 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
 err_lat:
 	kmem_cache_free(ve_cachep, ve);
 err_ve:
-	ve_set_state(ve, VE_STATE_STOPPED);
 	return ERR_PTR(err);
 }
 

^ permalink raw reply	[relevance 5%]

* [Devel] [PATCH RHEL10 COMMIT] fs: enforce container device-mount policy in the common mount path
  2026-08-04 12:53  3% [Devel] [PATCH vz10 v8 1/1] fs: enforce container device-mount policy in the common mount path Vasileios Almpanis
  2026-08-05 16:41  0% ` Pavel Tikhomirov
@ 2026-08-06 15:49  4% ` Konstantin Khorenko
  1 sibling, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-06 15:49 UTC (permalink / raw)


The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.3.vz10
------>
commit 37940d776226f393a8f9baf1e3c65fd4e792544c
Author: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
Date:   Tue Aug 4 12:53:56 2026 +0000

    fs: enforce container device-mount policy in the common mount path
    
    In a container the per-device ve_devmnt policy restricts which options
    a device may be mounted with and force-inserts a set of hidden options.
    The check used to run inside the option-string parser
    (vfs_parse_monolithic_sep) and, for remount, in a separate helper. Two
    things escaped it:
    
      - MS_* flags from the legacy mount(2)/fsconfig(2) API are folded into
        fc->sb_flags and never appear in the option string, so a container
        could set MS_RDONLY, MS_SYNCHRONOUS, MS_MANDLOCK, ... outside its
        allowed set.
    
      - The check sat in filesystem-selectable callbacks (->parse_monolithic,
        ->mount), so a filesystem not routing through them evaded the policy,
        and a skipped hidden-option insertion dropped a container's mandated
        options without error.
    
    Enforce the policy in the fs-agnostic common mount path instead:
    vfs_get_tree() for a new mount and reconfigure_super() for a remount.
    The device is taken from the mounted superblock, so fc->source cannot be
    raced to target another device, and fc->sb_flags is vetted alongside the
    option string. On a new mount the forced options must also be present,
    so a filesystem that skipped inserting them has its mount refused rather
    than silently losing them.
    
    The parse-time ve_devmnt_process() call is kept as a best-effort early
    reject, so a disallowed device or option is refused before the
    filesystem's fill_super() runs.
    
    Fix a bug where a containers that use the legacy mount(2) syscall are able
    to reconfigure a mount and change the superblock flags, for example
    from RO to RW. Compute the effective superblock flags and emit rw incase
    SB_RDONLY is missing in vfs_format_sb_flags.
    
    Explicitly deny mounts for device backed legacy filesystems. So even
    if FS_VIRTUALISED is added to them, mounting will not succeed since
    legacy filesystems don't go through vfs_parse_monolithic_sep and the
    policy is not applied to them. Keep this check until all filesystems
    have migrated to the new mount API.
    
    https://virtuozzo.atlassian.net/browse/VSTOR-132330
    Feature: ve: ve generic structures
    Fixes: 263467c864c5 ("ve/fs/devmnt: process mount options")
    Signed-off-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
    Co-developed-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
    Signed-off-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
    Reviewed-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
---
 fs/fs_context.c            | 193 +++++++++++++++++++++++++++++++++++++++++++++
 fs/internal.h              |   8 ++
 fs/namespace.c             | 110 ++++++++++++++++++++------
 fs/super.c                 |  12 +++
 include/linux/fs_context.h |   2 +
 include/linux/mount.h      |   2 +
 6 files changed, 304 insertions(+), 23 deletions(-)

diff --git a/fs/fs_context.c b/fs/fs_context.c
index 76f34f3d468ea..b2bd21a420836 100644
--- a/fs/fs_context.c
+++ b/fs/fs_context.c
@@ -81,6 +81,70 @@ static int vfs_parse_sb_flag(struct fs_context *fc, const char *key)
 	return -ENOPARAM;
 }
 
+/*
+ * Emit option @name into @buff at *@off, prefixed with ',' if the buffer
+ * already holds text. Advances *@off. Returns 0 or -E2BIG if @buff is full.
+ */
+static int __vfs_emit_flag(const char *name, char *buff,
+			   size_t size, size_t *off)
+{
+	ssize_t ret;
+
+	if (*off) {
+		if (*off + 1 >= size)
+			return -E2BIG;
+		buff[(*off)++] = ',';
+	}
+
+	ret = strscpy(buff + *off, name, size - *off);
+	if (ret < 0)
+		return -E2BIG;
+	*off += ret;
+	return 0;
+}
+
+static int __vfs_format_flags(const struct constant_table *p, unsigned int flags,
+			      char *buff, size_t size, size_t *off)
+{
+	for (; p->name; p++) {
+		int ret;
+
+		if (!(flags & p->value))
+			continue;
+		ret = __vfs_emit_flag(p->name, buff, size, off);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int vfs_format_sb_flags(char *buff, size_t size, size_t *off,
+			       unsigned int sb_flags)
+{
+	int err;
+
+	err = __vfs_format_flags(common_set_sb_flag, sb_flags, buff, size, off);
+	if (err)
+		return err;
+
+	/*
+	 * "rw" has no flag bit of its own - it is simply the absence of
+	 * SB_RDONLY. Emit it explicitly so the ve_devmnt policy can allow or
+	 * deny read-write access as a first-class option; otherwise a mount or
+	 * remount that leaves the superblock read-write carries no token and
+	 * slips past the "every option must be allowed" check.
+	 *
+	 * @sb_flags is the effective post-operation flag word, so this reflects
+	 * the state the superblock actually ends up in. A remount that only
+	 * touches an unrelated flag (e.g. "sync") keeps its current SB_RDONLY
+	 * and so does not emit "rw".
+	 */
+	if (!(sb_flags & SB_RDONLY))
+		return __vfs_emit_flag("rw", buff, size, off);
+
+	return 0;
+}
+
 /**
  * vfs_parse_fs_param_source - Handle setting "source" via parameter
  * @fc: The filesystem context to modify
@@ -224,6 +288,119 @@ static inline int fscontext_lookup_bdev(struct fs_context *fc, dev_t *s_dev)
 	return -ENODEV;
 }
 
+/*
+ * ve_devmnt_deny_legacy - refuse a device mount that escapes the policy
+ * @fc: the mount context
+ *
+ * A legacy context, one for a filesystem with no ->init_fs_context passes
+ * its mount data straight to ->mount()/->remount_fs() through
+ * legacy_parse_monolithic(), so vfs_parse_monolithic_sep() never runs on it.
+ * The container's forced options are then never inserted and fc->ve_final_opts
+ * stays empty, which would leave ve_devmnt_verify_fc() vetting an empty option
+ * string while the filesystem acts on the full one userspace passed.
+ *
+ * Returns 0 if the mount may go ahead, -EPERM if it must not.
+ */
+static int ve_devmnt_deny_legacy(struct fs_context *fc)
+{
+	struct ve_struct *ve = get_exec_env();
+
+	if (fc->ops != &legacy_fs_context_ops)
+		return 0;
+
+	ve_pr_warn_ratelimited(VE_LOG_BOTH,
+			       "VE%s: refusing to mount %s: filesystem has no fs_context support\n",
+			       ve_name(ve), fc->fs_type->name);
+	return -EPERM;
+}
+
+/*
+ * ve_devmnt_verify_fc - check a mount against the container device-mount policy
+ * @fc: the mount context, with fc->root set
+ * @new_mount: true at vfs_get_tree() (new mount), false at reconfigure_super()
+ *
+ * Vets the stashed userspace option string plus the synthesized SB_* flag
+ * names against the mounted superblock's device. A device absent from the
+ * policy that is mounted with no userspace options is allowed. Returns 0 when
+ * permitted (or no check applies), or a negative errno.
+ */
+int ve_devmnt_verify_fc(struct fs_context *fc, bool new_mount)
+{
+	struct ve_struct *ve = get_exec_env();
+	unsigned int sb_flags;
+	bool have_user_opts;
+	size_t off = 0;
+	char *page;
+	int err;
+
+	if (ve_is_super(ve))
+		return 0;
+
+	if (!fc->fs_type || !(fc->fs_type->fs_flags & FS_REQUIRES_DEV))
+		return 0;
+
+	/*
+	 * Keep this check until all filesystems have migrated to the new
+	 * mount API
+	 */
+	err = ve_devmnt_deny_legacy(fc);
+	if (err)
+		return err;
+
+	/*
+	 * Filesystems with binary mount data (e.g. btrfs) bypass option
+	 * string parsing entirely, so our checks cannot apply here.
+	 */
+	if (fc->fs_type->fs_flags & FS_BINARY_MOUNTDATA)
+		return 0;
+
+	if (WARN_ON_ONCE(!fc->root))
+		return -EINVAL;
+
+	page = (char *)__get_free_page(GFP_KERNEL);
+	if (!page)
+		return -ENOMEM;
+
+	/*
+	 * Track whether userspace actually supplied options. @page below also
+	 * gets the synthesized ro/rw flag token, so its length cannot answer
+	 * this; ve_final_opts holds only the userspace string.
+	 */
+	have_user_opts = fc->ve_final_opts && *fc->ve_final_opts;
+	if (have_user_opts) {
+		ssize_t ret = strscpy(page, fc->ve_final_opts, PAGE_SIZE);
+
+		if (ret < 0) {
+			err = -E2BIG;
+			goto out;
+		}
+		off = ret;
+	}
+
+	/*
+	 * On a remount fc->sb_flags holds only the bits being changed, so
+	 * combine them with the current superblock flags to get the state the
+	 * sb will actually have - the same value reconfigure_super() writes
+	 * back. On a new mount fc->sb_flags is already the full flag word.
+	 */
+	sb_flags = fc->sb_flags;
+	if (!new_mount)
+		sb_flags = (fc->root->d_sb->s_flags & ~fc->sb_flags_mask) |
+			   (fc->sb_flags & fc->sb_flags_mask);
+
+	err = vfs_format_sb_flags(page, PAGE_SIZE, &off, sb_flags);
+	if (err)
+		goto out;
+
+	page[off] = '\0';
+	err = ve_devmnt_verify(ve, fc->root->d_sb->s_dev, page, new_mount,
+			       have_user_opts);
+
+out:
+	free_page((unsigned long)page);
+	return err;
+}
+
 static int fscontext_init_lazy_opts(struct fs_context *fc)
 {
 	struct ve_struct *ve = get_exec_env();
@@ -389,10 +566,22 @@ int vfs_parse_monolithic_sep(struct fs_context *fc, void *data,
 			return -ENODEV;
 		}
 
+		/* Early reject and hidden-option insertion; verified for real later. */
 		ret = ve_devmnt_process(ve, bd_dev, (void **) &options,
 				fc->purpose == FS_CONTEXT_FOR_RECONFIGURE);
 		if (ret)
 			return ret;
+
+		/* Stash what the filesystem parses; checked in the common mount path. */
+		if (options) {
+			kfree(fc->ve_final_opts);
+			fc->ve_final_opts = kstrdup(options, GFP_KERNEL);
+			if (!fc->ve_final_opts) {
+				if (options != options_orig)
+					free_page((unsigned long)options);
+				return -ENOMEM;
+			}
+		}
 	}
 
 	/*
@@ -614,6 +803,7 @@ struct fs_context *vfs_dup_fs_context(struct fs_context *src_fc)
 	fc->s_fs_info	= NULL;
 	fc->source	= NULL;
 	fc->security	= NULL;
+	fc->ve_final_opts = NULL;
 	get_filesystem(fc->fs_type);
 	get_net(fc->net_ns);
 	get_user_ns(fc->user_ns);
@@ -742,6 +932,7 @@ void put_fs_context(struct fs_context *fc)
 	put_filesystem(fc->fs_type);
 	if (fc->lazy_opts)
 		free_page((unsigned long)fc->lazy_opts);
+	kfree(fc->ve_final_opts);
 	kfree(fc->source);
 	kfree(fc);
 }
@@ -962,6 +1153,8 @@ void vfs_clean_context(struct fs_context *fc)
 		free_page((unsigned long)fc->lazy_opts);
 		fc->lazy_opts = NULL;
 	}
+	kfree(fc->ve_final_opts);
+	fc->ve_final_opts = NULL;
 	kfree(fc->source);
 	fc->source = NULL;
 	fc->exclusive = false;
diff --git a/fs/internal.h b/fs/internal.h
index 3647ce69b2c7a..f1892959db48e 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -46,6 +46,14 @@ extern void __init chrdev_init(void);
  */
 extern const struct fs_context_operations legacy_fs_context_ops;
 extern int parse_monolithic_mount_data(struct fs_context *, void *);
+#ifdef CONFIG_VE
+extern int ve_devmnt_verify_fc(struct fs_context *fc, bool new_mount);
+#else
+static inline int ve_devmnt_verify_fc(struct fs_context *fc, bool new_mount)
+{
+	return 0;
+}
+#endif
 extern void vfs_clean_context(struct fs_context *fc);
 extern int finish_clean_context(struct fs_context *fc);
 
diff --git a/fs/namespace.c b/fs/namespace.c
index 43493f779c592..4d4dc52903508 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -3258,6 +3258,92 @@ int ve_devmnt_process(struct ve_struct *ve, dev_t dev, void **data_pp, int remou
 	return err;
 }
 
+/* Return 0 if every option in @options is listed in @a or @b, else -EPERM. */
+static int ve_devmnt_options_subset(char *options, char *a, char *b)
+{
+	char *copy, *cur, *p;
+	int err = 0;
+
+	if (!options || !*options)
+		return 0;
+	if (!a && !b)
+		return -EPERM;
+
+	copy = cur = kstrdup(options, GFP_KERNEL);
+	if (!copy)
+		return -ENOMEM;
+
+	while ((p = strsep(&cur, ",")) != NULL) {
+		if (!*p)
+			continue;
+		if ((!a || !strstr_separated(a, p, ',')) &&
+		    (!b || !strstr_separated(b, p, ','))) {
+			err = -EPERM;
+			break;
+		}
+	}
+
+	kfree(copy);
+	return err;
+}
+
+/*
+ * ve_devmnt_verify - enforce the container device-mount policy for @dev
+ * @ve: the container
+ * @dev: device taken from the mounted superblock (not from a raceable path)
+ * @opts: mount options plus the SB_* flag names to vet
+ * @new_mount: true for a new mount, false for a remount
+ * @have_user_opts: true if userspace supplied any mount options. @opts always
+ *	carries the kernel-synthesized ro/rw flag token, so it is never empty
+ *	and cannot answer this on its own.
+ *
+ * Every supplied option must be allowed or forced. On a new mount the forced
+ * ("hidden") options must also be present: a filesystem that skipped inserting
+ * them is refused rather than silently dropping a container's mandated option
+ */
+int ve_devmnt_verify(struct ve_struct *ve, dev_t dev, char *opts, bool new_mount,
+		     bool have_user_opts)
+{
+	struct ve_devmnt *devmnt;
+	char *allowed = NULL, *hidden = NULL;
+	bool found = false;
+	int err = 0;
+
+	if (ve->is_pseudosuper)
+		return 0;
+
+	mutex_lock(&ve->devmnt_mutex);
+	list_for_each_entry(devmnt, &ve->devmnt_list, link) {
+		if (devmnt->dev == dev) {
+			allowed = devmnt->allowed_options;
+			hidden = devmnt->hidden_options;
+			found = true;
+			break;
+		}
+	}
+
+	/*
+	 * Enforce for a listed device, or for any mount carrying userspace
+	 * options. An unlisted device with no userspace options is unconstrained
+	 * here, so the synthesized ro/rw token in @opts does not deny it.
+	 */
+	if (found || have_user_opts) {
+		/* every supplied option must be either allowed or forced */
+		err = ve_devmnt_options_subset(opts, allowed, hidden);
+
+		/* on a new mount every forced option must have reached the fs */
+		if (!err && new_mount)
+			err = ve_devmnt_options_subset(hidden, opts, NULL);
+	}
+	mutex_unlock(&ve->devmnt_mutex);
+
+	if (err == -EPERM)
+		ve_pr_warn_ratelimited(VE_LOG_BOTH,
+				       "VE%s: mount options not permitted for device %u:%u\n",
+				       ve_name(ve), MAJOR(dev), MINOR(dev));
+	return err;
+}
+
 static inline int ve_mount_allowed(void)
 {
 	struct ve_struct *ve = get_exec_env();
@@ -3308,23 +3394,6 @@ static inline void ve_mount_nr_inc(struct mount *mnt, struct ve_struct *ve) { }
 static inline void ve_mount_nr_dec(struct mount *mnt) { }
 #endif /* CONFIG_VE */
 
-static int ve_prepare_mount_options(struct fs_context *fc, void *data)
-{
-#ifdef CONFIG_VE
-	struct super_block *sb = fc->root->d_sb;
-	struct ve_struct *ve = get_exec_env();
-
-	if (sb->s_bdev && data && !ve_is_super(ve)) {
-		int err;
-
-		err = ve_devmnt_process(ve, sb->s_bdev->bd_dev, &data, 1);
-		if (err)
-			return err;
-	}
-#endif
-	return 0;
-}
-
 /*
  * change filesystem flags. dir should be a physical root of filesystem.
  * If you've mounted a non-root directory somewhere and want to do remount
@@ -3357,12 +3426,6 @@ static int do_remount(struct path *path, int ms_flags, int sb_flags,
 	 */
 	fc->oldapi = true;
 
-	err = ve_prepare_mount_options(fc, data);
-	if (err) {
-		put_fs_context(fc);
-		return err;
-	}
-
 	err = parse_monolithic_mount_data(fc, data);
 	if (!err) {
 		down_write(&sb->s_umount);
@@ -3816,6 +3879,7 @@ static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
 					  subtype, strlen(subtype));
 	if (!err && name)
 		err = vfs_parse_fs_string(fc, "source", name, strlen(name));
+	/* Container device-mount policy is enforced later, in vfs_get_tree(). */
 	if (!err)
 		err = parse_monolithic_mount_data(fc, data);
 	if (!err && !mount_capable(fc))
diff --git a/fs/super.c b/fs/super.c
index 1adebbf358032..c0c067eb2d8e1 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -1085,6 +1085,11 @@ int reconfigure_super(struct fs_context *fc)
 	if (retval)
 		return retval;
 
+	/* Enforce the container device-mount policy on the remount options. */
+	retval = ve_devmnt_verify_fc(fc, false);
+	if (retval)
+		return retval;
+
 	if (fc->sb_flags_mask & SB_RDONLY) {
 #ifdef CONFIG_BLOCK
 		if (!(fc->sb_flags & SB_RDONLY) && sb->s_bdev &&
@@ -1924,6 +1929,13 @@ int vfs_get_tree(struct fs_context *fc)
 		return error;
 	}
 
+	/* Enforce the container device-mount policy against the real device. */
+	error = ve_devmnt_verify_fc(fc, true);
+	if (unlikely(error)) {
+		fc_drop_locked(fc);
+		return error;
+	}
+
 	/*
 	 * filesystems should never set s_maxbytes larger than MAX_LFS_FILESIZE
 	 * but s_maxbytes was an unsigned long long for many releases. Throw
diff --git a/include/linux/fs_context.h b/include/linux/fs_context.h
index 1801aed1da67c..2ca586e2cc2a2 100644
--- a/include/linux/fs_context.h
+++ b/include/linux/fs_context.h
@@ -93,6 +93,8 @@ struct fs_context {
 	struct file_system_type	*fs_type;
 	void			*fs_private;	/* The filesystem's context */
 	void			*lazy_opts;	/* mount options which can't be checked at fsconfig() time */
+	/* option string handed to the fs, for the ve_devmnt policy check */
+	char			*ve_final_opts;
 	void			*sget_key;
 	struct dentry		*root;		/* The root and superblock */
 	struct user_namespace	*user_ns;	/* The user namespace for this mount */
diff --git a/include/linux/mount.h b/include/linux/mount.h
index 0cbc6f6893c01..ab0ea4f7afc6f 100644
--- a/include/linux/mount.h
+++ b/include/linux/mount.h
@@ -127,5 +127,7 @@ extern int cifs_root_data(char **dev, char **opts);
 
 struct ve_struct;
 extern int ve_devmnt_process(struct ve_struct *, dev_t, void **, int);
+extern int ve_devmnt_verify(struct ve_struct *ve, dev_t dev, char *opts,
+			    bool new_mount, bool have_user_opts);
 
 #endif /* _LINUX_MOUNT_H */

^ permalink raw reply	[relevance 4%]

* Re: [Devel] [PATCH VZ10 v5 7/9] ve: Introduce per-VE failcount
  2026-08-02 11:40  9% ` [Devel] [PATCH VZ10 v5 7/9] ve: Introduce per-VE failcount Vladimir Riabchun
@ 2026-08-07  9:25  0%   ` Vasileios Almpanis
  0 siblings, 0 replies; 119+ results
From: Vasileios Almpanis @ 2026-08-07  9:25 UTC (permalink / raw)



On 8/2/26 1:40 PM, Vladimir Riabchun wrote:
> It may be useful to have a history of resource limit hits for every VE,
> this may simplify debugging and provide some information about the
> resources usage.
>
> This information is provided by ve.failcount file, any write to it
> resets all failcounts.
>
> To add a new failcounter we need to create a new atomic_t field
> name_failcount in ve structure and add a new VE_FC_ENTRY in
> ve_failcounts array.
>
> One change, unrelated to failcounts: aio fields are now initialized
> in ve0.
>
> https://virtuozzo.atlassian.net/browse/VSTOR-135520
>
> Feature: per-ve failcounters
> Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
> ---
>   fs/aio.c                 |  1 +
>   fs/namespace.c           |  2 ++
>   include/linux/ve.h       |  6 ++++
>   kernel/bpf/syscall.c     |  1 +
>   kernel/ve/ve.c           | 67 ++++++++++++++++++++++++++++++++++++++++
>   net/core/dev.c           |  2 ++
>   net/core/neighbour.c     |  1 +
>   net/core/net_namespace.c |  4 ++-
>   8 files changed, 83 insertions(+), 1 deletion(-)
>
> diff --git a/fs/aio.c b/fs/aio.c
> index cb63416af135..3fa07cc626f8 100644
> --- a/fs/aio.c
> +++ b/fs/aio.c
> @@ -814,6 +814,7 @@ static struct kioctx *ioctx_alloc(unsigned nr_events)
>   	spin_lock(&ve->aio_nr_lock);
>   	if (ve->aio_nr + ctx->max_reqs > ve->aio_max_nr ||
>   	    ve->aio_nr + ctx->max_reqs < ve->aio_nr) {
> +		atomic_inc(&ve->aio_failcount);
>   		spin_unlock(&ve->aio_nr_lock);
>   		err = -EAGAIN;
>   		goto err_ctx;
> diff --git a/fs/namespace.c b/fs/namespace.c
> index 2550aeba2f1e..c4e2c7f7f725 100644
> --- a/fs/namespace.c
> +++ b/fs/namespace.c
> @@ -3283,6 +3283,8 @@ static inline int ve_try_reserve_mount(struct ve_struct *ve)
>   
>   	if (ret)
>   		get_ve(ve);
> +	else
> +		atomic_inc(&ve->mnt_failcount);
>   	return ret;
>   }
>   
> diff --git a/include/linux/ve.h b/include/linux/ve.h
> index 5687faad46ff..9e73527e970e 100644
> --- a/include/linux/ve.h
> +++ b/include/linux/ve.h
> @@ -72,12 +72,15 @@ struct ve_struct {
>   	struct kmapset_key	proc_perms_key;
>   
>   	atomic_t		netns_avail_nr;
> +	atomic_t		netns_failcount;
>   	int			netns_max_nr;
>   
>   	atomic_t		netif_avail_nr;
> +	atomic_t		netif_failcount;
>   	int			netif_max_nr;
>   
>   	atomic_t		bpf_prog_avail_nr;
> +	atomic_t		bpf_prog_failcount;
>   	int			bpf_prog_max_nr;
>   
>   	atomic64_t		_uevent_seqnum;
> @@ -86,6 +89,7 @@ struct ve_struct {
>   
>   	atomic_t		arp_neigh_nr;
>   	atomic_t		nd_neigh_nr;
> +	atomic_t		neigh_tbl_failcount;
>   	unsigned long		meminfo_val;
>   
>   	/*
> @@ -94,6 +98,7 @@ struct ve_struct {
>   	 * other containers.
>   	 */
>   	atomic_t		mnt_avail_nr; /* number of available VE mounts */
> +	atomic_t		mnt_failcount;
>   	int			mnt_max_nr;
>   
>   #ifdef CONFIG_COREDUMP
> @@ -121,6 +126,7 @@ struct ve_struct {
>   	spinlock_t		aio_nr_lock;
>   	unsigned long		aio_nr;
>   	unsigned long		aio_max_nr;
> +	atomic_t		aio_failcount;
>   #endif
>   	struct vfsmount		*devtmpfs_mnt;
>   };
> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> index ff2a51c59f04..95e806fa19f4 100644
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
> @@ -2891,6 +2891,7 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
>   	if (!bpf_cap && type == BPF_PROG_TYPE_CGROUP_DEVICE) {
>   		load_ve = get_exec_env();
>   		if (atomic_dec_if_positive(&load_ve->bpf_prog_avail_nr) < 0) {
> +			atomic_inc(&load_ve->bpf_prog_failcount);
>   			load_ve = NULL;
>   			err = -ENOSPC;
>   			goto put_token;
> diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
> index dc3faa0b1d76..a5c93be759ac 100644
> --- a/kernel/ve/ve.c
> +++ b/kernel/ve/ve.c
> @@ -99,10 +99,13 @@ struct ve_struct ve0 = {
>   	.features		= -1,
>   	.sched_lat_ve.cur	= &ve0_lat_stats,
>   	.netns_avail_nr		= ATOMIC_INIT(INT_MAX),
> +	.netns_failcount	= ATOMIC_INIT(0),
>   	.netns_max_nr		= INT_MAX,
>   	.netif_avail_nr		= ATOMIC_INIT(INT_MAX),
> +	.netif_failcount	= ATOMIC_INIT(0),
>   	.netif_max_nr		= INT_MAX,
>   	.bpf_prog_avail_nr	= ATOMIC_INIT(INT_MAX),
> +	.bpf_prog_failcount	= ATOMIC_INIT(0),
>   	.bpf_prog_max_nr	= INT_MAX,
>   	.fsync_enable		= FSYNC_FILTERED,
>   	._randomize_va_space	=
> @@ -114,8 +117,16 @@ struct ve_struct ve0 = {
>   
>   	.arp_neigh_nr		= ATOMIC_INIT(0),
>   	.nd_neigh_nr		= ATOMIC_INIT(0),
> +	.neigh_tbl_failcount	= ATOMIC_INIT(0),
>   	.mnt_avail_nr		= ATOMIC_INIT(INT_MAX),
>   	.mnt_max_nr		= INT_MAX,
> +	.mnt_failcount		= ATOMIC_INIT(0),
> +#ifdef CONFIG_AIO
> +	.aio_nr_lock		= __SPIN_LOCK_UNLOCKED(aio_nr_lock),
> +	.aio_nr			= 0,
> +	.aio_max_nr		= AIO_MAX_NR_DEFAULT,
> +	.aio_failcount		= ATOMIC_INIT(0),
> +#endif
>   	.meminfo_val		= VE_MEMINFO_SYSTEM,
>   	.umh_running_helpers	= ATOMIC_INIT(0),
>   	.umh_helpers_waitq	= __WAIT_QUEUE_HEAD_INITIALIZER(ve0.umh_helpers_waitq),
> @@ -780,12 +791,15 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
>   	ve->fsync_enable = FSYNC_FILTERED;
>   
>   	atomic_set(&ve->netns_avail_nr, NETNS_MAX_NR_DEFAULT);
> +	atomic_set(&ve->netns_failcount, 0);
>   	ve->netns_max_nr = NETNS_MAX_NR_DEFAULT;
>   
>   	atomic_set(&ve->netif_avail_nr, NETIF_MAX_NR_DEFAULT);
> +	atomic_set(&ve->netif_failcount, 0);
>   	ve->netif_max_nr = NETIF_MAX_NR_DEFAULT;
>   
>   	atomic_set(&ve->bpf_prog_avail_nr, BPF_PROG_MAX_NR_DEFAULT);
> +	atomic_set(&ve->bpf_prog_failcount, 0);
>   	ve->bpf_prog_max_nr = BPF_PROG_MAX_NR_DEFAULT;
>   
>   	err = ve_log_init(ve);
> @@ -812,7 +826,9 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
>   
>   	atomic_set(&ve->arp_neigh_nr, 0);
>   	atomic_set(&ve->nd_neigh_nr, 0);
> +	atomic_set(&ve->neigh_tbl_failcount, 0);
>   	ve->mnt_max_nr = MNT_MAX_NR_DEFAULT;
> +	atomic_set(&ve->mnt_failcount, 0);
>   	atomic_set(&ve->mnt_avail_nr, MNT_MAX_NR_DEFAULT);
>   
>   #ifdef CONFIG_COREDUMP
> @@ -825,6 +841,7 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
>   	spin_lock_init(&ve->aio_nr_lock);
>   	ve->aio_nr = 0;
>   	ve->aio_max_nr = AIO_MAX_NR_DEFAULT;
> +	atomic_set(&ve->aio_failcount, 0);
>   #endif
>   
>   	return &ve->css;
> @@ -1066,6 +1083,50 @@ VE_RESOURCE(mnt);
>   VE_RESOURCE(netif);
>   VE_RESOURCE(bpf_prog);
>   
> +static const struct ve_failcount_entry {
> +	const char *name;
> +	size_t offset;
> +} ve_failcounts[] = {
> +#define VE_FC_ENTRY(name) { #name, offsetof(struct ve_struct, name##_failcount) }
> +	VE_FC_ENTRY(netns),
> +	VE_FC_ENTRY(mnt),
> +	VE_FC_ENTRY(netif),
> +	VE_FC_ENTRY(bpf_prog),
> +	VE_FC_ENTRY(neigh_tbl),
> +#ifdef CONFIG_AIO
> +	VE_FC_ENTRY(aio),
> +#endif
> +	{}
> +};
> +
> +static int ve_failcount_read(struct seq_file *sf, void *v)
> +{
> +	struct ve_struct *ve = css_to_ve(seq_css(sf));
> +	struct ve_failcount_entry *entry;
> +	atomic_t *fc;
> +
kernel/ve/ve.c: In function ?ve_failcount_read?:
kernel/ve/ve.c:1107:20: warning: assignment discards ?const? qualifier 
from pointer target type [-Wdiscarded-qualifiers]
 ?1107 |? ? ? ? ?for (entry = ve_failcounts; entry->name; entry++) {
 ? ? ? |? ? ? ? ? ? ? ? ? ? ^
kernel/ve/ve.c: In function ?ve_failcount_write?:
kernel/ve/ve.c:1121:20: warning: assignment discards ?const? qualifier 
from pointer target type [-Wdiscarded-qualifiers]
 ?1121 |? ? ? ? ?for (entry = ve_failcounts; entry->name; entry++) {

the left-hand pointee must carry all qualifiers of the right-hand pointee according to C11


> +	for (entry = ve_failcounts; entry->name; entry++) {
> +		fc = (void *)ve + entry->offset;
> +		seq_printf(sf, "%s: %d\n", entry->name, atomic_read(fc));
> +	}
> +	return 0;
> +}
> +
> +static ssize_t ve_failcount_write(struct kernfs_open_file *of, char *buf,
> +				  size_t nbytes, loff_t off)
> +{
> +	struct ve_struct *ve = css_to_ve(of_css(of));
> +	struct ve_failcount_entry *entry;
> +	atomic_t *fc;
> +
> +	for (entry = ve_failcounts; entry->name; entry++) {
> +		fc = (void *)ve + entry->offset;
> +		atomic_set(fc, 0);
> +	}
> +
> +	return nbytes;
> +}
> +
>   static int ve_os_release_read(struct seq_file *sf, void *v)
>   {
>   	struct cgroup_subsys_state *css = seq_css(sf);
> @@ -1603,6 +1664,12 @@ static struct cftype ve_cftypes[] = {
>   		.flags			= CFTYPE_NOT_ON_ROOT,
>   		.write_u64		= ve_rpc_kill_write,
>   	},
> +	{
> +		.name			= "failcount",
> +		.flags			= CFTYPE_NOT_ON_ROOT,
> +		.seq_show		= ve_failcount_read,
> +		.write			= ve_failcount_write,
> +	},
>   	{ }
>   };
>   
> diff --git a/net/core/dev.c b/net/core/dev.c
> index c7dddb200489..05e0b9b6ba23 100644
> --- a/net/core/dev.c
> +++ b/net/core/dev.c
> @@ -10997,6 +10997,7 @@ int register_netdevice(struct net_device *dev)
>   
>   	ret = -ENOMEM;
>   	if (atomic_dec_if_positive(&net->owner_ve->netif_avail_nr) < 0) {
> +		atomic_inc(&net->owner_ve->netif_failcount);
>   		ve_pr_warn_ratelimited(VE_LOG_BOTH,
>   			"CT%s: hits max number of network devices, "
>   			"increase ve::netif_max_nr parameter\n",
> @@ -12211,6 +12212,7 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net,
>   
>   	err = -ENOMEM;
>   	if (atomic_dec_if_positive(&net->owner_ve->netif_avail_nr) < 0) {
> +		atomic_inc(&net->owner_ve->netif_failcount);
>   		ve_pr_warn_ratelimited(VE_LOG_BOTH,
>   			"CT%s: hits max number of network devices, "
>   			"increase ve::netif_max_nr parameter\n",
> diff --git a/net/core/neighbour.c b/net/core/neighbour.c
> index f90deb17fb25..57a49d9c98a7 100644
> --- a/net/core/neighbour.c
> +++ b/net/core/neighbour.c
> @@ -520,6 +520,7 @@ static struct neighbour *neigh_alloc(struct neigh_table *tbl,
>   	    (glob_entries >= READ_ONCE(tbl->gc_thresh2) &&
>   	     time_after(now, READ_ONCE(tbl->last_flush) + 5 * HZ))) {
>   		if (!neigh_forced_gc(tbl, ve) && entries >= gc_thresh3) {
> +			atomic_inc(&ve->neigh_tbl_failcount);
>   			net_info_ratelimited("%s: neighbor table overflow!\n",
>   					     tbl->id);
>   			NEIGH_CACHE_STAT_INC(tbl, table_fulls);
> diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
> index b3d54cad984a..9a3376d2682f 100644
> --- a/net/core/net_namespace.c
> +++ b/net/core/net_namespace.c
> @@ -486,8 +486,10 @@ void net_drop_ns(void *p)
>   #ifdef CONFIG_VE
>   static int dec_netns_avail(struct ve_struct *ve)
>   {
> -	if (atomic_dec_if_positive(&ve->netns_avail_nr) < 0)
> +	if (atomic_dec_if_positive(&ve->netns_avail_nr) < 0) {
> +		atomic_inc(&ve->netns_failcount);
>   		return -ENOSPC;
> +	}
>   	return 0;
>   }
>   

-- 
Best regards, Vasileios Almpanis
Software Developer, Virtuozzo.


^ permalink raw reply	[relevance 0%]

* Re: [Devel] [PATCH VZ10 v5 9/9] selftests/ve: Add mount accounting selftest
  2026-08-02 11:40  6% ` [Devel] [PATCH VZ10 v5 9/9] selftests/ve: Add mount accounting selftest Vladimir Riabchun
@ 2026-08-07 10:01  0%   ` Vasileios Almpanis
  0 siblings, 0 replies; 119+ results
From: Vasileios Almpanis @ 2026-08-07 10:01 UTC (permalink / raw)



On 8/2/26 1:40 PM, Vladimir Riabchun wrote:
> There are 6 test cases, covered in the new test:
> 1. Simple mount accouting correctness, just mount/umount.
> 2. Verification of correct limit hits and changes, including
>     negative values.
> 3. Patial mounts test, when mount limit is hit in the middle
>     of creation.
nit: Partial
> 4. Test that enabled pseudosuper allows overuse.
> 5. Test that pseudosuper doesn't affect mount accoutning.
nit: accounting
> 6. Failcount feature verification.
>
> https://virtuozzo.atlassian.net/browse/VSTOR-135520
>
> Feature: per-ve failcounters
> Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
> ---
> v4 -> v5:
>   - Splitted pseudosuper test into two.
>   - Added new test to check that things go smoothly when
>     we run out of mounts in the middle of a new mount.
>
>   tools/testing/selftests/ve/.gitignore         |   1 +
>   tools/testing/selftests/ve/Makefile           |   1 +
>   .../selftests/ve/ve_mount_accounting_test.c   | 419 ++++++++++++++++++
>   3 files changed, 421 insertions(+)
>   create mode 100644 tools/testing/selftests/ve/ve_mount_accounting_test.c
>
> diff --git a/tools/testing/selftests/ve/.gitignore b/tools/testing/selftests/ve/.gitignore
> index afa4c568c2c9..3df4d05888dc 100644
> --- a/tools/testing/selftests/ve/.gitignore
> +++ b/tools/testing/selftests/ve/.gitignore
> @@ -1,2 +1,3 @@
>   ve_ns_owner_test
>   ve_perms_test
> +ve_mount_accounting_test
> diff --git a/tools/testing/selftests/ve/Makefile b/tools/testing/selftests/ve/Makefile
> index ec40cbc7b3a1..c6efe7c4b4fb 100644
> --- a/tools/testing/selftests/ve/Makefile
> +++ b/tools/testing/selftests/ve/Makefile
> @@ -4,5 +4,6 @@ CFLAGS += -g -Wall -O2
>   
>   TEST_GEN_PROGS += ve_ns_owner_test
>   TEST_GEN_PROGS += ve_perms_test
> +TEST_GEN_PROGS += ve_mount_accounting_test
>   
>   include ../lib.mk
> diff --git a/tools/testing/selftests/ve/ve_mount_accounting_test.c b/tools/testing/selftests/ve/ve_mount_accounting_test.c
> new file mode 100644
> index 000000000000..b295290ec6e8
> --- /dev/null
> +++ b/tools/testing/selftests/ve/ve_mount_accounting_test.c
> @@ -0,0 +1,419 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * ve_mount_accounting selftests
> + *
> + * Tests to check the correctness of mount accounting.
> + */
> +#define _GNU_SOURCE
> +#include <linux/sched.h>
> +#include <linux/mount.h>
Is linux/mount needed here? You include both linux/mount and sys/mount.
You only call mount,umount so dropping it looks generally safe.
> +#include <sched.h>
> +#include <sys/wait.h>
> +#include <sys/syscall.h>
> +#include <unistd.h>
> +#include <asm/unistd.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <fcntl.h>
> +#include <string.h>
> +#include <sys/stat.h>
> +#include <sys/mount.h>
> +#include <linux/limits.h>
> +#include <errno.h>
> +
> +#include "../kselftest_harness.h"
> +#include "ve_selftest.h"
> +
> +#define TMP_DIR			"/ve-mnt-tmp/"
> +#define VE_MOUNTS_MAX		128
> +
> +static int set_pseudosuper(int cgv2_fd, int ctid, int value)
> +{
> +	char path[64];
> +
> +	snprintf(path, sizeof(path), "%d/ve.pseudosuper", ctid);
> +	return write_u64_at(cgv2_fd, path, value);
> +}
> +
> +static int _create_mount(void *id_ptr)
> +{
> +	char path[PATH_MAX];
> +	int id = *(int *)id_ptr, ret;
> +
> +	snprintf(path, sizeof(path), TMP_DIR "%d", id);
> +
> +	if (mkdir(path, 0755) < 0) {
> +		fprintf(stderr, "Failed to create directory %s: %s\n", path, strerror(errno));
> +		return -1;
> +	}
> +	ret = mount("tmpfs", path, "tmpfs", 0, "size=1M");
> +	if (!ret)
> +		return 0;
> +	fprintf(stderr, "Failed to mount tmpfs to %s: %s\n", path, strerror(errno));
> +
> +	rmdir(path);
> +	return ret;
> +}
> +
> +static int create_mount(int cgv2_fd, int ctid, int id)
> +{
> +	int ret = run_in_ve(cgv2_fd, ctid, CLONE_NEWVE, _create_mount, &id);
> +	/*
> +	 * If mount fails, cleanup by free_vfsmnt will be called
> +	 * via call_rcu, need to wait for update.
> +	 */
> +	sleep(1);
> +	return ret;
> +}
> +
> +static int _destroy_mount(void *id_ptr)
> +{
> +	char path[PATH_MAX];
> +	struct stat st;
> +	int id = *(int *)id_ptr;
> +
> +	snprintf(path, sizeof(path), TMP_DIR "%d", id);
> +
> +	if (stat(path, &st))
> +		return 1;
> +	if (umount(path)) {
> +		fprintf(stderr, "failed to umount directory %s: %s\n", path, strerror(errno));
> +		return -1;
> +	}
> +	if (rmdir(path)) {
> +		fprintf(stderr, "failed to remove directory %s: %s\n", path, strerror(errno));
> +		return -1;
> +	}
> +	return 0;
> +}
> +
> +static int destroy_mount(int cgv2_fd, int ctid, int id)
> +{
> +	int ret = run_in_ve(cgv2_fd, ctid, CLONE_NEWVE, _destroy_mount, &id);
> +	/* free_vfsmnt is called via call_rcu, need to wait for update */
> +	sleep(1);
> +	return ret;
> +}
> +
> +#define MAX_MNT_ID 32
> +
> +static int get_free_mnt_id(void)
> +{
> +	int i;
> +	struct stat st;
> +	char path[PATH_MAX];
> +
> +	for (i = 0; i < MAX_MNT_ID; i++) {
> +		snprintf(path, sizeof(path), TMP_DIR "%d", i);
> +		if (stat(path, &st))
> +			return i;
> +	}
> +	return -1;
> +}
> +
> +static int get_mount_cost(int cgv2_fd, int ctid)
> +{
> +	int avail1, avail2, mnt_id;
> +	char path[64];
> +
> +	mnt_id = get_free_mnt_id();
> +
> +	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", ctid);
> +	if (mnt_id < 0 ||
> +	    read_s32_at(cgv2_fd, path, &avail1) ||
> +	    create_mount(cgv2_fd, ctid, mnt_id) ||
> +	    read_s32_at(cgv2_fd, path, &avail2) ||
> +	    destroy_mount(cgv2_fd, ctid, mnt_id))
> +		return -1;
> +
> +	return avail1 - avail2;
> +}
> +
> +/* Expect mount success and return new avail value */
> +static int mount_and_get_avail(struct __test_metadata *_metadata,
> +			int cgv2_fd, int ctid, int mnt_id)
> +{
> +	char path_avail[64];
> +	int mnt_avail_nr;
> +
> +	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", ctid);
> +
> +	ASSERT_EQ(create_mount(cgv2_fd, ctid, mnt_id), 0);
> +	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	return mnt_avail_nr;
> +}
> +
> +/* Expect mount failure and ensure intact avail number */
> +static void assert_mount_fails(struct __test_metadata *_metadata,
> +			int cgv2_fd, int ctid, int mnt_id, int avail_count)
> +{
> +	char path_avail[64];
> +	int mnt_avail_nr;
> +
> +	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", ctid);
> +
> +	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, avail_count);
> +	ASSERT_LT(create_mount(cgv2_fd, ctid, mnt_id), 0);
> +	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, avail_count);
> +}
> +
> +FIXTURE(ve_mnt_acc)
> +{
> +	int cgv2_fd;
> +	int ctid;
> +};
> +
> +FIXTURE_SETUP(ve_mnt_acc)
> +{
> +	unsigned long long initial_mnt_avail_nr;
> +	char path[64];
> +
> +	self->cgv2_fd = mount_cg2_fd();
> +	ASSERT_GE(self->cgv2_fd, 0);
> +	mkdir(TMP_DIR, 0755);
> +
> +	ASSERT_EQ(write_file_at(self->cgv2_fd, "cgroup.subtree_control",
> +		  VE_CONTROLLERS), 0);
> +
> +	self->ctid = make_ve(self->cgv2_fd, CTID_MIN);
> +	ASSERT_GE(self->ctid, 0);
> +
> +	snprintf(path, sizeof(path), "%d/ve.mnt_max_nr", self->ctid);
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path, VE_MOUNTS_MAX), 0);
> +
> +	/*
> +	 * The new ve cgroup has not been entered by anything yet, so its
> +	 * mnt_avail_nr counter should be VE_MOUNTS_MAX.
> +	 */
> +	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", self->ctid);
> +	ASSERT_EQ(read_u64_at(self->cgv2_fd, path, &initial_mnt_avail_nr), 0);
> +	ASSERT_EQ(initial_mnt_avail_nr, VE_MOUNTS_MAX);
> +};
> +
> +FIXTURE_TEARDOWN(ve_mnt_acc)
> +{
Since we mount tmpfs on host mount namespace (we dont pass CLONE_NEWNS), 
should we iterate here and umount all the ids that remain mounted after 
tests bail? There are maybe places where create_mount is tried and if 
assertion fails the mount remains and leaks to the host possibly also 
pinning the ve namespace since in ve_try_reserve_mount we get a refcount 
on it.

> +	destroy_ve(self->cgv2_fd, self->ctid);
> +	close(self->cgv2_fd);
> +	rmdir(TMP_DIR);
> +}
> +
> +/* Simple test to check mount/umount accounting correctness */
> +TEST_F(ve_mnt_acc, mount_umount)
> +{
> +	int original_mnt_avail, mnt_avail_nr;
> +	char path[64];
> +
> +	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", self->ctid);
> +
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path, &original_mnt_avail), 0);
> +
> +	ASSERT_LT(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
> +		  original_mnt_avail);
> +
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, original_mnt_avail);
> +}
> +
> +/* Test mount limit hits */
> +TEST_F(ve_mnt_acc, hit_limits)
> +{
> +	int original_mnt_avail, mnt_avail_nr, mnt_cost;
> +	int original_have_mnt;
> +	char path_avail[64], path_max_nr[64];
> +
> +	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
> +	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
> +
> +	mnt_cost = get_mount_cost(self->cgv2_fd, self->ctid);
> +	ASSERT_GE(mnt_cost, 1);
> +
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &original_mnt_avail), 0);
> +	original_have_mnt = VE_MOUNTS_MAX - original_mnt_avail;
> +
> +	/* Step 1: reduce number of available mounts to mnt_cost */
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 1 * mnt_cost), 0);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, 1 * mnt_cost);
> +
> +	/* Step 2: do one mount, no mounts should be available */
> +	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
> +		  0);
> +
> +	/* Step 3: check that one more mount falils */
> +	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 1, 0);
> +
> +	/* Step 4: increase mount limit a little bit, mount should still fail */
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr,
> +				original_have_mnt + 2 * mnt_cost - 1), 0);
> +	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 1, mnt_cost - 1);
> +
> +	/* Step 5: increase by 1 and win now */
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 2 * mnt_cost), 0);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, mnt_cost);
> +
> +	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 1),
> +		  0);
> +
> +	/* Step 6: reduce mnt_max_nr so we have more mounts than allowed */
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 1 * mnt_cost), 0);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, -1 * mnt_cost);
> +
> +	/* Step 7: try to do mount when avail < 0, ensure number is intact */
> +	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, -1 * mnt_cost);
> +
> +	/* Step 8: remove one mount, check avail value update, mount should fail */
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
> +	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, 0);
> +
> +	/* Step 9: remove one more mount and check that new mount succeeds */
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 1), 0);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, 1 * mnt_cost);
> +	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 2),
> +		  0);
> +
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 2), 0);
> +}
> +
> +/*
> + * Mount propagation makes one mount cost more.
> + * This test check that if we run out or mounts in the middle of creating
> + * a new one, everything is restored smoothly and nothing leaks.
> + */
> +TEST_F(ve_mnt_acc, partial_mounts)
> +{
> +	char path_avail[64], path_max_nr[64];
> +	int mount_cost, i, orig_have, orig_mnt_avail;
> +
> +	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
> +	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
> +
> +	mount_cost = get_mount_cost(self->cgv2_fd, self->ctid);
> +	ASSERT_GE(mount_cost, 1);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &orig_mnt_avail), 0);
> +	orig_have = VE_MOUNTS_MAX - orig_mnt_avail;
> +
> +	if (mount_cost == 1)
> +		SKIP(return, "mount cost is 1, no partial mounts possible");
> +
> +	for (i = 0; i < mount_cost; i++) {
> +		ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, orig_have + i), 0);
> +		assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 0, i);
> +	}
> +}
> +
> +/* Test that pseudosuper allows negative avail with correct accounting. */
> +TEST_F(ve_mnt_acc, pseudosuper_allows_overuse)
> +{
> +	int orig_mnt_avail, orig_have;
> +	int mount_cost;
> +	char path_avail[64], path_max_nr[64];
> +
> +	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
> +	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &orig_mnt_avail), 0);
> +	orig_have = VE_MOUNTS_MAX - orig_mnt_avail;
> +
> +	mount_cost = get_mount_cost(self->cgv2_fd, self->ctid);
> +	ASSERT_GE(mount_cost, 1);
> +
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, orig_have + mount_cost), 0);
> +	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 1), 0);
> +
> +	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
> +		  0);
> +	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 1),
> +		  -1 * mount_cost);
> +
> +	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 0), 0);
> +
> +	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, -1 * mount_cost);
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
> +	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, 0);
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 1), 0);
> +	ASSERT_EQ(get_mount_cost(self->cgv2_fd, self->ctid), mount_cost);
> +}
> +
> +/* Test that pseudosuper doesn't disable accounting. */
> +TEST_F(ve_mnt_acc, pseudosuper_continues_accounting)
> +{
> +	int orig_mnt_avail, mount_cost, mnt_avail_nr;
> +	char path_avail[64];
> +
> +	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
> +
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &orig_mnt_avail), 0);
> +	mount_cost = get_mount_cost(self->cgv2_fd, self->ctid);
> +	ASSERT_GE(mount_cost, 1);
> +
> +	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 0), 0);
> +
> +	/* mnt 0 - mounted without pseudosuper, umounted with it. */
> +	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
> +		  orig_mnt_avail - mount_cost);
> +	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 1), 0);
> +
> +	/* Cost is the same when mount/umount happen under pseudosuper. */
> +	ASSERT_EQ(get_mount_cost(self->cgv2_fd, self->ctid), mount_cost);
> +
> +	/* mnt 1 - mounted with pseudosuper, umounted without it. */
> +	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 1),
> +		  orig_mnt_avail - 2 * mount_cost);
> +
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, orig_mnt_avail - mount_cost);
> +
> +	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 0), 0);
> +
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 1), 0);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, orig_mnt_avail);
> +}
> +
> +/* Test failcount feature */
> +TEST_F(ve_mnt_acc, failcount)
> +{
> +	char path_fc[64], failcount_str[512], path_max_nr[64];
> +
> +	snprintf(path_fc, sizeof(path_fc), "%d/ve.failcount", self->ctid);
> +	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
> +
> +	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
> +			       failcount_str, sizeof(failcount_str)), 0);
> +	ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL);
> +
> +	/* Check successful mount doesn't affect failcount */
> +	mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0);
> +	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
> +			       failcount_str, sizeof(failcount_str)), 0);
> +	ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL);
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
> +
> +	/* Check failcount update when mount fails */
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, 0), 0);
> +	ASSERT_LT(create_mount(self->cgv2_fd, self->ctid, 1), 0);
> +	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
> +			       failcount_str, sizeof(failcount_str)), 0);
> +	ASSERT_TRUE(strstr(failcount_str, "mnt: 1\n") != NULL);
> +
> +	/* Check failcount flush */
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_fc, 0), 0);
> +	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
> +			       failcount_str, sizeof(failcount_str)), 0);
> +	ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL);
> +
> +	/* Check failcount update when mount fails again */
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, 0), 0);
> +	ASSERT_LT(create_mount(self->cgv2_fd, self->ctid, 1), 0);
> +	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
> +			       failcount_str, sizeof(failcount_str)), 0);
> +	ASSERT_TRUE(strstr(failcount_str, "mnt: 1\n") != NULL);
> +}
> +
> +TEST_HARNESS_MAIN

-- 
Best regards, Vasileios Almpanis
Software Developer, Virtuozzo.


^ permalink raw reply	[relevance 0%]

* Re: [Devel] [PATCH vz10] selftests: build test modules against the kernel tree
  @ 2026-08-10 16:29  4% ` Konstantin Khorenko
  0 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-10 16:29 UTC (permalink / raw)


1. the patch should go along with changes in kernel.spec i believe because at the moment "livepatch" is not even in the TARGETS list in kerne.spec

2. before committing this please show me 
* the PR with successfully built those .ko files
* and list of files which are included now in the kselftests rpm
* and the successful run of the kselftests - logs of all those 3 TARGETS, how did they run.

3. Please note https://virtuozzo.atlassian.net/browse/VSTOR-139647?focusedCommentId=3980596
when you'll update kernel.spec.

On 8/9/26 20:18, Eva Kurchatova wrote:
> Selftests livepatch, cgroup and mm/page_frag build out-of-tree kernel
> modules and default KDIR to /lib/modules/$(uname -r)/build, which is
> the running kernel build tree on the currently building host.
> That is wrong whenever the selftests are built as part of a kernel
> package: `uname -r` is not the kernel being packaged, so the modules
> either are not built at all (no source tree for the builders kernel)
> or come out with the wrong vermagic and symbol layout.
> Either way no usable test modules end up in the packaged test suite,
> and a testing host may not have the kernel build tree to compile them
> either, so the tests will fail.
> 
> Default KDIR to the kernel tree the tests are built from, or to its O=
> build directory, whenever that tree is configured and built.
> This is exactly what bpf/test_kmods has been already doing all along,
> and its modules are packaged and loaded successfully today.
> Testing for include/config/auto.conf and Module.symvers keeps the previous
> behaviour for a bare source checkout, and for a source tree that was
> mrpropered after the kernel had been built (packaging copies
> Module.symvers back into such a tree), so nothing that builds today
> starts failing.
> 
> Also test for $(KDIR)/Makefile rather than for $(KDIR) itself before
> descending into the kernel tree: /lib/modules/$(uname -r)/build is
> commonly a dangling symlink, which the previous test accepted before
> failing in make -C. Same protection is applied to bpf/page_frag, as it
> had no such check at all.
> 
> With this, `make kselftest TARGETS=livepatch` in a built kernel tree
> builds the modules for that kernel and `make install` ships them, so an
> installed testsuite runs against pre-built modules and needs no kernel
> build tree at all.
> 
> Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
> 
> https://virtuozzo.atlassian.net/browse/VSTOR-139647
> Feature: fix selftests
> ---
>  .../selftests/cgroup/test_modules/Makefile    | 25 ++++++++++++++++---
>  .../selftests/livepatch/test_modules/Makefile | 25 ++++++++++++++++---
>  tools/testing/selftests/mm/Makefile           | 14 +++++++++++
>  tools/testing/selftests/mm/page_frag/Makefile | 22 ++++++++++++++++
>  4 files changed, 78 insertions(+), 8 deletions(-)
> 
> diff --git a/tools/testing/selftests/cgroup/test_modules/Makefile b/tools/testing/selftests/cgroup/test_modules/Makefile
> index 3f39eeda3a92..a47af9b7f18a 100644
> --- a/tools/testing/selftests/cgroup/test_modules/Makefile
> +++ b/tools/testing/selftests/cgroup/test_modules/Makefile
> @@ -1,17 +1,34 @@
>  TESTMODS_DIR := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
> +
> +# Kernel build tree to compile the test modules against. Prefer the
> +# kernel tree these tests are part of (or its O= build directory) when it
> +# is configured and built, like bpf/test_kmods already does: When the

s/When/when/

> +# selftests are built as part of a kernel package, uname -r is the build
> +# host's kernel and not the kernel being packaged, so the resulting
> +# modules would carry the wrong vermagic and symbol layout. Fall back to
> +# the running kernel's build tree for standalone builds.
> +KDIR_TREE := $(if $(O),$(abspath $(O)),$(abspath $(TESTMODS_DIR)/../../../../..))
> +ifneq (,$(wildcard $(KDIR_TREE)/include/config/auto.conf))
> +ifneq (,$(wildcard $(KDIR_TREE)/Module.symvers))
> +KDIR ?= $(KDIR_TREE)
> +endif
> +endif
>  KDIR ?= /lib/modules/$(shell uname -r)/build
>  
>  obj-m += cg_freezer_hang.o \
>  	cg_freezer_kthread.o
>  
> -# Ensure that KDIR exists, otherwise skip the compilation
> +# Ensure that KDIR is a kernel build tree, otherwise skip the compilation.
> +# Testing for the Makefile rather than for the directory itself also covers
> +# a dangling /lib/modules/$(uname -r)/build symlink, and an installed
> +# testsuite, which ships pre-built modules and has no kernel tree above it.
>  modules:
> -ifneq ("$(wildcard $(KDIR))", "")
> +ifneq ("$(wildcard $(KDIR)/Makefile)", "")
>  	$(Q)$(MAKE) -C $(KDIR) modules KBUILD_EXTMOD=$(TESTMODS_DIR)
>  endif
>  
> -# Ensure that KDIR exists, otherwise skip the clean target
> +# Ensure that KDIR is a kernel build tree, otherwise skip the clean target
>  clean:
> -ifneq ("$(wildcard $(KDIR))", "")
> +ifneq ("$(wildcard $(KDIR)/Makefile)", "")
>  	$(Q)$(MAKE) -C $(KDIR) clean KBUILD_EXTMOD=$(TESTMODS_DIR)
>  endif
> diff --git a/tools/testing/selftests/livepatch/test_modules/Makefile b/tools/testing/selftests/livepatch/test_modules/Makefile
> index 939230e571f5..7dc026fd42f7 100644
> --- a/tools/testing/selftests/livepatch/test_modules/Makefile
> +++ b/tools/testing/selftests/livepatch/test_modules/Makefile
> @@ -1,4 +1,18 @@
>  TESTMODS_DIR := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
> +
> +# Kernel build tree to compile the test modules against. Prefer the
> +# kernel tree these tests are part of (or its O= build directory) when it
> +# is configured and built, like bpf/test_kmods already does: when the
> +# selftests are built as part of a kernel package, uname -r is the build
> +# host's kernel and not the kernel being packaged, so the resulting
> +# modules would carry the wrong vermagic and symbol CRCs. Fall back to
> +# the running kernel's build tree for standalone builds.
> +KDIR_TREE := $(if $(O),$(abspath $(O)),$(abspath $(TESTMODS_DIR)/../../../../..))
> +ifneq (,$(wildcard $(KDIR_TREE)/include/config/auto.conf))
> +ifneq (,$(wildcard $(KDIR_TREE)/Module.symvers))
> +KDIR ?= $(KDIR_TREE)
> +endif
> +endif
>  KDIR ?= /lib/modules/$(shell uname -r)/build

i do not think we need to fix this right now, but just for the record:
if we are in <kernel tree>/selftests/livepatch and call
  make -C livepatch O=../../../../out3

the compilation will fail (because of the relative path in O=)

>  
>  obj-m += test_klp_atomic_replace.o \
> @@ -14,14 +28,17 @@ obj-m += test_klp_atomic_replace.o \
>  	test_klp_state3.o \
>  	test_klp_syscall.o
>  
> -# Ensure that KDIR exists, otherwise skip the compilation
> +# Ensure that KDIR is a kernel build tree, otherwise skip the compilation.
> +# Testing for the Makefile rather than for the directory itself also covers
> +# a dangling /lib/modules/$(uname -r)/build symlink, and an installed
> +# testsuite, which ships pre-built modules and has no kernel tree above it.
>  modules:
> -ifneq ("$(wildcard $(KDIR))", "")
> +ifneq ("$(wildcard $(KDIR)/Makefile)", "")
>  	$(Q)$(MAKE) -C $(KDIR) modules KBUILD_EXTMOD=$(TESTMODS_DIR)
>  endif
>  
> -# Ensure that KDIR exists, otherwise skip the clean target
> +# Ensure that KDIR is a kernel build tree, otherwise skip the clean target
>  clean:
> -ifneq ("$(wildcard $(KDIR))", "")
> +ifneq ("$(wildcard $(KDIR)/Makefile)", "")
>  	$(Q)$(MAKE) -C $(KDIR) clean KBUILD_EXTMOD=$(TESTMODS_DIR)
>  endif
> diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
> index 3de23ea4663f..3e643834d3a2 100644
> --- a/tools/testing/selftests/mm/Makefile
> +++ b/tools/testing/selftests/mm/Makefile
> @@ -36,6 +36,20 @@ MAKEFLAGS += --no-builtin-rules
>  CFLAGS = -Wall -I $(top_srcdir) $(EXTRA_CFLAGS) $(KHDR_INCLUDES) $(TOOLS_INCLUDES)
>  LDLIBS = -lrt -lpthread -lm
>  
> +# Kernel build tree to compile page_frag_test.ko against. Prefer the
> +# kernel tree these tests are part of (or its O= build directory) when it
> +# is configured and built, like bpf/test_kmods already does: when the
> +# selftests are built as part of a kernel package, uname -r is the build
> +# host's kernel and not the kernel being packaged, so the resulting module
> +# would carry the wrong vermagic and symbol CRCs. Fall back to the
> +# running kernel's build tree for standalone builds. Keep this in sync
> +# with page_frag/Makefile, which is invoked separately by lib.mk.
> +KDIR_TREE := $(if $(O),$(abspath $(O)),$(abspath $(CURDIR)/../../../..))
> +ifneq (,$(wildcard $(KDIR_TREE)/include/config/auto.conf))
> +ifneq (,$(wildcard $(KDIR_TREE)/Module.symvers))
> +KDIR ?= $(KDIR_TREE)
> +endif
> +endif
>  KDIR ?= /lib/modules/$(shell uname -r)/build
>  ifneq (,$(wildcard $(KDIR)/Module.symvers))
>  ifneq (,$(wildcard $(KDIR)/include/linux/page_frag_cache.h))
> diff --git a/tools/testing/selftests/mm/page_frag/Makefile b/tools/testing/selftests/mm/page_frag/Makefile
> index 8c8bb39ffa28..2c2918bbe0eb 100644
> --- a/tools/testing/selftests/mm/page_frag/Makefile
> +++ b/tools/testing/selftests/mm/page_frag/Makefile
> @@ -1,4 +1,18 @@
>  PAGE_FRAG_TEST_DIR := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
> +
> +# Kernel build tree to compile the test module against. Prefer the kernel
> +# tree this test is part of (or its O= build directory) when it is
> +# configured and built, like bpf/test_kmods already does: when the
> +# selftests are built as part of a kernel package, uname -r is the build
> +# host's kernel and not the kernel being packaged, so the resulting module
> +# would carry the wrong vermagic and symbol CRCs. Fall back to the
> +# running kernel's build tree for standalone builds.
> +KDIR_TREE := $(if $(O),$(abspath $(O)),$(abspath $(PAGE_FRAG_TEST_DIR)/../../../../..))
> +ifneq (,$(wildcard $(KDIR_TREE)/include/config/auto.conf))
> +ifneq (,$(wildcard $(KDIR_TREE)/Module.symvers))
> +KDIR ?= $(KDIR_TREE)
> +endif
> +endif
>  KDIR ?= /lib/modules/$(shell uname -r)/build

  make O=/tmp/vz10-obj -j$(nproc)       # to generate include/config/auto.conf and Module.symvers
  make O=/tmp/vz10-obj kselftest-all TARGETS=mm

You will see:
  Warning: missing page_frag_cache.h, please use a newer kernel. page_frag test will be skipped.


  The O= kernel build breaks, and only for one module - page_frag_test.ko. The other three Makefiles from the patch
  (livepatch, cgroup, page_frag/Makefile) handle O= correctly.

  In tools/testing/selftests/mm/Makefile, right below the new block, there is an old check the patch did not touch:

  KDIR ?= /lib/modules/$(shell uname -r)/build
  ifneq (,$(wildcard $(KDIR)/Module.symvers))
  ifneq (,$(wildcard $(KDIR)/include/linux/page_frag_cache.h))
  TEST_GEN_MODS_DIR := page_frag
  else
  PAGE_FRAG_WARNING = "missing page_frag_cache.h, please use a newer kernel"

  It looks for the source header include/linux/page_frag_cache.h inside KDIR. After the patch, in an O= build KDIR points at
  the build directory ($(O)), which only contains include/config/ and include/generated/ - the source headers stay in the
  source tree. The first check (Module.symvers) passes, the second one does not.

  Further down the chain: TEST_GEN_MODS_DIR is not set, lib.mk never descends into the page_frag/ directory, the module is
  not built and does not get into make install, and test_page_frag.sh hits if [ ! -f $DRIVER ] at run time and exits with
  the skip code. On top of that, the build prints missing page_frag_cache.h, please use a newer kernel, which points at an
  entirely wrong cause.

  Before the patch, in this scenario KDIR was /lib/modules/$(uname -r)/build, where the header is present, so the module was
  built and loaded on that same machine. In other words, the commit message claim "nothing that builds today starts
  failing" does not hold for this case.

  An in-tree build (no O=) is fine: there both Module.symvers and the header live in the same directory.

  How to fix it

  The minimal and safe option is to also look for the header through the source symlink that outputmakefile creates in the
  build directory (ln -fsn $(srctree) source, present in this tree):

  PAGE_FRAG_HDR := $(firstword $(wildcard \
        $(KDIR)/include/linux/page_frag_cache.h \
        $(KDIR)/source/include/linux/page_frag_cache.h))
  ifneq (,$(PAGE_FRAG_HDR))
  TEST_GEN_MODS_DIR := page_frag

>  
>  ifeq ($(V),1)
> @@ -11,8 +25,16 @@ MODULES = page_frag_test.ko
>  
>  obj-m += page_frag_test.o
>  
> +# Ensure that KDIR is a kernel build tree, otherwise skip the compilation.
> +# Testing for the Makefile rather than for the directory itself also covers
> +# a dangling /lib/modules/$(uname -r)/build symlink, and an installed
> +# testsuite, which ships a pre-built module and has no kernel tree above it.
>  all:
> +ifneq ("$(wildcard $(KDIR)/Makefile)", "")
>  	+$(Q)make -C $(KDIR) M=$(PAGE_FRAG_TEST_DIR) modules
> +endif
>  
>  clean:
> +ifneq ("$(wildcard $(KDIR)/Makefile)", "")
>  	+$(Q)make -C $(KDIR) M=$(PAGE_FRAG_TEST_DIR) clean
> +endif


^ permalink raw reply	[relevance 4%]

* [Devel] [PATCH VZ10] x86/bugs: Make Safe-RET robust against interrupt injection
@ 2026-08-11 11:49 18% Pavel Tikhomirov
  2026-08-11 14:48 17% ` [Devel] [PATCH RHEL10 COMMIT] ms/x86/bugs: " Konstantin Khorenko
  0 siblings, 1 reply; 119+ results
From: Pavel Tikhomirov @ 2026-08-11 11:49 UTC (permalink / raw)


From: "Borislav Petkov (AMD)" <bp@alien8.de>

commit 7e7f81cf6f5ca3311e526308f55d7c54d3ba71f9 upstream.

An attacker injecting interrupts while the Safe-RET mitigation executes
on machines affected by SRSO can neutralize the safe return sequence,
potentially leading to data leakage through speculative execution.

Fixup register state as if the Safe-RET sequence executed successfully
by "emulating" it, in a manner of speaking, and avoid executing a RET
instruction after returning from the interrupt.

Co-developed-by: David Kaplan <David.Kaplan@amd.com>
Signed-off-by: David Kaplan <David.Kaplan@amd.com>
Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

Also adding pt_regs forward declaration to avoid possible compilation
warnings.

CVE-2026-68480
https://virtuozzo.atlassian.net/browse/VSTOR-140991
(cherry picked from commit bfe7f9993467ba431b2731437949ac1e2634e771)
Signed-off-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
---
 arch/x86/entry/entry_64.S            |  8 +++-
 arch/x86/include/asm/nospec-branch.h | 59 ++++++++++++++++++++++++++++
 arch/x86/kernel/cpu/bugs.c           | 39 ++++++++++++++++++
 arch/x86/lib/retpoline.S             | 20 ++++++++++
 4 files changed, 125 insertions(+), 1 deletion(-)

diff --git a/arch/x86/entry/entry_64.S b/arch/x86/entry/entry_64.S
index b4cd6ddde9747..a14e425554d8c 100644
--- a/arch/x86/entry/entry_64.S
+++ b/arch/x86/entry/entry_64.S
@@ -935,6 +935,8 @@ SYM_CODE_START(paranoid_entry)
 	IBRS_ENTER save_reg=%r15
 	UNTRAIN_RET_FROM_CALL
 
+	HANDLE_INTR_SAFERET 8(%rsp)
+
 	RET
 SYM_CODE_END(paranoid_entry)
 
@@ -1037,6 +1039,11 @@ SYM_CODE_START(error_entry)
 	movl	%ecx, %eax			/* zero extend */
 	cmpq	%rax, RIP+8(%rsp)
 	je	.Lbstep_iret
+
+	VALIDATE_UNRET_END
+
+	HANDLE_INTR_SAFERET 8(%rsp)
+
 	cmpq	$.Lgs_change, RIP+8(%rsp)
 	jne	.Lerror_entry_done_lfence
 
@@ -1055,7 +1062,6 @@ SYM_CODE_START(error_entry)
 	FENCE_SWAPGS_KERNEL_ENTRY
 	CALL_DEPTH_ACCOUNT
 	leaq	8(%rsp), %rax			/* return pt_regs pointer */
-	VALIDATE_UNRET_END
 	RET
 
 .Lbstep_iret:
diff --git a/arch/x86/include/asm/nospec-branch.h b/arch/x86/include/asm/nospec-branch.h
index deb105c07de92..7fb9ad9597d20 100644
--- a/arch/x86/include/asm/nospec-branch.h
+++ b/arch/x86/include/asm/nospec-branch.h
@@ -13,6 +13,7 @@
 #include <asm/unwind_hints.h>
 #include <asm/percpu.h>
 #include <asm/current.h>
+#include <asm/ptrace-abi.h>
 
 /*
  * Call depth tracking for Intel SKL CPUs to address the RSB underflow
@@ -177,6 +178,50 @@
 	add	$(BITS_PER_LONG/8), %_ASM_SP;		\
 	lfence;
 
+/*
+ * Helper for detecting if an interrupt occurred at an unsafe location within
+ * Safe-RET.  If Safe-RET is interrupted after the CALL or LEA the RSB may get
+ * poisoned by the interrupt handler.
+ *
+ * The Safe-RET sequence is:
+ *
+ * CALL
+ * LEA 8(%RSP), %RSP
+ * RET
+ *
+ * The two CMPs below check whether RIP points to after the CALL or after the
+ * LEA.
+ *
+ * The LFENCE below is to address this particular speculation case:
+ *
+ * 1. Userspace runs and poisons the BTB around the safe-RET routine
+ *
+ * 2. Userspace triggers some kind of exception
+ *
+ * 3. Kernel executes error_entry() and mis-speculates the branch into thinking
+ *    it actually came from kernel space
+ *
+ * 4. The kernel then further mis-speculates that the exception occurred due
+ *    to an interrupted safe-RET
+ *
+ * 5. The handle_interrupted_saferet() routine speculatively executes and
+ *    speculatively does a safe-RET. But this is unsafe since it was never
+ *    untrained.
+ *
+ * The LFENCE fixes this by ensuring step 5 is never reached speculatively.
+ * Note that this LFENCE only occurs if safe-RET was actually interrupted (so
+ * it's outside of the normal path).
+ */
+#define __HANDLE_INTR_SAFERET(name, pt_regs)		\
+	cmpq	$(name), RIP+pt_regs;			\
+	jb	1f;					\
+	cmpq	$(name)+5, RIP+pt_regs;			\
+	ja	1f;					\
+	lfence;						\
+	leaq	pt_regs, %rdi;				\
+	call	handle_interrupted_saferet;		\
+	1:
+
 #ifdef __ASSEMBLY__
 
 /*
@@ -295,6 +340,14 @@
 #define UNTRAIN_RET_FROM_CALL \
 	__UNTRAIN_RET X86_FEATURE_ENTRY_IBPB, __stringify(RESET_CALL_DEPTH_FROM_CALL)
 
+.macro HANDLE_INTR_SAFERET pt_regs
+#ifdef CONFIG_MITIGATION_SRSO
+	ALTERNATIVE_2 "", \
+	__stringify(__HANDLE_INTR_SAFERET(srso_safe_ret, \pt_regs)), X86_FEATURE_SRSO, \
+	__stringify(__HANDLE_INTR_SAFERET(srso_alias_safe_ret, \pt_regs)), X86_FEATURE_SRSO_ALIAS
+
+#endif
+.endm
 
 .macro CALL_DEPTH_ACCOUNT
 #ifdef CONFIG_MITIGATION_CALL_DEPTH_TRACKING
@@ -618,6 +671,12 @@ static __always_inline void x86_idle_clear_cpu_buffers(void)
 		x86_clear_cpu_buffers();
 }
 
+struct pt_regs;
+
+void srso_safe_ret(void);
+void srso_alias_safe_ret(void);
+void handle_interrupted_saferet(struct pt_regs *regs);
+
 #endif /* __ASSEMBLY__ */
 
 #endif /* _ASM_X86_NOSPEC_BRANCH_H_ */
diff --git a/arch/x86/kernel/cpu/bugs.c b/arch/x86/kernel/cpu/bugs.c
index b8daba654d85f..c722fcc1cd627 100644
--- a/arch/x86/kernel/cpu/bugs.c
+++ b/arch/x86/kernel/cpu/bugs.c
@@ -3794,3 +3794,42 @@ void __warn_thunk(void)
 {
 	WARN_ONCE(1, "Unpatched return thunk in use. This should not happen!\n");
 }
+
+#ifdef CONFIG_MITIGATION_SRSO
+/*
+ * Called during exception/interrupt entry if interrupted during the
+ * safe-RET sequence.  The safe-RET sequence consists of 3 instructions:
+ *
+ *	CALL
+ *	LEA 8(%RSP), %RSP
+ *	RET
+ *
+ * An interrupt after the CALL or after the LEA could potentially lead
+ * to branch predictor poisoning and results in the sequence not being
+ * able to be safely resumed.
+ *
+ * Therefore, modify the regs state as if the remaining part of the
+ * safe-RET sequence executed so the interrupt returns back to the
+ * desired return target, instead of the to the safe-RET sequence.
+ */
+void noinstr handle_interrupted_saferet(struct pt_regs *regs)
+{
+	unsigned long rip = regs->ip;
+
+	if (rip == (unsigned long) srso_safe_ret ||
+	    rip == (unsigned long) srso_alias_safe_ret) {
+	    /* Modify stack pointer as if LEA executed: */
+	    regs->sp += 8;
+	}
+
+	/*
+	 * Adjust registers as if RET executed:
+	 *
+	 * 1. Read the return address off the stack and into rIP:
+	 */
+	regs->ip = *(unsigned long *)(regs->sp);
+
+	/* 2. Pop rIP off the stack: */
+	regs->sp += 8;
+}
+#endif /* CONFIG_MITIGATION_SRSO */
diff --git a/arch/x86/lib/retpoline.S b/arch/x86/lib/retpoline.S
index 614fb9aee2ff6..bc66ce29ccc8b 100644
--- a/arch/x86/lib/retpoline.S
+++ b/arch/x86/lib/retpoline.S
@@ -168,10 +168,24 @@ __EXPORT_THUNK(srso_alias_untrain_ret)
 
 	.pushsection .text..__x86.rethunk_safe
 SYM_CODE_START_NOALIGN(srso_alias_safe_ret)
+
+	/*
+	 * Tell objtool that those are not function pointers referenced by
+	 * __HANDLE_INTR_SAFERET(). Below too.
+	 */
+	ANNOTATE_NOENDBR
+
+	/*
+	 * Safe-RET sequence. If you need to change it, adjust
+	 * handle_interrupted_saferet() too.
+	 */
 	lea 8(%_ASM_SP), %_ASM_SP
 	UNWIND_HINT_FUNC
+
+	ANNOTATE_NOENDBR
 	ANNOTATE_UNRET_SAFE
 	ret
+	/* End of Safe-RET sequence */
 	int3
 SYM_FUNC_END(srso_alias_safe_ret)
 
@@ -206,8 +220,14 @@ SYM_CODE_START_LOCAL_NOALIGN(srso_untrain_ret)
  * the stack.
  */
 SYM_INNER_LABEL(srso_safe_ret, SYM_L_GLOBAL)
+	/*
+	 * Safe-RET sequence. If you need to change it, adjust
+	 * handle_interrupted_saferet() too.
+	 */
 	lea 8(%_ASM_SP), %_ASM_SP
 	ret
+	/* End of Safe-RET sequence */
+
 	int3
 	int3
 	/* end of movabs */
-- 
2.55.0


^ permalink raw reply	[relevance 18%]

* [Devel] [PATCH RHEL10 COMMIT] ms/x86/bugs: Make Safe-RET robust against interrupt injection
  2026-08-11 11:49 18% [Devel] [PATCH VZ10] x86/bugs: Make Safe-RET robust against interrupt injection Pavel Tikhomirov
@ 2026-08-11 14:48 17% ` Konstantin Khorenko
  0 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-11 14:48 UTC (permalink / raw)


The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.3.vz10
------>
commit 7c5f71404ed2e471b416cab264e8cb9ecb9845d5
Author: Borislav Petkov (AMD) <bp@alien8.de>
Date:   Tue Aug 11 13:49:35 2026 +0200

    ms/x86/bugs: Make Safe-RET robust against interrupt injection
    
    commit 7e7f81cf6f5ca3311e526308f55d7c54d3ba71f9 upstream.
    
    An attacker injecting interrupts while the Safe-RET mitigation executes
    on machines affected by SRSO can neutralize the safe return sequence,
    potentially leading to data leakage through speculative execution.
    
    Fixup register state as if the Safe-RET sequence executed successfully
    by "emulating" it, in a manner of speaking, and avoid executing a RET
    instruction after returning from the interrupt.
    
    Co-developed-by: David Kaplan <David.Kaplan@amd.com>
    Signed-off-by: David Kaplan <David.Kaplan@amd.com>
    Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
    Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
    
    Also adding pt_regs forward declaration to avoid possible compilation
    warnings.
    
    CVE-2026-68480
    Feature: fix ms/x86
    https://virtuozzo.atlassian.net/browse/VSTOR-140991
    (cherry picked from commit 7e7f81cf6f5ca3311e526308f55d7c54d3ba71f9)
    Signed-off-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
---
 arch/x86/entry/entry_64.S            |  8 ++++-
 arch/x86/include/asm/nospec-branch.h | 59 ++++++++++++++++++++++++++++++++++++
 arch/x86/kernel/cpu/bugs.c           | 39 ++++++++++++++++++++++++
 arch/x86/lib/retpoline.S             | 20 ++++++++++++
 4 files changed, 125 insertions(+), 1 deletion(-)

diff --git a/arch/x86/entry/entry_64.S b/arch/x86/entry/entry_64.S
index b4cd6ddde9747..a14e425554d8c 100644
--- a/arch/x86/entry/entry_64.S
+++ b/arch/x86/entry/entry_64.S
@@ -935,6 +935,8 @@ SYM_CODE_START(paranoid_entry)
 	IBRS_ENTER save_reg=%r15
 	UNTRAIN_RET_FROM_CALL
 
+	HANDLE_INTR_SAFERET 8(%rsp)
+
 	RET
 SYM_CODE_END(paranoid_entry)
 
@@ -1037,6 +1039,11 @@ SYM_CODE_START(error_entry)
 	movl	%ecx, %eax			/* zero extend */
 	cmpq	%rax, RIP+8(%rsp)
 	je	.Lbstep_iret
+
+	VALIDATE_UNRET_END
+
+	HANDLE_INTR_SAFERET 8(%rsp)
+
 	cmpq	$.Lgs_change, RIP+8(%rsp)
 	jne	.Lerror_entry_done_lfence
 
@@ -1055,7 +1062,6 @@ SYM_CODE_START(error_entry)
 	FENCE_SWAPGS_KERNEL_ENTRY
 	CALL_DEPTH_ACCOUNT
 	leaq	8(%rsp), %rax			/* return pt_regs pointer */
-	VALIDATE_UNRET_END
 	RET
 
 .Lbstep_iret:
diff --git a/arch/x86/include/asm/nospec-branch.h b/arch/x86/include/asm/nospec-branch.h
index deb105c07de92..7fb9ad9597d20 100644
--- a/arch/x86/include/asm/nospec-branch.h
+++ b/arch/x86/include/asm/nospec-branch.h
@@ -13,6 +13,7 @@
 #include <asm/unwind_hints.h>
 #include <asm/percpu.h>
 #include <asm/current.h>
+#include <asm/ptrace-abi.h>
 
 /*
  * Call depth tracking for Intel SKL CPUs to address the RSB underflow
@@ -177,6 +178,50 @@
 	add	$(BITS_PER_LONG/8), %_ASM_SP;		\
 	lfence;
 
+/*
+ * Helper for detecting if an interrupt occurred at an unsafe location within
+ * Safe-RET.  If Safe-RET is interrupted after the CALL or LEA the RSB may get
+ * poisoned by the interrupt handler.
+ *
+ * The Safe-RET sequence is:
+ *
+ * CALL
+ * LEA 8(%RSP), %RSP
+ * RET
+ *
+ * The two CMPs below check whether RIP points to after the CALL or after the
+ * LEA.
+ *
+ * The LFENCE below is to address this particular speculation case:
+ *
+ * 1. Userspace runs and poisons the BTB around the safe-RET routine
+ *
+ * 2. Userspace triggers some kind of exception
+ *
+ * 3. Kernel executes error_entry() and mis-speculates the branch into thinking
+ *    it actually came from kernel space
+ *
+ * 4. The kernel then further mis-speculates that the exception occurred due
+ *    to an interrupted safe-RET
+ *
+ * 5. The handle_interrupted_saferet() routine speculatively executes and
+ *    speculatively does a safe-RET. But this is unsafe since it was never
+ *    untrained.
+ *
+ * The LFENCE fixes this by ensuring step 5 is never reached speculatively.
+ * Note that this LFENCE only occurs if safe-RET was actually interrupted (so
+ * it's outside of the normal path).
+ */
+#define __HANDLE_INTR_SAFERET(name, pt_regs)		\
+	cmpq	$(name), RIP+pt_regs;			\
+	jb	1f;					\
+	cmpq	$(name)+5, RIP+pt_regs;			\
+	ja	1f;					\
+	lfence;						\
+	leaq	pt_regs, %rdi;				\
+	call	handle_interrupted_saferet;		\
+	1:
+
 #ifdef __ASSEMBLY__
 
 /*
@@ -295,6 +340,14 @@
 #define UNTRAIN_RET_FROM_CALL \
 	__UNTRAIN_RET X86_FEATURE_ENTRY_IBPB, __stringify(RESET_CALL_DEPTH_FROM_CALL)
 
+.macro HANDLE_INTR_SAFERET pt_regs
+#ifdef CONFIG_MITIGATION_SRSO
+	ALTERNATIVE_2 "", \
+	__stringify(__HANDLE_INTR_SAFERET(srso_safe_ret, \pt_regs)), X86_FEATURE_SRSO, \
+	__stringify(__HANDLE_INTR_SAFERET(srso_alias_safe_ret, \pt_regs)), X86_FEATURE_SRSO_ALIAS
+
+#endif
+.endm
 
 .macro CALL_DEPTH_ACCOUNT
 #ifdef CONFIG_MITIGATION_CALL_DEPTH_TRACKING
@@ -618,6 +671,12 @@ static __always_inline void x86_idle_clear_cpu_buffers(void)
 		x86_clear_cpu_buffers();
 }
 
+struct pt_regs;
+
+void srso_safe_ret(void);
+void srso_alias_safe_ret(void);
+void handle_interrupted_saferet(struct pt_regs *regs);
+
 #endif /* __ASSEMBLY__ */
 
 #endif /* _ASM_X86_NOSPEC_BRANCH_H_ */
diff --git a/arch/x86/kernel/cpu/bugs.c b/arch/x86/kernel/cpu/bugs.c
index b8daba654d85f..c722fcc1cd627 100644
--- a/arch/x86/kernel/cpu/bugs.c
+++ b/arch/x86/kernel/cpu/bugs.c
@@ -3794,3 +3794,42 @@ void __warn_thunk(void)
 {
 	WARN_ONCE(1, "Unpatched return thunk in use. This should not happen!\n");
 }
+
+#ifdef CONFIG_MITIGATION_SRSO
+/*
+ * Called during exception/interrupt entry if interrupted during the
+ * safe-RET sequence.  The safe-RET sequence consists of 3 instructions:
+ *
+ *	CALL
+ *	LEA 8(%RSP), %RSP
+ *	RET
+ *
+ * An interrupt after the CALL or after the LEA could potentially lead
+ * to branch predictor poisoning and results in the sequence not being
+ * able to be safely resumed.
+ *
+ * Therefore, modify the regs state as if the remaining part of the
+ * safe-RET sequence executed so the interrupt returns back to the
+ * desired return target, instead of the to the safe-RET sequence.
+ */
+void noinstr handle_interrupted_saferet(struct pt_regs *regs)
+{
+	unsigned long rip = regs->ip;
+
+	if (rip == (unsigned long) srso_safe_ret ||
+	    rip == (unsigned long) srso_alias_safe_ret) {
+	    /* Modify stack pointer as if LEA executed: */
+	    regs->sp += 8;
+	}
+
+	/*
+	 * Adjust registers as if RET executed:
+	 *
+	 * 1. Read the return address off the stack and into rIP:
+	 */
+	regs->ip = *(unsigned long *)(regs->sp);
+
+	/* 2. Pop rIP off the stack: */
+	regs->sp += 8;
+}
+#endif /* CONFIG_MITIGATION_SRSO */
diff --git a/arch/x86/lib/retpoline.S b/arch/x86/lib/retpoline.S
index 614fb9aee2ff6..bc66ce29ccc8b 100644
--- a/arch/x86/lib/retpoline.S
+++ b/arch/x86/lib/retpoline.S
@@ -168,10 +168,24 @@ SYM_FUNC_END(srso_alias_untrain_ret)
 
 	.pushsection .text..__x86.rethunk_safe
 SYM_CODE_START_NOALIGN(srso_alias_safe_ret)
+
+	/*
+	 * Tell objtool that those are not function pointers referenced by
+	 * __HANDLE_INTR_SAFERET(). Below too.
+	 */
+	ANNOTATE_NOENDBR
+
+	/*
+	 * Safe-RET sequence. If you need to change it, adjust
+	 * handle_interrupted_saferet() too.
+	 */
 	lea 8(%_ASM_SP), %_ASM_SP
 	UNWIND_HINT_FUNC
+
+	ANNOTATE_NOENDBR
 	ANNOTATE_UNRET_SAFE
 	ret
+	/* End of Safe-RET sequence */
 	int3
 SYM_FUNC_END(srso_alias_safe_ret)
 
@@ -206,8 +220,14 @@ /*
  * the stack.
  */
 SYM_INNER_LABEL(srso_safe_ret, SYM_L_GLOBAL)
+	/*
+	 * Safe-RET sequence. If you need to change it, adjust
+	 * handle_interrupted_saferet() too.
+	 */
 	lea 8(%_ASM_SP), %_ASM_SP
 	ret
+	/* End of Safe-RET sequence */
+
 	int3
 	int3
 	/* end of movabs */

^ permalink raw reply	[relevance 17%]

* [Devel] [PATCH DRAFT vz10 1/5] Revert "ve/net/gre: Disable ERSPAN support in ip_gre module"
  @ 2026-08-12 13:03  4% ` Konstantin Khorenko
  0 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-12 13:03 UTC (permalink / raw)


This reverts commit a6adc8063402a38c6d951113453df6bf8ddcfb0e.

ERSPAN was disabled under CONFIG_VE because at the time it was not wired
into the per-Container GRE infrastructure: erspan devices did not set
NETIF_F_VIRTUAL, so register_netdevice() would reject them inside a CT,
and erspan_net_ops was not gated by any VE feature bit. Rather than
carry the feature permanently disabled, bring the code back so that the
following commit can properly containerize ERSPAN under a dedicated
VE feature bit.

https://virtuozzo.atlassian.net/browse/VSTOR-141173

Feature: net: disable ERSPAN support in ip_gre module
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
 net/ipv4/ip_gre.c | 33 ++++-----------------------------
 1 file changed, 4 insertions(+), 29 deletions(-)

diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c
index f618aae250334..d776eb8d9f76c 100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c
@@ -1134,7 +1134,6 @@ static int ipgre_tap_validate(struct nlattr *tb[], struct nlattr *data[],
 	return ipgre_tunnel_validate(tb, data, extack);
 }
 
-#ifndef CONFIG_VE
 static int erspan_validate(struct nlattr *tb[], struct nlattr *data[],
 			   struct netlink_ext_ack *extack)
 {
@@ -1174,7 +1173,6 @@ static int erspan_validate(struct nlattr *tb[], struct nlattr *data[],
 
 	return 0;
 }
-#endif
 
 static int ipgre_netlink_parms(struct net_device *dev,
 				struct nlattr *data[],
@@ -1245,7 +1243,6 @@ static int ipgre_netlink_parms(struct net_device *dev,
 	return 0;
 }
 
-#ifndef CONFIG_VE
 static int erspan_netlink_parms(struct net_device *dev,
 				struct nlattr *data[],
 				struct nlattr *tb[],
@@ -1289,7 +1286,6 @@ static int erspan_netlink_parms(struct net_device *dev,
 
 	return 0;
 }
-#endif
 
 /* This function returns true when ENCAP attributes are present in the nl msg */
 static bool ipgre_netlink_encap_parms(struct nlattr *data[],
@@ -1428,7 +1424,6 @@ static int ipgre_newlink(struct net *src_net, struct net_device *dev,
 	return ip_tunnel_newlink(dev, tb, &p, fwmark);
 }
 
-#ifndef CONFIG_VE
 static int erspan_newlink(struct net *src_net, struct net_device *dev,
 			  struct nlattr *tb[], struct nlattr *data[],
 			  struct netlink_ext_ack *extack)
@@ -1446,7 +1441,6 @@ static int erspan_newlink(struct net *src_net, struct net_device *dev,
 		return err;
 	return ip_tunnel_newlink(dev, tb, &p, fwmark);
 }
-#endif
 
 static int ipgre_changelink(struct net_device *dev, struct nlattr *tb[],
 			    struct nlattr *data[],
@@ -1477,7 +1471,6 @@ static int ipgre_changelink(struct net_device *dev, struct nlattr *tb[],
 	return 0;
 }
 
-#ifndef CONFIG_VE
 static int erspan_changelink(struct net_device *dev, struct nlattr *tb[],
 			     struct nlattr *data[],
 			     struct netlink_ext_ack *extack)
@@ -1504,7 +1497,6 @@ static int erspan_changelink(struct net_device *dev, struct nlattr *tb[],
 
 	return 0;
 }
-#endif
 
 static size_t ipgre_get_size(const struct net_device *dev)
 {
@@ -1602,7 +1594,6 @@ static int ipgre_fill_info(struct sk_buff *skb, const struct net_device *dev)
 	return -EMSGSIZE;
 }
 
-#ifndef CONFIG_VE
 static int erspan_fill_info(struct sk_buff *skb, const struct net_device *dev)
 {
 	struct ip_tunnel *t = netdev_priv(dev);
@@ -1643,7 +1634,6 @@ static void erspan_setup(struct net_device *dev)
 	ip_tunnel_setup(dev, erspan_net_id);
 	t->erspan_ver = 1;
 }
-#endif
 
 static const struct nla_policy ipgre_policy[IFLA_GRE_MAX + 1] = {
 	[IFLA_GRE_LINK]		= { .type = NLA_U32 },
@@ -1699,7 +1689,6 @@ static struct rtnl_link_ops ipgre_tap_ops __read_mostly = {
 	.get_link_net	= ip_tunnel_get_link_net,
 };
 
-#ifndef CONFIG_VE
 static struct rtnl_link_ops erspan_link_ops __read_mostly = {
 	.kind		= "erspan",
 	.maxtype	= IFLA_GRE_MAX,
@@ -1714,7 +1703,6 @@ static struct rtnl_link_ops erspan_link_ops __read_mostly = {
 	.fill_info	= erspan_fill_info,
 	.get_link_net	= ip_tunnel_get_link_net,
 };
-#endif
 
 struct net_device *gretap_fb_dev_create(struct net *net, const char *name,
 					u8 name_assign_type)
@@ -1786,7 +1774,6 @@ static struct pernet_operations ipgre_tap_net_ops = {
 	.size = sizeof(struct ip_tunnel_net),
 };
 
-#ifndef CONFIG_VE
 static int __net_init erspan_init_net(struct net *net)
 {
 	return ip_tunnel_init_net(net, erspan_net_id,
@@ -1806,7 +1793,6 @@ static struct pernet_operations erspan_net_ops = {
 	.id   = &erspan_net_id,
 	.size = sizeof(struct ip_tunnel_net),
 };
-#endif
 
 static int __init ipgre_init(void)
 {
@@ -1821,11 +1807,11 @@ static int __init ipgre_init(void)
 	err = register_pernet_device(&ipgre_tap_net_ops);
 	if (err < 0)
 		goto pnet_tap_failed;
-#ifndef CONFIG_VE
+
 	err = register_pernet_device(&erspan_net_ops);
 	if (err < 0)
 		goto pnet_erspan_failed;
-#endif
+
 	err = gre_add_protocol(&ipgre_protocol, GREPROTO_CISCO);
 	if (err < 0) {
 		pr_info("%s: can't add protocol\n", __func__);
@@ -1839,27 +1825,22 @@ static int __init ipgre_init(void)
 	err = rtnl_link_register(&ipgre_tap_ops);
 	if (err < 0)
 		goto tap_ops_failed;
-#ifndef CONFIG_VE
+
 	err = rtnl_link_register(&erspan_link_ops);
 	if (err < 0)
 		goto erspan_link_failed;
-#endif
 
 	return 0;
 
-#ifndef CONFIG_VE
 erspan_link_failed:
-#endif
 	rtnl_link_unregister(&ipgre_tap_ops);
 tap_ops_failed:
 	rtnl_link_unregister(&ipgre_link_ops);
 rtnl_link_failed:
 	gre_del_protocol(&ipgre_protocol, GREPROTO_CISCO);
 add_proto_failed:
-#ifndef CONFIG_VE
 	unregister_pernet_device(&erspan_net_ops);
 pnet_erspan_failed:
-#endif
 	unregister_pernet_device(&ipgre_tap_net_ops);
 pnet_tap_failed:
 	unregister_pernet_device(&ipgre_net_ops);
@@ -1870,15 +1851,11 @@ static void __exit ipgre_fini(void)
 {
 	rtnl_link_unregister(&ipgre_tap_ops);
 	rtnl_link_unregister(&ipgre_link_ops);
-#ifndef CONFIG_VE
 	rtnl_link_unregister(&erspan_link_ops);
-#endif
 	gre_del_protocol(&ipgre_protocol, GREPROTO_CISCO);
 	unregister_pernet_device(&ipgre_tap_net_ops);
 	unregister_pernet_device(&ipgre_net_ops);
-#ifndef CONFIG_VE
 	unregister_pernet_device(&erspan_net_ops);
-#endif
 }
 
 module_init(ipgre_init);
@@ -1887,9 +1864,7 @@ MODULE_DESCRIPTION("IPv4 GRE tunnels over IP library");
 MODULE_LICENSE("GPL");
 MODULE_ALIAS_RTNL_LINK("gre");
 MODULE_ALIAS_RTNL_LINK("gretap");
-#ifndef CONFIG_VE
 MODULE_ALIAS_RTNL_LINK("erspan");
-MODULE_ALIAS_NETDEV("erspan0");
-#endif
 MODULE_ALIAS_NETDEV("gre0");
 MODULE_ALIAS_NETDEV("gretap0");
+MODULE_ALIAS_NETDEV("erspan0");
-- 
2.43.0


^ permalink raw reply	[relevance 4%]

* [Devel] [PATCH vz10 0/3] ve/fs: make mount ownership follow the mount namespace
@ 2026-08-17  7:16  3% Mirian Shilakadze
  2026-08-17  7:16  6% ` [Devel] [PATCH vz10 1/3] ve/fs: unlink the mount namespace on the copy_mnt_ns() error path Mirian Shilakadze
  2026-08-18 10:40  0% ` [Devel] [PATCH vz10 0/3] ve/fs: make mount ownership follow the mount namespace Vasileios Almpanis
  0 siblings, 2 replies; 119+ results
From: Mirian Shilakadze @ 2026-08-17  7:16 UTC (permalink / raw)


mnt->ve_owner is meant to say which VE a mount belongs to. It is read by
ve_check_trusted_file(), which stops ve0 executing content a container
could have written, and by the per-VE mount accounting behind ve.mnt_nr.

It is wrong in two different ways, and each one alone defeats the trusted
exec check. Fixing the second exposed a third bug, a host panic, which is
patch 1 and the reason this is one series rather than two.

Patch 1 fixes the panic. alloc_mnt_ns() links every namespace onto
all_mntns_list and takes a reference on its owning VE, and both are undone
only in free_mnt_ns(). copy_mnt_ns()'s copy_tree() failure path does not
call it, so it frees a namespace that is still linked and leaks the VE
reference, and the next namespace creation runs list_add_tail() through
the dangling entry. A container at its own sysctl_ve_mount_nr limit
calling unshare(CLONE_NEWNS) reaches that path deterministically and
panics the host. This is VSTOR-141545.

Patch 2 fixes ownership not being updated when a mount moves. ve_owner is
assigned once in ve_mount_nr_inc() from alloc_vfsmnt() and never changes,
so a mount the host hands to a container keeps ve_owner == ve0 while
living in the container's mount namespace, and a host tmpfs bindmounted
into a container is trusted even though the container can write to it.
This is VSTOR-141322.

Patch 3 fixes ownership being wrong at creation. A new mount takes its
owner from get_exec_env(), the VE of the task, rather than the VE of the
mount namespace being worked in. Those differ for a ve0 task that entered
a container's mount namespace with nsenter -m. copy_mnt_ns() and
open_detached_copy() both build mounts that way and neither reaches
commit_tree(), so a plain unshare(CLONE_NEWNS) or an
open_tree(OPEN_TREE_CLONE) hands back ve0 owned copies of container
content. This is VSTOR-141429.

Patches 2 and 3 apply the same rule, the one already used when a namespace
is copied at container creation: ownership comes from the mount namespace,
never from the calling task.

Testing
=======

Built on 6.12.0-211.39.1.16.4.vz10, debug flavour, with KASAN, lockdep,
PROVE_LOCKING, DEBUG_ATOMIC_SLEEP, DEBUG_LIST, DEBUG_VM and gcov on
fs/namespace.o. Also compiled with CONFIG_VE=n. Each commit builds
standalone, so bisect is safe.

Before and after on the same host, same commands. Stock
6.12.0-211.30.1.14.4.vz10 on the left, the series on the right:

  container at its mount limit, unshare(CLONE_NEWNS)  PANIC    -> survives
  CT tmpfs, plain nsenter (control)                   refused  -> refused
  nsenter + unshare -m                                EXECUTED -> refused
  nsenter + open_tree + execveat                      EXECUTED -> refused
  host tmpfs lent via --bindmount_add                 EXECUTED -> refused

The panic was captured on stock over netconsole: "list_add corruption ...
kernel BUG at lib/list_debug.c:32", Comm: unshare, ve: 900, trace
alloc_mnt_ns <- copy_mnt_ns <- unshare. Against the series the identical
sequence, 150 container unshares at the limit followed by 300 host
namespace creations, completes with no corruption and no dump. The
control case behaving the same on both kernels shows the difference is
the change and not the environment.

No KASAN, lockdep, atomic sleep, refcount or list corruption reports
across any of it. DEBUG_ATOMIC_SLEEP staying quiet covers the one thing
worth asking about in patch 2, that get_ve()/put_ve() are called under
lock_mount_hash().

ve.mnt_nr drifted by 0 over 15 bindmount add and remove cycles, and
nr_dying_descendants moved by 1 across 150 opportunities to leak a VE
reference, so patch 1 releases the reference rather than merely not
crashing.

Coverage from gcov, so the new code is known to have run rather than just
linked: ve_mount_reown() called 54300 times with the ownership transfer
branch taken 958 times, and both commit_tree() call sites exercised,
20024 for the moved tree and 24081 for the propagation loop.

selftests: mount 2/2, mount_setattr 21/21, ve_perms 14/14, ve_ns_owner
2/2. The last one matters most, it asserts ve.mnt_nr behaviour around
CLONE_NEWVE, which is the line patch 3 changes.

vzctl functional suite: 538 of 603 passed. About 25 of the failures are
vzctl returning exit 21 where the suite asserts 20 for an unrecognized
option, which never reaches the kernel. The other ten were re-run one at
a time: two passed, and the rest fail in container creation or disk setup
on a test filesystem out of space, not on the mount operations they
exercise.

KCSAN, on a separate build of the same series with KCSAN enabled at
runtime: 2520 reports over a 15 minute run, drained continuously so that
is every report rather than what happened to survive in the ring buffer,
while KCSAN's own counter went from 5174 to 10179 data races. None of
them names ve_owner, commit_tree(), ve_check_trusted_file(),
is_sb_ve_accessible() or mnt_ns_unlink(). The code carrying the new store
was hot and instrumented throughout: attach_recursive_mnt(), which calls
commit_tree(), appears in 201 stack traces, and 193 of the reports are
races in propagate_one() and propagate_mnt() beside it. Those are
pre-existing upstream races on inode and mount fields, not on ve_owner.

Not addressed here
==================

Patch 2 lets a container be pushed above sysctl_ve_mount_nr by mounts the
host gives it, and while over it the container's own mounts are refused
until the count drops. The default limit is 4096 so this takes an unusual
number of lent mounts, and the container can unmount them, but it is the
host's action that spends the container's budget. Enforcing the limit at
handover is possible, attach_recursive_mnt() already does the equivalent
for sysctl_mount_max in count_mounts(), but ve_mount_allowed() tests the
creating task's VE while the counter follows the owner, so the limit
currently has two meanings and picking one is a separate decision.

Mirian Shilakadze (3):
  ve/fs: unlink the mount namespace on the copy_mnt_ns() error path
  ve/fs: transfer mount ownership when a mount enters another VE
  ve/fs: take the owner of copied mounts from the namespace, not the task

 fs/mount.h     |  2 +-
 fs/namespace.c | 73 ++++++++++++++++++++++++++++++++++++++++++++------
 kernel/ve/ve.c |  6 ++++-
 3 files changed, 71 insertions(+), 10 deletions(-)

-- 
2.43.0

^ permalink raw reply	[relevance 3%]

* [Devel] [PATCH vz10 1/3] ve/fs: unlink the mount namespace on the copy_mnt_ns() error path
  2026-08-17  7:16  3% [Devel] [PATCH vz10 0/3] ve/fs: make mount ownership follow the mount namespace Mirian Shilakadze
@ 2026-08-17  7:16  6% ` Mirian Shilakadze
  2026-08-26 15:38  5%   ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
  2026-08-18 10:40  0% ` [Devel] [PATCH vz10 0/3] ve/fs: make mount ownership follow the mount namespace Vasileios Almpanis
  1 sibling, 1 reply; 119+ results
From: Mirian Shilakadze @ 2026-08-17  7:16 UTC (permalink / raw)


alloc_mnt_ns() does two things upstream does not: it links the namespace
onto all_mntns_list and takes a reference on its owning VE. Both are
undone in free_mnt_ns(), which holds the only list_del() of mntns_list
and the only put_ve(ns->ve_owner) in the file.

copy_mnt_ns() does not call it when copy_tree() fails. It open codes the
teardown and finishes with mnt_ns_release(), which drops the passive
count and kfree()s the namespace without unlinking it, so the namespace
is freed while all_mntns_list still points at it and the VE reference is
leaked. The next namespace creation runs list_add_tail() through the
dangling entry.

A container reaches this deterministically. alloc_vfsmnt() returns NULL
when !ve_mount_allowed(), that is when the VE is at sysctl_ve_mount_nr,
clone_mnt() turns that into -ENOMEM and copy_tree() propagates it. So a
container sitting at its own mount limit that calls unshare(CLONE_NEWNS)
takes the error path every time, with no memory pressure and nothing
beyond CAP_SYS_ADMIN in its own user namespace, and panics the host:

  list_add corruption. prev->next should be next (ffffffffa9ca77f0), but
  was ff2834a3cdfaeed0. (prev=ff2834a3cdfaeed0).
  kernel BUG at lib/list_debug.c:32!
  CPU: 94 UID: 0 PID: 7139 Comm: unshare ve: 900
   alloc_mnt_ns+0xd5/0x210
   copy_mnt_ns+0x82/0x3c0
   create_new_namespaces+0x5d/0x2f0
   unshare_nsproxy_namespaces+0x69/0xc0
   ksys_unshare+0x213/0x3f0

prev->next == prev is INIT_LIST_HEAD() on reallocated memory, the freed
namespace reused while the list still referenced it. CONFIG_DEBUG_LIST is
only what makes it a clean BUG, without it the same list_add_tail()
writes through the dangling pointer silently.

The path used to call free_mnt_ns() and was correct. Upstream replaced
that with the open coded sequence because free_mnt_ns() reaches
mnt_ns_tree_remove(), which rb_erase()s a node that copy_mnt_ns() has not
inserted yet, mnt_ns_tree_add() running only after the copy succeeds.
Upstream is unaffected by the replacement because its free_mnt_ns()
carries nothing else. Ours does.

So do not restore the free_mnt_ns() call, that would reintroduce the
rb_erase() upstream fixed. Split the part that is ours into
mnt_ns_unlink() and call it from both places, so a future addition to
namespace teardown has one home rather than two that can drift apart,
which is how this happened.

Fixes: 229fd15908fe ("fs: don't try and remove empty rbtree node")
Fixes: 1db60e545f65 ("ve/mntns: add ve_owner to struct mnt_namespace")
https://virtuozzo.atlassian.net/browse/VSTOR-141545
Feature: ve: ve generic structures
Signed-off-by: Mirian Shilakadze <mirian.shilakadze@virtuozzo.com>
---
 fs/namespace.c | 24 +++++++++++++++++++-----
 1 file changed, 19 insertions(+), 5 deletions(-)

diff --git a/fs/namespace.c b/fs/namespace.c
index 4d4dc5290350..7e27537dcdaf 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -4243,17 +4243,30 @@ static void dec_mnt_namespaces(struct ucounts *ucounts)
 static LIST_HEAD(all_mntns_list);
 static DEFINE_SPINLOCK(all_mntns_list_lock);
 
-static void free_mnt_ns(struct mnt_namespace *ns)
+/*
+ * Undo the bookkeeping alloc_mnt_ns() sets up beyond what upstream does: the
+ * entry on all_mntns_list and the reference on the owning VE.
+ *
+ * Kept separate from free_mnt_ns() because copy_mnt_ns() has to unwind a
+ * namespace that is not in mnt_ns_tree yet, so it cannot use free_mnt_ns()
+ * without rb_erase()ing a node that was never inserted.
+ */
+static void mnt_ns_unlink(struct mnt_namespace *ns)
 {
-	if (!is_anon_ns(ns))
-		ns_free_inum(&ns->ns);
-	dec_mnt_namespaces(ns->ucounts);
-
 	spin_lock(&all_mntns_list_lock);
 	list_del(&ns->mntns_list);
 	spin_unlock(&all_mntns_list_lock);
 
 	put_ve(ns->ve_owner);
+}
+
+static void free_mnt_ns(struct mnt_namespace *ns)
+{
+	if (!is_anon_ns(ns))
+		ns_free_inum(&ns->ns);
+	dec_mnt_namespaces(ns->ucounts);
+
+	mnt_ns_unlink(ns);
 
 	mnt_ns_tree_remove(ns);
 }
@@ -4347,6 +4360,7 @@ struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns,
 		namespace_unlock();
 		ns_free_inum(&new_ns->ns);
 		dec_mnt_namespaces(new_ns->ucounts);
+		mnt_ns_unlink(new_ns);
 		mnt_ns_release(new_ns);
 		return ERR_CAST(new);
 	}
-- 
2.43.0


^ permalink raw reply	[relevance 6%]

* Re: [Devel] [PATCH vz10 0/3] ve/fs: make mount ownership follow the mount namespace
  2026-08-17  7:16  3% [Devel] [PATCH vz10 0/3] ve/fs: make mount ownership follow the mount namespace Mirian Shilakadze
  2026-08-17  7:16  6% ` [Devel] [PATCH vz10 1/3] ve/fs: unlink the mount namespace on the copy_mnt_ns() error path Mirian Shilakadze
@ 2026-08-18 10:40  0% ` Vasileios Almpanis
  1 sibling, 0 replies; 119+ results
From: Vasileios Almpanis @ 2026-08-18 10:40 UTC (permalink / raw)


> mnt->ve_owner is meant to say which VE a mount belongs to. It is read by
> ve_check_trusted_file(), which stops ve0 executing content a container
> could have written, and by the per-VE mount accounting behind ve.mnt_nr.
> 
> It is wrong in two different ways, and each one alone defeats the trusted
> exec check. Fixing the second exposed a third bug, a host panic, which is
> patch 1 and the reason this is one series rather than two.
> 
> Patch 1 fixes the panic. alloc_mnt_ns() links every namespace onto
> all_mntns_list and takes a reference on its owning VE, and both are undone
> only in free_mnt_ns(). copy_mnt_ns()'s copy_tree() failure path does not
> call it, so it frees a namespace that is still linked and leaks the VE
> reference, and the next namespace creation runs list_add_tail() through
> the dangling entry. A container at its own sysctl_ve_mount_nr limit
> calling unshare(CLONE_NEWNS) reaches that path deterministically and
> panics the host. This is VSTOR-141545.
> 
> Patch 2 fixes ownership not being updated when a mount moves. ve_owner is
> assigned once in ve_mount_nr_inc() from alloc_vfsmnt() and never changes,
> so a mount the host hands to a container keeps ve_owner == ve0 while
> living in the container's mount namespace, and a host tmpfs bindmounted
> into a container is trusted even though the container can write to it.
> This is VSTOR-141322.
> 
> Patch 3 fixes ownership being wrong at creation. A new mount takes its
> owner from get_exec_env(), the VE of the task, rather than the VE of the
> mount namespace being worked in. Those differ for a ve0 task that entered
> a container's mount namespace with nsenter -m. copy_mnt_ns() and
> open_detached_copy() both build mounts that way and neither reaches
> commit_tree(), so a plain unshare(CLONE_NEWNS) or an
> open_tree(OPEN_TREE_CLONE) hands back ve0 owned copies of container
> content. This is VSTOR-141429.
> 
> Patches 2 and 3 apply the same rule, the one already used when a namespace
> is copied at container creation: ownership comes from the mount namespace,
> never from the calling task.
> 
Generally LGTM. The only problematic scenario I see now that we gate
host execution based on namespace is that we could have tmpfs with two
mounts one in host and one in CT. CT can still tamper with things and on
the fs and ve_check_trusted_file will still return true allowing us to
execute on host. I don't thing this should be covered in this series as its
irrelevant I just wanted to mention it in case other reviewers think we
need to do something about it. If deemed necessary we could solve it
in O(1) time, by using some superblock flag and checking against that
in ve_check_trusted_file.
> Testing
> =======
> 
> Built on 6.12.0-211.39.1.16.4.vz10, debug flavour, with KASAN, lockdep,
> PROVE_LOCKING, DEBUG_ATOMIC_SLEEP, DEBUG_LIST, DEBUG_VM and gcov on
> fs/namespace.o. Also compiled with CONFIG_VE=n. Each commit builds
> standalone, so bisect is safe.
> 
> Before and after on the same host, same commands. Stock
> 6.12.0-211.30.1.14.4.vz10 on the left, the series on the right:
> 
>   container at its mount limit, unshare(CLONE_NEWNS)  PANIC    -> survives
>   CT tmpfs, plain nsenter (control)                   refused  -> refused
>   nsenter + unshare -m                                EXECUTED -> refused
>   nsenter + open_tree + execveat                      EXECUTED -> refused
>   host tmpfs lent via --bindmount_add                 EXECUTED -> refused
> 
> The panic was captured on stock over netconsole: "list_add corruption ...
> kernel BUG at lib/list_debug.c:32", Comm: unshare, ve: 900, trace
> alloc_mnt_ns <- copy_mnt_ns <- unshare. Against the series the identical
> sequence, 150 container unshares at the limit followed by 300 host
> namespace creations, completes with no corruption and no dump. The
> control case behaving the same on both kernels shows the difference is
> the change and not the environment.
> 
> No KASAN, lockdep, atomic sleep, refcount or list corruption reports
> across any of it. DEBUG_ATOMIC_SLEEP staying quiet covers the one thing
> worth asking about in patch 2, that get_ve()/put_ve() are called under
> lock_mount_hash().
> 
> ve.mnt_nr drifted by 0 over 15 bindmount add and remove cycles, and
> nr_dying_descendants moved by 1 across 150 opportunities to leak a VE
> reference, so patch 1 releases the reference rather than merely not
> crashing.
> 
> Coverage from gcov, so the new code is known to have run rather than just
> linked: ve_mount_reown() called 54300 times with the ownership transfer
> branch taken 958 times, and both commit_tree() call sites exercised,
> 20024 for the moved tree and 24081 for the propagation loop.
> 
> selftests: mount 2/2, mount_setattr 21/21, ve_perms 14/14, ve_ns_owner
> 2/2. The last one matters most, it asserts ve.mnt_nr behaviour around
> CLONE_NEWVE, which is the line patch 3 changes.
> 
> vzctl functional suite: 538 of 603 passed. About 25 of the failures are
> vzctl returning exit 21 where the suite asserts 20 for an unrecognized
> option, which never reaches the kernel. The other ten were re-run one at
> a time: two passed, and the rest fail in container creation or disk setup
> on a test filesystem out of space, not on the mount operations they
> exercise.
> 
> KCSAN, on a separate build of the same series with KCSAN enabled at
> runtime: 2520 reports over a 15 minute run, drained continuously so that
> is every report rather than what happened to survive in the ring buffer,
> while KCSAN's own counter went from 5174 to 10179 data races. None of
> them names ve_owner, commit_tree(), ve_check_trusted_file(),
> is_sb_ve_accessible() or mnt_ns_unlink(). The code carrying the new store
> was hot and instrumented throughout: attach_recursive_mnt(), which calls
> commit_tree(), appears in 201 stack traces, and 193 of the reports are
> races in propagate_one() and propagate_mnt() beside it. Those are
> pre-existing upstream races on inode and mount fields, not on ve_owner.
> 
> Not addressed here
> ==================
> 
> Patch 2 lets a container be pushed above sysctl_ve_mount_nr by mounts the
> host gives it, and while over it the container's own mounts are refused
> until the count drops. The default limit is 4096 so this takes an unusual
> number of lent mounts, and the container can unmount them, but it is the
> host's action that spends the container's budget. Enforcing the limit at
> handover is possible, attach_recursive_mnt() already does the equivalent
> for sysctl_mount_max in count_mounts(), but ve_mount_allowed() tests the
> creating task's VE while the counter follows the owner, so the limit
> currently has two meanings and picking one is a separate decision.
> 
> Mirian Shilakadze (3):
>   ve/fs: unlink the mount namespace on the copy_mnt_ns() error path
>   ve/fs: transfer mount ownership when a mount enters another VE
>   ve/fs: take the owner of copied mounts from the namespace, not the task
> 
>  fs/mount.h     |  2 +-
>  fs/namespace.c | 73 ++++++++++++++++++++++++++++++++++++++++++++------
>  kernel/ve/ve.c |  6 ++++-
>  3 files changed, 71 insertions(+), 10 deletions(-)
> 
> --
> 2.43.0
> _______________________________________________
> Devel mailing list
> Devel at openvz.org
> https://lists.openvz.org/mailman/listinfo/devel

Reviewed-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>

-- 
Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>

^ permalink raw reply	[relevance 0%]

* [Devel] [PATCH VZ10 5/5] drivers/vhost/blk: rework queue/backend setup
  @ 2026-08-18 15:09  9% ` Andrey Zhadchenko
  0 siblings, 0 replies; 119+ results
From: Andrey Zhadchenko @ 2026-08-18 15:09 UTC (permalink / raw)


vhost_blk_setup() is pretty bad: silently refusing changed vq->num
if requests are already allocated, fetching user input second time
(double-fetch vulnerability).
To handle this, tie request allocation to backend existence. After
all, if there is no backend, there is no point in having requests.
Also expand it to get rid of boilerplate drop_backend, flush,
fput sequence in a few places.

https://virtuozzo.atlassian.net/browse/VSTOR-138640
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
---
 drivers/vhost/blk.c | 126 +++++++++++++++++++-------------------------
 1 file changed, 55 insertions(+), 71 deletions(-)

diff --git a/drivers/vhost/blk.c b/drivers/vhost/blk.c
index 2f94c987c62d8..fd12d4ee317a0 100644
--- a/drivers/vhost/blk.c
+++ b/drivers/vhost/blk.c
@@ -653,11 +653,14 @@ static void vhost_blk_flush(struct vhost_blk *blk)
 	spin_unlock(&blk->flush_lock);
 }
 
-static inline void vhost_blk_drop_backends(struct vhost_blk *blk)
+static void vhost_blk_drop_backend(struct vhost_blk *blk)
 {
 	struct vhost_virtqueue *vq;
 	int i;
 
+	if (!blk->backend)
+		return;
+
 	for (i = 0; i < VHOST_BLK_VQ_MAX; i++) {
 		vq = &blk->vqs[i].vq;
 
@@ -665,6 +668,45 @@ static inline void vhost_blk_drop_backends(struct vhost_blk *blk)
 		vhost_vq_set_backend(vq, NULL);
 		mutex_unlock(&vq->mutex);
 	}
+
+	vhost_blk_flush(blk);
+
+	for (i = 0; i < VHOST_BLK_VQ_MAX; i++) {
+		kvfree(blk->vqs[i].req);
+		blk->vqs[i].req = NULL;
+	}
+
+	fput(blk->backend);
+	blk->backend = NULL;
+}
+
+static int vhost_blk_setup_vqs(struct vhost_blk *blk)
+{
+	struct vhost_virtqueue *vq;
+	int i;
+
+	for (i = 0; i < VHOST_BLK_VQ_MAX; i++) {
+		vq = &blk->vqs[i].vq;
+
+		if (!vhost_vq_is_setup(vq))
+			continue;
+
+		blk->vqs[i].req = kvmalloc_array(vq->num, sizeof(struct vhost_blk_req),
+						 GFP_KERNEL);
+		if (!blk->vqs[i].req)
+			return -ENOMEM;
+
+		mutex_lock(&vq->mutex);
+		vhost_vq_set_backend(vq, blk->backend);
+		if (vhost_vq_init_access(vq)) {
+			mutex_unlock(&vq->mutex);
+			return -EFAULT;
+		}
+		mutex_unlock(&vq->mutex);
+
+	}
+
+	return 0;
 }
 
 static int vhost_blk_open(struct inode *inode, struct file *file)
@@ -717,16 +759,10 @@ static int vhost_blk_open(struct inode *inode, struct file *file)
 static int vhost_blk_release(struct inode *inode, struct file *f)
 {
 	struct vhost_blk *blk = f->private_data;
-	int i;
 
-	vhost_blk_drop_backends(blk);
-	vhost_blk_flush(blk);
+	vhost_blk_drop_backend(blk);
 	vhost_dev_stop(&blk->dev);
-	if (blk->backend)
-		fput(blk->backend);
 	vhost_dev_cleanup(&blk->dev);
-	for (i = 0; i < VHOST_BLK_VQ_MAX; i++)
-		kvfree(blk->vqs[i].req);
 	kfree(blk->dev.vqs);
 	kvfree(blk);
 
@@ -760,32 +796,19 @@ static int vhost_blk_set_features(struct vhost_blk *blk, u64 features)
 
 static long vhost_blk_set_backend(struct vhost_blk *blk, int fd)
 {
-	struct vhost_virtqueue *vq;
 	struct file *file;
 	struct inode *inode;
-	int ret, i;
+	int ret;
 
 	mutex_lock(&blk->dev.mutex);
 	ret = vhost_dev_check_owner(&blk->dev);
 	if (ret)
 		goto out_dev;
 
-	/*
-	 * fd < 0 means "stop the device".  Detach the backend from every vq so
-	 * vhost_blk_handle_guest_kick() stops fetching descriptors, drain the
-	 * in-flight requests, and release the backing file.
-	 */
+	/* fd < 0 means "stop the device" */
 	if (fd < 0) {
-		if (!blk->backend) {
-			ret = 0;		/* already stopped */
-			goto out_dev;
-		}
-		vhost_blk_drop_backends(blk);
-		vhost_blk_flush(blk);
-		fput(blk->backend);
-		blk->backend = NULL;
 		ret = 0;
-		goto out_dev;
+		goto out_drop;
 	}
 
 	if (blk->backend) {
@@ -802,31 +825,21 @@ static long vhost_blk_set_backend(struct vhost_blk *blk, int fd)
 	inode = file->f_mapping->host;
 	if (!S_ISBLK(inode->i_mode)) {
 		ret = -EFAULT;
-		goto out_file;
+		fput(file);
+		goto out_dev;
 	}
 
-	for (i = 0; i < VHOST_BLK_VQ_MAX; i++) {
-		vq = &blk->vqs[i].vq;
-		if (!vhost_vq_access_ok(vq)) {
-			ret = -EFAULT;
-			goto out_drop;
-		}
-
-		mutex_lock(&vq->mutex);
-		vhost_vq_set_backend(vq, file);
-		ret = vhost_vq_init_access(vq);
-		mutex_unlock(&vq->mutex);
-	}
 
 	blk->backend = file;
+	ret = vhost_blk_setup_vqs(blk);
+	if (ret)
+		goto out_drop;
 
 	mutex_unlock(&blk->dev.mutex);
 	return 0;
 
 out_drop:
-	vhost_blk_drop_backends(blk);
-out_file:
-	fput(file);
+	vhost_blk_drop_backend(blk);
 out_dev:
 	mutex_unlock(&blk->dev.mutex);
 	return ret;
@@ -835,7 +848,7 @@ static long vhost_blk_set_backend(struct vhost_blk *blk, int fd)
 static long vhost_blk_reset_owner(struct vhost_blk *blk)
 {
 	struct vhost_iotlb *umem;
-	int err, i;
+	int err;
 
 	mutex_lock(&blk->dev.mutex);
 	err = vhost_dev_check_owner(&blk->dev);
@@ -846,42 +859,15 @@ static long vhost_blk_reset_owner(struct vhost_blk *blk)
 		err = -ENOMEM;
 		goto done;
 	}
-	vhost_blk_drop_backends(blk);
-	if (blk->backend) {
-		fput(blk->backend);
-		blk->backend = NULL;
-	}
-	vhost_blk_flush(blk);
+	vhost_blk_drop_backend(blk);
 	vhost_dev_stop(&blk->dev);
 	vhost_dev_reset_owner(&blk->dev, umem);
 
-	for (i = 0; i < VHOST_BLK_VQ_MAX; i++) {
-		kvfree(blk->vqs[i].req);
-		blk->vqs[i].req = NULL;
-	}
-
 done:
 	mutex_unlock(&blk->dev.mutex);
 	return err;
 }
 
-static int vhost_blk_setup(struct vhost_blk *blk, void __user *argp)
-{
-	struct vhost_vring_state s;
-
-	if (copy_from_user(&s, argp, sizeof(s)))
-		return -EFAULT;
-
-	if (blk->vqs[s.index].req)
-		return 0;
-
-	blk->vqs[s.index].req = kvmalloc(sizeof(struct vhost_blk_req) * s.num, GFP_KERNEL);
-	if (!blk->vqs[s.index].req)
-		return -ENOMEM;
-
-	return 0;
-}
-
 static long vhost_blk_ioctl(struct file *f, unsigned int ioctl,
 			    unsigned long arg)
 {
@@ -919,8 +905,6 @@ static long vhost_blk_ioctl(struct file *f, unsigned int ioctl,
 		ret = vhost_dev_ioctl(&blk->dev, ioctl, argp);
 		if (ret == -ENOIOCTLCMD)
 			ret = vhost_vring_ioctl(&blk->dev, ioctl, argp);
-		if (!ret && ioctl == VHOST_SET_VRING_NUM)
-			ret = vhost_blk_setup(blk, argp);
 		vhost_blk_flush(blk);
 		mutex_unlock(&blk->dev.mutex);
 		return ret;
-- 
2.43.5


^ permalink raw reply	[relevance 9%]

* Re: [Devel] [PATCH vz10 3/5] fixup! vhost/vsock: only refuse connection when guest has never been ready
       [not found]     ` <20260625181637.1555685-3-eva.kurchatova@virtuozzo.com>
@ 2026-08-18 16:53  5%   ` Konstantin Khorenko
  0 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-18 16:53 UTC (permalink / raw)


On 6/25/26 20:16, Eva Kurchatova wrote:
> From: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
> 
> Commit 4ff28534c799 ("ms/vhost/vsock: Refuse the connection immediately
> when guest isn't ready") added a check which immediately returns
> EHOSTUNREACH when the guest isn't ready yet.  Namely, we check that guest
> hasn't enabled the RX vq yet, i.e. virtio-vsock has never beed enabled.

s/beed/been/

> 
> However, the check also affects the transient state when backend is
> temporarily set to NULL during VHOST_VSOCK_SET_RUNNING(0).  Notably,
> this is the case with qemu-update operation, during which we perform
> VHOST_RESET_OWNER.  In this case sendmsg()/connect() on otherwise healthy
> connection gets EHOSTUNREACH.
> 
> Gate the fast-fail on a sticky started_once bit set in
> vhost_vsock_start() and never cleared.  Once the guest has brought
> up virtio-vsock at least once, a NULL backend means a transient stop
> window and the packet must be queued for vhost_vsock_start() to drain
> on re-attach.
> 
> Fixes: 4ff28534c799 ("ms/vhost/vsock: Refuse the connection immediately when guest isn't ready")
> https://virtuozzo.atlassian.net/browse/VSTOR-131956
> Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
> ---
>  drivers/vhost/vsock.c | 31 ++++++++++++++-----------------
>  1 file changed, 14 insertions(+), 17 deletions(-)
> 
> diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c
> index d4c3f94308db..f652f47956d7 100644
> --- a/drivers/vhost/vsock.c
> +++ b/drivers/vhost/vsock.c
> @@ -59,6 +59,7 @@ struct vhost_vsock {
>  
>  	u32 guest_cid;
>  	bool seqpacket_allow;
> +	bool started_once;	/* latched in vhost_vsock_start(); never cleared */
>  	bool cpr_paused;	/* between stop and next start; queues sends */
>  };

static int vhost_vsock_dev_open(struct inode *inode, struct file *file)
{
...
        vsock = kvmalloc(sizeof(*vsock), GFP_KERNEL | __GFP_RETRY_MAYFAIL);

So, not zeroing struct fields here on allocation.
.started_once is not directly initialized => can have any start value.

>  
> @@ -289,24 +290,17 @@ vhost_transport_send_pkt(struct sk_buff *skb, struct net *net)
>  		return -ENODEV;
>  	}
>  
> -	/* Fast-fail if the guest hasn't enabled the RX vq yet. Queuing the packet
> -	 * and making the caller wait is pointless: even if the guest manages to init
> -	 * within the timeout, it'll immediately reply with RST, because there's no
> -	 * listener on the port yet.
> -	 *


> -	 * vhost_vq_get_backend() without vq->mutex is acceptable here: locking
> -	 * the mutex would be too expensive in this hot path, and we already have
> -	 * all the outcomes covered: if the backend becomes NULL right after the check,
> -	 * vhost_transport_do_send_pkt() will check it under the mutex anyway.

Why you drop this part of the comment? It looks useful.

> +	/*
> +	 * Fast-fail only when the guest has never enabled virtio-vsock.
> +	 * Once it has, a NULL backend means a transient SET_RUNNING(0)
> +	 * window (e.g. VHOST_RESET_OWNER); the packet must be
> +	 * queued for vhost_vsock_start() to drain on re-attach.
>  	 */
> -	/* cpr_paused: queue across CPR; else NULL backend means not ready. */
> -	if (unlikely(!data_race(vhost_vq_get_backend(&vsock->vqs[VSOCK_VQ_RX])))) {
> -		smp_rmb();	/* pairs with smp_wmb() in start/drop_backends */
> -		if (!READ_ONCE(vsock->cpr_paused)) {

(kostja at f0)/git/vzkernel.vz10:git grep cpr_paused
drivers/vhost/vsock.c:  bool cpr_paused;        /* between stop and next start; queues sends */
drivers/vhost/vsock.c:  /* cpr_paused: queue across CPR; else NULL backend means not ready. */
drivers/vhost/vsock.c:          if (!READ_ONCE(vsock->cpr_paused)) {
drivers/vhost/vsock.c:  WRITE_ONCE(vsock->cpr_paused, false);
drivers/vhost/vsock.c:          WRITE_ONCE(vsock->cpr_paused, true);
drivers/vhost/vsock.c:  vsock->cpr_paused = false;

So you are dropping the only READ of this vsock->cpr_paused, so it's not needed anymore after this patch.

> -			rcu_read_unlock();
> -			kfree_skb(skb);
> -			return -EHOSTUNREACH;
> -		}
> +	if (unlikely(!READ_ONCE(vsock->started_once)) &&
> +	    !data_race(vhost_vq_get_backend(&vsock->vqs[VSOCK_VQ_RX]))) {
> +		rcu_read_unlock();
> +		kfree_skb(skb);
> +		return -EHOSTUNREACH;
>  	}
>  
>  	if (virtio_vsock_skb_reply(skb))
> @@ -637,6 +631,9 @@ static int vhost_vsock_start(struct vhost_vsock *vsock)
>  	 */
>  	vhost_vq_work_queue(&vsock->vqs[VSOCK_VQ_RX], &vsock->send_pkt_work);
>  
> +	/* See vhost_transport_send_pkt(); never cleared. */
> +	WRITE_ONCE(vsock->started_once, true);
> +
>  	mutex_unlock(&vsock->dev.mutex);
>  	return 0;
>  


^ permalink raw reply	[relevance 5%]

* [Devel] [PATCH VZ10 v6 3/9] ve/fs: Rework per-ve mount count
  @ 2026-08-19  9:07  3% ` Vladimir Riabchun
  2026-08-19 13:16  0%   ` Vasileios Almpanis
  2026-08-19  9:07  9% ` [Devel] [PATCH VZ10 v6 7/9] ve: Introduce per-VE failcount Vladimir Riabchun
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 119+ results
From: Vladimir Riabchun @ 2026-08-19  9:07 UTC (permalink / raw)


Previous approach with current mounts counter had an issue:
there was a gap between ve_mount_allowed check and ve_mount_nr_inc,
which could allow CT to have more mounts than expected.

Fix this by tracking the number of available mounts instead
of current ones. This also makes resources accounting
more consistent - we are using ***_avail_nr approach more.

One more issue with inconsistent ve value is fixed:
ve_mount_allowed always used ve from get_exec_env, but
ve_mount_nr_inc operated with owner_ve.
Now actual ve value is calculated in the beginning of alloc_vfsmnt.

To avoid incorrect accounting when is_pseudosuper is changed,
update avail_nr count without > 0 check if VE is ve0 or pseudosuper.
This also simplifies ve_mount_put, since increment is
now unconditional.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Feature: per-ve failcounters
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
 fs/namespace.c     | 71 +++++++++++++++++++++++++++-------------------
 include/linux/ve.h |  2 +-
 kernel/ve/ve.c     | 12 ++++----
 3 files changed, 49 insertions(+), 36 deletions(-)

diff --git a/fs/namespace.c b/fs/namespace.c
index 68e0efb73d7c..c9e2ab9b3b57 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -317,18 +317,21 @@ int mnt_get_count(struct mount *mnt)
 #endif
 }
 
-static inline int ve_mount_allowed(void);
-static inline void ve_mount_nr_inc(struct mount *mnt, struct ve_struct *ve);
-static inline void ve_mount_nr_dec(struct mount *mnt);
+static inline int ve_try_reserve_mount(struct ve_struct *ve);
+static inline void ve_mount_put(struct mount *mnt, struct ve_struct *ve);
 
 static struct mount *alloc_vfsmnt(const char *name, struct ve_struct *owner_ve)
 {
 	struct mount *mnt;
+	struct ve_struct *ve = owner_ve;
 
-	if (!ve_mount_allowed()) {
+	if (!ve)
+		ve = get_exec_env();
+
+	if (!ve_try_reserve_mount(ve)) {
 		pr_warn_ratelimited(
 			"CT#%s reached the limit on mounts.\n",
-			ve_name(get_exec_env()));
+			ve_name(ve));
 		return NULL;
 	}
 
@@ -336,6 +339,14 @@ static struct mount *alloc_vfsmnt(const char *name, struct ve_struct *owner_ve)
 	if (mnt) {
 		int err;
 
+#ifdef CONFIG_VE
+		/*
+		 * Got ve reference in ve_try_reserve_mount, set mnt ve data
+		 * here, so in case of error ve_mount_put sees correct info.
+		 */
+		mnt->ve_owner = ve;
+#endif
+
 		err = mnt_alloc_id(mnt);
 		if (err)
 			goto out_free_cache;
@@ -370,7 +381,8 @@ static struct mount *alloc_vfsmnt(const char *name, struct ve_struct *owner_ve)
 		INIT_LIST_HEAD(&mnt->mnt_umounting);
 		INIT_HLIST_HEAD(&mnt->mnt_stuck_children);
 		mnt->mnt.mnt_idmap = &nop_mnt_idmap;
-		ve_mount_nr_inc(mnt, owner_ve);
+	} else {
+		ve_mount_put(mnt, ve);
 	}
 	return mnt;
 
@@ -381,6 +393,8 @@ static struct mount *alloc_vfsmnt(const char *name, struct ve_struct *owner_ve)
 out_free_id:
 	mnt_free_id(mnt);
 out_free_cache:
+	/* Got ve reference in ve_try_reserve_mount */
+	ve_mount_put(mnt, ve);
 	kmem_cache_free(mnt_cache, mnt);
 	return NULL;
 }
@@ -750,7 +764,7 @@ int sb_prepare_remount_readonly(struct super_block *sb)
 static void free_vfsmnt(struct mount *mnt)
 {
 	mnt_idmap_put(mnt_idmap(&mnt->mnt));
-	ve_mount_nr_dec(mnt);
+	ve_mount_put(mnt, mnt->ve_owner);
 	kfree_const(mnt->mnt_devname);
 #ifdef CONFIG_SMP
 	free_percpu(mnt->mnt_pcp);
@@ -3205,7 +3219,7 @@ int ve_devmnt_process(struct ve_struct *ve, dev_t dev, void **data_pp, int remou
 		if (devmnt->dev == dev) {
 			err = ve_devmnt_check(data, devmnt->allowed_options);
 			/*
-			 * In case of @is_pseudouser set, ie restore procedure,
+			 * In case of @is_pseudosuper set, ie restore procedure,
 			 * we don't check for allowed options filtering, since
 			 * restore mode is special.
 			 */
@@ -3344,30 +3358,30 @@ int ve_devmnt_verify(struct ve_struct *ve, dev_t dev, char *opts, bool new_mount
 	return err;
 }
 
-static inline int ve_mount_allowed(void)
+static inline int ve_try_reserve_mount(struct ve_struct *ve)
 {
-	struct ve_struct *ve = get_exec_env();
-
-	return ve_is_super(ve) || ve->is_pseudosuper ||
-		atomic_read(&ve->mnt_nr) < (int)sysctl_ve_mount_nr;
-}
-
-static inline void ve_mount_nr_inc(struct mount *mnt, struct ve_struct *ve)
-{
-	if (!ve)
-		ve = get_exec_env();
+	int ret = ve_is_super(ve) || ve->is_pseudosuper;
+	/* Ignore limits in ve0 and pseudosuper cases, but still count. */
+	if (ret)
+		atomic_dec(&ve->mnt_avail_nr);
+	else
+		ret = atomic_dec_if_positive(&ve->mnt_avail_nr) >= 0;
 
-	mnt->ve_owner = get_ve(ve);
-	atomic_inc(&ve->mnt_nr);
+	if (ret)
+		get_ve(ve);
+	return ret;
 }
 
-static inline void ve_mount_nr_dec(struct mount *mnt)
+static inline void ve_mount_put(struct mount *mnt, struct ve_struct *ve)
 {
-	struct ve_struct *ve = mnt->ve_owner;
-
-	atomic_dec(&ve->mnt_nr);
+	/*
+	 * ve argument is needed to reuse this function in alloc_vfsmnt error path.
+	 * Other users should pass mnt->ve_owner value.
+	 */
+	atomic_inc(&ve->mnt_avail_nr);
 	put_ve(ve);
-	mnt->ve_owner = NULL;
+	if (mnt)
+		mnt->ve_owner = NULL;
 }
 
 bool is_sb_ve_accessible(struct ve_struct *ve, struct super_block *sb)
@@ -3389,9 +3403,8 @@ bool is_sb_ve_accessible(struct ve_struct *ve, struct super_block *sb)
 
 #else /* CONFIG_VE */
 
-static inline int ve_mount_allowed(void) { return 1; }
-static inline void ve_mount_nr_inc(struct mount *mnt, struct ve_struct *ve) { }
-static inline void ve_mount_nr_dec(struct mount *mnt) { }
+static inline int ve_try_reserve_mount(struct ve_struct *ve) { return 1; }
+static inline void ve_mount_put(struct mount *mnt, struct ve_struct *ve) { }
 #endif /* CONFIG_VE */
 
 /*
diff --git a/include/linux/ve.h b/include/linux/ve.h
index 3facbd1759df..cca0a2bc1aac 100644
--- a/include/linux/ve.h
+++ b/include/linux/ve.h
@@ -88,7 +88,7 @@ struct ve_struct {
 	atomic_t		nd_neigh_nr;
 	unsigned long		meminfo_val;
 
-	atomic_t		mnt_nr; /* number of present VE mounts */
+	atomic_t		mnt_avail_nr; /* number of available VE mounts */
 
 #ifdef CONFIG_COREDUMP
 	char			core_pattern[CORENAME_MAX_SIZE];
diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
index dffb35da22bd..42669a832993 100644
--- a/kernel/ve/ve.c
+++ b/kernel/ve/ve.c
@@ -81,7 +81,7 @@ struct ve_struct ve0 = {
 
 	.arp_neigh_nr		= ATOMIC_INIT(0),
 	.nd_neigh_nr		= ATOMIC_INIT(0),
-	.mnt_nr			= ATOMIC_INIT(0),
+	.mnt_avail_nr		= ATOMIC_INIT(INT_MAX),
 	.meminfo_val		= VE_MEMINFO_SYSTEM,
 	.umh_running_helpers	= ATOMIC_INIT(0),
 	.umh_helpers_waitq	= __WAIT_QUEUE_HEAD_INITIALIZER(ve0.umh_helpers_waitq),
@@ -778,7 +778,7 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
 
 	atomic_set(&ve->arp_neigh_nr, 0);
 	atomic_set(&ve->nd_neigh_nr, 0);
-	atomic_set(&ve->mnt_nr, 0);
+	atomic_set(&ve->mnt_avail_nr, sysctl_ve_mount_nr);
 
 #ifdef CONFIG_COREDUMP
 	strcpy(ve->core_pattern, "core");
@@ -1054,9 +1054,9 @@ static u64 ve_netns_avail_nr_read(struct cgroup_subsys_state *css, struct cftype
 	return atomic_read(&css_to_ve(css)->netns_avail_nr);
 }
 
-static u64 ve_mnt_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
+static s64 ve_mnt_avail_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
 {
-	return atomic_read(&css_to_ve(css)->mnt_nr);
+	return atomic_read(&css_to_ve(css)->mnt_avail_nr);
 }
 
 static u64 ve_netif_max_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
@@ -1616,8 +1616,8 @@ static struct cftype ve_cftypes[] = {
 		.read_u64		= ve_netns_avail_nr_read,
 	},
 	{
-		.name			= "mnt_nr",
-		.read_u64		= ve_mnt_nr_read,
+		.name			= "mnt_avail_nr",
+		.read_s64		= ve_mnt_avail_nr_read,
 	},
 	{
 		.name			= "netif_max_nr",
-- 
2.47.1


^ permalink raw reply	[relevance 3%]

* [Devel] [PATCH VZ10 v6 7/9] ve: Introduce per-VE failcount
    2026-08-19  9:07  3% ` [Devel] [PATCH VZ10 v6 3/9] ve/fs: Rework per-ve mount count Vladimir Riabchun
@ 2026-08-19  9:07  9% ` Vladimir Riabchun
  2026-08-19 13:16  0%   ` Vasileios Almpanis
  2026-08-19  9:07  7% ` [Devel] [PATCH VZ10 v6 8/9] selftests/ve: Add more helpers Vladimir Riabchun
  2026-08-19  9:07  6% ` [Devel] [PATCH VZ10 v6 9/9] selftests/ve: Add mount accounting selftest Vladimir Riabchun
  3 siblings, 1 reply; 119+ results
From: Vladimir Riabchun @ 2026-08-19  9:07 UTC (permalink / raw)


It may be useful to have a history of resource limit hits for every VE,
this may simplify debugging and provide some information about the
resources usage.

This information is provided by ve.failcount file, any write to it
resets all failcounts.

To add a new failcounter we need to create a new atomic_t field
name_failcount in ve structure and add a new VE_FC_ENTRY in
ve_failcounts array.

One change, unrelated to failcounts: aio fields are now initialized
in ve0.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Feature: per-ve failcounters
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
 fs/aio.c                 |  1 +
 fs/namespace.c           |  2 ++
 include/linux/ve.h       |  6 ++++
 kernel/bpf/syscall.c     |  1 +
 kernel/ve/ve.c           | 67 ++++++++++++++++++++++++++++++++++++++++
 net/core/dev.c           |  2 ++
 net/core/neighbour.c     |  1 +
 net/core/net_namespace.c |  4 ++-
 8 files changed, 83 insertions(+), 1 deletion(-)

diff --git a/fs/aio.c b/fs/aio.c
index cb63416af135..3fa07cc626f8 100644
--- a/fs/aio.c
+++ b/fs/aio.c
@@ -814,6 +814,7 @@ static struct kioctx *ioctx_alloc(unsigned nr_events)
 	spin_lock(&ve->aio_nr_lock);
 	if (ve->aio_nr + ctx->max_reqs > ve->aio_max_nr ||
 	    ve->aio_nr + ctx->max_reqs < ve->aio_nr) {
+		atomic_inc(&ve->aio_failcount);
 		spin_unlock(&ve->aio_nr_lock);
 		err = -EAGAIN;
 		goto err_ctx;
diff --git a/fs/namespace.c b/fs/namespace.c
index c9e2ab9b3b57..eeeb2f780e46 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -3369,6 +3369,8 @@ static inline int ve_try_reserve_mount(struct ve_struct *ve)
 
 	if (ret)
 		get_ve(ve);
+	else
+		atomic_inc(&ve->mnt_failcount);
 	return ret;
 }
 
diff --git a/include/linux/ve.h b/include/linux/ve.h
index 5687faad46ff..9e73527e970e 100644
--- a/include/linux/ve.h
+++ b/include/linux/ve.h
@@ -72,12 +72,15 @@ struct ve_struct {
 	struct kmapset_key	proc_perms_key;
 
 	atomic_t		netns_avail_nr;
+	atomic_t		netns_failcount;
 	int			netns_max_nr;
 
 	atomic_t		netif_avail_nr;
+	atomic_t		netif_failcount;
 	int			netif_max_nr;
 
 	atomic_t		bpf_prog_avail_nr;
+	atomic_t		bpf_prog_failcount;
 	int			bpf_prog_max_nr;
 
 	atomic64_t		_uevent_seqnum;
@@ -86,6 +89,7 @@ struct ve_struct {
 
 	atomic_t		arp_neigh_nr;
 	atomic_t		nd_neigh_nr;
+	atomic_t		neigh_tbl_failcount;
 	unsigned long		meminfo_val;
 
 	/*
@@ -94,6 +98,7 @@ struct ve_struct {
 	 * other containers.
 	 */
 	atomic_t		mnt_avail_nr; /* number of available VE mounts */
+	atomic_t		mnt_failcount;
 	int			mnt_max_nr;
 
 #ifdef CONFIG_COREDUMP
@@ -121,6 +126,7 @@ struct ve_struct {
 	spinlock_t		aio_nr_lock;
 	unsigned long		aio_nr;
 	unsigned long		aio_max_nr;
+	atomic_t		aio_failcount;
 #endif
 	struct vfsmount		*devtmpfs_mnt;
 };
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index c94d4240e3d3..9d57e7999ae0 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -2891,6 +2891,7 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
 	if (!bpf_cap && type == BPF_PROG_TYPE_CGROUP_DEVICE) {
 		load_ve = get_exec_env();
 		if (atomic_dec_if_positive(&load_ve->bpf_prog_avail_nr) < 0) {
+			atomic_inc(&load_ve->bpf_prog_failcount);
 			load_ve = NULL;
 			err = -ENOSPC;
 			goto put_token;
diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
index 0f02835765ff..55a83b0b5981 100644
--- a/kernel/ve/ve.c
+++ b/kernel/ve/ve.c
@@ -99,10 +99,13 @@ struct ve_struct ve0 = {
 	.features		= -1,
 	.sched_lat_ve.cur	= &ve0_lat_stats,
 	.netns_avail_nr		= ATOMIC_INIT(INT_MAX),
+	.netns_failcount	= ATOMIC_INIT(0),
 	.netns_max_nr		= INT_MAX,
 	.netif_avail_nr		= ATOMIC_INIT(INT_MAX),
+	.netif_failcount	= ATOMIC_INIT(0),
 	.netif_max_nr		= INT_MAX,
 	.bpf_prog_avail_nr	= ATOMIC_INIT(INT_MAX),
+	.bpf_prog_failcount	= ATOMIC_INIT(0),
 	.bpf_prog_max_nr	= INT_MAX,
 	.fsync_enable		= FSYNC_FILTERED,
 	._randomize_va_space	=
@@ -114,8 +117,16 @@ struct ve_struct ve0 = {
 
 	.arp_neigh_nr		= ATOMIC_INIT(0),
 	.nd_neigh_nr		= ATOMIC_INIT(0),
+	.neigh_tbl_failcount	= ATOMIC_INIT(0),
 	.mnt_avail_nr		= ATOMIC_INIT(INT_MAX),
 	.mnt_max_nr		= INT_MAX,
+	.mnt_failcount		= ATOMIC_INIT(0),
+#ifdef CONFIG_AIO
+	.aio_nr_lock		= __SPIN_LOCK_UNLOCKED(aio_nr_lock),
+	.aio_nr			= 0,
+	.aio_max_nr		= AIO_MAX_NR_DEFAULT,
+	.aio_failcount		= ATOMIC_INIT(0),
+#endif
 	.meminfo_val		= VE_MEMINFO_SYSTEM,
 	.umh_running_helpers	= ATOMIC_INIT(0),
 	.umh_helpers_waitq	= __WAIT_QUEUE_HEAD_INITIALIZER(ve0.umh_helpers_waitq),
@@ -780,12 +791,15 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
 	ve->fsync_enable = FSYNC_FILTERED;
 
 	atomic_set(&ve->netns_avail_nr, NETNS_MAX_NR_DEFAULT);
+	atomic_set(&ve->netns_failcount, 0);
 	ve->netns_max_nr = NETNS_MAX_NR_DEFAULT;
 
 	atomic_set(&ve->netif_avail_nr, NETIF_MAX_NR_DEFAULT);
+	atomic_set(&ve->netif_failcount, 0);
 	ve->netif_max_nr = NETIF_MAX_NR_DEFAULT;
 
 	atomic_set(&ve->bpf_prog_avail_nr, BPF_PROG_MAX_NR_DEFAULT);
+	atomic_set(&ve->bpf_prog_failcount, 0);
 	ve->bpf_prog_max_nr = BPF_PROG_MAX_NR_DEFAULT;
 
 	err = ve_log_init(ve);
@@ -812,7 +826,9 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
 
 	atomic_set(&ve->arp_neigh_nr, 0);
 	atomic_set(&ve->nd_neigh_nr, 0);
+	atomic_set(&ve->neigh_tbl_failcount, 0);
 	ve->mnt_max_nr = MNT_MAX_NR_DEFAULT;
+	atomic_set(&ve->mnt_failcount, 0);
 	atomic_set(&ve->mnt_avail_nr, MNT_MAX_NR_DEFAULT);
 
 #ifdef CONFIG_COREDUMP
@@ -825,6 +841,7 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
 	spin_lock_init(&ve->aio_nr_lock);
 	ve->aio_nr = 0;
 	ve->aio_max_nr = AIO_MAX_NR_DEFAULT;
+	atomic_set(&ve->aio_failcount, 0);
 #endif
 
 	return &ve->css;
@@ -1065,6 +1082,50 @@ VE_RESOURCE(mnt);
 VE_RESOURCE(netif);
 VE_RESOURCE(bpf_prog);
 
+static const struct ve_failcount_entry {
+	const char *name;
+	size_t offset;
+} ve_failcounts[] = {
+#define VE_FC_ENTRY(name) { #name, offsetof(struct ve_struct, name##_failcount) }
+	VE_FC_ENTRY(netns),
+	VE_FC_ENTRY(mnt),
+	VE_FC_ENTRY(netif),
+	VE_FC_ENTRY(bpf_prog),
+	VE_FC_ENTRY(neigh_tbl),
+#ifdef CONFIG_AIO
+	VE_FC_ENTRY(aio),
+#endif
+	{}
+};
+
+static int ve_failcount_read(struct seq_file *sf, void *v)
+{
+	struct ve_struct *ve = css_to_ve(seq_css(sf));
+	const struct ve_failcount_entry *entry;
+	atomic_t *fc;
+
+	for (entry = ve_failcounts; entry->name; entry++) {
+		fc = (void *)ve + entry->offset;
+		seq_printf(sf, "%s: %d\n", entry->name, atomic_read(fc));
+	}
+	return 0;
+}
+
+static ssize_t ve_failcount_write(struct kernfs_open_file *of, char *buf,
+				  size_t nbytes, loff_t off)
+{
+	struct ve_struct *ve = css_to_ve(of_css(of));
+	const struct ve_failcount_entry *entry;
+	atomic_t *fc;
+
+	for (entry = ve_failcounts; entry->name; entry++) {
+		fc = (void *)ve + entry->offset;
+		atomic_set(fc, 0);
+	}
+
+	return nbytes;
+}
+
 static int ve_os_release_read(struct seq_file *sf, void *v)
 {
 	struct cgroup_subsys_state *css = seq_css(sf);
@@ -1602,6 +1663,12 @@ static struct cftype ve_cftypes[] = {
 		.flags			= CFTYPE_NOT_ON_ROOT,
 		.write_u64		= ve_rpc_kill_write,
 	},
+	{
+		.name			= "failcount",
+		.flags			= CFTYPE_NOT_ON_ROOT,
+		.seq_show		= ve_failcount_read,
+		.write			= ve_failcount_write,
+	},
 	{ }
 };
 
diff --git a/net/core/dev.c b/net/core/dev.c
index c7dddb200489..05e0b9b6ba23 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -10997,6 +10997,7 @@ int register_netdevice(struct net_device *dev)
 
 	ret = -ENOMEM;
 	if (atomic_dec_if_positive(&net->owner_ve->netif_avail_nr) < 0) {
+		atomic_inc(&net->owner_ve->netif_failcount);
 		ve_pr_warn_ratelimited(VE_LOG_BOTH,
 			"CT%s: hits max number of network devices, "
 			"increase ve::netif_max_nr parameter\n",
@@ -12211,6 +12212,7 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net,
 
 	err = -ENOMEM;
 	if (atomic_dec_if_positive(&net->owner_ve->netif_avail_nr) < 0) {
+		atomic_inc(&net->owner_ve->netif_failcount);
 		ve_pr_warn_ratelimited(VE_LOG_BOTH,
 			"CT%s: hits max number of network devices, "
 			"increase ve::netif_max_nr parameter\n",
diff --git a/net/core/neighbour.c b/net/core/neighbour.c
index f90deb17fb25..57a49d9c98a7 100644
--- a/net/core/neighbour.c
+++ b/net/core/neighbour.c
@@ -520,6 +520,7 @@ static struct neighbour *neigh_alloc(struct neigh_table *tbl,
 	    (glob_entries >= READ_ONCE(tbl->gc_thresh2) &&
 	     time_after(now, READ_ONCE(tbl->last_flush) + 5 * HZ))) {
 		if (!neigh_forced_gc(tbl, ve) && entries >= gc_thresh3) {
+			atomic_inc(&ve->neigh_tbl_failcount);
 			net_info_ratelimited("%s: neighbor table overflow!\n",
 					     tbl->id);
 			NEIGH_CACHE_STAT_INC(tbl, table_fulls);
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index b3d54cad984a..9a3376d2682f 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -486,8 +486,10 @@ void net_drop_ns(void *p)
 #ifdef CONFIG_VE
 static int dec_netns_avail(struct ve_struct *ve)
 {
-	if (atomic_dec_if_positive(&ve->netns_avail_nr) < 0)
+	if (atomic_dec_if_positive(&ve->netns_avail_nr) < 0) {
+		atomic_inc(&ve->netns_failcount);
 		return -ENOSPC;
+	}
 	return 0;
 }
 
-- 
2.47.1


^ permalink raw reply	[relevance 9%]

* [Devel] [PATCH VZ10 v6 8/9] selftests/ve: Add more helpers
    2026-08-19  9:07  3% ` [Devel] [PATCH VZ10 v6 3/9] ve/fs: Rework per-ve mount count Vladimir Riabchun
  2026-08-19  9:07  9% ` [Devel] [PATCH VZ10 v6 7/9] ve: Introduce per-VE failcount Vladimir Riabchun
@ 2026-08-19  9:07  7% ` Vladimir Riabchun
  2026-08-19  9:07  6% ` [Devel] [PATCH VZ10 v6 9/9] selftests/ve: Add mount accounting selftest Vladimir Riabchun
  3 siblings, 0 replies; 119+ results
From: Vladimir Riabchun @ 2026-08-19  9:07 UTC (permalink / raw)


Some more read/write helpers may be useful.

Also, add a helper to execute functions in child process with
switched namespaces and cgroup.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Feature: per-ve failcounters
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
 tools/testing/selftests/ve/ve_selftest.h | 81 ++++++++++++++++++++++--
 1 file changed, 75 insertions(+), 6 deletions(-)

diff --git a/tools/testing/selftests/ve/ve_selftest.h b/tools/testing/selftests/ve/ve_selftest.h
index 69c0a52dd7ef..48bb7d1871bd 100644
--- a/tools/testing/selftests/ve/ve_selftest.h
+++ b/tools/testing/selftests/ve/ve_selftest.h
@@ -43,6 +43,14 @@ static inline int write_file_at(int dirfd, const char *path, const char *val)
 	return (ret == (int)len) ? 0 : -1;
 }
 
+static inline int write_u64_at(int dirfd, const char *path, unsigned long long val)
+{
+	char s[20];
+
+	snprintf(s, sizeof(s), "%llu", val);
+	return write_file_at(dirfd, path, s);
+}
+
 static inline int read_file_at(int dirfd, const char *path, char *buf,
 			       size_t buflen)
 {
@@ -73,19 +81,31 @@ static inline int read_u64_at(int dirfd, const char *path,
 			      unsigned long long *out)
 {
 	char buf[32] = {0}, *end;
-	int fd, ret;
+	int ret;
 
-	fd = openat(dirfd, path, O_RDONLY);
-	if (fd < 0)
+	ret = read_file_at(dirfd, path, buf, sizeof(buf));
+	if (ret <= 0)
 		return -1;
 
-	ret = read(fd, buf, sizeof(buf) - 1);
-	close(fd);
+	errno = 0;
+	*out = strtoull(buf, &end, 10);
+	if (errno || end == buf)
+		return -1;
+	return 0;
+}
+
+static inline int read_s32_at(int dirfd, const char *path,
+			      int *out)
+{
+	char buf[32] = {0}, *end;
+	int ret;
+
+	ret = read_file_at(dirfd, path, buf, sizeof(buf));
 	if (ret <= 0)
 		return -1;
 
 	errno = 0;
-	*out = strtoull(buf, &end, 10);
+	*out = strtol(buf, &end, 10);
 	if (errno || end == buf)
 		return -1;
 	return 0;
@@ -134,6 +154,55 @@ static inline int enter_cgroup(int cgv2_fd, int ctid)
 	return ret;
 }
 
+/*
+ * Run function in VE cgroup and new namespaces.
+ *
+ * Namespaces are provided via unshare_flags.
+ * CLONE_NEWVE flag is set by this function.
+ * Return values:
+ *  -  0 if function returns zero
+ *  - -1 if function returns negative value
+ *  -  1 if setup fails or function returns positive value
+ */
+static inline int run_in_ve(int cgv2_fd, int ctid, int unshare_flags,
+		int (*fn)(void *), void *arg)
+{
+	int status;
+	pid_t pid;
+
+	unshare_flags |= CLONE_NEWVE;
+	pid = fork();
+	if (pid < 0) {
+		fprintf(stderr, "%s: fork failed\n", __func__);
+		return 1;
+	}
+	if (pid == 0) {
+		int ret;
+
+		if (enter_cgroup(cgv2_fd, ctid) < 0) {
+			fprintf(stderr, "%s: enter_cgroup failed\n", __func__);
+			_exit(255);
+		}
+		if (unshare(unshare_flags) < 0) {
+			fprintf(stderr, "%s: unshare(%d) failed\n",
+				__func__, unshare_flags);
+			_exit(255);
+		}
+		ret = fn(arg);
+		if (ret < 0)
+			ret = 1;
+		else if (ret > 0)
+			ret = 255;
+		_exit(ret);
+	}
+	if (waitpid(pid, &status, 0) < 0 || !WIFEXITED(status) || WEXITSTATUS(status) == 255)
+		return 1;
+	if (WEXITSTATUS(status))
+		return -1;
+	return 0;
+
+}
+
 /*
  * Create a fresh VE cgroup at the first free id at or after @from and unhide
  * its ve.* control files. Return the new id, or -1.
-- 
2.47.1


^ permalink raw reply	[relevance 7%]

* [Devel] [PATCH VZ10 v6 9/9] selftests/ve: Add mount accounting selftest
                     ` (2 preceding siblings ...)
  2026-08-19  9:07  7% ` [Devel] [PATCH VZ10 v6 8/9] selftests/ve: Add more helpers Vladimir Riabchun
@ 2026-08-19  9:07  6% ` Vladimir Riabchun
  2026-08-19 13:16  0%   ` Vasileios Almpanis
  3 siblings, 1 reply; 119+ results
From: Vladimir Riabchun @ 2026-08-19  9:07 UTC (permalink / raw)


There are 6 test cases, covered in the new test:
1. Simple mount accouting correctness, just mount/umount.
2. Verification of correct limit hits and changes, including
   negative values.
3. Partial mounts test, when mount limit is hit in the middle
   of creation.
4. Test that enabled pseudosuper allows overuse.
5. Test that pseudosuper doesn't affect mount accounting.
6. Failcount feature verification.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Feature: per-ve failcounters
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
 tools/testing/selftests/ve/.gitignore         |   1 +
 tools/testing/selftests/ve/Makefile           |   1 +
 .../selftests/ve/ve_mount_accounting_test.c   | 421 ++++++++++++++++++
 3 files changed, 423 insertions(+)
 create mode 100644 tools/testing/selftests/ve/ve_mount_accounting_test.c

diff --git a/tools/testing/selftests/ve/.gitignore b/tools/testing/selftests/ve/.gitignore
index afa4c568c2c9..3df4d05888dc 100644
--- a/tools/testing/selftests/ve/.gitignore
+++ b/tools/testing/selftests/ve/.gitignore
@@ -1,2 +1,3 @@
 ve_ns_owner_test
 ve_perms_test
+ve_mount_accounting_test
diff --git a/tools/testing/selftests/ve/Makefile b/tools/testing/selftests/ve/Makefile
index ec40cbc7b3a1..c6efe7c4b4fb 100644
--- a/tools/testing/selftests/ve/Makefile
+++ b/tools/testing/selftests/ve/Makefile
@@ -4,5 +4,6 @@ CFLAGS += -g -Wall -O2
 
 TEST_GEN_PROGS += ve_ns_owner_test
 TEST_GEN_PROGS += ve_perms_test
+TEST_GEN_PROGS += ve_mount_accounting_test
 
 include ../lib.mk
diff --git a/tools/testing/selftests/ve/ve_mount_accounting_test.c b/tools/testing/selftests/ve/ve_mount_accounting_test.c
new file mode 100644
index 000000000000..a270a8cb8c51
--- /dev/null
+++ b/tools/testing/selftests/ve/ve_mount_accounting_test.c
@@ -0,0 +1,421 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ve_mount_accounting selftests
+ *
+ * Tests to check the correctness of mount accounting.
+ */
+#define _GNU_SOURCE
+#include <asm/unistd.h>
+#include <linux/sched.h>
+#include <linux/limits.h>
+#include <sys/wait.h>
+#include <sys/syscall.h>
+#include <sys/stat.h>
+#include <sys/mount.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "../kselftest_harness.h"
+#include "ve_selftest.h"
+
+#define TMP_DIR			"/ve-mnt-tmp/"
+#define VE_MOUNTS_MAX		128
+
+static int set_pseudosuper(int cgv2_fd, int ctid, int value)
+{
+	char path[64];
+
+	snprintf(path, sizeof(path), "%d/ve.pseudosuper", ctid);
+	return write_u64_at(cgv2_fd, path, value);
+}
+
+static int _create_mount(void *id_ptr)
+{
+	char path[PATH_MAX];
+	int id = *(int *)id_ptr, ret;
+
+	snprintf(path, sizeof(path), TMP_DIR "%d", id);
+
+	if (mkdir(path, 0755) < 0) {
+		fprintf(stderr, "Failed to create directory %s: %s\n", path, strerror(errno));
+		return -1;
+	}
+	ret = mount("tmpfs", path, "tmpfs", 0, "size=1M");
+	if (!ret)
+		return 0;
+	fprintf(stderr, "Failed to mount tmpfs to %s: %s\n", path, strerror(errno));
+
+	rmdir(path);
+	return ret;
+}
+
+static int create_mount(int cgv2_fd, int ctid, int id)
+{
+	int ret = run_in_ve(cgv2_fd, ctid, CLONE_NEWVE, _create_mount, &id);
+	/*
+	 * If mount fails, cleanup by free_vfsmnt will be called
+	 * via call_rcu, need to wait for update.
+	 */
+	sleep(1);
+	return ret;
+}
+
+static int _destroy_mount(void *id_ptr)
+{
+	char path[PATH_MAX];
+	struct stat st;
+	int id = *(int *)id_ptr;
+
+	snprintf(path, sizeof(path), TMP_DIR "%d", id);
+
+	if (stat(path, &st))
+		return 1;
+	if (umount(path)) {
+		fprintf(stderr, "failed to umount directory %s: %s\n", path, strerror(errno));
+		return -1;
+	}
+	if (rmdir(path)) {
+		fprintf(stderr, "failed to remove directory %s: %s\n", path, strerror(errno));
+		return -1;
+	}
+	return 0;
+}
+
+static int destroy_mount(int cgv2_fd, int ctid, int id)
+{
+	int ret = run_in_ve(cgv2_fd, ctid, CLONE_NEWVE, _destroy_mount, &id);
+	/* free_vfsmnt is called via call_rcu, need to wait for update */
+	sleep(1);
+	return ret;
+}
+
+#define MAX_MNT_ID 32
+
+static int get_free_mnt_id(void)
+{
+	int i;
+	struct stat st;
+	char path[PATH_MAX];
+
+	for (i = 0; i < MAX_MNT_ID; i++) {
+		snprintf(path, sizeof(path), TMP_DIR "%d", i);
+		if (stat(path, &st))
+			return i;
+	}
+	return -1;
+}
+
+static int get_mount_cost(int cgv2_fd, int ctid)
+{
+	int avail1, avail2, mnt_id;
+	char path[64];
+
+	mnt_id = get_free_mnt_id();
+
+	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", ctid);
+	if (mnt_id < 0 ||
+	    read_s32_at(cgv2_fd, path, &avail1) ||
+	    create_mount(cgv2_fd, ctid, mnt_id) ||
+	    read_s32_at(cgv2_fd, path, &avail2) ||
+	    destroy_mount(cgv2_fd, ctid, mnt_id))
+		return -1;
+
+	return avail1 - avail2;
+}
+
+/* Expect mount success and return new avail value */
+static int mount_and_get_avail(struct __test_metadata *_metadata,
+			int cgv2_fd, int ctid, int mnt_id)
+{
+	char path_avail[64];
+	int mnt_avail_nr;
+
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", ctid);
+
+	ASSERT_EQ(create_mount(cgv2_fd, ctid, mnt_id), 0);
+	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	return mnt_avail_nr;
+}
+
+/* Expect mount failure and ensure intact avail number */
+static void assert_mount_fails(struct __test_metadata *_metadata,
+			int cgv2_fd, int ctid, int mnt_id, int avail_count)
+{
+	char path_avail[64];
+	int mnt_avail_nr;
+
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", ctid);
+
+	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, avail_count);
+	ASSERT_LT(create_mount(cgv2_fd, ctid, mnt_id), 0);
+	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, avail_count);
+}
+
+FIXTURE(ve_mnt_acc)
+{
+	int cgv2_fd;
+	int ctid;
+};
+
+FIXTURE_SETUP(ve_mnt_acc)
+{
+	unsigned long long initial_mnt_avail_nr;
+	char path[64];
+
+	self->cgv2_fd = mount_cg2_fd();
+	ASSERT_GE(self->cgv2_fd, 0);
+	mkdir(TMP_DIR, 0755);
+
+	ASSERT_EQ(write_file_at(self->cgv2_fd, "cgroup.subtree_control",
+		  VE_CONTROLLERS), 0);
+
+	self->ctid = make_ve(self->cgv2_fd, CTID_MIN);
+	ASSERT_GE(self->ctid, 0);
+
+	snprintf(path, sizeof(path), "%d/ve.mnt_max_nr", self->ctid);
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path, VE_MOUNTS_MAX), 0);
+
+	/*
+	 * The new ve cgroup has not been entered by anything yet, so its
+	 * mnt_avail_nr counter should be VE_MOUNTS_MAX.
+	 */
+	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", self->ctid);
+	ASSERT_EQ(read_u64_at(self->cgv2_fd, path, &initial_mnt_avail_nr), 0);
+	ASSERT_EQ(initial_mnt_avail_nr, VE_MOUNTS_MAX);
+};
+
+FIXTURE_TEARDOWN(ve_mnt_acc)
+{
+	for (int i = 0; i < MAX_MNT_ID; i++)
+		_destroy_mount((void *)&i);
+
+	destroy_ve(self->cgv2_fd, self->ctid);
+	close(self->cgv2_fd);
+	rmdir(TMP_DIR);
+}
+
+/* Simple test to check mount/umount accounting correctness */
+TEST_F(ve_mnt_acc, mount_umount)
+{
+	int original_mnt_avail, mnt_avail_nr;
+	char path[64];
+
+	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", self->ctid);
+
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path, &original_mnt_avail), 0);
+
+	ASSERT_LT(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
+		  original_mnt_avail);
+
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, original_mnt_avail);
+}
+
+/* Test mount limit hits */
+TEST_F(ve_mnt_acc, hit_limits)
+{
+	int original_mnt_avail, mnt_avail_nr, mnt_cost;
+	int original_have_mnt;
+	char path_avail[64], path_max_nr[64];
+
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
+	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
+
+	mnt_cost = get_mount_cost(self->cgv2_fd, self->ctid);
+	ASSERT_GE(mnt_cost, 1);
+
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &original_mnt_avail), 0);
+	original_have_mnt = VE_MOUNTS_MAX - original_mnt_avail;
+
+	/* Step 1: reduce number of available mounts to mnt_cost */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 1 * mnt_cost), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, 1 * mnt_cost);
+
+	/* Step 2: do one mount, no mounts should be available */
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
+		  0);
+
+	/* Step 3: check that one more mount falils */
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 1, 0);
+
+	/* Step 4: increase mount limit a little bit, mount should still fail */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr,
+				original_have_mnt + 2 * mnt_cost - 1), 0);
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 1, mnt_cost - 1);
+
+	/* Step 5: increase by 1 and win now */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 2 * mnt_cost), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, mnt_cost);
+
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 1),
+		  0);
+
+	/* Step 6: reduce mnt_max_nr so we have more mounts than allowed */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 1 * mnt_cost), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, -1 * mnt_cost);
+
+	/* Step 7: try to do mount when avail < 0, ensure number is intact */
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, -1 * mnt_cost);
+
+	/* Step 8: remove one mount, check avail value update, mount should fail */
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, 0);
+
+	/* Step 9: remove one more mount and check that new mount succeeds */
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 1), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, 1 * mnt_cost);
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 2),
+		  0);
+
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 2), 0);
+}
+
+/*
+ * Mount propagation makes one mount cost more.
+ * This test check that if we run out or mounts in the middle of creating
+ * a new one, everything is restored smoothly and nothing leaks.
+ */
+TEST_F(ve_mnt_acc, partial_mounts)
+{
+	char path_avail[64], path_max_nr[64];
+	int mount_cost, i, orig_have, orig_mnt_avail;
+
+	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
+
+	mount_cost = get_mount_cost(self->cgv2_fd, self->ctid);
+	ASSERT_GE(mount_cost, 1);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &orig_mnt_avail), 0);
+	orig_have = VE_MOUNTS_MAX - orig_mnt_avail;
+
+	if (mount_cost == 1)
+		SKIP(return, "mount cost is 1, no partial mounts possible");
+
+	for (i = 0; i < mount_cost; i++) {
+		ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, orig_have + i), 0);
+		assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 0, i);
+	}
+}
+
+/* Test that pseudosuper allows negative avail with correct accounting. */
+TEST_F(ve_mnt_acc, pseudosuper_allows_overuse)
+{
+	int orig_mnt_avail, orig_have;
+	int mount_cost;
+	char path_avail[64], path_max_nr[64];
+
+	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &orig_mnt_avail), 0);
+	orig_have = VE_MOUNTS_MAX - orig_mnt_avail;
+
+	mount_cost = get_mount_cost(self->cgv2_fd, self->ctid);
+	ASSERT_GE(mount_cost, 1);
+
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, orig_have + mount_cost), 0);
+	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 1), 0);
+
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
+		  0);
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 1),
+		  -1 * mount_cost);
+
+	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 0), 0);
+
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, -1 * mount_cost);
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, 0);
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 1), 0);
+	ASSERT_EQ(get_mount_cost(self->cgv2_fd, self->ctid), mount_cost);
+}
+
+/* Test that pseudosuper doesn't disable accounting. */
+TEST_F(ve_mnt_acc, pseudosuper_continues_accounting)
+{
+	int orig_mnt_avail, mount_cost, mnt_avail_nr;
+	char path_avail[64];
+
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
+
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &orig_mnt_avail), 0);
+	mount_cost = get_mount_cost(self->cgv2_fd, self->ctid);
+	ASSERT_GE(mount_cost, 1);
+
+	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 0), 0);
+
+	/* mnt 0 - mounted without pseudosuper, umounted with it. */
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
+		  orig_mnt_avail - mount_cost);
+	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 1), 0);
+
+	/* Cost is the same when mount/umount happen under pseudosuper. */
+	ASSERT_EQ(get_mount_cost(self->cgv2_fd, self->ctid), mount_cost);
+
+	/* mnt 1 - mounted with pseudosuper, umounted without it. */
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 1),
+		  orig_mnt_avail - 2 * mount_cost);
+
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, orig_mnt_avail - mount_cost);
+
+	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 0), 0);
+
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 1), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, orig_mnt_avail);
+}
+
+/* Test failcount feature */
+TEST_F(ve_mnt_acc, failcount)
+{
+	char path_fc[64], failcount_str[512], path_max_nr[64];
+
+	snprintf(path_fc, sizeof(path_fc), "%d/ve.failcount", self->ctid);
+	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
+
+	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
+			       failcount_str, sizeof(failcount_str)), 0);
+	ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL);
+
+	/* Check successful mount doesn't affect failcount */
+	mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0);
+	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
+			       failcount_str, sizeof(failcount_str)), 0);
+	ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL);
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
+
+	/* Check failcount update when mount fails */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, 0), 0);
+	ASSERT_LT(create_mount(self->cgv2_fd, self->ctid, 1), 0);
+	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
+			       failcount_str, sizeof(failcount_str)), 0);
+	ASSERT_TRUE(strstr(failcount_str, "mnt: 1\n") != NULL);
+
+	/* Check failcount flush */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_fc, 0), 0);
+	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
+			       failcount_str, sizeof(failcount_str)), 0);
+	ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL);
+
+	/* Check failcount update when mount fails again */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, 0), 0);
+	ASSERT_LT(create_mount(self->cgv2_fd, self->ctid, 1), 0);
+	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
+			       failcount_str, sizeof(failcount_str)), 0);
+	ASSERT_TRUE(strstr(failcount_str, "mnt: 1\n") != NULL);
+}
+
+TEST_HARNESS_MAIN
-- 
2.47.1


^ permalink raw reply	[relevance 6%]

* Re: [Devel] [PATCH VZ10 v2 01/10] drivers/md/dm-qcow2: fix revert_cluster_alloc() for ext_l2 case
  @ 2026-08-19 10:27  5%   ` Pavel Tikhomirov
  0 siblings, 0 replies; 119+ results
From: Pavel Tikhomirov @ 2026-08-19 10:27 UTC (permalink / raw)




On 8/12/26 22:19, Andrey Zhadchenko wrote:
> This function walks over all changed u64 values in md. With ext_l2
> half of them holds subcluster description. Firstly it reverts these
> values to saved pe_page, which is fine, but then it tries to revert
> r1r2 changes. It makes no sense when the value is a subcluster
> description.
> Teach the function to skip subcluster descriptions: do it based on
> a new lx_level in struct wb_desc. Add new argument to
> prepare_l_entry_update() and set it there.
> The warning also could have tripped for ext_l2 entries, so drop
> it entirely.
> The function effectively reverts not only cluster alloc, so rename
> it to revert_l_entries_update() to mimic prepare_l_entries_update.

prepare_l_entrie(s)_update is probably a misspelled prepare_l_entry_update

> 
> Feature: dm-qcow2: block device over QCOW2 files driver
> https://virtuozzo.atlassian.net/browse/VSTOR-139406
> Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
> ---
>  drivers/md/dm-qcow2-map.c    | 28 ++++++++++++++++++----------
>  drivers/md/dm-qcow2-target.c |  2 +-
>  drivers/md/dm-qcow2.h        |  1 +
>  3 files changed, 20 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
> index db21efb45e17a..2cd8e174049f8 100644
> --- a/drivers/md/dm-qcow2-map.c
> +++ b/drivers/md/dm-qcow2-map.c
> @@ -1297,18 +1297,19 @@ static void do_md_page_read_complete(int ret, struct qcow2 *qcow2,
>  }
>  
>  /* Be careful with dirty_or_writeback()/etc! Check races. */
> -static void revert_clusters_alloc(struct qcow2 *qcow2, struct wb_desc *wbd)
> +static void revert_l_entries_update(struct qcow2 *qcow2, struct wb_desc *wbd)
>  {
>  	struct qcow2_map_item r1, r2;
>  	struct page *pe_page;
> +	bool skip_odd;
>  	u64 pos, old;
>  	int i, ret;
>  
> +	skip_odd = qcow2->ext_l2 && wbd->lx_level == L2_LEVEL;
> +
>  	lockdep_assert_held(&qcow2->md_pages_lock);
>  	for_each_set_bit(i, wbd->changed_indexes, LX_INDEXES_PER_PAGE) {
>  		pos = get_u64_from_be_page(wbd->md->page, i);
> -		WARN_ON_ONCE(!(pos & ~LX_REFCOUNT_EXACTLY_ONE) ||
> -			     !(pos & LX_REFCOUNT_EXACTLY_ONE));
>  
>  		/* Here we restore prealloced and compressed clu mappings */
>  		pe_page = wbd->pe_page;
> @@ -1321,6 +1322,9 @@ static void revert_clusters_alloc(struct qcow2 *qcow2, struct wb_desc *wbd)
>  		}
>  
>  		set_u64_to_be_page(wbd->md->page, i, 0);
> +		if (skip_odd && (i & 1))
> +			continue; /* pos contains ext_l2 part of L2 entry */
> +
>  		spin_unlock(&qcow2->md_pages_lock);
>  		pos &= ~LX_REFCOUNT_EXACTLY_ONE;
>  
> @@ -1369,7 +1373,7 @@ static void complete_wbd(struct qcow2 *qcow2, struct wb_desc *wbd)
>  		unsigned long flags;
>  
>  		spin_lock_irqsave(&qcow2->md_pages_lock, flags);
> -		revert_clusters_alloc(qcow2, wbd);
> +		revert_l_entries_update(qcow2, wbd);
>  		clear_writeback_status(qcow2, wbd->md, wbd->ret,
>  				       &wait_list, &end_list);
>  		spin_unlock_irqrestore(&qcow2->md_pages_lock, flags);
> @@ -2433,7 +2437,7 @@ static loff_t allocate_cluster(struct qcow2 *qcow2, struct qio *qio,
>  #define LU_IGN_CHANGED_IND	(1 << 3)
>  static int prepare_l_entry_update(struct qcow2 *qcow2, struct qio *qio,
>  				  struct md_page *md, u32 index_in_page,
> -				  u64 *pval, u32 arg_mask)
> +				  u64 *pval, u32 arg_mask, u8 lx_level)
>  {
>  	bool wants_pe_page = (arg_mask & LU_WANTS_PE_PAGE);
>  	struct wb_desc *new_wbd = NULL;
> @@ -2453,6 +2457,7 @@ static int prepare_l_entry_update(struct qcow2 *qcow2, struct qio *qio,
>  		if (!new_wbd)
>  			return -ENOMEM;
>  		new_wbd->md = md;
> +		new_wbd->lx_level = lx_level;
>  	} else if (wants_pe_page && !md->wbd->pe_page) {
>  		pe_page = alloc_page(GFP_NOIO|__GFP_ZERO);
>  		if (!pe_page)
> @@ -2515,7 +2520,8 @@ static int prepare_l1l2_allocation(struct qcow2 *qcow2, struct qio *qio,
>  		/* Allocate cluster for L2 entries, and prepare L1 update */
>  		ret = prepare_l_entry_update(qcow2, qio, map->l1.md,
>  					     map->l1.index_in_page, &val,
> -					     LU_SET_ONE_MASK|LU_WANTS_ALLOC);
> +					     LU_SET_ONE_MASK|LU_WANTS_ALLOC,
> +					     L1_LEVEL);
>  		if (ret <= 0)
>  			return ret;
>  
> @@ -2541,7 +2547,8 @@ static int prepare_l1l2_allocation(struct qcow2 *qcow2, struct qio *qio,
>  
>  		ret = prepare_l_entry_update(qcow2, qio, map->l2.md,
>  					     map->l2.index_in_page,
> -					     &map->data_clu_pos, arg_mask);
> +					     &map->data_clu_pos, arg_mask,
> +					     L2_LEVEL);
>  		if (ret <= 0)
>  			return ret;
>  
> @@ -2562,7 +2569,7 @@ static int prepare_l1l2_allocation(struct qcow2 *qcow2, struct qio *qio,
>  
>  	return prepare_l_entry_update(qcow2, qio, map->l2.md,
>  				      map->l2.index_in_page + 1,
> -				      &val, arg_mask);
> +				      &val, arg_mask, L2_LEVEL);
>  }
>  
>  /*
> @@ -3975,7 +3982,7 @@ static void process_cow_indexes_write(struct qcow2 *qcow2,
>  		ret = prepare_l_entry_update(qcow2, qio, lx_md,
>  					     ext->lx_index_in_page,
>  					     &ext->allocated_clu_pos,
> -					     arg_mask);
> +					     arg_mask, ext->cow_level);
>  		if (ret < 0) {
>  			qio->bi_status = errno_to_blk_status(ret);
>  			qio_endio(qio);
> @@ -3986,7 +3993,8 @@ static void process_cow_indexes_write(struct qcow2 *qcow2,
>  			arg_mask &= ~LU_SET_ONE_MASK;
>  			ret = prepare_l_entry_update(qcow2, qio, lx_md,
>  					     ext->lx_index_in_page + 1,
> -					   &ext->new_ext_l2, arg_mask);
> +					     &ext->new_ext_l2, arg_mask,
> +					     L2_LEVEL);
>  			WARN_ON_ONCE(ret < 0);
>  		}
>  
> diff --git a/drivers/md/dm-qcow2-target.c b/drivers/md/dm-qcow2-target.c
> index 3f65897ce9da2..0f1d3e3c5258e 100644
> --- a/drivers/md/dm-qcow2-target.c
> +++ b/drivers/md/dm-qcow2-target.c
> @@ -174,7 +174,7 @@ void qcow2_flush_deferred_activity(struct qcow2_target *tgt, struct qcow2 *qcow2
>  	int i;
>  
>  	/*
> -	 * We need second iteration, since revert_clusters_alloc()
> +	 * We need second iteration, since revert_l_entries_update()
>  	 * may start timer again after failed wb.
>  	 */
>  	for (i = 0; i < 2; i++) {
> diff --git a/drivers/md/dm-qcow2.h b/drivers/md/dm-qcow2.h
> index 86f0688e7345a..aa3487007523f 100644
> --- a/drivers/md/dm-qcow2.h
> +++ b/drivers/md/dm-qcow2.h
> @@ -118,6 +118,7 @@ struct wb_desc {
>  	struct list_head dependent_list;
>  	int nr_submitted;
>  	bool completed;
> +	u8 lx_level;
>  	int ret;
>  };
>  

-- 
Best regards, Pavel Tikhomirov
Senior Software Developer, Virtuozzo.


^ permalink raw reply	[relevance 5%]

* Re: [Devel] [PATCH VZ10 v6 7/9] ve: Introduce per-VE failcount
  2026-08-19  9:07  9% ` [Devel] [PATCH VZ10 v6 7/9] ve: Introduce per-VE failcount Vladimir Riabchun
@ 2026-08-19 13:16  0%   ` Vasileios Almpanis
  0 siblings, 0 replies; 119+ results
From: Vasileios Almpanis @ 2026-08-19 13:16 UTC (permalink / raw)


> It may be useful to have a history of resource limit hits for every VE,
> this may simplify debugging and provide some information about the
> resources usage.
> 
> This information is provided by ve.failcount file, any write to it
> resets all failcounts.
> 
> To add a new failcounter we need to create a new atomic_t field
> name_failcount in ve structure and add a new VE_FC_ENTRY in
> ve_failcounts array.
> 
> One change, unrelated to failcounts: aio fields are now initialized
> in ve0.
> 
> https://virtuozzo.atlassian.net/browse/VSTOR-135520
> 
> Feature: per-ve failcounters
> Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
>
> diff --git a/fs/aio.c b/fs/aio.c
> index cb63416af135..3fa07cc626f8 100644
> --- a/fs/aio.c
> +++ b/fs/aio.c
> @@ -814,6 +814,7 @@ static struct kioctx *ioctx_alloc(unsigned nr_events)
>  	spin_lock(&ve->aio_nr_lock);
>  	if (ve->aio_nr + ctx->max_reqs > ve->aio_max_nr ||
>  	    ve->aio_nr + ctx->max_reqs < ve->aio_nr) {
> +		atomic_inc(&ve->aio_failcount);
>  		spin_unlock(&ve->aio_nr_lock);
>  		err = -EAGAIN;
>  		goto err_ctx;
> diff --git a/fs/namespace.c b/fs/namespace.c
> index c9e2ab9b3b57..eeeb2f780e46 100644
> --- a/fs/namespace.c
> +++ b/fs/namespace.c
> @@ -3369,6 +3369,8 @@ static inline int ve_try_reserve_mount(struct ve_struct *ve)
>  
>  	if (ret)
>  		get_ve(ve);
> +	else
> +		atomic_inc(&ve->mnt_failcount);
>  	return ret;
>  }
>  
> diff --git a/include/linux/ve.h b/include/linux/ve.h
> index 5687faad46ff..9e73527e970e 100644
> --- a/include/linux/ve.h
> +++ b/include/linux/ve.h
> @@ -72,12 +72,15 @@ struct ve_struct {
>  	struct kmapset_key	proc_perms_key;
>  
>  	atomic_t		netns_avail_nr;
> +	atomic_t		netns_failcount;
>  	int			netns_max_nr;
>  
>  	atomic_t		netif_avail_nr;
> +	atomic_t		netif_failcount;
>  	int			netif_max_nr;
>  
>  	atomic_t		bpf_prog_avail_nr;
> +	atomic_t		bpf_prog_failcount;
>  	int			bpf_prog_max_nr;
>  
>  	atomic64_t		_uevent_seqnum;
> @@ -86,6 +89,7 @@ struct ve_struct {
>  
>  	atomic_t		arp_neigh_nr;
>  	atomic_t		nd_neigh_nr;
> +	atomic_t		neigh_tbl_failcount;
>  	unsigned long		meminfo_val;
>  
>  	/*
> @@ -94,6 +98,7 @@ struct ve_struct {
>  	 * other containers.
>  	 */
>  	atomic_t		mnt_avail_nr; /* number of available VE mounts */
> +	atomic_t		mnt_failcount;
>  	int			mnt_max_nr;
>  
>  #ifdef CONFIG_COREDUMP
> @@ -121,6 +126,7 @@ struct ve_struct {
>  	spinlock_t		aio_nr_lock;
>  	unsigned long		aio_nr;
>  	unsigned long		aio_max_nr;
> +	atomic_t		aio_failcount;
>  #endif
>  	struct vfsmount		*devtmpfs_mnt;
>  };
> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> index c94d4240e3d3..9d57e7999ae0 100644
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
> @@ -2891,6 +2891,7 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
>  	if (!bpf_cap && type == BPF_PROG_TYPE_CGROUP_DEVICE) {
>  		load_ve = get_exec_env();
>  		if (atomic_dec_if_positive(&load_ve->bpf_prog_avail_nr) < 0) {
> +			atomic_inc(&load_ve->bpf_prog_failcount);
>  			load_ve = NULL;
>  			err = -ENOSPC;
>  			goto put_token;
> diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
> index 0f02835765ff..55a83b0b5981 100644
> --- a/kernel/ve/ve.c
> +++ b/kernel/ve/ve.c
> @@ -99,10 +99,13 @@ struct ve_struct ve0 = {
>  	.features		= -1,
>  	.sched_lat_ve.cur	= &ve0_lat_stats,
>  	.netns_avail_nr		= ATOMIC_INIT(INT_MAX),
> +	.netns_failcount	= ATOMIC_INIT(0),
>  	.netns_max_nr		= INT_MAX,
>  	.netif_avail_nr		= ATOMIC_INIT(INT_MAX),
> +	.netif_failcount	= ATOMIC_INIT(0),
>  	.netif_max_nr		= INT_MAX,
>  	.bpf_prog_avail_nr	= ATOMIC_INIT(INT_MAX),
> +	.bpf_prog_failcount	= ATOMIC_INIT(0),
>  	.bpf_prog_max_nr	= INT_MAX,
>  	.fsync_enable		= FSYNC_FILTERED,
>  	._randomize_va_space	=
> @@ -114,8 +117,16 @@ struct ve_struct ve0 = {
>  
>  	.arp_neigh_nr		= ATOMIC_INIT(0),
>  	.nd_neigh_nr		= ATOMIC_INIT(0),
> +	.neigh_tbl_failcount	= ATOMIC_INIT(0),
>  	.mnt_avail_nr		= ATOMIC_INIT(INT_MAX),
>  	.mnt_max_nr		= INT_MAX,
> +	.mnt_failcount		= ATOMIC_INIT(0),
> +#ifdef CONFIG_AIO
> +	.aio_nr_lock		= __SPIN_LOCK_UNLOCKED(aio_nr_lock),
> +	.aio_nr			= 0,
> +	.aio_max_nr		= AIO_MAX_NR_DEFAULT,
> +	.aio_failcount		= ATOMIC_INIT(0),
> +#endif
>  	.meminfo_val		= VE_MEMINFO_SYSTEM,
>  	.umh_running_helpers	= ATOMIC_INIT(0),
>  	.umh_helpers_waitq	= __WAIT_QUEUE_HEAD_INITIALIZER(ve0.umh_helpers_waitq),
> @@ -780,12 +791,15 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
>  	ve->fsync_enable = FSYNC_FILTERED;
>  
>  	atomic_set(&ve->netns_avail_nr, NETNS_MAX_NR_DEFAULT);
> +	atomic_set(&ve->netns_failcount, 0);
>  	ve->netns_max_nr = NETNS_MAX_NR_DEFAULT;
>  
>  	atomic_set(&ve->netif_avail_nr, NETIF_MAX_NR_DEFAULT);
> +	atomic_set(&ve->netif_failcount, 0);
>  	ve->netif_max_nr = NETIF_MAX_NR_DEFAULT;
>  
>  	atomic_set(&ve->bpf_prog_avail_nr, BPF_PROG_MAX_NR_DEFAULT);
> +	atomic_set(&ve->bpf_prog_failcount, 0);
>  	ve->bpf_prog_max_nr = BPF_PROG_MAX_NR_DEFAULT;
>  
>  	err = ve_log_init(ve);
> @@ -812,7 +826,9 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
>  
>  	atomic_set(&ve->arp_neigh_nr, 0);
>  	atomic_set(&ve->nd_neigh_nr, 0);
> +	atomic_set(&ve->neigh_tbl_failcount, 0);
>  	ve->mnt_max_nr = MNT_MAX_NR_DEFAULT;
> +	atomic_set(&ve->mnt_failcount, 0);
>  	atomic_set(&ve->mnt_avail_nr, MNT_MAX_NR_DEFAULT);
>  
>  #ifdef CONFIG_COREDUMP
> @@ -825,6 +841,7 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
>  	spin_lock_init(&ve->aio_nr_lock);
>  	ve->aio_nr = 0;
>  	ve->aio_max_nr = AIO_MAX_NR_DEFAULT;
> +	atomic_set(&ve->aio_failcount, 0);
>  #endif
>  
>  	return &ve->css;
> @@ -1065,6 +1082,50 @@ VE_RESOURCE(mnt);
>  VE_RESOURCE(netif);
>  VE_RESOURCE(bpf_prog);
>  
> +static const struct ve_failcount_entry {
> +	const char *name;
> +	size_t offset;
> +} ve_failcounts[] = {
> +#define VE_FC_ENTRY(name) { #name, offsetof(struct ve_struct, name##_failcount) }
> +	VE_FC_ENTRY(netns),
> +	VE_FC_ENTRY(mnt),
> +	VE_FC_ENTRY(netif),
> +	VE_FC_ENTRY(bpf_prog),
> +	VE_FC_ENTRY(neigh_tbl),
> +#ifdef CONFIG_AIO
> +	VE_FC_ENTRY(aio),
> +#endif
> +	{}
> +};
> +
> +static int ve_failcount_read(struct seq_file *sf, void *v)
> +{
> +	struct ve_struct *ve = css_to_ve(seq_css(sf));
> +	const struct ve_failcount_entry *entry;
> +	atomic_t *fc;
> +
> +	for (entry = ve_failcounts; entry->name; entry++) {
> +		fc = (void *)ve + entry->offset;
> +		seq_printf(sf, "%s: %d\n", entry->name, atomic_read(fc));
> +	}
> +	return 0;
> +}
> +
> +static ssize_t ve_failcount_write(struct kernfs_open_file *of, char *buf,
> +				  size_t nbytes, loff_t off)
> +{
> +	struct ve_struct *ve = css_to_ve(of_css(of));
> +	const struct ve_failcount_entry *entry;
> +	atomic_t *fc;
Should we allow the container itself to reset the failcount? All other
resource write handlers have a check that return EPERM when we are not
super ve. Here we will allow the container to reset its failcount so it
will no longer be trustworthy information.

-- 
Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>

^ permalink raw reply	[relevance 0%]

* Re: [Devel] [PATCH VZ10 v6 3/9] ve/fs: Rework per-ve mount count
  2026-08-19  9:07  3% ` [Devel] [PATCH VZ10 v6 3/9] ve/fs: Rework per-ve mount count Vladimir Riabchun
@ 2026-08-19 13:16  0%   ` Vasileios Almpanis
  0 siblings, 0 replies; 119+ results
From: Vasileios Almpanis @ 2026-08-19 13:16 UTC (permalink / raw)


> Previous approach with current mounts counter had an issue:
> there was a gap between ve_mount_allowed check and ve_mount_nr_inc,
> which could allow CT to have more mounts than expected.
> 
> Fix this by tracking the number of available mounts instead
> of current ones. This also makes resources accounting
> more consistent - we are using ***_avail_nr approach more.
> 
> One more issue with inconsistent ve value is fixed:
> ve_mount_allowed always used ve from get_exec_env, but
> ve_mount_nr_inc operated with owner_ve.
> Now actual ve value is calculated in the beginning of alloc_vfsmnt.
> 
> To avoid incorrect accounting when is_pseudosuper is changed,
> update avail_nr count without > 0 check if VE is ve0 or pseudosuper.
> This also simplifies ve_mount_put, since increment is
> now unconditional.
> 
> https://virtuozzo.atlassian.net/browse/VSTOR-135520
> 
> Feature: per-ve failcounters
> Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
>
> diff --git a/fs/namespace.c b/fs/namespace.c
> index 68e0efb73d7c..c9e2ab9b3b57 100644
> --- a/fs/namespace.c
> +++ b/fs/namespace.c
> @@ -317,18 +317,21 @@ int mnt_get_count(struct mount *mnt)
>  #endif
>  }
>  
> -static inline int ve_mount_allowed(void);
> -static inline void ve_mount_nr_inc(struct mount *mnt, struct ve_struct *ve);
> -static inline void ve_mount_nr_dec(struct mount *mnt);
> +static inline int ve_try_reserve_mount(struct ve_struct *ve);
> +static inline void ve_mount_put(struct mount *mnt, struct ve_struct *ve);
>  
>  static struct mount *alloc_vfsmnt(const char *name, struct ve_struct *owner_ve)
>  {
>  	struct mount *mnt;
> +	struct ve_struct *ve = owner_ve;
>  
> -	if (!ve_mount_allowed()) {
> +	if (!ve)
> +		ve = get_exec_env();
> +
> +	if (!ve_try_reserve_mount(ve)) {
>  		pr_warn_ratelimited(
>  			"CT#%s reached the limit on mounts.\n",
> -			ve_name(get_exec_env()));
> +			ve_name(ve));
>  		return NULL;
>  	}
>  
> @@ -336,6 +339,14 @@ static struct mount *alloc_vfsmnt(const char *name, struct ve_struct *owner_ve)
>  	if (mnt) {
>  		int err;
>  
> +#ifdef CONFIG_VE
> +		/*
> +		 * Got ve reference in ve_try_reserve_mount, set mnt ve data
> +		 * here, so in case of error ve_mount_put sees correct info.
> +		 */
> +		mnt->ve_owner = ve;
> +#endif
> +
>  		err = mnt_alloc_id(mnt);
>  		if (err)
>  			goto out_free_cache;
> @@ -370,7 +381,8 @@ static struct mount *alloc_vfsmnt(const char *name, struct ve_struct *owner_ve)
>  		INIT_LIST_HEAD(&mnt->mnt_umounting);
>  		INIT_HLIST_HEAD(&mnt->mnt_stuck_children);
>  		mnt->mnt.mnt_idmap = &nop_mnt_idmap;
> -		ve_mount_nr_inc(mnt, owner_ve);
> +	} else {
> +		ve_mount_put(mnt, ve);
>  	}
>  	return mnt;
>  
> @@ -381,6 +393,8 @@ static struct mount *alloc_vfsmnt(const char *name, struct ve_struct *owner_ve)
>  out_free_id:
>  	mnt_free_id(mnt);
>  out_free_cache:
> +	/* Got ve reference in ve_try_reserve_mount */
> +	ve_mount_put(mnt, ve);
>  	kmem_cache_free(mnt_cache, mnt);
>  	return NULL;
>  }
> @@ -750,7 +764,7 @@ int sb_prepare_remount_readonly(struct super_block *sb)
>  static void free_vfsmnt(struct mount *mnt)
>  {
>  	mnt_idmap_put(mnt_idmap(&mnt->mnt));
> -	ve_mount_nr_dec(mnt);
> +	ve_mount_put(mnt, mnt->ve_owner);
this breaks compilation with CONFIG_VE=n. ve_onwer doesn't exist there.
Easiest solution would be to just wrap it under ifdef since the getting
stub also does nothing incase CONFIG_VE=n

-- 
Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>

^ permalink raw reply	[relevance 0%]

* Re: [Devel] [PATCH VZ10 v6 9/9] selftests/ve: Add mount accounting selftest
  2026-08-19  9:07  6% ` [Devel] [PATCH VZ10 v6 9/9] selftests/ve: Add mount accounting selftest Vladimir Riabchun
@ 2026-08-19 13:16  0%   ` Vasileios Almpanis
  0 siblings, 0 replies; 119+ results
From: Vasileios Almpanis @ 2026-08-19 13:16 UTC (permalink / raw)


> There are 6 test cases, covered in the new test:
> 1. Simple mount accouting correctness, just mount/umount.
> 2. Verification of correct limit hits and changes, including
>    negative values.
> 3. Partial mounts test, when mount limit is hit in the middle
>    of creation.
> 4. Test that enabled pseudosuper allows overuse.
> 5. Test that pseudosuper doesn't affect mount accounting.
> 6. Failcount feature verification.
> 
> https://virtuozzo.atlassian.net/browse/VSTOR-135520
> 
> Feature: per-ve failcounters
> Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
>
> diff --git a/tools/testing/selftests/ve/.gitignore b/tools/testing/selftests/ve/.gitignore
> index afa4c568c2c9..3df4d05888dc 100644
> --- a/tools/testing/selftests/ve/.gitignore
> +++ b/tools/testing/selftests/ve/.gitignore
> @@ -1,2 +1,3 @@
>  ve_ns_owner_test
>  ve_perms_test
> +ve_mount_accounting_test
> diff --git a/tools/testing/selftests/ve/Makefile b/tools/testing/selftests/ve/Makefile
> index ec40cbc7b3a1..c6efe7c4b4fb 100644
> --- a/tools/testing/selftests/ve/Makefile
> +++ b/tools/testing/selftests/ve/Makefile
> @@ -4,5 +4,6 @@ CFLAGS += -g -Wall -O2
>  
>  TEST_GEN_PROGS += ve_ns_owner_test
>  TEST_GEN_PROGS += ve_perms_test
> +TEST_GEN_PROGS += ve_mount_accounting_test
>  
>  include ../lib.mk
> diff --git a/tools/testing/selftests/ve/ve_mount_accounting_test.c b/tools/testing/selftests/ve/ve_mount_accounting_test.c
> new file mode 100644
> index 000000000000..a270a8cb8c51
> --- /dev/null
> +++ b/tools/testing/selftests/ve/ve_mount_accounting_test.c
> @@ -0,0 +1,421 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * ve_mount_accounting selftests
> + *
> + * Tests to check the correctness of mount accounting.
> + */
> +#define _GNU_SOURCE
> +#include <asm/unistd.h>
> +#include <linux/sched.h>
> +#include <linux/limits.h>
> +#include <sys/wait.h>
> +#include <sys/syscall.h>
> +#include <sys/stat.h>
> +#include <sys/mount.h>
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <sched.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <string.h>
> +
> +#include "../kselftest_harness.h"
> +#include "ve_selftest.h"
> +
> +#define TMP_DIR			"/ve-mnt-tmp/"
> +#define VE_MOUNTS_MAX		128
> +
> +static int set_pseudosuper(int cgv2_fd, int ctid, int value)
> +{
> +	char path[64];
> +
> +	snprintf(path, sizeof(path), "%d/ve.pseudosuper", ctid);
> +	return write_u64_at(cgv2_fd, path, value);
> +}
> +
> +static int _create_mount(void *id_ptr)
> +{
> +	char path[PATH_MAX];
> +	int id = *(int *)id_ptr, ret;
> +
> +	snprintf(path, sizeof(path), TMP_DIR "%d", id);
> +
> +	if (mkdir(path, 0755) < 0) {
> +		fprintf(stderr, "Failed to create directory %s: %s\n", path, strerror(errno));
> +		return -1;
> +	}
> +	ret = mount("tmpfs", path, "tmpfs", 0, "size=1M");
> +	if (!ret)
> +		return 0;
> +	fprintf(stderr, "Failed to mount tmpfs to %s: %s\n", path, strerror(errno));
> +
> +	rmdir(path);
> +	return ret;
> +}
> +
> +static int create_mount(int cgv2_fd, int ctid, int id)
> +{
> +	int ret = run_in_ve(cgv2_fd, ctid, CLONE_NEWVE, _create_mount, &id);
> +	/*
> +	 * If mount fails, cleanup by free_vfsmnt will be called
> +	 * via call_rcu, need to wait for update.
> +	 */
> +	sleep(1);
> +	return ret;
> +}
> +
> +static int _destroy_mount(void *id_ptr)
> +{
> +	char path[PATH_MAX];
> +	struct stat st;
> +	int id = *(int *)id_ptr;
> +
> +	snprintf(path, sizeof(path), TMP_DIR "%d", id);
> +
> +	if (stat(path, &st))
> +		return 1;
> +	if (umount(path)) {
> +		fprintf(stderr, "failed to umount directory %s: %s\n", path, strerror(errno));
> +		return -1;
> +	}
> +	if (rmdir(path)) {
> +		fprintf(stderr, "failed to remove directory %s: %s\n", path, strerror(errno));
> +		return -1;
> +	}
> +	return 0;
> +}
> +
> +static int destroy_mount(int cgv2_fd, int ctid, int id)
> +{
> +	int ret = run_in_ve(cgv2_fd, ctid, CLONE_NEWVE, _destroy_mount, &id);
> +	/* free_vfsmnt is called via call_rcu, need to wait for update */
> +	sleep(1);
> +	return ret;
> +}
> +
> +#define MAX_MNT_ID 32
> +
> +static int get_free_mnt_id(void)
> +{
> +	int i;
> +	struct stat st;
> +	char path[PATH_MAX];
> +
> +	for (i = 0; i < MAX_MNT_ID; i++) {
> +		snprintf(path, sizeof(path), TMP_DIR "%d", i);
> +		if (stat(path, &st))
> +			return i;
> +	}
> +	return -1;
> +}
> +
> +static int get_mount_cost(int cgv2_fd, int ctid)
> +{
> +	int avail1, avail2, mnt_id;
> +	char path[64];
> +
> +	mnt_id = get_free_mnt_id();
> +
> +	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", ctid);
> +	if (mnt_id < 0 ||
> +	    read_s32_at(cgv2_fd, path, &avail1) ||
> +	    create_mount(cgv2_fd, ctid, mnt_id) ||
> +	    read_s32_at(cgv2_fd, path, &avail2) ||
> +	    destroy_mount(cgv2_fd, ctid, mnt_id))
> +		return -1;
> +
> +	return avail1 - avail2;
> +}
> +
> +/* Expect mount success and return new avail value */
> +static int mount_and_get_avail(struct __test_metadata *_metadata,
> +			int cgv2_fd, int ctid, int mnt_id)
> +{
> +	char path_avail[64];
> +	int mnt_avail_nr;
> +
> +	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", ctid);
> +
> +	ASSERT_EQ(create_mount(cgv2_fd, ctid, mnt_id), 0);
> +	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	return mnt_avail_nr;
> +}
> +
> +/* Expect mount failure and ensure intact avail number */
> +static void assert_mount_fails(struct __test_metadata *_metadata,
> +			int cgv2_fd, int ctid, int mnt_id, int avail_count)
> +{
> +	char path_avail[64];
> +	int mnt_avail_nr;
> +
> +	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", ctid);
> +
> +	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, avail_count);
> +	ASSERT_LT(create_mount(cgv2_fd, ctid, mnt_id), 0);
> +	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, avail_count);
> +}
> +
> +FIXTURE(ve_mnt_acc)
> +{
> +	int cgv2_fd;
> +	int ctid;
> +};
> +
> +FIXTURE_SETUP(ve_mnt_acc)
> +{
> +	unsigned long long initial_mnt_avail_nr;
> +	char path[64];
> +
> +	self->cgv2_fd = mount_cg2_fd();
> +	ASSERT_GE(self->cgv2_fd, 0);
> +	mkdir(TMP_DIR, 0755);
> +
> +	ASSERT_EQ(write_file_at(self->cgv2_fd, "cgroup.subtree_control",
> +		  VE_CONTROLLERS), 0);
> +
> +	self->ctid = make_ve(self->cgv2_fd, CTID_MIN);
> +	ASSERT_GE(self->ctid, 0);
> +
> +	snprintf(path, sizeof(path), "%d/ve.mnt_max_nr", self->ctid);
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path, VE_MOUNTS_MAX), 0);
> +
> +	/*
> +	 * The new ve cgroup has not been entered by anything yet, so its
> +	 * mnt_avail_nr counter should be VE_MOUNTS_MAX.
> +	 */
> +	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", self->ctid);
> +	ASSERT_EQ(read_u64_at(self->cgv2_fd, path, &initial_mnt_avail_nr), 0);
> +	ASSERT_EQ(initial_mnt_avail_nr, VE_MOUNTS_MAX);
> +};
> +
> +FIXTURE_TEARDOWN(ve_mnt_acc)
> +{
> +	for (int i = 0; i < MAX_MNT_ID; i++)
> +		_destroy_mount((void *)&i);
> +
> +	destroy_ve(self->cgv2_fd, self->ctid);
> +	close(self->cgv2_fd);
> +	rmdir(TMP_DIR);
> +}
> +
> +/* Simple test to check mount/umount accounting correctness */
> +TEST_F(ve_mnt_acc, mount_umount)
> +{
> +	int original_mnt_avail, mnt_avail_nr;
> +	char path[64];
> +
> +	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", self->ctid);
> +
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path, &original_mnt_avail), 0);
> +
> +	ASSERT_LT(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
> +		  original_mnt_avail);
> +
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, original_mnt_avail);
> +}
> +
> +/* Test mount limit hits */
> +TEST_F(ve_mnt_acc, hit_limits)
> +{
> +	int original_mnt_avail, mnt_avail_nr, mnt_cost;
> +	int original_have_mnt;
> +	char path_avail[64], path_max_nr[64];
> +
> +	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
> +	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
> +
> +	mnt_cost = get_mount_cost(self->cgv2_fd, self->ctid);
> +	ASSERT_GE(mnt_cost, 1);
> +
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &original_mnt_avail), 0);
> +	original_have_mnt = VE_MOUNTS_MAX - original_mnt_avail;
> +
> +	/* Step 1: reduce number of available mounts to mnt_cost */
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 1 * mnt_cost), 0);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, 1 * mnt_cost);
> +
> +	/* Step 2: do one mount, no mounts should be available */
> +	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
> +		  0);
> +
> +	/* Step 3: check that one more mount falils */
NIT: fails
> +	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 1, 0);
> +
> +	/* Step 4: increase mount limit a little bit, mount should still fail */
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr,
> +				original_have_mnt + 2 * mnt_cost - 1), 0);
> +	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 1, mnt_cost - 1);
> +
> +	/* Step 5: increase by 1 and win now */
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 2 * mnt_cost), 0);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, mnt_cost);
> +
> +	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 1),
> +		  0);
> +
> +	/* Step 6: reduce mnt_max_nr so we have more mounts than allowed */
> +	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 1 * mnt_cost), 0);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, -1 * mnt_cost);
> +
> +	/* Step 7: try to do mount when avail < 0, ensure number is intact */
> +	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, -1 * mnt_cost);
> +
> +	/* Step 8: remove one mount, check avail value update, mount should fail */
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
> +	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, 0);
> +
> +	/* Step 9: remove one more mount and check that new mount succeeds */
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 1), 0);
> +	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
> +	ASSERT_EQ(mnt_avail_nr, 1 * mnt_cost);
> +	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 2),
> +		  0);
> +
> +	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 2), 0);
> +}
> +
> +/*
> + * Mount propagation makes one mount cost more.
> + * This test check that if we run out or mounts in the middle of creating
NIT: This test checks.. out of mounts..

-- 
Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>

^ permalink raw reply	[relevance 0%]

* Re: [Devel] [PATCH vz10] selftests/damon: add script dir to sys.path for PYTHONSAFEPATH compatibility
       [not found]     ` <4492431c-87f8-4de6-9221-4e57107113bc@virtuozzo.com>
@ 2026-08-19 14:12  0%   ` Konstantin Khorenko
  0 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-19 14:12 UTC (permalink / raw)


On 6/26/26 16:28, Pavel Tikhomirov wrote:
> On 6/26/26 13:34, Eva Kurchatova wrote:
>> The RHEL10 import (commit 9f055df11343) changed the Python shebang from
>> '#!/usr/bin/python3 -s' to '#!/usr/bin/python3 -sP'. The -P flag enables
>> Python's safe path mode (PYTHONSAFEPATH, added in Python 3.11), which
>> prevents prepending the script's directory to sys.path.
>>
>> This breaks all 7 DAMON Python selftests that import the _damon_sysfs
>> helper module located in the same directory:
>>
>>   ModuleNotFoundError: No module named '_damon_sysfs'
>>
>> Fix this by explicitly adding the script's directory to sys.path before
>> importing _damon_sysfs, following the same pattern used in commit
>> c3b3eb565bd7 ("tools: ynl: add script dir to sys.path") which fixed the
>> identical issue for the YNL tools.
>>
>> Fixes: 9f055df11343 ("rh10: import RHEL10 kernel-6.12.0-211.16.1.el10")
>> Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
> 
> There is no -sP in mainstream so this test is not meant to be run with those
> flags, we should either avoid running this test or just remove the flags.

Well, i have to disagree here.
Yes, mainstream does not have -sP option at the moment,
at the same time they have already accepted the patch
  c3b3eb565bd7 ("tools: ynl: add script dir to sys.path")

which fixes for example tools/net/ynl/pyynl/cli.py (the files was moved later here)
which also lacks -sP python option, but still ther argument

>>>>>>>>
    Python options like PYTHONSAFEPATH or -P [1] do not add script
    directory to PYTHONPATH. ynl depends on this path to build and run.

    [1] This option is default for Fedora rpmbuild since introduction of
        https://fedoraproject.org/wiki/Changes/PythonSafePath
<<<<<<<<

worked fine. So i think that will be a direction in ms.

Currently RHEL10 kernel has ~120 files with "python -sP".

> 
>>
>> https://virtuozzo.atlassian.net/browse/VSTOR-132453
>> Feature: fix selftests
>>
>> ---
>>  tools/testing/selftests/damon/damon_nr_regions.py              | 3 +++
>>  tools/testing/selftests/damon/damos_apply_interval.py          | 3 +++
>>  tools/testing/selftests/damon/damos_quota.py                   | 3 +++
>>  tools/testing/selftests/damon/damos_quota_goal.py              | 3 +++
>>  tools/testing/selftests/damon/damos_tried_regions.py           | 3 +++
>>  .../selftests/damon/sysfs_update_schemes_tried_regions_hang.py | 3 +++
>>  .../damon/sysfs_update_schemes_tried_regions_wss_estimation.py | 3 +++
>>  7 files changed, 21 insertions(+)
>>
>> diff --git a/tools/testing/selftests/damon/damon_nr_regions.py b/tools/testing/selftests/damon/damon_nr_regions.py
>> index cb31cd140d22..8d0a352f7e85 100755
>> --- a/tools/testing/selftests/damon/damon_nr_regions.py
>> +++ b/tools/testing/selftests/damon/damon_nr_regions.py
>> @@ -1,9 +1,12 @@
>>  #! /usr/bin/python3 -sP
>>  # SPDX-License-Identifier: GPL-2.0
>>  
>> +import os
>>  import subprocess
>> +import sys
>>  import time
>>  
>> +sys.path.append(os.path.dirname(os.path.abspath(__file__)))
>>  import _damon_sysfs
>>  
>>  def test_nr_regions(real_nr_regions, min_nr_regions, max_nr_regions):
>> diff --git a/tools/testing/selftests/damon/damos_apply_interval.py b/tools/testing/selftests/damon/damos_apply_interval.py
>> index e81de3c06a8d..b1d78397bc41 100755
>> --- a/tools/testing/selftests/damon/damos_apply_interval.py
>> +++ b/tools/testing/selftests/damon/damos_apply_interval.py
>> @@ -1,9 +1,12 @@
>>  #! /usr/bin/python3 -sP
>>  # SPDX-License-Identifier: GPL-2.0
>>  
>> +import os
>>  import subprocess
>> +import sys
>>  import time
>>  
>> +sys.path.append(os.path.dirname(os.path.abspath(__file__)))
>>  import _damon_sysfs
>>  
>>  def main():
>> diff --git a/tools/testing/selftests/damon/damos_quota.py b/tools/testing/selftests/damon/damos_quota.py
>> index 7498b9472e9f..45b8b17f940a 100755
>> --- a/tools/testing/selftests/damon/damos_quota.py
>> +++ b/tools/testing/selftests/damon/damos_quota.py
>> @@ -1,9 +1,12 @@
>>  #! /usr/bin/python3 -sP
>>  # SPDX-License-Identifier: GPL-2.0
>>  
>> +import os
>>  import subprocess
>> +import sys
>>  import time
>>  
>> +sys.path.append(os.path.dirname(os.path.abspath(__file__)))
>>  import _damon_sysfs
>>  
>>  def main():
>> diff --git a/tools/testing/selftests/damon/damos_quota_goal.py b/tools/testing/selftests/damon/damos_quota_goal.py
>> index da43f69ed30d..a2d3abd780ae 100755
>> --- a/tools/testing/selftests/damon/damos_quota_goal.py
>> +++ b/tools/testing/selftests/damon/damos_quota_goal.py
>> @@ -1,9 +1,12 @@
>>  #! /usr/bin/python3 -sP
>>  # SPDX-License-Identifier: GPL-2.0
>>  
>> +import os
>>  import subprocess
>> +import sys
>>  import time
>>  
>> +sys.path.append(os.path.dirname(os.path.abspath(__file__)))
>>  import _damon_sysfs
>>  
>>  def main():
>> diff --git a/tools/testing/selftests/damon/damos_tried_regions.py b/tools/testing/selftests/damon/damos_tried_regions.py
>> index 7d1a44dbbe3d..188097475aa4 100755
>> --- a/tools/testing/selftests/damon/damos_tried_regions.py
>> +++ b/tools/testing/selftests/damon/damos_tried_regions.py
>> @@ -1,9 +1,12 @@
>>  #! /usr/bin/python3 -sP
>>  # SPDX-License-Identifier: GPL-2.0
>>  
>> +import os
>>  import subprocess
>> +import sys
>>  import time
>>  
>> +sys.path.append(os.path.dirname(os.path.abspath(__file__)))
>>  import _damon_sysfs
>>  
>>  def main():
>> diff --git a/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_hang.py b/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_hang.py
>> index 1ae639549f6d..b0710fb371f1 100755
>> --- a/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_hang.py
>> +++ b/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_hang.py
>> @@ -1,9 +1,12 @@
>>  #! /usr/bin/python3 -sP
>>  # SPDX-License-Identifier: GPL-2.0
>>  
>> +import os
>>  import subprocess
>> +import sys
>>  import time
>>  
>> +sys.path.append(os.path.dirname(os.path.abspath(__file__)))
>>  import _damon_sysfs
>>  
>>  def main():
>> diff --git a/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py b/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py
>> index b7d35ca4c129..cd8d901b8263 100755
>> --- a/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py
>> +++ b/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py
>> @@ -1,9 +1,12 @@
>>  #! /usr/bin/python3 -sP
>>  # SPDX-License-Identifier: GPL-2.0
>>  
>> +import os
>>  import subprocess
>> +import sys
>>  import time
>>  
>> +sys.path.append(os.path.dirname(os.path.abspath(__file__)))
>>  import _damon_sysfs
>>  
>>  def main():
> 


^ permalink raw reply	[relevance 0%]

* [Devel] [PATCH RHEL10 COMMIT] selftests/damon: add script dir to sys.path for PYTHONSAFEPATH compatibility
       [not found]     <20260626113435.2210877-1-eva.kurchatova@virtuozzo.com>
       [not found]     ` <4492431c-87f8-4de6-9221-4e57107113bc@virtuozzo.com>
@ 2026-08-19 14:16  4% ` Konstantin Khorenko
  1 sibling, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-19 14:16 UTC (permalink / raw)


The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.6.vz10
------>
commit e6c430f5a2cc31a0244492728ab9af8d0530710a
Author: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
Date:   Fri Jun 26 14:34:27 2026 +0300

    selftests/damon: add script dir to sys.path for PYTHONSAFEPATH compatibility
    
    The RHEL10 import (commit 9f055df11343) changed the Python shebang from
    '#!/usr/bin/python3 -s' to '#!/usr/bin/python3 -sP'. The -P flag enables
    Python's safe path mode (PYTHONSAFEPATH, added in Python 3.11), which
    prevents prepending the script's directory to sys.path.
    
    This breaks all 7 DAMON Python selftests that import the _damon_sysfs
    helper module located in the same directory:
    
      ModuleNotFoundError: No module named '_damon_sysfs'
    
    Fix this by explicitly adding the script's directory to sys.path before
    importing _damon_sysfs, following the same pattern used in commit
    c3b3eb565bd7 ("tools: ynl: add script dir to sys.path") which fixed the
    identical issue for the YNL tools.
    
    Note: CentOS 10 Stream git does NOT have "-sP" due to some reason [1].
    
    [1] https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-10/-/blob/main/tools/testing/selftests/damon/damon_nr_regions.py
    
    Fixes: 9f055df11343 ("rh10: import RHEL10 kernel-6.12.0-211.16.1.el10")
    https://virtuozzo.atlassian.net/browse/VSTOR-132453
    Feature: fix selftests
    Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
    Acked-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
 tools/testing/selftests/damon/damon_nr_regions.py                      | 3 +++
 tools/testing/selftests/damon/damos_apply_interval.py                  | 3 +++
 tools/testing/selftests/damon/damos_quota.py                           | 3 +++
 tools/testing/selftests/damon/damos_quota_goal.py                      | 3 +++
 tools/testing/selftests/damon/damos_tried_regions.py                   | 3 +++
 .../testing/selftests/damon/sysfs_update_schemes_tried_regions_hang.py | 3 +++
 .../damon/sysfs_update_schemes_tried_regions_wss_estimation.py         | 3 +++
 7 files changed, 21 insertions(+)

diff --git a/tools/testing/selftests/damon/damon_nr_regions.py b/tools/testing/selftests/damon/damon_nr_regions.py
index cb31cd140d220..8d0a352f7e85c 100755
--- a/tools/testing/selftests/damon/damon_nr_regions.py
+++ b/tools/testing/selftests/damon/damon_nr_regions.py
@@ -1,9 +1,12 @@
 #! /usr/bin/python3 -sP
 # SPDX-License-Identifier: GPL-2.0
 
+import os
 import subprocess
+import sys
 import time
 
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 import _damon_sysfs
 
 def test_nr_regions(real_nr_regions, min_nr_regions, max_nr_regions):
diff --git a/tools/testing/selftests/damon/damos_apply_interval.py b/tools/testing/selftests/damon/damos_apply_interval.py
index e81de3c06a8d2..b1d78397bc41d 100755
--- a/tools/testing/selftests/damon/damos_apply_interval.py
+++ b/tools/testing/selftests/damon/damos_apply_interval.py
@@ -1,9 +1,12 @@
 #! /usr/bin/python3 -sP
 # SPDX-License-Identifier: GPL-2.0
 
+import os
 import subprocess
+import sys
 import time
 
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 import _damon_sysfs
 
 def main():
diff --git a/tools/testing/selftests/damon/damos_quota.py b/tools/testing/selftests/damon/damos_quota.py
index 7498b9472e9f7..45b8b17f940a0 100755
--- a/tools/testing/selftests/damon/damos_quota.py
+++ b/tools/testing/selftests/damon/damos_quota.py
@@ -1,9 +1,12 @@
 #! /usr/bin/python3 -sP
 # SPDX-License-Identifier: GPL-2.0
 
+import os
 import subprocess
+import sys
 import time
 
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 import _damon_sysfs
 
 def main():
diff --git a/tools/testing/selftests/damon/damos_quota_goal.py b/tools/testing/selftests/damon/damos_quota_goal.py
index da43f69ed30db..a2d3abd780aeb 100755
--- a/tools/testing/selftests/damon/damos_quota_goal.py
+++ b/tools/testing/selftests/damon/damos_quota_goal.py
@@ -1,9 +1,12 @@
 #! /usr/bin/python3 -sP
 # SPDX-License-Identifier: GPL-2.0
 
+import os
 import subprocess
+import sys
 import time
 
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 import _damon_sysfs
 
 def main():
diff --git a/tools/testing/selftests/damon/damos_tried_regions.py b/tools/testing/selftests/damon/damos_tried_regions.py
index 7d1a44dbbe3df..188097475aa41 100755
--- a/tools/testing/selftests/damon/damos_tried_regions.py
+++ b/tools/testing/selftests/damon/damos_tried_regions.py
@@ -1,9 +1,12 @@
 #! /usr/bin/python3 -sP
 # SPDX-License-Identifier: GPL-2.0
 
+import os
 import subprocess
+import sys
 import time
 
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 import _damon_sysfs
 
 def main():
diff --git a/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_hang.py b/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_hang.py
index 1ae639549f6de..b0710fb371f14 100755
--- a/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_hang.py
+++ b/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_hang.py
@@ -1,9 +1,12 @@
 #! /usr/bin/python3 -sP
 # SPDX-License-Identifier: GPL-2.0
 
+import os
 import subprocess
+import sys
 import time
 
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 import _damon_sysfs
 
 def main():
diff --git a/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py b/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py
index b7d35ca4c129a..cd8d901b8263b 100755
--- a/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py
+++ b/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py
@@ -1,9 +1,12 @@
 #! /usr/bin/python3 -sP
 # SPDX-License-Identifier: GPL-2.0
 
+import os
 import subprocess
+import sys
 import time
 
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 import _damon_sysfs
 
 def main():

^ permalink raw reply	[relevance 4%]

* [Devel] [PATCH vz10 v2] ve: downgrade the trusted exec/mmap denial from WARN to pr_warn
       [not found]     <20260706110002.1024515-9-khorenko@virtuozzo.com>
@ 2026-08-19 16:25 16% ` Konstantin Khorenko
  2026-08-26 16:49  0%   ` Konstantin Khorenko
  0 siblings, 1 reply; 119+ results
From: Konstantin Khorenko @ 2026-08-19 16:25 UTC (permalink / raw)


ve_check_trusted_exec()/ve_check_trusted_mmap() protect the host, but
the WARN(1, ...) is too much here:

 * it taints the kernel while this is not a kernel bug which we beed to
   debug and fix
 * the backtrace only shows the exec/mmap path, which is already known
 * on a host booted with panic_on_warn a denied exec takes the host down

The SIGSEGV and the core dump of the offending process are enough to
report and investigate such an attempt. Downgrade the WARN() to
pr_warn(), keeping the message, the rate limiting and the signal.

Fixes: fc7157b84c32 ("trusted/ve/mmap: Protect from unsecure library load from CT image")
Fixes: cc218b70c6b4 ("trusted/ve/fs/exec: Send SIGSEGV to a process trying to execute untrusted files")
Feature: security/fs: tructed exec feature
https://virtuozzo.atlassian.net/browse/VSTOR-137234
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
v1 -> v2: commit message rewritten
---
 kernel/ve/ve.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
index 73d1c3b4873e5..0d43dc3253ea3 100644
--- a/kernel/ve/ve.c
+++ b/kernel/ve/ve.c
@@ -1745,8 +1745,8 @@ bool ve_check_trusted_mmap(struct file *file)
 	if (file->f_path.dentry)
 		filename = file->f_path.dentry->d_name.name;
 
-	WARN(1, "VE0 %s tried to map code from file '%s' from VEX\n",
-			current->comm, filename);
+	pr_warn("VE0 %s tried to map code from file '%s' from VEX\n",
+		current->comm, filename);
 	force_sigsegv(SIGSEGV);
 	return false;
 }
@@ -1765,7 +1765,7 @@ bool ve_check_trusted_exec(struct file *file, struct filename *name)
 	if (!__ratelimit(&sigsegv_rs))
 		return false;
 
-	WARN(1, "VE0's %s tried to execute untrusted file %s from VEX\n",
+	pr_warn("VE0's %s tried to execute untrusted file %s from VEX\n",
 		current->comm, name->name);
 	force_sigsegv(SIGSEGV);
 	return false;
-- 
2.43.0


^ permalink raw reply	[relevance 16%]

* [Devel] [PATCH vz10] selftests/damon: wait for the merge to apply the new max_nr_regions
@ 2026-08-21 15:13 15% Eva Kurchatova
  2026-08-31 23:22  0% ` Eva Kurchatova (Virtuozzo)
  0 siblings, 1 reply; 119+ results
From: Eva Kurchatova @ 2026-08-21 15:13 UTC (permalink / raw)


damon_nr_regions sleeps 0.3s after committing max_nr_regions and then
reads the number of regions back, which assumes the merge has happened
by then.  On a machine that is not idle it has not, and the test fails
on a region count that is still the old one.

Poll until the count settles instead of sleeping for a fixed time.

https://virtuozzo.atlassian.net/browse/VSTOR-132453
Feature: fix selftests
Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
---
 .../selftests/damon/damon_nr_regions.py       | 35 +++++++++++--------
 1 file changed, 20 insertions(+), 15 deletions(-)

diff --git a/tools/testing/selftests/damon/damon_nr_regions.py b/tools/testing/selftests/damon/damon_nr_regions.py
index cb31cd140d22..39063ce5b27d 100755
--- a/tools/testing/selftests/damon/damon_nr_regions.py
+++ b/tools/testing/selftests/damon/damon_nr_regions.py
@@ -4,6 +4,7 @@
 import subprocess
 import time
 
+
 import _damon_sysfs
 
 def test_nr_regions(real_nr_regions, min_nr_regions, max_nr_regions):
@@ -114,27 +115,31 @@ def main():
         proc.terminate()
         print('commit failed: %s' % err)
         exit(1)
-    # wait for next merge operation is executed
-    time.sleep(0.3)
+    # wait for the merge operations to apply the new max_nr_regions
+    nr_tried_regions = 0
+    for _ in range(50):
+        time.sleep(0.1)
 
-    err = kdamonds.kdamonds[0].update_schemes_tried_regions()
-    if err is not None:
-        proc.terminate()
-        print('tried regions update failed: %s' % err)
-        exit(1)
+        err = kdamonds.kdamonds[0].update_schemes_tried_regions()
+        if err is not None:
+            proc.terminate()
+            print('tried regions update failed: %s' % err)
+            exit(1)
 
-    scheme = kdamonds.kdamonds[0].contexts[0].schemes[0]
-    if scheme.tried_regions is None:
-        proc.terminate()
-        print('tried regions is not collected')
-        exit(1)
+        scheme = kdamonds.kdamonds[0].contexts[0].schemes[0]
+        if scheme.tried_regions is None:
+            proc.terminate()
+            print('tried regions is not collected')
+            exit(1)
+
+        nr_tried_regions = len(scheme.tried_regions)
+        if 0 < nr_tried_regions <= 7:
+            break
+    proc.terminate()
 
-    nr_tried_regions = len(scheme.tried_regions)
     if nr_tried_regions <= 0:
-        proc.terminate()
         print('tried regions is not created')
         exit(1)
-    proc.terminate()
 
     if nr_tried_regions > 7:
         print('fail online-tuned max_nr_regions: %d > 7' % nr_tried_regions)
-- 
2.55.0


^ permalink raw reply	[relevance 15%]

* [Devel] [PATCH vz10 10/32] mm, proc: build the /proc/meminfo virtualization only with CONFIG_VE
  @ 2026-08-21 16:36  6% ` Konstantin Khorenko
  2026-08-21 16:42  5%   ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
  2026-08-21 16:37 11% ` [Devel] [PATCH vz10 29/32] ms/pcmcia: cistpl: Constify 'struct bin_attribute' Konstantin Khorenko
  2026-08-21 16:37  5% ` [Devel] [PATCH vz10 32/32] kunit: add the script dir to sys.path for PYTHONSAFEPATH compatibility Konstantin Khorenko
  2 siblings, 1 reply; 119+ results
From: Konstantin Khorenko @ 2026-08-21 16:36 UTC (permalink / raw)


From: Eva Kurchatova <eva.kurchatova@virtuozzo.com>

The Container view of /proc/meminfo is built from the memory cgroup of
the Container: si_meminfo_ve() reads memcg->memory / memcg->memsw and
fill_meminfo_ve() looks the cgroup up by memory_cgrp_id.  Both are
compiled unconditionally, so with CONFIG_MEMCG=n - which CONFIG_VE=n
allows, and plain "make defconfig" used to produce - the build fails:

  mm/show_mem.c: error: 'memory_cgrp_id' undeclared (first use in this
                 function)

CONFIG_VE selects CONFIG_MEMCG, so CONFIG_VE is the condition to guard
with, and it is the better one anyway: with CONFIG_VE=n the Container view
is dead code even where CONFIG_MEMCG is on.  Compile si_meminfo_ve(),
fill_meminfo_ve() and the code that formats that view under it.

meminfo_proc_show_mi() has to go under the same guard as its only caller,
or it is left as an unused static function - an error rather than a
warning in a CONFIG_VE=n build with CONFIG_WERROR=y, which x86_64
defconfig sets.

The callers of si_meminfo_ve() in do_sysinfo() and swaps_open() are
reached only when !ve_is_super(get_exec_env()), which is a compile-time
false with CONFIG_VE=n, so the compiler drops those branches and no stub
is needed.

Fixes: 7710faa9d4f0 ("ve/proc: virtualize /proc/meminfo in a Container")
Feature: procfs: virtualize /proc/meminfo
https://virtuozzo.atlassian.net/browse/VSTOR-134732
Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
 fs/proc/meminfo.c | 4 ++++
 mm/show_mem.c     | 2 ++
 2 files changed, 6 insertions(+)

diff --git a/fs/proc/meminfo.c b/fs/proc/meminfo.c
index ac3c88e68728..27e8f921f31a 100644
--- a/fs/proc/meminfo.c
+++ b/fs/proc/meminfo.c
@@ -34,6 +34,7 @@ static void show_val_kb(struct seq_file *m, const char *s, unsigned long num)
 	seq_write(m, " kB\n", 4);
 }
 
+#ifdef CONFIG_VE
 static int meminfo_proc_show_mi(struct seq_file *m, struct meminfo *mi)
 {
 	unsigned long *pages;
@@ -84,6 +85,7 @@ static void fill_meminfo_ve(struct meminfo *mi, struct ve_struct *ve)
 	css_put(css);
 
 }
+#endif /* CONFIG_VE */
 
 static int meminfo_proc_show_ve(struct seq_file *m, void *v,
 				struct ve_struct *ve)
@@ -104,11 +106,13 @@ static int meminfo_proc_show_ve(struct seq_file *m, void *v,
         mi.si = &i;
         mi.ve = ve;
 
+#ifdef CONFIG_VE
 	if (!ve_is_super(ve) && ve->meminfo_val == VE_MEMINFO_DEFAULT) {
 		fill_meminfo_ve(&mi, ve);
 
 		return meminfo_proc_show_mi(m, &mi);
 	}
+#endif
 
 	committed = vm_memory_committed();
 
diff --git a/mm/show_mem.c b/mm/show_mem.c
index 3ab11c945bf4..15be6c6050e4 100644
--- a/mm/show_mem.c
+++ b/mm/show_mem.c
@@ -91,6 +91,7 @@ void si_meminfo(struct sysinfo *val)
 
 EXPORT_SYMBOL(si_meminfo);
 
+#ifdef CONFIG_VE
 void si_meminfo_ve(struct sysinfo *si, struct ve_struct *ve)
 {
 	unsigned long memtotal, memused, swaptotal, swapused;
@@ -138,6 +139,7 @@ void si_meminfo_ve(struct sysinfo *si, struct ve_struct *ve)
 	/* bufferram, totalhigh and freehigh left 0 */
 }
 EXPORT_SYMBOL(si_meminfo_ve);
+#endif /* CONFIG_VE */
 
 #ifdef CONFIG_NUMA
 void si_meminfo_node(struct sysinfo *val, int nid)
-- 
2.47.1


^ permalink raw reply	[relevance 6%]

* [Devel] [PATCH vz10 29/32] ms/pcmcia: cistpl: Constify 'struct bin_attribute'
    2026-08-21 16:36  6% ` [Devel] [PATCH vz10 10/32] mm, proc: build the /proc/meminfo virtualization only with CONFIG_VE Konstantin Khorenko
@ 2026-08-21 16:37 11% ` Konstantin Khorenko
  2026-08-21 16:42 11%   ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
  2026-08-21 16:37  5% ` [Devel] [PATCH vz10 32/32] kunit: add the script dir to sys.path for PYTHONSAFEPATH compatibility Konstantin Khorenko
  2 siblings, 1 reply; 119+ results
From: Konstantin Khorenko @ 2026-08-21 16:37 UTC (permalink / raw)


From: Thomas Wei?schuh <linux@weissschuh.net>

The sysfs core now allows instances of 'struct bin_attribute' to be
moved into read-only memory. Make use of that to protect them against
accidental or malicious modifications.

Signed-off-by: Thomas Wei?schuh <linux@weissschuh.net>
Link: https://lore.kernel.org/r/20241215-sysfs-const-bin_attr-pcmcia-v1-1-ebb82e47d834@weissschuh.net
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
(cherry picked from commit 05a9896fa9e15466456a1b1dc9d2eacdf3551b79)

Needed here because the RHEL10.2 base carries the sysfs side of the
conversion but not the pcmcia one, so the initializer of pccard_cis_attr
does not compile at all:

  drivers/pcmcia/cistpl.c:1608:17: error: initialization of
    'ssize_t (*)(struct file *, struct kobject *,
                 const struct bin_attribute *, char *, loff_t, size_t)'
    from incompatible pointer type [-Wincompatible-pointer-types]

Our shipped configs have CONFIG_PCCARD=n so it goes unnoticed there,
while plain x86_64 defconfig - which we now want to keep building for
KUnit - enables it.  Applies as is: this tree's struct bin_attribute has
both the ::read/::write and the ::read_new/::write_new members, and
sysfs_kf_bin_read() prefers the latter.

https://virtuozzo.atlassian.net/browse/VSTOR-134732
Feature: fix ms/pcmcia
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
 drivers/pcmcia/cistpl.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/pcmcia/cistpl.c b/drivers/pcmcia/cistpl.c
index d018f36f3a89..0c801e4ccc6c 100644
--- a/drivers/pcmcia/cistpl.c
+++ b/drivers/pcmcia/cistpl.c
@@ -1540,7 +1540,7 @@ static ssize_t pccard_extract_cis(struct pcmcia_socket *s, char *buf,
 
 
 static ssize_t pccard_show_cis(struct file *filp, struct kobject *kobj,
-			       struct bin_attribute *bin_attr,
+			       const struct bin_attribute *bin_attr,
 			       char *buf, loff_t off, size_t count)
 {
 	unsigned int size = 0x200;
@@ -1571,7 +1571,7 @@ static ssize_t pccard_show_cis(struct file *filp, struct kobject *kobj,
 
 
 static ssize_t pccard_store_cis(struct file *filp, struct kobject *kobj,
-				struct bin_attribute *bin_attr,
+				const struct bin_attribute *bin_attr,
 				char *buf, loff_t off, size_t count)
 {
 	struct pcmcia_socket *s;
@@ -1605,6 +1605,6 @@ static ssize_t pccard_store_cis(struct file *filp, struct kobject *kobj,
 const struct bin_attribute pccard_cis_attr = {
 	.attr = { .name = "cis", .mode = S_IRUGO | S_IWUSR },
 	.size = 0x200,
-	.read = pccard_show_cis,
-	.write = pccard_store_cis,
+	.read_new = pccard_show_cis,
+	.write_new = pccard_store_cis,
 };
-- 
2.47.1


^ permalink raw reply	[relevance 11%]

* [Devel] [PATCH vz10 32/32] kunit: add the script dir to sys.path for PYTHONSAFEPATH compatibility
    2026-08-21 16:36  6% ` [Devel] [PATCH vz10 10/32] mm, proc: build the /proc/meminfo virtualization only with CONFIG_VE Konstantin Khorenko
  2026-08-21 16:37 11% ` [Devel] [PATCH vz10 29/32] ms/pcmcia: cistpl: Constify 'struct bin_attribute' Konstantin Khorenko
@ 2026-08-21 16:37  5% ` Konstantin Khorenko
  2026-08-21 16:42  4%   ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
  2 siblings, 1 reply; 119+ results
From: Konstantin Khorenko @ 2026-08-21 16:37 UTC (permalink / raw)


The RHEL10 import changed the Python shebang of the in-tree tools from
'#!/usr/bin/python3 -s' to '#!/usr/bin/python3 -sP'.  The -P flag turns on
safe path mode (PYTHONSAFEPATH, Python 3.11+), which stops Python from
prepending the script's own directory to sys.path - so kunit.py cannot
import the modules sitting right next to it, and the KUnit tool does not
start at all:

  $ ./tools/testing/kunit/kunit.py run
  Traceback (most recent call last):
    File "tools/testing/kunit/kunit.py", line 23, in <module>
      import kunit_json
  ModuleNotFoundError: No module named 'kunit_json'

Add the script's directory to sys.path before the local imports in the two
executable scripts that have them, kunit.py and kunit_tool_test.py,
following commit e6c430f5a2cc ("selftests/damon: add script dir to
sys.path for PYTHONSAFEPATH compatibility") which fixed the same breakage
for the DAMON selftests.

Fixes: 9f055df11343 ("rh10: import RHEL10 kernel-6.12.0-211.16.1.el10")
Feature: fix KUnit tests
https://virtuozzo.atlassian.net/browse/VSTOR-134732
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
 tools/testing/kunit/kunit.py           | 1 +
 tools/testing/kunit/kunit_tool_test.py | 2 ++
 2 files changed, 3 insertions(+)

diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py
index 13ccb9993776..b4bc6c742718 100755
--- a/tools/testing/kunit/kunit.py
+++ b/tools/testing/kunit/kunit.py
@@ -20,6 +20,7 @@ from dataclasses import dataclass
 from enum import Enum, auto
 from typing import Iterable, List, Optional, Sequence, Tuple
 
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 import kunit_json
 import kunit_kernel
 import kunit_parser
diff --git a/tools/testing/kunit/kunit_tool_test.py b/tools/testing/kunit/kunit_tool_test.py
index 3121e133e949..265a8999aada 100755
--- a/tools/testing/kunit/kunit_tool_test.py
+++ b/tools/testing/kunit/kunit_tool_test.py
@@ -16,8 +16,10 @@ import json
 import os
 import signal
 import subprocess
+import sys
 from typing import Iterable
 
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 import kunit_config
 import kunit_parser
 import kunit_kernel
-- 
2.47.1


^ permalink raw reply	[relevance 5%]

* [Devel] [PATCH vz10 1/3] selftests: net: Adapt ethtool mq tests to fix in qdisc graft
@ 2026-08-21 16:41  5% Eva Kurchatova
  2026-08-24 13:36  0% ` Konstantin Khorenko
  2026-08-24 14:36  5% ` [Devel] [PATCH RHEL10 COMMIT] ms/selftests: " Konstantin Khorenko
  0 siblings, 2 replies; 119+ results
From: Eva Kurchatova @ 2026-08-21 16:41 UTC (permalink / raw)


From: Victor Nogueira <victor@mojatatu.com>

Because of patch[1] the graft behaviour changed

So the command:

tcq replace parent 100:1 handle 204:

Is no longer valid and will not delete 100:4 added by command:

tcq replace parent 100:4 handle 204: pfifo_fast

So to maintain the original behaviour, this patch manually deletes 100:4
and grafts 100:1

Note: This change will also work fine without [1]

[1] https://lore.kernel.org/netdev/20250111151455.75480-1-jhs at mojatatu.com/T/#u

Signed-off-by: Victor Nogueira <victor@mojatatu.com>
Reviewed-by: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
(cherry picked from commit 0a5b8fff01bde1b9908f00004c676f2e2459333b)

https://virtuozzo.atlassian.net/browse/VSTOR-139651
Feature: fix selftests
Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
---
 .../selftests/drivers/net/netdevsim/tc-mq-visibility.sh  | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/tools/testing/selftests/drivers/net/netdevsim/tc-mq-visibility.sh b/tools/testing/selftests/drivers/net/netdevsim/tc-mq-visibility.sh
index fd13c8cfb7a8..b411fe66510f 100755
--- a/tools/testing/selftests/drivers/net/netdevsim/tc-mq-visibility.sh
+++ b/tools/testing/selftests/drivers/net/netdevsim/tc-mq-visibility.sh
@@ -58,9 +58,12 @@ for root in mq mqprio; do
     ethtool -L $NDEV combined 4
     n_child_assert 4 "One real queue, rest default"
 
-    # Graft some
-    tcq replace parent 100:1 handle 204:
-    n_child_assert 3 "Grafted"
+    # Remove real one
+    tcq del parent 100:4 handle 204:
+
+    # Replace default with pfifo
+    tcq replace parent 100:1 handle 205: pfifo limit 1000
+    n_child_assert 3 "Deleting real one, replacing default one with pfifo"
 
     ethtool -L $NDEV combined 1
     n_child_assert 1 "Grafted, one"
-- 
2.55.0


^ permalink raw reply	[relevance 5%]

* [Devel] [PATCH RHEL10 COMMIT] mm, proc: build the /proc/meminfo virtualization only with CONFIG_VE
  2026-08-21 16:36  6% ` [Devel] [PATCH vz10 10/32] mm, proc: build the /proc/meminfo virtualization only with CONFIG_VE Konstantin Khorenko
@ 2026-08-21 16:42  5%   ` Konstantin Khorenko
  0 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-21 16:42 UTC (permalink / raw)


The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.6.vz10
------>
commit 3bb167409cb19772fcb7e26a2a51afd77ef4dd1c
Author: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
Date:   Fri Aug 21 18:36:56 2026 +0200

    mm, proc: build the /proc/meminfo virtualization only with CONFIG_VE
    
    The Container view of /proc/meminfo is built from the memory cgroup of
    the Container: si_meminfo_ve() reads memcg->memory / memcg->memsw and
    fill_meminfo_ve() looks the cgroup up by memory_cgrp_id.  Both are
    compiled unconditionally, so with CONFIG_MEMCG=n - which CONFIG_VE=n
    allows, and plain "make defconfig" used to produce - the build fails:
    
      mm/show_mem.c: error: 'memory_cgrp_id' undeclared (first use in this
                     function)
    
    CONFIG_VE selects CONFIG_MEMCG, so CONFIG_VE is the condition to guard
    with, and it is the better one anyway: with CONFIG_VE=n the Container view
    is dead code even where CONFIG_MEMCG is on.  Compile si_meminfo_ve(),
    fill_meminfo_ve() and the code that formats that view under it.
    
    meminfo_proc_show_mi() has to go under the same guard as its only caller,
    or it is left as an unused static function - an error rather than a
    warning in a CONFIG_VE=n build with CONFIG_WERROR=y, which x86_64
    defconfig sets.
    
    The callers of si_meminfo_ve() in do_sysinfo() and swaps_open() are
    reached only when !ve_is_super(get_exec_env()), which is a compile-time
    false with CONFIG_VE=n, so the compiler drops those branches and no stub
    is needed.
    
    Fixes: 7710faa9d4f0 ("ve/proc: virtualize /proc/meminfo in a Container")
    Feature: procfs: virtualize /proc/meminfo
    https://virtuozzo.atlassian.net/browse/VSTOR-134732
    Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
    
    Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
 fs/proc/meminfo.c | 4 ++++
 mm/show_mem.c     | 2 ++
 2 files changed, 6 insertions(+)

diff --git a/fs/proc/meminfo.c b/fs/proc/meminfo.c
index ac3c88e68728a..27e8f921f31af 100644
--- a/fs/proc/meminfo.c
+++ b/fs/proc/meminfo.c
@@ -34,6 +34,7 @@ static void show_val_kb(struct seq_file *m, const char *s, unsigned long num)
 	seq_write(m, " kB\n", 4);
 }
 
+#ifdef CONFIG_VE
 static int meminfo_proc_show_mi(struct seq_file *m, struct meminfo *mi)
 {
 	unsigned long *pages;
@@ -84,6 +85,7 @@ static void fill_meminfo_ve(struct meminfo *mi, struct ve_struct *ve)
 	css_put(css);
 
 }
+#endif /* CONFIG_VE */
 
 static int meminfo_proc_show_ve(struct seq_file *m, void *v,
 				struct ve_struct *ve)
@@ -104,11 +106,13 @@ static int meminfo_proc_show_ve(struct seq_file *m, void *v,
         mi.si = &i;
         mi.ve = ve;
 
+#ifdef CONFIG_VE
 	if (!ve_is_super(ve) && ve->meminfo_val == VE_MEMINFO_DEFAULT) {
 		fill_meminfo_ve(&mi, ve);
 
 		return meminfo_proc_show_mi(m, &mi);
 	}
+#endif
 
 	committed = vm_memory_committed();
 
diff --git a/mm/show_mem.c b/mm/show_mem.c
index 3ab11c945bf4a..15be6c6050e4d 100644
--- a/mm/show_mem.c
+++ b/mm/show_mem.c
@@ -91,6 +91,7 @@ void si_meminfo(struct sysinfo *val)
 
 EXPORT_SYMBOL(si_meminfo);
 
+#ifdef CONFIG_VE
 void si_meminfo_ve(struct sysinfo *si, struct ve_struct *ve)
 {
 	unsigned long memtotal, memused, swaptotal, swapused;
@@ -138,6 +139,7 @@ void si_meminfo_ve(struct sysinfo *si, struct ve_struct *ve)
 	/* bufferram, totalhigh and freehigh left 0 */
 }
 EXPORT_SYMBOL(si_meminfo_ve);
+#endif /* CONFIG_VE */
 
 #ifdef CONFIG_NUMA
 void si_meminfo_node(struct sysinfo *val, int nid)

^ permalink raw reply	[relevance 5%]

* [Devel] [PATCH RHEL10 COMMIT] ms/pcmcia: cistpl: Constify 'struct bin_attribute'
  2026-08-21 16:37 11% ` [Devel] [PATCH vz10 29/32] ms/pcmcia: cistpl: Constify 'struct bin_attribute' Konstantin Khorenko
@ 2026-08-21 16:42 11%   ` Konstantin Khorenko
  0 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-21 16:42 UTC (permalink / raw)


The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.6.vz10
------>
commit 889c35dad8118bbce4445b873c67a6d7a931db22
Author: Thomas Wei??schuh <linux@weissschuh.net>
Date:   Fri Aug 21 18:37:15 2026 +0200

    ms/pcmcia: cistpl: Constify 'struct bin_attribute'
    
    The sysfs core now allows instances of 'struct bin_attribute' to be
    moved into read-only memory. Make use of that to protect them against
    accidental or malicious modifications.
    
    Signed-off-by: Thomas Wei??schuh <linux@weissschuh.net>
    Link: https://lore.kernel.org/r/20241215-sysfs-const-bin_attr-pcmcia-v1-1-ebb82e47d834 at weissschuh.net
    Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
    (cherry picked from commit 05a9896fa9e15466456a1b1dc9d2eacdf3551b79)
    
    Needed here because the RHEL10.2 base carries the sysfs side of the
    conversion but not the pcmcia one, so the initializer of pccard_cis_attr
    does not compile at all:
    
      drivers/pcmcia/cistpl.c:1608:17: error: initialization of
        'ssize_t (*)(struct file *, struct kobject *,
                     const struct bin_attribute *, char *, loff_t, size_t)'
        from incompatible pointer type [-Wincompatible-pointer-types]
    
    Our shipped configs have CONFIG_PCCARD=n so it goes unnoticed there,
    while plain x86_64 defconfig - which we now want to keep building for
    KUnit - enables it.  Applies as is: this tree's struct bin_attribute has
    both the ::read/::write and the ::read_new/::write_new members, and
    sysfs_kf_bin_read() prefers the latter.
    
    https://virtuozzo.atlassian.net/browse/VSTOR-134732
    Feature: fix ms/pcmcia
    Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
 drivers/pcmcia/cistpl.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/pcmcia/cistpl.c b/drivers/pcmcia/cistpl.c
index d018f36f3a893..0c801e4ccc6c2 100644
--- a/drivers/pcmcia/cistpl.c
+++ b/drivers/pcmcia/cistpl.c
@@ -1540,7 +1540,7 @@ static ssize_t pccard_extract_cis(struct pcmcia_socket *s, char *buf,
 
 
 static ssize_t pccard_show_cis(struct file *filp, struct kobject *kobj,
-			       struct bin_attribute *bin_attr,
+			       const struct bin_attribute *bin_attr,
 			       char *buf, loff_t off, size_t count)
 {
 	unsigned int size = 0x200;
@@ -1571,7 +1571,7 @@ static ssize_t pccard_show_cis(struct file *filp, struct kobject *kobj,
 
 
 static ssize_t pccard_store_cis(struct file *filp, struct kobject *kobj,
-				struct bin_attribute *bin_attr,
+				const struct bin_attribute *bin_attr,
 				char *buf, loff_t off, size_t count)
 {
 	struct pcmcia_socket *s;
@@ -1605,6 +1605,6 @@ static ssize_t pccard_store_cis(struct file *filp, struct kobject *kobj,
 const struct bin_attribute pccard_cis_attr = {
 	.attr = { .name = "cis", .mode = S_IRUGO | S_IWUSR },
 	.size = 0x200,
-	.read = pccard_show_cis,
-	.write = pccard_store_cis,
+	.read_new = pccard_show_cis,
+	.write_new = pccard_store_cis,
 };

^ permalink raw reply	[relevance 11%]

* [Devel] [PATCH RHEL10 COMMIT] kunit: add the script dir to sys.path for PYTHONSAFEPATH compatibility
  2026-08-21 16:37  5% ` [Devel] [PATCH vz10 32/32] kunit: add the script dir to sys.path for PYTHONSAFEPATH compatibility Konstantin Khorenko
@ 2026-08-21 16:42  4%   ` Konstantin Khorenko
  0 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-21 16:42 UTC (permalink / raw)


The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.6.vz10
------>
commit 7a2f379b54dc30a92d1f758221cddbb78f7c1f74
Author: Konstantin Khorenko <khorenko@virtuozzo.com>
Date:   Fri Aug 21 18:37:18 2026 +0200

    kunit: add the script dir to sys.path for PYTHONSAFEPATH compatibility
    
    The RHEL10 import changed the Python shebang of the in-tree tools from
    '#!/usr/bin/python3 -s' to '#!/usr/bin/python3 -sP'.  The -P flag turns on
    safe path mode (PYTHONSAFEPATH, Python 3.11+), which stops Python from
    prepending the script's own directory to sys.path - so kunit.py cannot
    import the modules sitting right next to it, and the KUnit tool does not
    start at all:
    
      $ ./tools/testing/kunit/kunit.py run
      Traceback (most recent call last):
        File "tools/testing/kunit/kunit.py", line 23, in <module>
          import kunit_json
      ModuleNotFoundError: No module named 'kunit_json'
    
    Add the script's directory to sys.path before the local imports in the two
    executable scripts that have them, kunit.py and kunit_tool_test.py,
    following commit e6c430f5a2cc ("selftests/damon: add script dir to
    sys.path for PYTHONSAFEPATH compatibility") which fixed the same breakage
    for the DAMON selftests.
    
    Fixes: 9f055df11343 ("rh10: import RHEL10 kernel-6.12.0-211.16.1.el10")
    Feature: fix KUnit tests
    https://virtuozzo.atlassian.net/browse/VSTOR-134732
    Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
 tools/testing/kunit/kunit.py           | 1 +
 tools/testing/kunit/kunit_tool_test.py | 2 ++
 2 files changed, 3 insertions(+)

diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py
index 13ccb99937764..b4bc6c7427182 100755
--- a/tools/testing/kunit/kunit.py
+++ b/tools/testing/kunit/kunit.py
@@ -20,6 +20,7 @@ from dataclasses import dataclass
 from enum import Enum, auto
 from typing import Iterable, List, Optional, Sequence, Tuple
 
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 import kunit_json
 import kunit_kernel
 import kunit_parser
diff --git a/tools/testing/kunit/kunit_tool_test.py b/tools/testing/kunit/kunit_tool_test.py
index 3121e133e9492..265a8999aada7 100755
--- a/tools/testing/kunit/kunit_tool_test.py
+++ b/tools/testing/kunit/kunit_tool_test.py
@@ -16,8 +16,10 @@ import json
 import os
 import signal
 import subprocess
+import sys
 from typing import Iterable
 
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 import kunit_config
 import kunit_parser
 import kunit_kernel

^ permalink raw reply	[relevance 4%]

* [Devel] [PATCH vz10 v2] selftests/uevent: do not fail on a netlink receive buffer overrun
  @ 2026-08-24 13:09  4% ` Konstantin Khorenko
  2026-08-26 16:49  0%   ` Konstantin Khorenko
                     ` (2 more replies)
  0 siblings, 3 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-24 13:09 UTC (permalink / raw)


SO_RCVBUF is set to __UEVENT_BUFFER_SIZE, 4KB, which a busy machine
overruns while the test is listening:

  No buffer space available - Failed to receive uevent

Two things are wrong here.

The socket queue is sized after a single message, while do_test()
deliberately triggers ten uevents "to account for the case where the
kernel might drop some", so the queue has to hold more than one.

Give it its own size and leave the message buffer alone: the kernel caps
a single uevent at UEVENT_BUFFER_SIZE, 2048 bytes, so 4KB per message is
already generous.

The receive loop then treats every error as fatal, ENOBUFS included,
which defeats those ten uevents. Netlink clears the error after
reporting it once, so the copies still queued, or still on their way,
are perfectly receivable. Retry instead.

do_test() bounds the listener with a two second sigtimedwait(), so a
retry cannot hang the test.

https://virtuozzo.atlassian.net/browse/VSTOR-139674
Feature: fix selftests
Reported-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
Changes in v2:
- keep __UEVENT_BUFFER_SIZE at 4KB and give SO_RCVBUF its own
  __UEVENT_RCVBUF_SIZE.  v1 raised the shared macro, which sized the
  socket queue correctly but also turned the per message buffer into a
  128KB zero initialized array on the stack, while the kernel caps a
  single uevent at UEVENT_BUFFER_SIZE, 2048 bytes.
- retry recvmsg() on ENOBUFS instead of failing.  v1 only made the
  overrun less likely; the test still died on the first one, even
  though do_test() triggers ten uevents precisely so that drops are
  tolerated.
- subject and commit message updated accordingly.

 tools/testing/selftests/uevent/uevent_filtering.c | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/uevent/uevent_filtering.c b/tools/testing/selftests/uevent/uevent_filtering.c
index 8062804ff759..735eb8138c44 100644
--- a/tools/testing/selftests/uevent/uevent_filtering.c
+++ b/tools/testing/selftests/uevent/uevent_filtering.c
@@ -23,6 +23,11 @@
 
 #define __DEV_FULL "/sys/devices/virtual/mem/full/uevent"
 #define __UEVENT_BUFFER_SIZE (2048 * 2)
+/*
+ * The socket queue has to hold more than a single message: the test
+ * triggers ten uevents and a busy machine overruns a small buffer.
+ */
+#define __UEVENT_RCVBUF_SIZE (2048 * 64)
 #define __UEVENT_HEADER "add@/devices/virtual/mem/full"
 #define __UEVENT_HEADER_LEN sizeof("add@/devices/virtual/mem/full")
 #define __UEVENT_LISTEN_ALL -1
@@ -78,7 +83,7 @@ static int uevent_listener(unsigned long post_flags, bool expect_uevent,
 {
 	int sk_fd, ret;
 	socklen_t sk_addr_len;
-	int rcv_buf_sz = __UEVENT_BUFFER_SIZE;
+	int rcv_buf_sz = __UEVENT_RCVBUF_SIZE;
 	uint64_t sync_add = 1;
 	struct sockaddr_nl sk_addr = { 0 }, rcv_addr = { 0 };
 	char buf[__UEVENT_BUFFER_SIZE] = { 0 };
@@ -158,6 +163,12 @@ static int uevent_listener(unsigned long post_flags, bool expect_uevent,
 		ssize_t r;
 
 		r = recvmsg(sk_fd, &hdr, 0);
+		/*
+		 * The queue overran.  The kernel clears the error after
+		 * reporting it once and more uevents are on their way.
+		 */
+		if (r < 0 && errno == ENOBUFS)
+			continue;
 		if (r <= 0) {
 			fprintf(stderr, "%s - Failed to receive uevent\n", strerror(errno));
 			ret = -1;
-- 
2.47.1


^ permalink raw reply	[relevance 4%]

* Re: [Devel] [PATCH vz10 1/3] selftests: net: Adapt ethtool mq tests to fix in qdisc graft
  2026-08-21 16:41  5% [Devel] [PATCH vz10 1/3] selftests: net: Adapt ethtool mq tests to fix in qdisc graft Eva Kurchatova
@ 2026-08-24 13:36  0% ` Konstantin Khorenko
  2026-08-24 14:36  5% ` [Devel] [PATCH RHEL10 COMMIT] ms/selftests: " Konstantin Khorenko
  1 sibling, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-24 13:36 UTC (permalink / raw)


ack

--
Best regards,

Konstantin Khorenko,
Virtuozzo Linux Kernel Team

On 8/21/26 18:41, Eva Kurchatova wrote:
> From: Victor Nogueira <victor@mojatatu.com>
> 
> Because of patch[1] the graft behaviour changed
> 
> So the command:
> 
> tcq replace parent 100:1 handle 204:
> 
> Is no longer valid and will not delete 100:4 added by command:
> 
> tcq replace parent 100:4 handle 204: pfifo_fast
> 
> So to maintain the original behaviour, this patch manually deletes 100:4
> and grafts 100:1
> 
> Note: This change will also work fine without [1]
> 
> [1] https://lore.kernel.org/netdev/20250111151455.75480-1-jhs at mojatatu.com/T/#u
> 
> Signed-off-by: Victor Nogueira <victor@mojatatu.com>
> Reviewed-by: Jamal Hadi Salim <jhs@mojatatu.com>
> Signed-off-by: David S. Miller <davem@davemloft.net>
> (cherry picked from commit 0a5b8fff01bde1b9908f00004c676f2e2459333b)
> 
> https://virtuozzo.atlassian.net/browse/VSTOR-139651
> Feature: fix selftests
> Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
> ---
>  .../selftests/drivers/net/netdevsim/tc-mq-visibility.sh  | 9 ++++++---
>  1 file changed, 6 insertions(+), 3 deletions(-)
> 
> diff --git a/tools/testing/selftests/drivers/net/netdevsim/tc-mq-visibility.sh b/tools/testing/selftests/drivers/net/netdevsim/tc-mq-visibility.sh
> index fd13c8cfb7a8..b411fe66510f 100755
> --- a/tools/testing/selftests/drivers/net/netdevsim/tc-mq-visibility.sh
> +++ b/tools/testing/selftests/drivers/net/netdevsim/tc-mq-visibility.sh
> @@ -58,9 +58,12 @@ for root in mq mqprio; do
>      ethtool -L $NDEV combined 4
>      n_child_assert 4 "One real queue, rest default"
>  
> -    # Graft some
> -    tcq replace parent 100:1 handle 204:
> -    n_child_assert 3 "Grafted"
> +    # Remove real one
> +    tcq del parent 100:4 handle 204:
> +
> +    # Replace default with pfifo
> +    tcq replace parent 100:1 handle 205: pfifo limit 1000
> +    n_child_assert 3 "Deleting real one, replacing default one with pfifo"
>  
>      ethtool -L $NDEV combined 1
>      n_child_assert 1 "Grafted, one"


^ permalink raw reply	[relevance 0%]

* [Devel] [PATCH VZ10 v7 3/9] ve/fs: Rework per-ve mount count
  @ 2026-08-24 13:54  3% ` Vladimir Riabchun
  2026-08-24 13:54  9% ` [Devel] [PATCH VZ10 v7 7/9] ve: Introduce per-VE failcount Vladimir Riabchun
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 119+ results
From: Vladimir Riabchun @ 2026-08-24 13:54 UTC (permalink / raw)


Previous approach with current mounts counter had an issue:
there was a gap between ve_mount_allowed check and ve_mount_nr_inc,
which could allow CT to have more mounts than expected.

Fix this by tracking the number of available mounts instead
of current ones. This also makes resources accounting
more consistent - we are using ***_avail_nr approach more.

One more issue with inconsistent ve value is fixed:
ve_mount_allowed always used ve from get_exec_env, but
ve_mount_nr_inc operated with owner_ve.
Now actual ve value is calculated in the beginning of alloc_vfsmnt.

To avoid incorrect accounting when is_pseudosuper is changed,
update avail_nr count without > 0 check if VE is ve0 or pseudosuper.
This also simplifies ve_mount_put, since increment is
now unconditional.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Feature: per-ve failcounters
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
 fs/namespace.c     | 73 ++++++++++++++++++++++++++++------------------
 include/linux/ve.h |  2 +-
 kernel/ve/ve.c     | 12 ++++----
 3 files changed, 51 insertions(+), 36 deletions(-)

diff --git a/fs/namespace.c b/fs/namespace.c
index 68e0efb73d7c..e97f48204617 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -317,18 +317,21 @@ int mnt_get_count(struct mount *mnt)
 #endif
 }
 
-static inline int ve_mount_allowed(void);
-static inline void ve_mount_nr_inc(struct mount *mnt, struct ve_struct *ve);
-static inline void ve_mount_nr_dec(struct mount *mnt);
+static inline int ve_try_reserve_mount(struct ve_struct *ve);
+static inline void ve_mount_put(struct mount *mnt, struct ve_struct *ve);
 
 static struct mount *alloc_vfsmnt(const char *name, struct ve_struct *owner_ve)
 {
 	struct mount *mnt;
+	struct ve_struct *ve = owner_ve;
 
-	if (!ve_mount_allowed()) {
+	if (!ve)
+		ve = get_exec_env();
+
+	if (!ve_try_reserve_mount(ve)) {
 		pr_warn_ratelimited(
 			"CT#%s reached the limit on mounts.\n",
-			ve_name(get_exec_env()));
+			ve_name(ve));
 		return NULL;
 	}
 
@@ -336,6 +339,14 @@ static struct mount *alloc_vfsmnt(const char *name, struct ve_struct *owner_ve)
 	if (mnt) {
 		int err;
 
+#ifdef CONFIG_VE
+		/*
+		 * Got ve reference in ve_try_reserve_mount, set mnt ve data
+		 * here, so in case of error ve_mount_put sees correct info.
+		 */
+		mnt->ve_owner = ve;
+#endif
+
 		err = mnt_alloc_id(mnt);
 		if (err)
 			goto out_free_cache;
@@ -370,7 +381,8 @@ static struct mount *alloc_vfsmnt(const char *name, struct ve_struct *owner_ve)
 		INIT_LIST_HEAD(&mnt->mnt_umounting);
 		INIT_HLIST_HEAD(&mnt->mnt_stuck_children);
 		mnt->mnt.mnt_idmap = &nop_mnt_idmap;
-		ve_mount_nr_inc(mnt, owner_ve);
+	} else {
+		ve_mount_put(mnt, ve);
 	}
 	return mnt;
 
@@ -381,6 +393,8 @@ static struct mount *alloc_vfsmnt(const char *name, struct ve_struct *owner_ve)
 out_free_id:
 	mnt_free_id(mnt);
 out_free_cache:
+	/* Got ve reference in ve_try_reserve_mount */
+	ve_mount_put(mnt, ve);
 	kmem_cache_free(mnt_cache, mnt);
 	return NULL;
 }
@@ -750,7 +764,9 @@ int sb_prepare_remount_readonly(struct super_block *sb)
 static void free_vfsmnt(struct mount *mnt)
 {
 	mnt_idmap_put(mnt_idmap(&mnt->mnt));
-	ve_mount_nr_dec(mnt);
+#ifdef CONFIG_VE
+	ve_mount_put(mnt, mnt->ve_owner);
+#endif
 	kfree_const(mnt->mnt_devname);
 #ifdef CONFIG_SMP
 	free_percpu(mnt->mnt_pcp);
@@ -3205,7 +3221,7 @@ int ve_devmnt_process(struct ve_struct *ve, dev_t dev, void **data_pp, int remou
 		if (devmnt->dev == dev) {
 			err = ve_devmnt_check(data, devmnt->allowed_options);
 			/*
-			 * In case of @is_pseudouser set, ie restore procedure,
+			 * In case of @is_pseudosuper set, ie restore procedure,
 			 * we don't check for allowed options filtering, since
 			 * restore mode is special.
 			 */
@@ -3344,30 +3360,30 @@ int ve_devmnt_verify(struct ve_struct *ve, dev_t dev, char *opts, bool new_mount
 	return err;
 }
 
-static inline int ve_mount_allowed(void)
+static inline int ve_try_reserve_mount(struct ve_struct *ve)
 {
-	struct ve_struct *ve = get_exec_env();
-
-	return ve_is_super(ve) || ve->is_pseudosuper ||
-		atomic_read(&ve->mnt_nr) < (int)sysctl_ve_mount_nr;
-}
-
-static inline void ve_mount_nr_inc(struct mount *mnt, struct ve_struct *ve)
-{
-	if (!ve)
-		ve = get_exec_env();
+	int ret = ve_is_super(ve) || ve->is_pseudosuper;
+	/* Ignore limits in ve0 and pseudosuper cases, but still count. */
+	if (ret)
+		atomic_dec(&ve->mnt_avail_nr);
+	else
+		ret = atomic_dec_if_positive(&ve->mnt_avail_nr) >= 0;
 
-	mnt->ve_owner = get_ve(ve);
-	atomic_inc(&ve->mnt_nr);
+	if (ret)
+		get_ve(ve);
+	return ret;
 }
 
-static inline void ve_mount_nr_dec(struct mount *mnt)
+static inline void ve_mount_put(struct mount *mnt, struct ve_struct *ve)
 {
-	struct ve_struct *ve = mnt->ve_owner;
-
-	atomic_dec(&ve->mnt_nr);
+	/*
+	 * ve argument is needed to reuse this function in alloc_vfsmnt error path.
+	 * Other users should pass mnt->ve_owner value.
+	 */
+	atomic_inc(&ve->mnt_avail_nr);
 	put_ve(ve);
-	mnt->ve_owner = NULL;
+	if (mnt)
+		mnt->ve_owner = NULL;
 }
 
 bool is_sb_ve_accessible(struct ve_struct *ve, struct super_block *sb)
@@ -3389,9 +3405,8 @@ bool is_sb_ve_accessible(struct ve_struct *ve, struct super_block *sb)
 
 #else /* CONFIG_VE */
 
-static inline int ve_mount_allowed(void) { return 1; }
-static inline void ve_mount_nr_inc(struct mount *mnt, struct ve_struct *ve) { }
-static inline void ve_mount_nr_dec(struct mount *mnt) { }
+static inline int ve_try_reserve_mount(struct ve_struct *ve) { return 1; }
+static inline void ve_mount_put(struct mount *mnt, struct ve_struct *ve) { }
 #endif /* CONFIG_VE */
 
 /*
diff --git a/include/linux/ve.h b/include/linux/ve.h
index 3facbd1759df..cca0a2bc1aac 100644
--- a/include/linux/ve.h
+++ b/include/linux/ve.h
@@ -88,7 +88,7 @@ struct ve_struct {
 	atomic_t		nd_neigh_nr;
 	unsigned long		meminfo_val;
 
-	atomic_t		mnt_nr; /* number of present VE mounts */
+	atomic_t		mnt_avail_nr; /* number of available VE mounts */
 
 #ifdef CONFIG_COREDUMP
 	char			core_pattern[CORENAME_MAX_SIZE];
diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
index dffb35da22bd..42669a832993 100644
--- a/kernel/ve/ve.c
+++ b/kernel/ve/ve.c
@@ -81,7 +81,7 @@ struct ve_struct ve0 = {
 
 	.arp_neigh_nr		= ATOMIC_INIT(0),
 	.nd_neigh_nr		= ATOMIC_INIT(0),
-	.mnt_nr			= ATOMIC_INIT(0),
+	.mnt_avail_nr		= ATOMIC_INIT(INT_MAX),
 	.meminfo_val		= VE_MEMINFO_SYSTEM,
 	.umh_running_helpers	= ATOMIC_INIT(0),
 	.umh_helpers_waitq	= __WAIT_QUEUE_HEAD_INITIALIZER(ve0.umh_helpers_waitq),
@@ -778,7 +778,7 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
 
 	atomic_set(&ve->arp_neigh_nr, 0);
 	atomic_set(&ve->nd_neigh_nr, 0);
-	atomic_set(&ve->mnt_nr, 0);
+	atomic_set(&ve->mnt_avail_nr, sysctl_ve_mount_nr);
 
 #ifdef CONFIG_COREDUMP
 	strcpy(ve->core_pattern, "core");
@@ -1054,9 +1054,9 @@ static u64 ve_netns_avail_nr_read(struct cgroup_subsys_state *css, struct cftype
 	return atomic_read(&css_to_ve(css)->netns_avail_nr);
 }
 
-static u64 ve_mnt_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
+static s64 ve_mnt_avail_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
 {
-	return atomic_read(&css_to_ve(css)->mnt_nr);
+	return atomic_read(&css_to_ve(css)->mnt_avail_nr);
 }
 
 static u64 ve_netif_max_nr_read(struct cgroup_subsys_state *css, struct cftype *cft)
@@ -1616,8 +1616,8 @@ static struct cftype ve_cftypes[] = {
 		.read_u64		= ve_netns_avail_nr_read,
 	},
 	{
-		.name			= "mnt_nr",
-		.read_u64		= ve_mnt_nr_read,
+		.name			= "mnt_avail_nr",
+		.read_s64		= ve_mnt_avail_nr_read,
 	},
 	{
 		.name			= "netif_max_nr",
-- 
2.47.1


^ permalink raw reply	[relevance 3%]

* [Devel] [PATCH VZ10 v7 7/9] ve: Introduce per-VE failcount
    2026-08-24 13:54  3% ` [Devel] [PATCH VZ10 v7 3/9] ve/fs: Rework per-ve mount count Vladimir Riabchun
@ 2026-08-24 13:54  9% ` Vladimir Riabchun
  2026-08-28 16:52  5%   ` Pavel Tikhomirov
  2026-08-24 13:54  7% ` [Devel] [PATCH VZ10 v7 8/9] selftests/ve: Add more helpers Vladimir Riabchun
  2026-08-24 13:54  6% ` [Devel] [PATCH VZ10 v7 9/9] selftests/ve: Add mount accounting selftest Vladimir Riabchun
  3 siblings, 1 reply; 119+ results
From: Vladimir Riabchun @ 2026-08-24 13:54 UTC (permalink / raw)


It may be useful to have a history of resource limit hits for every VE,
this may simplify debugging and provide some information about the
resources usage.

This information is provided by ve.failcount file, any write to it
resets all failcounts.

To add a new failcounter we need to create a new atomic_t field
name_failcount in ve structure and add a new VE_FC_ENTRY in
ve_failcounts array.

One change, unrelated to failcounts: aio fields are now initialized
in ve0.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Feature: per-ve failcounters
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
 fs/aio.c                 |  1 +
 fs/namespace.c           |  2 ++
 include/linux/ve.h       |  6 ++++
 kernel/bpf/syscall.c     |  1 +
 kernel/ve/ve.c           | 70 ++++++++++++++++++++++++++++++++++++++++
 net/core/dev.c           |  2 ++
 net/core/neighbour.c     |  1 +
 net/core/net_namespace.c |  4 ++-
 8 files changed, 86 insertions(+), 1 deletion(-)

diff --git a/fs/aio.c b/fs/aio.c
index cb63416af135..3fa07cc626f8 100644
--- a/fs/aio.c
+++ b/fs/aio.c
@@ -814,6 +814,7 @@ static struct kioctx *ioctx_alloc(unsigned nr_events)
 	spin_lock(&ve->aio_nr_lock);
 	if (ve->aio_nr + ctx->max_reqs > ve->aio_max_nr ||
 	    ve->aio_nr + ctx->max_reqs < ve->aio_nr) {
+		atomic_inc(&ve->aio_failcount);
 		spin_unlock(&ve->aio_nr_lock);
 		err = -EAGAIN;
 		goto err_ctx;
diff --git a/fs/namespace.c b/fs/namespace.c
index e97f48204617..c30bbc370f2b 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -3371,6 +3371,8 @@ static inline int ve_try_reserve_mount(struct ve_struct *ve)
 
 	if (ret)
 		get_ve(ve);
+	else
+		atomic_inc(&ve->mnt_failcount);
 	return ret;
 }
 
diff --git a/include/linux/ve.h b/include/linux/ve.h
index 5687faad46ff..9e73527e970e 100644
--- a/include/linux/ve.h
+++ b/include/linux/ve.h
@@ -72,12 +72,15 @@ struct ve_struct {
 	struct kmapset_key	proc_perms_key;
 
 	atomic_t		netns_avail_nr;
+	atomic_t		netns_failcount;
 	int			netns_max_nr;
 
 	atomic_t		netif_avail_nr;
+	atomic_t		netif_failcount;
 	int			netif_max_nr;
 
 	atomic_t		bpf_prog_avail_nr;
+	atomic_t		bpf_prog_failcount;
 	int			bpf_prog_max_nr;
 
 	atomic64_t		_uevent_seqnum;
@@ -86,6 +89,7 @@ struct ve_struct {
 
 	atomic_t		arp_neigh_nr;
 	atomic_t		nd_neigh_nr;
+	atomic_t		neigh_tbl_failcount;
 	unsigned long		meminfo_val;
 
 	/*
@@ -94,6 +98,7 @@ struct ve_struct {
 	 * other containers.
 	 */
 	atomic_t		mnt_avail_nr; /* number of available VE mounts */
+	atomic_t		mnt_failcount;
 	int			mnt_max_nr;
 
 #ifdef CONFIG_COREDUMP
@@ -121,6 +126,7 @@ struct ve_struct {
 	spinlock_t		aio_nr_lock;
 	unsigned long		aio_nr;
 	unsigned long		aio_max_nr;
+	atomic_t		aio_failcount;
 #endif
 	struct vfsmount		*devtmpfs_mnt;
 };
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index c94d4240e3d3..9d57e7999ae0 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -2891,6 +2891,7 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
 	if (!bpf_cap && type == BPF_PROG_TYPE_CGROUP_DEVICE) {
 		load_ve = get_exec_env();
 		if (atomic_dec_if_positive(&load_ve->bpf_prog_avail_nr) < 0) {
+			atomic_inc(&load_ve->bpf_prog_failcount);
 			load_ve = NULL;
 			err = -ENOSPC;
 			goto put_token;
diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
index 0f02835765ff..826f72ad0a22 100644
--- a/kernel/ve/ve.c
+++ b/kernel/ve/ve.c
@@ -99,10 +99,13 @@ struct ve_struct ve0 = {
 	.features		= -1,
 	.sched_lat_ve.cur	= &ve0_lat_stats,
 	.netns_avail_nr		= ATOMIC_INIT(INT_MAX),
+	.netns_failcount	= ATOMIC_INIT(0),
 	.netns_max_nr		= INT_MAX,
 	.netif_avail_nr		= ATOMIC_INIT(INT_MAX),
+	.netif_failcount	= ATOMIC_INIT(0),
 	.netif_max_nr		= INT_MAX,
 	.bpf_prog_avail_nr	= ATOMIC_INIT(INT_MAX),
+	.bpf_prog_failcount	= ATOMIC_INIT(0),
 	.bpf_prog_max_nr	= INT_MAX,
 	.fsync_enable		= FSYNC_FILTERED,
 	._randomize_va_space	=
@@ -114,8 +117,16 @@ struct ve_struct ve0 = {
 
 	.arp_neigh_nr		= ATOMIC_INIT(0),
 	.nd_neigh_nr		= ATOMIC_INIT(0),
+	.neigh_tbl_failcount	= ATOMIC_INIT(0),
 	.mnt_avail_nr		= ATOMIC_INIT(INT_MAX),
 	.mnt_max_nr		= INT_MAX,
+	.mnt_failcount		= ATOMIC_INIT(0),
+#ifdef CONFIG_AIO
+	.aio_nr_lock		= __SPIN_LOCK_UNLOCKED(aio_nr_lock),
+	.aio_nr			= 0,
+	.aio_max_nr		= AIO_MAX_NR_DEFAULT,
+	.aio_failcount		= ATOMIC_INIT(0),
+#endif
 	.meminfo_val		= VE_MEMINFO_SYSTEM,
 	.umh_running_helpers	= ATOMIC_INIT(0),
 	.umh_helpers_waitq	= __WAIT_QUEUE_HEAD_INITIALIZER(ve0.umh_helpers_waitq),
@@ -780,12 +791,15 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
 	ve->fsync_enable = FSYNC_FILTERED;
 
 	atomic_set(&ve->netns_avail_nr, NETNS_MAX_NR_DEFAULT);
+	atomic_set(&ve->netns_failcount, 0);
 	ve->netns_max_nr = NETNS_MAX_NR_DEFAULT;
 
 	atomic_set(&ve->netif_avail_nr, NETIF_MAX_NR_DEFAULT);
+	atomic_set(&ve->netif_failcount, 0);
 	ve->netif_max_nr = NETIF_MAX_NR_DEFAULT;
 
 	atomic_set(&ve->bpf_prog_avail_nr, BPF_PROG_MAX_NR_DEFAULT);
+	atomic_set(&ve->bpf_prog_failcount, 0);
 	ve->bpf_prog_max_nr = BPF_PROG_MAX_NR_DEFAULT;
 
 	err = ve_log_init(ve);
@@ -812,7 +826,9 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
 
 	atomic_set(&ve->arp_neigh_nr, 0);
 	atomic_set(&ve->nd_neigh_nr, 0);
+	atomic_set(&ve->neigh_tbl_failcount, 0);
 	ve->mnt_max_nr = MNT_MAX_NR_DEFAULT;
+	atomic_set(&ve->mnt_failcount, 0);
 	atomic_set(&ve->mnt_avail_nr, MNT_MAX_NR_DEFAULT);
 
 #ifdef CONFIG_COREDUMP
@@ -825,6 +841,7 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
 	spin_lock_init(&ve->aio_nr_lock);
 	ve->aio_nr = 0;
 	ve->aio_max_nr = AIO_MAX_NR_DEFAULT;
+	atomic_set(&ve->aio_failcount, 0);
 #endif
 
 	return &ve->css;
@@ -1065,6 +1082,53 @@ VE_RESOURCE(mnt);
 VE_RESOURCE(netif);
 VE_RESOURCE(bpf_prog);
 
+static const struct ve_failcount_entry {
+	const char *name;
+	size_t offset;
+} ve_failcounts[] = {
+#define VE_FC_ENTRY(name) { #name, offsetof(struct ve_struct, name##_failcount) }
+	VE_FC_ENTRY(netns),
+	VE_FC_ENTRY(mnt),
+	VE_FC_ENTRY(netif),
+	VE_FC_ENTRY(bpf_prog),
+	VE_FC_ENTRY(neigh_tbl),
+#ifdef CONFIG_AIO
+	VE_FC_ENTRY(aio),
+#endif
+	{}
+};
+
+static int ve_failcount_read(struct seq_file *sf, void *v)
+{
+	struct ve_struct *ve = css_to_ve(seq_css(sf));
+	const struct ve_failcount_entry *entry;
+	atomic_t *fc;
+
+	for (entry = ve_failcounts; entry->name; entry++) {
+		fc = (void *)ve + entry->offset;
+		seq_printf(sf, "%s: %d\n", entry->name, atomic_read(fc));
+	}
+	return 0;
+}
+
+static ssize_t ve_failcount_write(struct kernfs_open_file *of, char *buf,
+				  size_t nbytes, loff_t off)
+{
+	struct ve_struct *ve = css_to_ve(of_css(of));
+	const struct ve_failcount_entry *entry;
+	atomic_t *fc;
+
+	if (!ve_is_super(get_exec_env()) && !ve->is_pseudosuper)
+		return -EPERM;
+
+	for (entry = ve_failcounts; entry->name; entry++) {
+		fc = (void *)ve + entry->offset;
+		atomic_set(fc, 0);
+	}
+
+	return nbytes;
+}
+
 static int ve_os_release_read(struct seq_file *sf, void *v)
 {
 	struct cgroup_subsys_state *css = seq_css(sf);
@@ -1602,6 +1666,12 @@ static struct cftype ve_cftypes[] = {
 		.flags			= CFTYPE_NOT_ON_ROOT,
 		.write_u64		= ve_rpc_kill_write,
 	},
+	{
+		.name			= "failcount",
+		.flags			= CFTYPE_NOT_ON_ROOT,
+		.seq_show		= ve_failcount_read,
+		.write			= ve_failcount_write,
+	},
 	{ }
 };
 
diff --git a/net/core/dev.c b/net/core/dev.c
index c7dddb200489..05e0b9b6ba23 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -10997,6 +10997,7 @@ int register_netdevice(struct net_device *dev)
 
 	ret = -ENOMEM;
 	if (atomic_dec_if_positive(&net->owner_ve->netif_avail_nr) < 0) {
+		atomic_inc(&net->owner_ve->netif_failcount);
 		ve_pr_warn_ratelimited(VE_LOG_BOTH,
 			"CT%s: hits max number of network devices, "
 			"increase ve::netif_max_nr parameter\n",
@@ -12211,6 +12212,7 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net,
 
 	err = -ENOMEM;
 	if (atomic_dec_if_positive(&net->owner_ve->netif_avail_nr) < 0) {
+		atomic_inc(&net->owner_ve->netif_failcount);
 		ve_pr_warn_ratelimited(VE_LOG_BOTH,
 			"CT%s: hits max number of network devices, "
 			"increase ve::netif_max_nr parameter\n",
diff --git a/net/core/neighbour.c b/net/core/neighbour.c
index f90deb17fb25..57a49d9c98a7 100644
--- a/net/core/neighbour.c
+++ b/net/core/neighbour.c
@@ -520,6 +520,7 @@ static struct neighbour *neigh_alloc(struct neigh_table *tbl,
 	    (glob_entries >= READ_ONCE(tbl->gc_thresh2) &&
 	     time_after(now, READ_ONCE(tbl->last_flush) + 5 * HZ))) {
 		if (!neigh_forced_gc(tbl, ve) && entries >= gc_thresh3) {
+			atomic_inc(&ve->neigh_tbl_failcount);
 			net_info_ratelimited("%s: neighbor table overflow!\n",
 					     tbl->id);
 			NEIGH_CACHE_STAT_INC(tbl, table_fulls);
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index b3d54cad984a..9a3376d2682f 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -486,8 +486,10 @@ void net_drop_ns(void *p)
 #ifdef CONFIG_VE
 static int dec_netns_avail(struct ve_struct *ve)
 {
-	if (atomic_dec_if_positive(&ve->netns_avail_nr) < 0)
+	if (atomic_dec_if_positive(&ve->netns_avail_nr) < 0) {
+		atomic_inc(&ve->netns_failcount);
 		return -ENOSPC;
+	}
 	return 0;
 }
 
-- 
2.47.1


^ permalink raw reply	[relevance 9%]

* [Devel] [PATCH VZ10 v7 8/9] selftests/ve: Add more helpers
    2026-08-24 13:54  3% ` [Devel] [PATCH VZ10 v7 3/9] ve/fs: Rework per-ve mount count Vladimir Riabchun
  2026-08-24 13:54  9% ` [Devel] [PATCH VZ10 v7 7/9] ve: Introduce per-VE failcount Vladimir Riabchun
@ 2026-08-24 13:54  7% ` Vladimir Riabchun
  2026-08-24 13:54  6% ` [Devel] [PATCH VZ10 v7 9/9] selftests/ve: Add mount accounting selftest Vladimir Riabchun
  3 siblings, 0 replies; 119+ results
From: Vladimir Riabchun @ 2026-08-24 13:54 UTC (permalink / raw)


Some more read/write helpers may be useful.

Also, add a helper to execute functions in child process with
switched namespaces and cgroup.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Feature: per-ve failcounters
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
 tools/testing/selftests/ve/ve_selftest.h | 81 ++++++++++++++++++++++--
 1 file changed, 75 insertions(+), 6 deletions(-)

diff --git a/tools/testing/selftests/ve/ve_selftest.h b/tools/testing/selftests/ve/ve_selftest.h
index 69c0a52dd7ef..48bb7d1871bd 100644
--- a/tools/testing/selftests/ve/ve_selftest.h
+++ b/tools/testing/selftests/ve/ve_selftest.h
@@ -43,6 +43,14 @@ static inline int write_file_at(int dirfd, const char *path, const char *val)
 	return (ret == (int)len) ? 0 : -1;
 }
 
+static inline int write_u64_at(int dirfd, const char *path, unsigned long long val)
+{
+	char s[20];
+
+	snprintf(s, sizeof(s), "%llu", val);
+	return write_file_at(dirfd, path, s);
+}
+
 static inline int read_file_at(int dirfd, const char *path, char *buf,
 			       size_t buflen)
 {
@@ -73,19 +81,31 @@ static inline int read_u64_at(int dirfd, const char *path,
 			      unsigned long long *out)
 {
 	char buf[32] = {0}, *end;
-	int fd, ret;
+	int ret;
 
-	fd = openat(dirfd, path, O_RDONLY);
-	if (fd < 0)
+	ret = read_file_at(dirfd, path, buf, sizeof(buf));
+	if (ret <= 0)
 		return -1;
 
-	ret = read(fd, buf, sizeof(buf) - 1);
-	close(fd);
+	errno = 0;
+	*out = strtoull(buf, &end, 10);
+	if (errno || end == buf)
+		return -1;
+	return 0;
+}
+
+static inline int read_s32_at(int dirfd, const char *path,
+			      int *out)
+{
+	char buf[32] = {0}, *end;
+	int ret;
+
+	ret = read_file_at(dirfd, path, buf, sizeof(buf));
 	if (ret <= 0)
 		return -1;
 
 	errno = 0;
-	*out = strtoull(buf, &end, 10);
+	*out = strtol(buf, &end, 10);
 	if (errno || end == buf)
 		return -1;
 	return 0;
@@ -134,6 +154,55 @@ static inline int enter_cgroup(int cgv2_fd, int ctid)
 	return ret;
 }
 
+/*
+ * Run function in VE cgroup and new namespaces.
+ *
+ * Namespaces are provided via unshare_flags.
+ * CLONE_NEWVE flag is set by this function.
+ * Return values:
+ *  -  0 if function returns zero
+ *  - -1 if function returns negative value
+ *  -  1 if setup fails or function returns positive value
+ */
+static inline int run_in_ve(int cgv2_fd, int ctid, int unshare_flags,
+		int (*fn)(void *), void *arg)
+{
+	int status;
+	pid_t pid;
+
+	unshare_flags |= CLONE_NEWVE;
+	pid = fork();
+	if (pid < 0) {
+		fprintf(stderr, "%s: fork failed\n", __func__);
+		return 1;
+	}
+	if (pid == 0) {
+		int ret;
+
+		if (enter_cgroup(cgv2_fd, ctid) < 0) {
+			fprintf(stderr, "%s: enter_cgroup failed\n", __func__);
+			_exit(255);
+		}
+		if (unshare(unshare_flags) < 0) {
+			fprintf(stderr, "%s: unshare(%d) failed\n",
+				__func__, unshare_flags);
+			_exit(255);
+		}
+		ret = fn(arg);
+		if (ret < 0)
+			ret = 1;
+		else if (ret > 0)
+			ret = 255;
+		_exit(ret);
+	}
+	if (waitpid(pid, &status, 0) < 0 || !WIFEXITED(status) || WEXITSTATUS(status) == 255)
+		return 1;
+	if (WEXITSTATUS(status))
+		return -1;
+	return 0;
+
+}
+
 /*
  * Create a fresh VE cgroup at the first free id at or after @from and unhide
  * its ve.* control files. Return the new id, or -1.
-- 
2.47.1


^ permalink raw reply	[relevance 7%]

* [Devel] [PATCH VZ10 v7 9/9] selftests/ve: Add mount accounting selftest
                     ` (2 preceding siblings ...)
  2026-08-24 13:54  7% ` [Devel] [PATCH VZ10 v7 8/9] selftests/ve: Add more helpers Vladimir Riabchun
@ 2026-08-24 13:54  6% ` Vladimir Riabchun
  3 siblings, 0 replies; 119+ results
From: Vladimir Riabchun @ 2026-08-24 13:54 UTC (permalink / raw)


There are 6 test cases, covered in the new test:
1. Simple mount accouting correctness, just mount/umount.
2. Verification of correct limit hits and changes, including
   negative values.
3. Partial mounts test, when mount limit is hit in the middle
   of creation.
4. Test that enabled pseudosuper allows overuse.
5. Test that pseudosuper doesn't affect mount accounting.
6. Failcount feature verification.

https://virtuozzo.atlassian.net/browse/VSTOR-135520

Feature: per-ve failcounters
Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
---
 tools/testing/selftests/ve/.gitignore         |   1 +
 tools/testing/selftests/ve/Makefile           |   1 +
 .../selftests/ve/ve_mount_accounting_test.c   | 421 ++++++++++++++++++
 3 files changed, 423 insertions(+)
 create mode 100644 tools/testing/selftests/ve/ve_mount_accounting_test.c

diff --git a/tools/testing/selftests/ve/.gitignore b/tools/testing/selftests/ve/.gitignore
index afa4c568c2c9..3df4d05888dc 100644
--- a/tools/testing/selftests/ve/.gitignore
+++ b/tools/testing/selftests/ve/.gitignore
@@ -1,2 +1,3 @@
 ve_ns_owner_test
 ve_perms_test
+ve_mount_accounting_test
diff --git a/tools/testing/selftests/ve/Makefile b/tools/testing/selftests/ve/Makefile
index ec40cbc7b3a1..c6efe7c4b4fb 100644
--- a/tools/testing/selftests/ve/Makefile
+++ b/tools/testing/selftests/ve/Makefile
@@ -4,5 +4,6 @@ CFLAGS += -g -Wall -O2
 
 TEST_GEN_PROGS += ve_ns_owner_test
 TEST_GEN_PROGS += ve_perms_test
+TEST_GEN_PROGS += ve_mount_accounting_test
 
 include ../lib.mk
diff --git a/tools/testing/selftests/ve/ve_mount_accounting_test.c b/tools/testing/selftests/ve/ve_mount_accounting_test.c
new file mode 100644
index 000000000000..4426ebc6a1ac
--- /dev/null
+++ b/tools/testing/selftests/ve/ve_mount_accounting_test.c
@@ -0,0 +1,421 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ve_mount_accounting selftests
+ *
+ * Tests to check the correctness of mount accounting.
+ */
+#define _GNU_SOURCE
+#include <asm/unistd.h>
+#include <linux/sched.h>
+#include <linux/limits.h>
+#include <sys/wait.h>
+#include <sys/syscall.h>
+#include <sys/stat.h>
+#include <sys/mount.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "../kselftest_harness.h"
+#include "ve_selftest.h"
+
+#define TMP_DIR			"/ve-mnt-tmp/"
+#define VE_MOUNTS_MAX		128
+
+static int set_pseudosuper(int cgv2_fd, int ctid, int value)
+{
+	char path[64];
+
+	snprintf(path, sizeof(path), "%d/ve.pseudosuper", ctid);
+	return write_u64_at(cgv2_fd, path, value);
+}
+
+static int _create_mount(void *id_ptr)
+{
+	char path[PATH_MAX];
+	int id = *(int *)id_ptr, ret;
+
+	snprintf(path, sizeof(path), TMP_DIR "%d", id);
+
+	if (mkdir(path, 0755) < 0) {
+		fprintf(stderr, "Failed to create directory %s: %s\n", path, strerror(errno));
+		return -1;
+	}
+	ret = mount("tmpfs", path, "tmpfs", 0, "size=1M");
+	if (!ret)
+		return 0;
+	fprintf(stderr, "Failed to mount tmpfs to %s: %s\n", path, strerror(errno));
+
+	rmdir(path);
+	return ret;
+}
+
+static int create_mount(int cgv2_fd, int ctid, int id)
+{
+	int ret = run_in_ve(cgv2_fd, ctid, CLONE_NEWVE, _create_mount, &id);
+	/*
+	 * If mount fails, cleanup by free_vfsmnt will be called
+	 * via call_rcu, need to wait for update.
+	 */
+	sleep(1);
+	return ret;
+}
+
+static int _destroy_mount(void *id_ptr)
+{
+	char path[PATH_MAX];
+	struct stat st;
+	int id = *(int *)id_ptr;
+
+	snprintf(path, sizeof(path), TMP_DIR "%d", id);
+
+	if (stat(path, &st))
+		return 1;
+	if (umount(path)) {
+		fprintf(stderr, "failed to umount directory %s: %s\n", path, strerror(errno));
+		return -1;
+	}
+	if (rmdir(path)) {
+		fprintf(stderr, "failed to remove directory %s: %s\n", path, strerror(errno));
+		return -1;
+	}
+	return 0;
+}
+
+static int destroy_mount(int cgv2_fd, int ctid, int id)
+{
+	int ret = run_in_ve(cgv2_fd, ctid, CLONE_NEWVE, _destroy_mount, &id);
+	/* free_vfsmnt is called via call_rcu, need to wait for update */
+	sleep(1);
+	return ret;
+}
+
+#define MAX_MNT_ID 32
+
+static int get_free_mnt_id(void)
+{
+	int i;
+	struct stat st;
+	char path[PATH_MAX];
+
+	for (i = 0; i < MAX_MNT_ID; i++) {
+		snprintf(path, sizeof(path), TMP_DIR "%d", i);
+		if (stat(path, &st))
+			return i;
+	}
+	return -1;
+}
+
+static int get_mount_cost(int cgv2_fd, int ctid)
+{
+	int avail1, avail2, mnt_id;
+	char path[64];
+
+	mnt_id = get_free_mnt_id();
+
+	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", ctid);
+	if (mnt_id < 0 ||
+	    read_s32_at(cgv2_fd, path, &avail1) ||
+	    create_mount(cgv2_fd, ctid, mnt_id) ||
+	    read_s32_at(cgv2_fd, path, &avail2) ||
+	    destroy_mount(cgv2_fd, ctid, mnt_id))
+		return -1;
+
+	return avail1 - avail2;
+}
+
+/* Expect mount success and return new avail value */
+static int mount_and_get_avail(struct __test_metadata *_metadata,
+			int cgv2_fd, int ctid, int mnt_id)
+{
+	char path_avail[64];
+	int mnt_avail_nr;
+
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", ctid);
+
+	ASSERT_EQ(create_mount(cgv2_fd, ctid, mnt_id), 0);
+	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	return mnt_avail_nr;
+}
+
+/* Expect mount failure and ensure intact avail number */
+static void assert_mount_fails(struct __test_metadata *_metadata,
+			int cgv2_fd, int ctid, int mnt_id, int avail_count)
+{
+	char path_avail[64];
+	int mnt_avail_nr;
+
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", ctid);
+
+	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, avail_count);
+	ASSERT_LT(create_mount(cgv2_fd, ctid, mnt_id), 0);
+	ASSERT_EQ(read_s32_at(cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, avail_count);
+}
+
+FIXTURE(ve_mnt_acc)
+{
+	int cgv2_fd;
+	int ctid;
+};
+
+FIXTURE_SETUP(ve_mnt_acc)
+{
+	unsigned long long initial_mnt_avail_nr;
+	char path[64];
+
+	self->cgv2_fd = mount_cg2_fd();
+	ASSERT_GE(self->cgv2_fd, 0);
+	mkdir(TMP_DIR, 0755);
+
+	ASSERT_EQ(write_file_at(self->cgv2_fd, "cgroup.subtree_control",
+		  VE_CONTROLLERS), 0);
+
+	self->ctid = make_ve(self->cgv2_fd, CTID_MIN);
+	ASSERT_GE(self->ctid, 0);
+
+	snprintf(path, sizeof(path), "%d/ve.mnt_max_nr", self->ctid);
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path, VE_MOUNTS_MAX), 0);
+
+	/*
+	 * The new ve cgroup has not been entered by anything yet, so its
+	 * mnt_avail_nr counter should be VE_MOUNTS_MAX.
+	 */
+	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", self->ctid);
+	ASSERT_EQ(read_u64_at(self->cgv2_fd, path, &initial_mnt_avail_nr), 0);
+	ASSERT_EQ(initial_mnt_avail_nr, VE_MOUNTS_MAX);
+};
+
+FIXTURE_TEARDOWN(ve_mnt_acc)
+{
+	for (int i = 0; i < MAX_MNT_ID; i++)
+		_destroy_mount((void *)&i);
+
+	destroy_ve(self->cgv2_fd, self->ctid);
+	close(self->cgv2_fd);
+	rmdir(TMP_DIR);
+}
+
+/* Simple test to check mount/umount accounting correctness */
+TEST_F(ve_mnt_acc, mount_umount)
+{
+	int original_mnt_avail, mnt_avail_nr;
+	char path[64];
+
+	snprintf(path, sizeof(path), "%d/ve.mnt_avail_nr", self->ctid);
+
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path, &original_mnt_avail), 0);
+
+	ASSERT_LT(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
+		  original_mnt_avail);
+
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, original_mnt_avail);
+}
+
+/* Test mount limit hits */
+TEST_F(ve_mnt_acc, hit_limits)
+{
+	int original_mnt_avail, mnt_avail_nr, mnt_cost;
+	int original_have_mnt;
+	char path_avail[64], path_max_nr[64];
+
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
+	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
+
+	mnt_cost = get_mount_cost(self->cgv2_fd, self->ctid);
+	ASSERT_GE(mnt_cost, 1);
+
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &original_mnt_avail), 0);
+	original_have_mnt = VE_MOUNTS_MAX - original_mnt_avail;
+
+	/* Step 1: reduce number of available mounts to mnt_cost */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 1 * mnt_cost), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, 1 * mnt_cost);
+
+	/* Step 2: do one mount, no mounts should be available */
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
+		  0);
+
+	/* Step 3: check that one more mount fails */
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 1, 0);
+
+	/* Step 4: increase mount limit a little bit, mount should still fail */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr,
+				original_have_mnt + 2 * mnt_cost - 1), 0);
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 1, mnt_cost - 1);
+
+	/* Step 5: increase by 1 and win now */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 2 * mnt_cost), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, mnt_cost);
+
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 1),
+		  0);
+
+	/* Step 6: reduce mnt_max_nr so we have more mounts than allowed */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, original_have_mnt + 1 * mnt_cost), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, -1 * mnt_cost);
+
+	/* Step 7: try to do mount when avail < 0, ensure number is intact */
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, -1 * mnt_cost);
+
+	/* Step 8: remove one mount, check avail value update, mount should fail */
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, 0);
+
+	/* Step 9: remove one more mount and check that new mount succeeds */
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 1), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, 1 * mnt_cost);
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 2),
+		  0);
+
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 2), 0);
+}
+
+/*
+ * Mount propagation makes one mount cost more.
+ * This test checks that if we run out of mounts in the middle of creating
+ * a new one, everything is restored smoothly and nothing leaks.
+ */
+TEST_F(ve_mnt_acc, partial_mounts)
+{
+	char path_avail[64], path_max_nr[64];
+	int mount_cost, i, orig_have, orig_mnt_avail;
+
+	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
+
+	mount_cost = get_mount_cost(self->cgv2_fd, self->ctid);
+	ASSERT_GE(mount_cost, 1);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &orig_mnt_avail), 0);
+	orig_have = VE_MOUNTS_MAX - orig_mnt_avail;
+
+	if (mount_cost == 1)
+		SKIP(return, "mount cost is 1, no partial mounts possible");
+
+	for (i = 0; i < mount_cost; i++) {
+		ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, orig_have + i), 0);
+		assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 0, i);
+	}
+}
+
+/* Test that pseudosuper allows negative avail with correct accounting. */
+TEST_F(ve_mnt_acc, pseudosuper_allows_overuse)
+{
+	int orig_mnt_avail, orig_have;
+	int mount_cost;
+	char path_avail[64], path_max_nr[64];
+
+	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &orig_mnt_avail), 0);
+	orig_have = VE_MOUNTS_MAX - orig_mnt_avail;
+
+	mount_cost = get_mount_cost(self->cgv2_fd, self->ctid);
+	ASSERT_GE(mount_cost, 1);
+
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, orig_have + mount_cost), 0);
+	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 1), 0);
+
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
+		  0);
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 1),
+		  -1 * mount_cost);
+
+	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 0), 0);
+
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, -1 * mount_cost);
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
+	assert_mount_fails(_metadata, self->cgv2_fd, self->ctid, 2, 0);
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 1), 0);
+	ASSERT_EQ(get_mount_cost(self->cgv2_fd, self->ctid), mount_cost);
+}
+
+/* Test that pseudosuper doesn't disable accounting. */
+TEST_F(ve_mnt_acc, pseudosuper_continues_accounting)
+{
+	int orig_mnt_avail, mount_cost, mnt_avail_nr;
+	char path_avail[64];
+
+	snprintf(path_avail, sizeof(path_avail), "%d/ve.mnt_avail_nr", self->ctid);
+
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &orig_mnt_avail), 0);
+	mount_cost = get_mount_cost(self->cgv2_fd, self->ctid);
+	ASSERT_GE(mount_cost, 1);
+
+	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 0), 0);
+
+	/* mnt 0 - mounted without pseudosuper, umounted with it. */
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0),
+		  orig_mnt_avail - mount_cost);
+	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 1), 0);
+
+	/* Cost is the same when mount/umount happen under pseudosuper. */
+	ASSERT_EQ(get_mount_cost(self->cgv2_fd, self->ctid), mount_cost);
+
+	/* mnt 1 - mounted with pseudosuper, umounted without it. */
+	ASSERT_EQ(mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 1),
+		  orig_mnt_avail - 2 * mount_cost);
+
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, orig_mnt_avail - mount_cost);
+
+	ASSERT_EQ(set_pseudosuper(self->cgv2_fd, self->ctid, 0), 0);
+
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 1), 0);
+	ASSERT_EQ(read_s32_at(self->cgv2_fd, path_avail, &mnt_avail_nr), 0);
+	ASSERT_EQ(mnt_avail_nr, orig_mnt_avail);
+}
+
+/* Test failcount feature */
+TEST_F(ve_mnt_acc, failcount)
+{
+	char path_fc[64], failcount_str[512], path_max_nr[64];
+
+	snprintf(path_fc, sizeof(path_fc), "%d/ve.failcount", self->ctid);
+	snprintf(path_max_nr, sizeof(path_max_nr), "%d/ve.mnt_max_nr", self->ctid);
+
+	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
+			       failcount_str, sizeof(failcount_str)), 0);
+	ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL);
+
+	/* Check successful mount doesn't affect failcount */
+	mount_and_get_avail(_metadata, self->cgv2_fd, self->ctid, 0);
+	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
+			       failcount_str, sizeof(failcount_str)), 0);
+	ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL);
+	ASSERT_EQ(destroy_mount(self->cgv2_fd, self->ctid, 0), 0);
+
+	/* Check failcount update when mount fails */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, 0), 0);
+	ASSERT_LT(create_mount(self->cgv2_fd, self->ctid, 1), 0);
+	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
+			       failcount_str, sizeof(failcount_str)), 0);
+	ASSERT_TRUE(strstr(failcount_str, "mnt: 1\n") != NULL);
+
+	/* Check failcount flush */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_fc, 0), 0);
+	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
+			       failcount_str, sizeof(failcount_str)), 0);
+	ASSERT_TRUE(strstr(failcount_str, "mnt: 0\n") != NULL);
+
+	/* Check failcount update when mount fails again */
+	ASSERT_EQ(write_u64_at(self->cgv2_fd, path_max_nr, 0), 0);
+	ASSERT_LT(create_mount(self->cgv2_fd, self->ctid, 1), 0);
+	ASSERT_GE(read_file_at(self->cgv2_fd, path_fc,
+			       failcount_str, sizeof(failcount_str)), 0);
+	ASSERT_TRUE(strstr(failcount_str, "mnt: 1\n") != NULL);
+}
+
+TEST_HARNESS_MAIN
-- 
2.47.1


^ permalink raw reply	[relevance 6%]

* [Devel] [PATCH RHEL10 COMMIT] ms/selftests: net: Adapt ethtool mq tests to fix in qdisc graft
@ 2026-08-24 14:36  5% Konstantin Khorenko
  0 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-24 14:36 UTC (permalink / raw)


The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.8.vz10
------>
commit b7c858a8bd7145f399251227af90c663ac1154e3
Author: Victor Nogueira <victor@mojatatu.com>
Date:   Sat Jan 11 18:15:15 2025 -0300

    ms/selftests: net: Adapt ethtool mq tests to fix in qdisc graft
    
    Because of patch[1] the graft behaviour changed
    
    So the command:
    
    tcq replace parent 100:1 handle 204:
    
    Is no longer valid and will not delete 100:4 added by command:
    
    tcq replace parent 100:4 handle 204: pfifo_fast
    
    So to maintain the original behaviour, this patch manually deletes 100:4
    and grafts 100:1
    
    Note: This change will also work fine without [1]
    
    [1] https://lore.kernel.org/netdev/20250111151455.75480-1-jhs at mojatatu.com/T/#u
    
    Signed-off-by: Victor Nogueira <victor@mojatatu.com>
    Reviewed-by: Jamal Hadi Salim <jhs@mojatatu.com>
    Signed-off-by: David S. Miller <davem@davemloft.net>
    (cherry picked from commit 0a5b8fff01bde1b9908f00004c676f2e2459333b)
    
    https://virtuozzo.atlassian.net/browse/VSTOR-139651
    Feature: fix selftests
    Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
---
 .../testing/selftests/drivers/net/netdevsim/tc-mq-visibility.sh  | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/tools/testing/selftests/drivers/net/netdevsim/tc-mq-visibility.sh b/tools/testing/selftests/drivers/net/netdevsim/tc-mq-visibility.sh
index fd13c8cfb7a8..b411fe66510f 100755
--- a/tools/testing/selftests/drivers/net/netdevsim/tc-mq-visibility.sh
+++ b/tools/testing/selftests/drivers/net/netdevsim/tc-mq-visibility.sh
@@ -58,9 +58,12 @@ for root in mq mqprio; do
     ethtool -L $NDEV combined 4
     n_child_assert 4 "One real queue, rest default"
 
-    # Graft some
-    tcq replace parent 100:1 handle 204:
-    n_child_assert 3 "Grafted"
+    # Remove real one
+    tcq del parent 100:4 handle 204:
+
+    # Replace default with pfifo
+    tcq replace parent 100:1 handle 205: pfifo limit 1000
+    n_child_assert 3 "Deleting real one, replacing default one with pfifo"
 
     ethtool -L $NDEV combined 1
     n_child_assert 1 "Grafted, one"

^ permalink raw reply	[relevance 5%]

* [Devel] [PATCH RHEL10 COMMIT] ms/selftests: net: Adapt ethtool mq tests to fix in qdisc graft
  2026-08-21 16:41  5% [Devel] [PATCH vz10 1/3] selftests: net: Adapt ethtool mq tests to fix in qdisc graft Eva Kurchatova
  2026-08-24 13:36  0% ` Konstantin Khorenko
@ 2026-08-24 14:36  5% ` Konstantin Khorenko
  1 sibling, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-24 14:36 UTC (permalink / raw)


The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.8.vz10
------>
commit edf5885dd6749ceb3a09c536a5d1ff81cc45cf66
Author: Victor Nogueira <victor@mojatatu.com>
Date:   Sat Jan 11 18:15:15 2025 -0300

    ms/selftests: net: Adapt ethtool mq tests to fix in qdisc graft
    
    Because of patch[1] the graft behaviour changed
    
    So the command:
    
    tcq replace parent 100:1 handle 204:
    
    Is no longer valid and will not delete 100:4 added by command:
    
    tcq replace parent 100:4 handle 204: pfifo_fast
    
    So to maintain the original behaviour, this patch manually deletes 100:4
    and grafts 100:1
    
    Note: This change will also work fine without [1]
    
    [1] https://lore.kernel.org/netdev/20250111151455.75480-1-jhs at mojatatu.com/T/#u
    
    Signed-off-by: Victor Nogueira <victor@mojatatu.com>
    Reviewed-by: Jamal Hadi Salim <jhs@mojatatu.com>
    Signed-off-by: David S. Miller <davem@davemloft.net>
    (cherry picked from commit 0a5b8fff01bde1b9908f00004c676f2e2459333b)
    
    https://virtuozzo.atlassian.net/browse/VSTOR-139651
    Feature: fix selftests
    Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
    Reviewed-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
 .../testing/selftests/drivers/net/netdevsim/tc-mq-visibility.sh  | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/tools/testing/selftests/drivers/net/netdevsim/tc-mq-visibility.sh b/tools/testing/selftests/drivers/net/netdevsim/tc-mq-visibility.sh
index fd13c8cfb7a8..b411fe66510f 100755
--- a/tools/testing/selftests/drivers/net/netdevsim/tc-mq-visibility.sh
+++ b/tools/testing/selftests/drivers/net/netdevsim/tc-mq-visibility.sh
@@ -58,9 +58,12 @@ for root in mq mqprio; do
     ethtool -L $NDEV combined 4
     n_child_assert 4 "One real queue, rest default"
 
-    # Graft some
-    tcq replace parent 100:1 handle 204:
-    n_child_assert 3 "Grafted"
+    # Remove real one
+    tcq del parent 100:4 handle 204:
+
+    # Replace default with pfifo
+    tcq replace parent 100:1 handle 205: pfifo limit 1000
+    n_child_assert 3 "Deleting real one, replacing default one with pfifo"
 
     ethtool -L $NDEV combined 1
     n_child_assert 1 "Grafted, one"

^ permalink raw reply	[relevance 5%]

* [Devel] [PATCH vz10 v2] selftests: ve_printk: match the conntrack overflow message
  @ 2026-08-24 14:53  4% ` Konstantin Khorenko
  2026-08-24 14:54  4%   ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
  0 siblings, 1 reply; 119+ results
From: Konstantin Khorenko @ 2026-08-24 14:53 UTC (permalink / raw)


From: Eva Kurchatova <eva.kurchatova@virtuozzo.com>

The kernel prints

  VE%s: nf_conntrack table full in netns %u, dropping packet

while the test looks for "nf_conntrack table full, dropping packet".
The "in netns %u" part sits between the two halves the test expects to
be adjacent, so strstr() never matched: ve_log_both counted zero
messages and failed even when the container produced all ten of them.

Match the part of the message which carries no netns number, and drop
the stale quote from the comment above the test.

Fixes: d5ec8836f50d ("tests: add ve_printk selftest")
https://virtuozzo.atlassian.net/browse/VSTOR-139673
Feature: fix vz selftests
Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
Changes in v2:
- fix the stale quote in the comment above ve_log_both as well.  It
  still spelled out the message the test used to look for, which is
  exactly what would mislead the next reader.
- add a Fixes: tag.  The kernel message has carried "in netns %u"
  since 2015, so the test has been looking for a string that never
  existed rather than falling behind a later kernel change.
- commit message: quote the kernel format string instead of a made up
  log line with a VE0 prefix.  Inside the test the message is emitted
  for the container's netns, so the prefix is the container, not VE0.

 tools/testing/selftests/ve_printk/ve_printk_test.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/tools/testing/selftests/ve_printk/ve_printk_test.c b/tools/testing/selftests/ve_printk/ve_printk_test.c
index 2df672d75e00..020df691e5d5 100644
--- a/tools/testing/selftests/ve_printk/ve_printk_test.c
+++ b/tools/testing/selftests/ve_printk/ve_printk_test.c
@@ -286,7 +286,7 @@ int ve_printk_test_logboth(void)
 	ret = TEST_RATELIMIT_BURST;
 	/* verify that only 10 records were added */
 	while (fgets(buf, sizeof(buf), pdmesg)) {
-		if (strstr(buf, "nf_conntrack table full, dropping packet")) {
+		if (strstr(buf, "nf_conntrack table full")) {
 			ret--;
 		}
 	}
@@ -614,10 +614,10 @@ TEST_F(ve_printk, ve0_log)
  * Test verifies net_veboth_ratelimited function which logs messages simultaneously
  * to both the container (VE_LOG) and VE0 (VE0_LOG), but with ratelimit throttling.
  * Inside the container, a small conntrack table (size 2) is configured, then many ping
- * packets are sent, causing table overflow and generation of "nf_conntrack table full,
- * dropping packet" messages. The test verifies that exactly TEST_RATELIMIT_BURST (10)
- * messages appeared on the host - ratelimit should limit the number of messages even
- * if more were generated.
+ * packets are sent, causing table overflow and generation of "nf_conntrack table
+ * full in netns N, dropping packet" messages. The test verifies that exactly
+ * TEST_RATELIMIT_BURST (10) messages appeared on the host - ratelimit should
+ * limit the number of messages even if more were generated.
  */
 TEST_F(ve_printk, ve_log_both)
 {
@@ -635,7 +635,7 @@ TEST_F(ve_printk, ve_log_both)
 	ASSERT_EQ(ret, 0);
 
 	while (fgets(buf, sizeof(buf), fdmesg)) {
-		if (strstr(buf, "nf_conntrack table full, dropping packet")) {
+		if (strstr(buf, "nf_conntrack table full")) {
 			ret++;
 		}
 	}
-- 
2.47.1


^ permalink raw reply	[relevance 4%]

* [Devel] [PATCH RHEL10 COMMIT] selftests: ve_printk: match the conntrack overflow message
  2026-08-24 14:53  4% ` [Devel] [PATCH vz10 v2] " Konstantin Khorenko
@ 2026-08-24 14:54  4%   ` Konstantin Khorenko
  0 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-24 14:54 UTC (permalink / raw)


The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.8.vz10
------>
commit b8602e1ad08847d26d25dbf1426cf1247ef24c26
Author: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
Date:   Fri Aug 21 18:32:21 2026 +0300

    selftests: ve_printk: match the conntrack overflow message
    
    The kernel prints
    
      VE%s: nf_conntrack table full in netns %u, dropping packet
    
    while the test looks for "nf_conntrack table full, dropping packet".
    The "in netns %u" part sits between the two halves the test expects to
    be adjacent, so strstr() never matched: ve_log_both counted zero
    messages and failed even when the container produced all ten of them.
    
    Match the part of the message which carries no netns number, and drop
    the stale quote from the comment above the test.
    
    Fixes: d5ec8836f50d ("tests: add ve_printk selftest")
    https://virtuozzo.atlassian.net/browse/VSTOR-139673
    Feature: fix vz selftests
    Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
    Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
 tools/testing/selftests/ve_printk/ve_printk_test.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/tools/testing/selftests/ve_printk/ve_printk_test.c b/tools/testing/selftests/ve_printk/ve_printk_test.c
index 2df672d75e00..020df691e5d5 100644
--- a/tools/testing/selftests/ve_printk/ve_printk_test.c
+++ b/tools/testing/selftests/ve_printk/ve_printk_test.c
@@ -286,7 +286,7 @@ int ve_printk_test_logboth(void)
 	ret = TEST_RATELIMIT_BURST;
 	/* verify that only 10 records were added */
 	while (fgets(buf, sizeof(buf), pdmesg)) {
-		if (strstr(buf, "nf_conntrack table full, dropping packet")) {
+		if (strstr(buf, "nf_conntrack table full")) {
 			ret--;
 		}
 	}
@@ -614,10 +614,10 @@ TEST_F(ve_printk, ve0_log)
  * Test verifies net_veboth_ratelimited function which logs messages simultaneously
  * to both the container (VE_LOG) and VE0 (VE0_LOG), but with ratelimit throttling.
  * Inside the container, a small conntrack table (size 2) is configured, then many ping
- * packets are sent, causing table overflow and generation of "nf_conntrack table full,
- * dropping packet" messages. The test verifies that exactly TEST_RATELIMIT_BURST (10)
- * messages appeared on the host - ratelimit should limit the number of messages even
- * if more were generated.
+ * packets are sent, causing table overflow and generation of "nf_conntrack table
+ * full in netns N, dropping packet" messages. The test verifies that exactly
+ * TEST_RATELIMIT_BURST (10) messages appeared on the host - ratelimit should
+ * limit the number of messages even if more were generated.
  */
 TEST_F(ve_printk, ve_log_both)
 {
@@ -635,7 +635,7 @@ TEST_F(ve_printk, ve_log_both)
 	ASSERT_EQ(ret, 0);
 
 	while (fgets(buf, sizeof(buf), fdmesg)) {
-		if (strstr(buf, "nf_conntrack table full, dropping packet")) {
+		if (strstr(buf, "nf_conntrack table full")) {
 			ret++;
 		}
 	}

^ permalink raw reply	[relevance 4%]

* [Devel] [PATCH VZ10 v2 5/5] drivers/vhost/blk: rework queue/backend setup
  @ 2026-08-24 15:59  9% ` Andrey Zhadchenko
  0 siblings, 0 replies; 119+ results
From: Andrey Zhadchenko @ 2026-08-24 15:59 UTC (permalink / raw)


vhost_blk_setup() is pretty bad: silently refusing changed vq->num
if requests are already allocated, fetching user input second time
(double-fetch vulnerability).
To handle this, tie request allocation to backend existence. After
all, if there is no backend, there is no point in having requests.
Also expand it to get rid of boilerplate drop_backend, flush,
fput sequence in a few places.

https://virtuozzo.atlassian.net/browse/VSTOR-138640
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
---
 drivers/vhost/blk.c | 126 +++++++++++++++++++-------------------------
 1 file changed, 54 insertions(+), 72 deletions(-)

diff --git a/drivers/vhost/blk.c b/drivers/vhost/blk.c
index 6a483e527990e..456ccfbe369f0 100644
--- a/drivers/vhost/blk.c
+++ b/drivers/vhost/blk.c
@@ -658,11 +658,14 @@ static void vhost_blk_flush(struct vhost_blk *blk)
 	spin_unlock(&blk->flush_lock);
 }
 
-static inline void vhost_blk_drop_backends(struct vhost_blk *blk)
+static void vhost_blk_drop_backend(struct vhost_blk *blk)
 {
 	struct vhost_virtqueue *vq;
 	int i;
 
+	if (!blk->backend)
+		return;
+
 	for (i = 0; i < VHOST_BLK_VQ_MAX; i++) {
 		vq = &blk->vqs[i].vq;
 
@@ -670,6 +673,44 @@ static inline void vhost_blk_drop_backends(struct vhost_blk *blk)
 		vhost_vq_set_backend(vq, NULL);
 		mutex_unlock(&vq->mutex);
 	}
+
+	vhost_blk_flush(blk);
+
+	for (i = 0; i < VHOST_BLK_VQ_MAX; i++) {
+		kvfree(blk->vqs[i].req);
+		blk->vqs[i].req = NULL;
+	}
+
+	fput(blk->backend);
+	blk->backend = NULL;
+}
+
+static int vhost_blk_setup_vqs(struct vhost_blk *blk)
+{
+	struct vhost_virtqueue *vq;
+	int i;
+
+	for (i = 0; i < VHOST_BLK_VQ_MAX; i++) {
+		vq = &blk->vqs[i].vq;
+
+		if (!vhost_vq_is_setup(vq))
+			continue;
+
+		blk->vqs[i].req = kvmalloc_array(vq->num, sizeof(struct vhost_blk_req),
+						 GFP_KERNEL);
+		if (!blk->vqs[i].req)
+			return -ENOMEM;
+
+		mutex_lock(&vq->mutex);
+		vhost_vq_set_backend(vq, blk->backend);
+		if (vhost_vq_init_access(vq)) {
+			mutex_unlock(&vq->mutex);
+			return -EFAULT;
+		}
+		mutex_unlock(&vq->mutex);
+	}
+
+	return 0;
 }
 
 static int vhost_blk_open(struct inode *inode, struct file *file)
@@ -722,16 +763,10 @@ static int vhost_blk_open(struct inode *inode, struct file *file)
 static int vhost_blk_release(struct inode *inode, struct file *f)
 {
 	struct vhost_blk *blk = f->private_data;
-	int i;
 
-	vhost_blk_drop_backends(blk);
-	vhost_blk_flush(blk);
+	vhost_blk_drop_backend(blk);
 	vhost_dev_stop(&blk->dev);
-	if (blk->backend)
-		fput(blk->backend);
 	vhost_dev_cleanup(&blk->dev);
-	for (i = 0; i < VHOST_BLK_VQ_MAX; i++)
-		kvfree(blk->vqs[i].req);
 	kfree(blk->dev.vqs);
 	kvfree(blk);
 
@@ -765,32 +800,19 @@ static int vhost_blk_set_features(struct vhost_blk *blk, u64 features)
 
 static long vhost_blk_set_backend(struct vhost_blk *blk, int fd)
 {
-	struct vhost_virtqueue *vq;
 	struct file *file;
 	struct inode *inode;
-	int ret, i;
+	int ret;
 
 	mutex_lock(&blk->dev.mutex);
 	ret = vhost_dev_check_owner(&blk->dev);
 	if (ret)
 		goto out_dev;
 
-	/*
-	 * fd < 0 means "stop the device".  Detach the backend from every vq so
-	 * vhost_blk_handle_guest_kick() stops fetching descriptors, drain the
-	 * in-flight requests, and release the backing file.
-	 */
+	/* fd < 0 means "stop the device" */
 	if (fd < 0) {
-		if (!blk->backend) {
-			ret = 0;		/* already stopped */
-			goto out_dev;
-		}
-		vhost_blk_drop_backends(blk);
-		vhost_blk_flush(blk);
-		fput(blk->backend);
-		blk->backend = NULL;
 		ret = 0;
-		goto out_dev;
+		goto out_drop;
 	}
 
 	if (blk->backend) {
@@ -807,31 +829,20 @@ static long vhost_blk_set_backend(struct vhost_blk *blk, int fd)
 	inode = file->f_mapping->host;
 	if (!S_ISBLK(inode->i_mode)) {
 		ret = -EFAULT;
-		goto out_file;
-	}
-
-	for (i = 0; i < VHOST_BLK_VQ_MAX; i++) {
-		vq = &blk->vqs[i].vq;
-		if (!vhost_vq_access_ok(vq)) {
-			ret = -EFAULT;
-			goto out_drop;
-		}
-
-		mutex_lock(&vq->mutex);
-		vhost_vq_set_backend(vq, file);
-		ret = vhost_vq_init_access(vq);
-		mutex_unlock(&vq->mutex);
+		fput(file);
+		goto out_dev;
 	}
 
 	blk->backend = file;
+	ret = vhost_blk_setup_vqs(blk);
+	if (ret)
+		goto out_drop;
 
 	mutex_unlock(&blk->dev.mutex);
 	return 0;
 
 out_drop:
-	vhost_blk_drop_backends(blk);
-out_file:
-	fput(file);
+	vhost_blk_drop_backend(blk);
 out_dev:
 	mutex_unlock(&blk->dev.mutex);
 	return ret;
@@ -840,7 +851,7 @@ static long vhost_blk_set_backend(struct vhost_blk *blk, int fd)
 static long vhost_blk_reset_owner(struct vhost_blk *blk)
 {
 	struct vhost_iotlb *umem;
-	int err, i;
+	int err;
 
 	mutex_lock(&blk->dev.mutex);
 	err = vhost_dev_check_owner(&blk->dev);
@@ -851,42 +862,15 @@ static long vhost_blk_reset_owner(struct vhost_blk *blk)
 		err = -ENOMEM;
 		goto done;
 	}
-	vhost_blk_drop_backends(blk);
-	if (blk->backend) {
-		fput(blk->backend);
-		blk->backend = NULL;
-	}
-	vhost_blk_flush(blk);
+	vhost_blk_drop_backend(blk);
 	vhost_dev_stop(&blk->dev);
 	vhost_dev_reset_owner(&blk->dev, umem);
 
-	for (i = 0; i < VHOST_BLK_VQ_MAX; i++) {
-		kvfree(blk->vqs[i].req);
-		blk->vqs[i].req = NULL;
-	}
-
 done:
 	mutex_unlock(&blk->dev.mutex);
 	return err;
 }
 
-static int vhost_blk_setup(struct vhost_blk *blk, void __user *argp)
-{
-	struct vhost_vring_state s;
-
-	if (copy_from_user(&s, argp, sizeof(s)))
-		return -EFAULT;
-
-	if (blk->vqs[s.index].req)
-		return 0;
-
-	blk->vqs[s.index].req = kvmalloc(sizeof(struct vhost_blk_req) * s.num, GFP_KERNEL);
-	if (!blk->vqs[s.index].req)
-		return -ENOMEM;
-
-	return 0;
-}
-
 static long vhost_blk_ioctl(struct file *f, unsigned int ioctl,
 			    unsigned long arg)
 {
@@ -924,8 +908,6 @@ static long vhost_blk_ioctl(struct file *f, unsigned int ioctl,
 		ret = vhost_dev_ioctl(&blk->dev, ioctl, argp);
 		if (ret == -ENOIOCTLCMD)
 			ret = vhost_vring_ioctl(&blk->dev, ioctl, argp);
-		if (!ret && ioctl == VHOST_SET_VRING_NUM)
-			ret = vhost_blk_setup(blk, argp);
 		vhost_blk_flush(blk);
 		mutex_unlock(&blk->dev.mutex);
 		return ret;
-- 
2.43.5


^ permalink raw reply	[relevance 9%]

* [Devel] [PATCH VZ10 v3 5/5] drivers/vhost/blk: rework queue/backend setup
  @ 2026-08-25 12:51  9% ` Andrey Zhadchenko
  2026-08-25 13:57  9%   ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
  0 siblings, 1 reply; 119+ results
From: Andrey Zhadchenko @ 2026-08-25 12:51 UTC (permalink / raw)


vhost_blk_setup() is pretty bad: silently refusing changed vq->num
if requests are already allocated, fetching user input second time
(double-fetch vulnerability).
To handle this, tie request allocation to backend existence. After
all, if there is no backend, there is no point in having requests.
Also expand it to get rid of boilerplate drop_backend, flush,
fput sequence in a few places.

https://virtuozzo.atlassian.net/browse/VSTOR-138640
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
---
 drivers/vhost/blk.c | 126 +++++++++++++++++++-------------------------
 1 file changed, 54 insertions(+), 72 deletions(-)

diff --git a/drivers/vhost/blk.c b/drivers/vhost/blk.c
index b2cf111bbb455..8e3025e934445 100644
--- a/drivers/vhost/blk.c
+++ b/drivers/vhost/blk.c
@@ -658,11 +658,14 @@ static void vhost_blk_flush(struct vhost_blk *blk)
 	spin_unlock(&blk->flush_lock);
 }
 
-static inline void vhost_blk_drop_backends(struct vhost_blk *blk)
+static void vhost_blk_drop_backend(struct vhost_blk *blk)
 {
 	struct vhost_virtqueue *vq;
 	int i;
 
+	if (!blk->backend)
+		return;
+
 	for (i = 0; i < VHOST_BLK_VQ_MAX; i++) {
 		vq = &blk->vqs[i].vq;
 
@@ -670,6 +673,44 @@ static inline void vhost_blk_drop_backends(struct vhost_blk *blk)
 		vhost_vq_set_backend(vq, NULL);
 		mutex_unlock(&vq->mutex);
 	}
+
+	vhost_blk_flush(blk);
+
+	for (i = 0; i < VHOST_BLK_VQ_MAX; i++) {
+		kvfree(blk->vqs[i].req);
+		blk->vqs[i].req = NULL;
+	}
+
+	fput(blk->backend);
+	blk->backend = NULL;
+}
+
+static int vhost_blk_setup_vqs(struct vhost_blk *blk)
+{
+	struct vhost_virtqueue *vq;
+	int i;
+
+	for (i = 0; i < VHOST_BLK_VQ_MAX; i++) {
+		vq = &blk->vqs[i].vq;
+
+		if (!vhost_vq_is_setup(vq))
+			continue;
+
+		blk->vqs[i].req = kvmalloc_array(vq->num, sizeof(struct vhost_blk_req),
+						 GFP_KERNEL);
+		if (!blk->vqs[i].req)
+			return -ENOMEM;
+
+		mutex_lock(&vq->mutex);
+		vhost_vq_set_backend(vq, blk->backend);
+		if (vhost_vq_init_access(vq)) {
+			mutex_unlock(&vq->mutex);
+			return -EFAULT;
+		}
+		mutex_unlock(&vq->mutex);
+	}
+
+	return 0;
 }
 
 static int vhost_blk_open(struct inode *inode, struct file *file)
@@ -722,16 +763,10 @@ static int vhost_blk_open(struct inode *inode, struct file *file)
 static int vhost_blk_release(struct inode *inode, struct file *f)
 {
 	struct vhost_blk *blk = f->private_data;
-	int i;
 
-	vhost_blk_drop_backends(blk);
-	vhost_blk_flush(blk);
+	vhost_blk_drop_backend(blk);
 	vhost_dev_stop(&blk->dev);
-	if (blk->backend)
-		fput(blk->backend);
 	vhost_dev_cleanup(&blk->dev);
-	for (i = 0; i < VHOST_BLK_VQ_MAX; i++)
-		kvfree(blk->vqs[i].req);
 	kfree(blk->dev.vqs);
 	kvfree(blk);
 
@@ -765,32 +800,19 @@ static int vhost_blk_set_features(struct vhost_blk *blk, u64 features)
 
 static long vhost_blk_set_backend(struct vhost_blk *blk, int fd)
 {
-	struct vhost_virtqueue *vq;
 	struct file *file;
 	struct inode *inode;
-	int ret, i;
+	int ret;
 
 	mutex_lock(&blk->dev.mutex);
 	ret = vhost_dev_check_owner(&blk->dev);
 	if (ret)
 		goto out_dev;
 
-	/*
-	 * fd < 0 means "stop the device".  Detach the backend from every vq so
-	 * vhost_blk_handle_guest_kick() stops fetching descriptors, drain the
-	 * in-flight requests, and release the backing file.
-	 */
+	/* fd < 0 means "stop the device" */
 	if (fd < 0) {
-		if (!blk->backend) {
-			ret = 0;		/* already stopped */
-			goto out_dev;
-		}
-		vhost_blk_drop_backends(blk);
-		vhost_blk_flush(blk);
-		fput(blk->backend);
-		blk->backend = NULL;
 		ret = 0;
-		goto out_dev;
+		goto out_drop;
 	}
 
 	if (blk->backend) {
@@ -807,31 +829,20 @@ static long vhost_blk_set_backend(struct vhost_blk *blk, int fd)
 	inode = file->f_mapping->host;
 	if (!S_ISBLK(inode->i_mode)) {
 		ret = -EFAULT;
-		goto out_file;
-	}
-
-	for (i = 0; i < VHOST_BLK_VQ_MAX; i++) {
-		vq = &blk->vqs[i].vq;
-		if (!vhost_vq_access_ok(vq)) {
-			ret = -EFAULT;
-			goto out_drop;
-		}
-
-		mutex_lock(&vq->mutex);
-		vhost_vq_set_backend(vq, file);
-		ret = vhost_vq_init_access(vq);
-		mutex_unlock(&vq->mutex);
+		fput(file);
+		goto out_dev;
 	}
 
 	blk->backend = file;
+	ret = vhost_blk_setup_vqs(blk);
+	if (ret)
+		goto out_drop;
 
 	mutex_unlock(&blk->dev.mutex);
 	return 0;
 
 out_drop:
-	vhost_blk_drop_backends(blk);
-out_file:
-	fput(file);
+	vhost_blk_drop_backend(blk);
 out_dev:
 	mutex_unlock(&blk->dev.mutex);
 	return ret;
@@ -840,7 +851,7 @@ static long vhost_blk_set_backend(struct vhost_blk *blk, int fd)
 static long vhost_blk_reset_owner(struct vhost_blk *blk)
 {
 	struct vhost_iotlb *umem;
-	int err, i;
+	int err;
 
 	mutex_lock(&blk->dev.mutex);
 	err = vhost_dev_check_owner(&blk->dev);
@@ -851,42 +862,15 @@ static long vhost_blk_reset_owner(struct vhost_blk *blk)
 		err = -ENOMEM;
 		goto done;
 	}
-	vhost_blk_drop_backends(blk);
-	if (blk->backend) {
-		fput(blk->backend);
-		blk->backend = NULL;
-	}
-	vhost_blk_flush(blk);
+	vhost_blk_drop_backend(blk);
 	vhost_dev_stop(&blk->dev);
 	vhost_dev_reset_owner(&blk->dev, umem);
 
-	for (i = 0; i < VHOST_BLK_VQ_MAX; i++) {
-		kvfree(blk->vqs[i].req);
-		blk->vqs[i].req = NULL;
-	}
-
 done:
 	mutex_unlock(&blk->dev.mutex);
 	return err;
 }
 
-static int vhost_blk_setup(struct vhost_blk *blk, void __user *argp)
-{
-	struct vhost_vring_state s;
-
-	if (copy_from_user(&s, argp, sizeof(s)))
-		return -EFAULT;
-
-	if (blk->vqs[s.index].req)
-		return 0;
-
-	blk->vqs[s.index].req = kvmalloc(sizeof(struct vhost_blk_req) * s.num, GFP_KERNEL);
-	if (!blk->vqs[s.index].req)
-		return -ENOMEM;
-
-	return 0;
-}
-
 static long vhost_blk_ioctl(struct file *f, unsigned int ioctl,
 			    unsigned long arg)
 {
@@ -924,8 +908,6 @@ static long vhost_blk_ioctl(struct file *f, unsigned int ioctl,
 		ret = vhost_dev_ioctl(&blk->dev, ioctl, argp);
 		if (ret == -ENOIOCTLCMD)
 			ret = vhost_vring_ioctl(&blk->dev, ioctl, argp);
-		if (!ret && ioctl == VHOST_SET_VRING_NUM)
-			ret = vhost_blk_setup(blk, argp);
 		vhost_blk_flush(blk);
 		mutex_unlock(&blk->dev.mutex);
 		return ret;
-- 
2.43.5


^ permalink raw reply	[relevance 9%]

* [Devel] [PATCH vz10 2/2] selftests/ve: check that hiding an entry does not unmount it
  @ 2026-08-25 13:29  7% ` Mirian Shilakadze
  2026-08-25 15:40  0%   ` Pavel Tikhomirov
  0 siblings, 1 reply; 119+ results
From: Mirian Shilakadze @ 2026-08-25 13:29 UTC (permalink / raw)


kernfs_dop_revalidate() answered the per VE visibility check with the same
"return 0" the staleness checks use, and the VFS reads 0 as a global fact:
d_invalidate() hands every mountpoint under that dentry to
__detach_mounts(), whose mountpoint hash is not scoped to a mount
namespace.  A single lookup from inside a Container unmounted the host's
bpffs, and libvzctl needs bpffs for the cgroup v2 device controller, so
the whole node stopped being manageable.

Mount a tmpfs on the entry the variant already keeps host only, look it up
from inside a VE, and require both that the VE is told ENOENT and that the
mount is still there afterwards.  The mount is made in the test's own
mount namespace so the machine running the test cannot lose a mount it
needs, while the dentry the mount hangs on is still the shared one the bug
worked through.

Fails without the preceding fix, on both the sysfs and the proc variant.

Feature: kernfs: per-CT entries visibility and permissions configuration
https://virtuozzo.atlassian.net/browse/VSTOR-142552
Signed-off-by: Mirian Shilakadze <mirian.shilakadze@virtuozzo.com>
---
 tools/testing/selftests/ve/ve_perms_test.c | 52 ++++++++++++++++++++++
 tools/testing/selftests/ve/ve_selftest.h   | 27 ++++++++++-
 2 files changed, 77 insertions(+), 2 deletions(-)

diff --git a/tools/testing/selftests/ve/ve_perms_test.c b/tools/testing/selftests/ve/ve_perms_test.c
index 4522950c17f2..25ffbd42c380 100644
--- a/tools/testing/selftests/ve/ve_perms_test.c
+++ b/tools/testing/selftests/ve/ve_perms_test.c
@@ -24,6 +24,7 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <limits.h>
+#include <sys/mount.h>
 #include <sys/wait.h>
 #include <errno.h>
 
@@ -412,4 +413,55 @@ TEST_F(ve_perms, enforce_denies)
 			      absent, O_RDONLY), EACCES);
 }
 
+/*
+ * Looking up an entry that a VE cannot see must not disturb a mount that
+ * sits on it.
+ *
+ * The lookup used to answer "this dentry is stale" where it meant "this name
+ * is not here for you", and the VFS acts on stale globally: d_invalidate()
+ * detaches every mount on that dentry in every mount namespace.  One lookup
+ * from inside a Container took the host's bpffs and tracefs with it.
+ *
+ * The tmpfs is mounted in the test's own mount namespace, so the machine
+ * running this cannot lose a mount it needs, while the dentry the mount hangs
+ * on is still the shared one the bug worked through.
+ */
+TEST_F(ve_perms, hidden_entry_keeps_its_mount)
+{
+	char path[PATH_MAX];
+	int status;
+	pid_t pid;
+
+	if (!entry_present(variant->dir_prefix, variant->dir))
+		SKIP(return, "%s/%s absent", variant->dir_prefix, variant->dir);
+	snprintf(path, sizeof(path), "%s/%s", variant->dir_prefix, variant->dir);
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0) {
+		if (unshare(CLONE_NEWNS) != 0 ||
+		    mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) != 0 ||
+		    mount("ve_selftest", path, "tmpfs", 0, NULL) != 0)
+			_exit(255);
+		if (is_mounted(path) != 1)
+			_exit(254);
+
+		/*
+		 * The lookup that used to unmount it.  What the VE is told
+		 * depends on the filesystem and on the mount now covering the
+		 * entry, and enforce_denies() already covers that.  Here only
+		 * the mount surviving the lookup is the point.
+		 */
+		ve_open_rel(self->cgv2_fd, self->ctid_a, variant->dir_prefix,
+			    variant->dir, O_RDONLY | O_DIRECTORY);
+
+		_exit(is_mounted(path) == 1 ? 0 : 2);
+	}
+	ASSERT_EQ(waitpid(pid, &status, 0), pid);
+	ASSERT_TRUE(WIFEXITED(status));
+	if (WEXITSTATUS(status) == 2)
+		TH_LOG("the VE lookup unmounted %s", path);
+	EXPECT_EQ(WEXITSTATUS(status), 0);
+}
+
 TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/ve/ve_selftest.h b/tools/testing/selftests/ve/ve_selftest.h
index 69c0a52dd7ef..83ace9e6db42 100644
--- a/tools/testing/selftests/ve/ve_selftest.h
+++ b/tools/testing/selftests/ve/ve_selftest.h
@@ -1,8 +1,8 @@
 /* SPDX-License-Identifier: GPL-2.0 */
 /*
  * Shared helpers for the ve selftests: a private cgroup2 mount, small file and
- * cgroup helpers, and VE cgroup create and destroy, used across the tests in
- * this directory.
+ * cgroup helpers, VE cgroup create and destroy, and a mount point query, used
+ * across the tests in this directory.
  */
 #ifndef __SELFTESTS_VE_VE_SELFTEST_H
 #define __SELFTESTS_VE_VE_SELFTEST_H
@@ -180,4 +180,27 @@ static inline void destroy_ve(int cgv2_fd, int id)
 		__func__, id, strerror(errno));
 }
 
+/* Is @path a mount point in this task's mount namespace? */
+static inline int is_mounted(const char *path)
+{
+	char line[PATH_MAX + 128], target[PATH_MAX];
+	int found = 0;
+	unsigned int u;
+	FILE *f;
+
+	f = fopen("/proc/self/mountinfo", "r");
+	if (!f)
+		return -1;
+	while (fgets(line, sizeof(line), f)) {
+		if (sscanf(line, "%u %u %u:%u %*s %s", &u, &u, &u, &u, target) != 5)
+			continue;
+		if (strcmp(target, path) == 0) {
+			found = 1;
+			break;
+		}
+	}
+	fclose(f);
+	return found;
+}
+
 #endif /* __SELFTESTS_VE_VE_SELFTEST_H */
-- 
2.43.0


^ permalink raw reply	[relevance 7%]

* [Devel] [PATCH RHEL10 COMMIT] drivers/vhost/blk: rework queue/backend setup
  2026-08-25 12:51  9% ` [Devel] [PATCH VZ10 v3 5/5] drivers/vhost/blk: rework queue/backend setup Andrey Zhadchenko
@ 2026-08-25 13:57  9%   ` Konstantin Khorenko
  0 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-25 13:57 UTC (permalink / raw)


The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.9.vz10
------>
commit 95d39b7ffa50047eadf53f48419e8cea84a175ea
Author: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
Date:   Tue Aug 25 15:51:52 2026 +0300

    drivers/vhost/blk: rework queue/backend setup
    
    vhost_blk_setup() is pretty bad: silently refusing changed vq->num
    if requests are already allocated, fetching user input second time
    (double-fetch vulnerability).
    To handle this, tie request allocation to backend existence. After
    all, if there is no backend, there is no point in having requests.
    Also expand it to get rid of boilerplate drop_backend, flush,
    fput sequence in a few places.
    
    https://virtuozzo.atlassian.net/browse/VSTOR-138640
    Fixes: d8722ff88c5d ("drivers/vhost: vhost-blk accelerator for virtio-blk guests")
    Feature: vhost-blk: in-kernel accelerator for virtio-blk guests
    Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
    Reviewed-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
    Reviewed-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
 drivers/vhost/blk.c | 125 ++++++++++++++++++++++------------------------------
 1 file changed, 53 insertions(+), 72 deletions(-)

diff --git a/drivers/vhost/blk.c b/drivers/vhost/blk.c
index b2cf111bbb455..fbdd04f5a7e28 100644
--- a/drivers/vhost/blk.c
+++ b/drivers/vhost/blk.c
@@ -658,11 +658,14 @@ static void vhost_blk_flush(struct vhost_blk *blk)
 	spin_unlock(&blk->flush_lock);
 }
 
-static inline void vhost_blk_drop_backends(struct vhost_blk *blk)
+static void vhost_blk_drop_backend(struct vhost_blk *blk)
 {
 	struct vhost_virtqueue *vq;
 	int i;
 
+	if (!blk->backend)
+		return;
+
 	for (i = 0; i < VHOST_BLK_VQ_MAX; i++) {
 		vq = &blk->vqs[i].vq;
 
@@ -670,6 +673,43 @@ static inline void vhost_blk_drop_backends(struct vhost_blk *blk)
 		vhost_vq_set_backend(vq, NULL);
 		mutex_unlock(&vq->mutex);
 	}
+
+	vhost_blk_flush(blk);
+
+	for (i = 0; i < VHOST_BLK_VQ_MAX; i++) {
+		kvfree(blk->vqs[i].req);
+		blk->vqs[i].req = NULL;
+	}
+
+	fput(blk->backend);
+	blk->backend = NULL;
+}
+
+static int vhost_blk_setup_vqs(struct vhost_blk *blk)
+{
+	struct vhost_virtqueue *vq;
+	int ret, i;
+
+	for (i = 0; i < VHOST_BLK_VQ_MAX; i++) {
+		vq = &blk->vqs[i].vq;
+
+		if (!vhost_vq_is_setup(vq))
+			continue;
+
+		blk->vqs[i].req = kvmalloc_array(vq->num, sizeof(struct vhost_blk_req),
+						 GFP_KERNEL);
+		if (!blk->vqs[i].req)
+			return -ENOMEM;
+
+		mutex_lock(&vq->mutex);
+		vhost_vq_set_backend(vq, blk->backend);
+		ret = vhost_vq_init_access(vq);
+		mutex_unlock(&vq->mutex);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
 }
 
 static int vhost_blk_open(struct inode *inode, struct file *file)
@@ -722,16 +762,10 @@ static int vhost_blk_open(struct inode *inode, struct file *file)
 static int vhost_blk_release(struct inode *inode, struct file *f)
 {
 	struct vhost_blk *blk = f->private_data;
-	int i;
 
-	vhost_blk_drop_backends(blk);
-	vhost_blk_flush(blk);
+	vhost_blk_drop_backend(blk);
 	vhost_dev_stop(&blk->dev);
-	if (blk->backend)
-		fput(blk->backend);
 	vhost_dev_cleanup(&blk->dev);
-	for (i = 0; i < VHOST_BLK_VQ_MAX; i++)
-		kvfree(blk->vqs[i].req);
 	kfree(blk->dev.vqs);
 	kvfree(blk);
 
@@ -765,32 +799,19 @@ static int vhost_blk_set_features(struct vhost_blk *blk, u64 features)
 
 static long vhost_blk_set_backend(struct vhost_blk *blk, int fd)
 {
-	struct vhost_virtqueue *vq;
 	struct file *file;
 	struct inode *inode;
-	int ret, i;
+	int ret;
 
 	mutex_lock(&blk->dev.mutex);
 	ret = vhost_dev_check_owner(&blk->dev);
 	if (ret)
 		goto out_dev;
 
-	/*
-	 * fd < 0 means "stop the device".  Detach the backend from every vq so
-	 * vhost_blk_handle_guest_kick() stops fetching descriptors, drain the
-	 * in-flight requests, and release the backing file.
-	 */
+	/* fd < 0 means "stop the device" */
 	if (fd < 0) {
-		if (!blk->backend) {
-			ret = 0;		/* already stopped */
-			goto out_dev;
-		}
-		vhost_blk_drop_backends(blk);
-		vhost_blk_flush(blk);
-		fput(blk->backend);
-		blk->backend = NULL;
 		ret = 0;
-		goto out_dev;
+		goto out_drop;
 	}
 
 	if (blk->backend) {
@@ -807,31 +828,20 @@ static long vhost_blk_set_backend(struct vhost_blk *blk, int fd)
 	inode = file->f_mapping->host;
 	if (!S_ISBLK(inode->i_mode)) {
 		ret = -EFAULT;
-		goto out_file;
-	}
-
-	for (i = 0; i < VHOST_BLK_VQ_MAX; i++) {
-		vq = &blk->vqs[i].vq;
-		if (!vhost_vq_access_ok(vq)) {
-			ret = -EFAULT;
-			goto out_drop;
-		}
-
-		mutex_lock(&vq->mutex);
-		vhost_vq_set_backend(vq, file);
-		ret = vhost_vq_init_access(vq);
-		mutex_unlock(&vq->mutex);
+		fput(file);
+		goto out_dev;
 	}
 
 	blk->backend = file;
+	ret = vhost_blk_setup_vqs(blk);
+	if (ret)
+		goto out_drop;
 
 	mutex_unlock(&blk->dev.mutex);
 	return 0;
 
 out_drop:
-	vhost_blk_drop_backends(blk);
-out_file:
-	fput(file);
+	vhost_blk_drop_backend(blk);
 out_dev:
 	mutex_unlock(&blk->dev.mutex);
 	return ret;
@@ -840,7 +850,7 @@ static long vhost_blk_set_backend(struct vhost_blk *blk, int fd)
 static long vhost_blk_reset_owner(struct vhost_blk *blk)
 {
 	struct vhost_iotlb *umem;
-	int err, i;
+	int err;
 
 	mutex_lock(&blk->dev.mutex);
 	err = vhost_dev_check_owner(&blk->dev);
@@ -851,42 +861,15 @@ static long vhost_blk_reset_owner(struct vhost_blk *blk)
 		err = -ENOMEM;
 		goto done;
 	}
-	vhost_blk_drop_backends(blk);
-	if (blk->backend) {
-		fput(blk->backend);
-		blk->backend = NULL;
-	}
-	vhost_blk_flush(blk);
+	vhost_blk_drop_backend(blk);
 	vhost_dev_stop(&blk->dev);
 	vhost_dev_reset_owner(&blk->dev, umem);
 
-	for (i = 0; i < VHOST_BLK_VQ_MAX; i++) {
-		kvfree(blk->vqs[i].req);
-		blk->vqs[i].req = NULL;
-	}
-
 done:
 	mutex_unlock(&blk->dev.mutex);
 	return err;
 }
 
-static int vhost_blk_setup(struct vhost_blk *blk, void __user *argp)
-{
-	struct vhost_vring_state s;
-
-	if (copy_from_user(&s, argp, sizeof(s)))
-		return -EFAULT;
-
-	if (blk->vqs[s.index].req)
-		return 0;
-
-	blk->vqs[s.index].req = kvmalloc(sizeof(struct vhost_blk_req) * s.num, GFP_KERNEL);
-	if (!blk->vqs[s.index].req)
-		return -ENOMEM;
-
-	return 0;
-}
-
 static long vhost_blk_ioctl(struct file *f, unsigned int ioctl,
 			    unsigned long arg)
 {
@@ -924,8 +907,6 @@ static long vhost_blk_ioctl(struct file *f, unsigned int ioctl,
 		ret = vhost_dev_ioctl(&blk->dev, ioctl, argp);
 		if (ret == -ENOIOCTLCMD)
 			ret = vhost_vring_ioctl(&blk->dev, ioctl, argp);
-		if (!ret && ioctl == VHOST_SET_VRING_NUM)
-			ret = vhost_blk_setup(blk, argp);
 		vhost_blk_flush(blk);
 		mutex_unlock(&blk->dev.mutex);
 		return ret;

^ permalink raw reply	[relevance 9%]

* Re: [Devel] [PATCH vz10 2/2] selftests/ve: check that hiding an entry does not unmount it
  2026-08-25 13:29  7% ` [Devel] [PATCH vz10 2/2] selftests/ve: check that hiding an entry does not unmount it Mirian Shilakadze
@ 2026-08-25 15:40  0%   ` Pavel Tikhomirov
  0 siblings, 0 replies; 119+ results
From: Pavel Tikhomirov @ 2026-08-25 15:40 UTC (permalink / raw)




On 8/25/26 15:29, Mirian Shilakadze wrote:
> kernfs_dop_revalidate() answered the per VE visibility check with the same
> "return 0" the staleness checks use, and the VFS reads 0 as a global fact:
> d_invalidate() hands every mountpoint under that dentry to
> __detach_mounts(), whose mountpoint hash is not scoped to a mount
> namespace.  A single lookup from inside a Container unmounted the host's
> bpffs, and libvzctl needs bpffs for the cgroup v2 device controller, so
> the whole node stopped being manageable.
> 
> Mount a tmpfs on the entry the variant already keeps host only, look it up
> from inside a VE, and require both that the VE is told ENOENT and that the
> mount is still there afterwards.  The mount is made in the test's own
> mount namespace so the machine running the test cannot lose a mount it
> needs, while the dentry the mount hangs on is still the shared one the bug
> worked through.
> 
> Fails without the preceding fix, on both the sysfs and the proc variant.
> 
> Feature: kernfs: per-CT entries visibility and permissions configuration
> https://virtuozzo.atlassian.net/browse/VSTOR-142552
> Signed-off-by: Mirian Shilakadze <mirian.shilakadze@virtuozzo.com>
> ---
>  tools/testing/selftests/ve/ve_perms_test.c | 52 ++++++++++++++++++++++
>  tools/testing/selftests/ve/ve_selftest.h   | 27 ++++++++++-
>  2 files changed, 77 insertions(+), 2 deletions(-)
> 
> diff --git a/tools/testing/selftests/ve/ve_perms_test.c b/tools/testing/selftests/ve/ve_perms_test.c
> index 4522950c17f2..25ffbd42c380 100644
> --- a/tools/testing/selftests/ve/ve_perms_test.c
> +++ b/tools/testing/selftests/ve/ve_perms_test.c
> @@ -24,6 +24,7 @@
>  #include <unistd.h>
>  #include <fcntl.h>
>  #include <limits.h>
> +#include <sys/mount.h>
>  #include <sys/wait.h>
>  #include <errno.h>
>  
> @@ -412,4 +413,55 @@ TEST_F(ve_perms, enforce_denies)
>  			      absent, O_RDONLY), EACCES);
>  }
>  
> +/*
> + * Looking up an entry that a VE cannot see must not disturb a mount that
> + * sits on it.
> + *
> + * The lookup used to answer "this dentry is stale" where it meant "this name
> + * is not here for you", and the VFS acts on stale globally: d_invalidate()
> + * detaches every mount on that dentry in every mount namespace.  One lookup
> + * from inside a Container took the host's bpffs and tracefs with it.
> + *
> + * The tmpfs is mounted in the test's own mount namespace, so the machine
> + * running this cannot lose a mount it needs, while the dentry the mount hangs
> + * on is still the shared one the bug worked through.
> + */
> +TEST_F(ve_perms, hidden_entry_keeps_its_mount)
> +{
> +	char path[PATH_MAX];
> +	int status;
> +	pid_t pid;
> +
> +	if (!entry_present(variant->dir_prefix, variant->dir))
> +		SKIP(return, "%s/%s absent", variant->dir_prefix, variant->dir);
> +	snprintf(path, sizeof(path), "%s/%s", variant->dir_prefix, variant->dir);
> +
> +	pid = fork();
> +	ASSERT_GE(pid, 0);
> +	if (pid == 0) {
> +		if (unshare(CLONE_NEWNS) != 0 ||
> +		    mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) != 0 ||
> +		    mount("ve_selftest", path, "tmpfs", 0, NULL) != 0)
> +			_exit(255);
> +		if (is_mounted(path) != 1)
> +			_exit(254);
> +
> +		/*
> +		 * The lookup that used to unmount it.  What the VE is told
> +		 * depends on the filesystem and on the mount now covering the
> +		 * entry, and enforce_denies() already covers that.  Here only
> +		 * the mount surviving the lookup is the point.
> +		 */
> +		ve_open_rel(self->cgv2_fd, self->ctid_a, variant->dir_prefix,
> +			    variant->dir, O_RDONLY | O_DIRECTORY);
> +
> +		_exit(is_mounted(path) == 1 ? 0 : 2);
> +	}
> +	ASSERT_EQ(waitpid(pid, &status, 0), pid);
> +	ASSERT_TRUE(WIFEXITED(status));
> +	if (WEXITSTATUS(status) == 2)
> +		TH_LOG("the VE lookup unmounted %s", path);
> +	EXPECT_EQ(WEXITSTATUS(status), 0);
> +}
> +
>  TEST_HARNESS_MAIN
> diff --git a/tools/testing/selftests/ve/ve_selftest.h b/tools/testing/selftests/ve/ve_selftest.h
> index 69c0a52dd7ef..83ace9e6db42 100644
> --- a/tools/testing/selftests/ve/ve_selftest.h
> +++ b/tools/testing/selftests/ve/ve_selftest.h
> @@ -1,8 +1,8 @@
>  /* SPDX-License-Identifier: GPL-2.0 */
>  /*
>   * Shared helpers for the ve selftests: a private cgroup2 mount, small file and
> - * cgroup helpers, and VE cgroup create and destroy, used across the tests in
> - * this directory.
> + * cgroup helpers, VE cgroup create and destroy, and a mount point query, used
> + * across the tests in this directory.
>   */
>  #ifndef __SELFTESTS_VE_VE_SELFTEST_H
>  #define __SELFTESTS_VE_VE_SELFTEST_H
> @@ -180,4 +180,27 @@ static inline void destroy_ve(int cgv2_fd, int id)
>  		__func__, id, strerror(errno));
>  }
>  
> +/* Is @path a mount point in this task's mount namespace? */
> +static inline int is_mounted(const char *path)
> +{
> +	char line[PATH_MAX + 128], target[PATH_MAX];
> +	int found = 0;
> +	unsigned int u;
> +	FILE *f;
> +
> +	f = fopen("/proc/self/mountinfo", "r");
> +	if (!f)
> +		return -1;
> +	while (fgets(line, sizeof(line), f)) {
> +		if (sscanf(line, "%u %u %u:%u %*s %s", &u, &u, &u, &u, target) != 5)
> +			continue;
> +		if (strcmp(target, path) == 0) {
> +			found = 1;
> +			break;
> +		}
> +	}

Take a look at openat2 RESOLVE_NO_XDEV, it detects the crossing of mount boundary
(open parent, open child from parent), and avoids heavy mountinfo reading.

> +	fclose(f);
> +	return found;
> +}
> +
>  #endif /* __SELFTESTS_VE_VE_SELFTEST_H */

-- 
Best regards, Pavel Tikhomirov
Senior Software Developer, Virtuozzo.


^ permalink raw reply	[relevance 0%]

* [Devel] [PATCH vz10 v2 2/2] selftests/ve: check that hiding an entry does not unmount it
  @ 2026-08-26 11:04  6% ` Mirian Shilakadze
  2026-08-26 16:15  6%   ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
  0 siblings, 1 reply; 119+ results
From: Mirian Shilakadze @ 2026-08-26 11:04 UTC (permalink / raw)
  To: khorenko, ptikhomirov; +Cc: devel

kernfs_dop_revalidate() answered the per VE visibility check with the same
"return 0" the staleness checks use, and the VFS reads 0 as a global fact:
d_invalidate() hands every mountpoint under that dentry to
__detach_mounts(), whose mountpoint hash is not scoped to a mount
namespace.  A single lookup from inside a Container unmounted the host's
bpffs, and libvzctl needs bpffs for the cgroup v2 device controller, so
the whole node stopped being manageable.

Mount a tmpfs on the entry the variant already keeps host only, look it up
from inside a VE, and require both that the VE is told ENOENT and that the
mount is still there afterwards.  The mount is made in the test's own
mount namespace so the machine running the test cannot lose a mount it
needs, while the dentry the mount hangs on is still the shared one the bug
worked through.

The mount check uses openat2() with RESOLVE_NO_XDEV, which fails with
EXDEV when the final component is a mount point, rather than reading
/proc/self/mountinfo.

Fails without the preceding fix, on both the sysfs and the proc variant.

Feature: kernfs: per-CT entries visibility and permissions configuration
https://virtuozzo.atlassian.net/browse/VSTOR-142552
Signed-off-by: Mirian Shilakadze <mirian.shilakadze@virtuozzo.com>
---
 tools/testing/selftests/ve/ve_perms_test.c | 52 ++++++++++++++++++++++
 tools/testing/selftests/ve/ve_selftest.h   | 40 ++++++++++++++++-
 2 files changed, 90 insertions(+), 2 deletions(-)

diff --git a/tools/testing/selftests/ve/ve_perms_test.c b/tools/testing/selftests/ve/ve_perms_test.c
index 4522950c17f2..25ffbd42c380 100644
--- a/tools/testing/selftests/ve/ve_perms_test.c
+++ b/tools/testing/selftests/ve/ve_perms_test.c
@@ -24,6 +24,7 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <limits.h>
+#include <sys/mount.h>
 #include <sys/wait.h>
 #include <errno.h>
 
@@ -412,4 +413,55 @@ TEST_F(ve_perms, enforce_denies)
 			      absent, O_RDONLY), EACCES);
 }
 
+/*
+ * Looking up an entry that a VE cannot see must not disturb a mount that
+ * sits on it.
+ *
+ * The lookup used to answer "this dentry is stale" where it meant "this name
+ * is not here for you", and the VFS acts on stale globally: d_invalidate()
+ * detaches every mount on that dentry in every mount namespace.  One lookup
+ * from inside a Container took the host's bpffs and tracefs with it.
+ *
+ * The tmpfs is mounted in the test's own mount namespace, so the machine
+ * running this cannot lose a mount it needs, while the dentry the mount hangs
+ * on is still the shared one the bug worked through.
+ */
+TEST_F(ve_perms, hidden_entry_keeps_its_mount)
+{
+	char path[PATH_MAX];
+	int status;
+	pid_t pid;
+
+	if (!entry_present(variant->dir_prefix, variant->dir))
+		SKIP(return, "%s/%s absent", variant->dir_prefix, variant->dir);
+	snprintf(path, sizeof(path), "%s/%s", variant->dir_prefix, variant->dir);
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0) {
+		if (unshare(CLONE_NEWNS) != 0 ||
+		    mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) != 0 ||
+		    mount("ve_selftest", path, "tmpfs", 0, NULL) != 0)
+			_exit(255);
+		if (is_mounted(path) != 1)
+			_exit(254);
+
+		/*
+		 * The lookup that used to unmount it.  What the VE is told
+		 * depends on the filesystem and on the mount now covering the
+		 * entry, and enforce_denies() already covers that.  Here only
+		 * the mount surviving the lookup is the point.
+		 */
+		ve_open_rel(self->cgv2_fd, self->ctid_a, variant->dir_prefix,
+			    variant->dir, O_RDONLY | O_DIRECTORY);
+
+		_exit(is_mounted(path) == 1 ? 0 : 2);
+	}
+	ASSERT_EQ(waitpid(pid, &status, 0), pid);
+	ASSERT_TRUE(WIFEXITED(status));
+	if (WEXITSTATUS(status) == 2)
+		TH_LOG("the VE lookup unmounted %s", path);
+	EXPECT_EQ(WEXITSTATUS(status), 0);
+}
+
 TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/ve/ve_selftest.h b/tools/testing/selftests/ve/ve_selftest.h
index 69c0a52dd7ef..c53bf7900d20 100644
--- a/tools/testing/selftests/ve/ve_selftest.h
+++ b/tools/testing/selftests/ve/ve_selftest.h
@@ -1,8 +1,8 @@
 /* SPDX-License-Identifier: GPL-2.0 */
 /*
  * Shared helpers for the ve selftests: a private cgroup2 mount, small file and
- * cgroup helpers, and VE cgroup create and destroy, used across the tests in
- * this directory.
+ * cgroup helpers, VE cgroup create and destroy, and a mount point query, used
+ * across the tests in this directory.
  */
 #ifndef __SELFTESTS_VE_VE_SELFTEST_H
 #define __SELFTESTS_VE_VE_SELFTEST_H
@@ -16,6 +16,8 @@
 #include <limits.h>
 #include <sys/stat.h>
 #include <sys/mount.h>
+#include <sys/syscall.h>
+#include <linux/openat2.h>
 
 #ifndef CLONE_NEWVE
 #define CLONE_NEWVE		0x00000040
@@ -180,4 +182,38 @@ static inline void destroy_ve(int cgv2_fd, int id)
 		__func__, id, strerror(errno));
 }
 
+/*
+ * Is @path a mount point?  RESOLVE_NO_XDEV makes openat2() fail with EXDEV
+ * when the final component is a mount point, which answers the question
+ * without reading the mount table.
+ */
+static inline int is_mounted(const char *path)
+{
+	struct open_how how = {
+		.flags = O_PATH | O_CLOEXEC,
+		.resolve = RESOLVE_NO_XDEV,
+	};
+	char buf[PATH_MAX], *dir, *base;
+	int dfd, fd;
+
+	if (snprintf(buf, sizeof(buf), "%s", path) >= (int)sizeof(buf))
+		return -1;
+	base = strrchr(buf, '/');
+	if (!base)
+		return -1;
+	*base++ = '\0';
+	dir = buf[0] ? buf : "/";
+
+	dfd = open(dir, O_PATH | O_DIRECTORY | O_CLOEXEC);
+	if (dfd < 0)
+		return -1;
+	fd = syscall(__NR_openat2, dfd, base, &how, sizeof(how));
+	close(dfd);
+	if (fd >= 0) {
+		close(fd);
+		return 0;
+	}
+	return errno == EXDEV ? 1 : -1;
+}
+
 #endif /* __SELFTESTS_VE_VE_SELFTEST_H */
-- 
2.43.0

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 6%]

* [Devel] [PATCH VZ10] b4: ship project defaults in .b4-config
@ 2026-08-26 14:44  6% Vasileios Almpanis
  2026-08-26 15:15  0% ` Pavel Tikhomirov
  2026-08-26 16:42  6% ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
  0 siblings, 2 replies; 119+ results
From: Vasileios Almpanis @ 2026-08-26 14:44 UTC (permalink / raw)
  To: Pavel Tikhomirov, Konstantin Khorenko; +Cc: devel

b4 fetches series from lore.kernel.org by default. To use it with our
list archive at lore.virtuozzo.com every developer had to set the same
handful of b4.* git config keys, plus two more to get the right
recipients on "b4 send" and one for the subject prefix.

b4 loads a .b4-config at the top of the tree as defaults for commands
run inside it, so put the archive URL masks, the default recipients and
the VZ10 subject prefix there. Add the file to the .gitignore dot-file
exceptions so it is tracked.

https://virtuozzo.atlassian.net/browse/VSTOR-142960
Signed-off-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>

Feature: internal
---
Signed-off-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
---
 .b4-config | 7 +++++++
 .gitignore | 1 +
 2 files changed, 8 insertions(+)

diff --git a/.b4-config b/.b4-config
new file mode 100644
index 000000000000..3f5aac9bb1cc
--- /dev/null
+++ b/.b4-config
@@ -0,0 +1,7 @@
+[b4]
+	midmask = http://lore.virtuozzo.com/%s
+	searchmask = http://lore.virtuozzo.com/all/?x=m&t=1&q=%s
+	linkmask = http://lore.virtuozzo.com/%s
+	send-series-to = "Konstantin Khorenko <khorenko@virtuozzo.com>"
+	send-series-cc = devel@openvz.org
+	send-prefixes = VZ10
diff --git a/.gitignore b/.gitignore
index 0c7ad0516fc6..b64c07bd537e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -103,6 +103,7 @@ modules.order
 #
 # We don't want to ignore the following even if they are dot-files
 #
+!.b4-config
 !.clang-format
 !.cocciconfig
 !.editorconfig

---
base-commit: 4305092314bbfbc90cb26f9ec8c6bf78c4513f04
change-id: 20260826-b4-setup-9a776f3efccd

Best regards,
--  
Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 6%]

* Re: [Devel] [PATCH VZ10] b4: ship project defaults in .b4-config
  2026-08-26 14:44  6% [Devel] [PATCH VZ10] b4: ship project defaults in .b4-config Vasileios Almpanis
@ 2026-08-26 15:15  0% ` Pavel Tikhomirov
  2026-08-26 16:42  6% ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
  1 sibling, 0 replies; 119+ results
From: Pavel Tikhomirov @ 2026-08-26 15:15 UTC (permalink / raw)
  To: Vasileios Almpanis, Konstantin Khorenko; +Cc: devel

Works for me.

Reviewed-by: Pavel Tikhomirov <ptikhomirov@viruozzo.com>

On 8/26/26 16:44, Vasileios Almpanis wrote:
> b4 fetches series from lore.kernel.org by default. To use it with our
> list archive at lore.virtuozzo.com every developer had to set the same
> handful of b4.* git config keys, plus two more to get the right
> recipients on "b4 send" and one for the subject prefix.
> 
> b4 loads a .b4-config at the top of the tree as defaults for commands
> run inside it, so put the archive URL masks, the default recipients and
> the VZ10 subject prefix there. Add the file to the .gitignore dot-file
> exceptions so it is tracked.
> 
> https://virtuozzo.atlassian.net/browse/VSTOR-142960
> Signed-off-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
> 
> Feature: internal
> ---
> Signed-off-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
> ---
>  .b4-config | 7 +++++++
>  .gitignore | 1 +
>  2 files changed, 8 insertions(+)
> 
> diff --git a/.b4-config b/.b4-config
> new file mode 100644
> index 000000000000..3f5aac9bb1cc
> --- /dev/null
> +++ b/.b4-config
> @@ -0,0 +1,7 @@
> +[b4]
> +	midmask = http://lore.virtuozzo.com/%s
> +	searchmask = http://lore.virtuozzo.com/all/?x=m&t=1&q=%s
> +	linkmask = http://lore.virtuozzo.com/%s
> +	send-series-to = "Konstantin Khorenko <khorenko@virtuozzo.com>"
> +	send-series-cc = devel@openvz.org
> +	send-prefixes = VZ10
> diff --git a/.gitignore b/.gitignore
> index 0c7ad0516fc6..b64c07bd537e 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -103,6 +103,7 @@ modules.order
>  #
>  # We don't want to ignore the following even if they are dot-files
>  #
> +!.b4-config
>  !.clang-format
>  !.cocciconfig
>  !.editorconfig
> 
> ---
> base-commit: 4305092314bbfbc90cb26f9ec8c6bf78c4513f04
> change-id: 20260826-b4-setup-9a776f3efccd
> 
> Best regards,
> --  
> Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
> 

-- 
Best regards, Pavel Tikhomirov
Senior Software Developer, Virtuozzo.

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 0%]

* Re: [Devel] [PATCH RHEL10 COMMIT] ve/fs: unlink the mount namespace on the copy_mnt_ns() error path
  2026-08-17  7:16  6% ` [Devel] [PATCH vz10 1/3] ve/fs: unlink the mount namespace on the copy_mnt_ns() error path Mirian Shilakadze
@ 2026-08-26 15:38  5%   ` Konstantin Khorenko
  0 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-26 15:38 UTC (permalink / raw)
  To: Mirian Shilakadze; +Cc: OpenVZ devel

The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git@bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.10.vz10
------>
commit 5e3e3c3facbcd29dfa49f7f6ab5aabb39a60d1f9
Author: Mirian Shilakadze <mirian.shilakadze@virtuozzo.com>
Date:   Mon Aug 17 11:16:39 2026 +0400

    ve/fs: unlink the mount namespace on the copy_mnt_ns() error path
    
    alloc_mnt_ns() does two things upstream does not: it links the namespace
    onto all_mntns_list and takes a reference on its owning VE. Both are
    undone in free_mnt_ns(), which holds the only list_del() of mntns_list
    and the only put_ve(ns->ve_owner) in the file.
    
    copy_mnt_ns() does not call it when copy_tree() fails. It open codes the
    teardown and finishes with mnt_ns_release(), which drops the passive
    count and kfree()s the namespace without unlinking it, so the namespace
    is freed while all_mntns_list still points at it and the VE reference is
    leaked. The next namespace creation runs list_add_tail() through the
    dangling entry.
    
    A container reaches this deterministically. alloc_vfsmnt() returns NULL
    when !ve_mount_allowed(), that is when the VE is at sysctl_ve_mount_nr,
    clone_mnt() turns that into -ENOMEM and copy_tree() propagates it. So a
    container sitting at its own mount limit that calls unshare(CLONE_NEWNS)
    takes the error path every time, with no memory pressure and nothing
    beyond CAP_SYS_ADMIN in its own user namespace, and panics the host:
    
      list_add corruption. prev->next should be next (ffffffffa9ca77f0), but
      was ff2834a3cdfaeed0. (prev=ff2834a3cdfaeed0).
      kernel BUG at lib/list_debug.c:32!
      CPU: 94 UID: 0 PID: 7139 Comm: unshare ve: 900
       alloc_mnt_ns+0xd5/0x210
       copy_mnt_ns+0x82/0x3c0
       create_new_namespaces+0x5d/0x2f0
       unshare_nsproxy_namespaces+0x69/0xc0
       ksys_unshare+0x213/0x3f0
    
    prev->next == prev is INIT_LIST_HEAD() on reallocated memory, the freed
    namespace reused while the list still referenced it. CONFIG_DEBUG_LIST is
    only what makes it a clean BUG, without it the same list_add_tail()
    writes through the dangling pointer silently.
    
    The path used to call free_mnt_ns() and was correct. Upstream replaced
    that with the open coded sequence because free_mnt_ns() reaches
    mnt_ns_tree_remove(), which rb_erase()s a node that copy_mnt_ns() has not
    inserted yet, mnt_ns_tree_add() running only after the copy succeeds.
    Upstream is unaffected by the replacement because its free_mnt_ns()
    carries nothing else. Ours does.
    
    So do not restore the free_mnt_ns() call, that would reintroduce the
    rb_erase() upstream fixed. Split the part that is ours into
    mnt_ns_unlink() and call it from both places, so a future addition to
    namespace teardown has one home rather than two that can drift apart,
    which is how this happened.
    
    Fixes: 229fd15908fe ("fs: don't try and remove empty rbtree node")
    Fixes: 1db60e545f65 ("ve/mntns: add ve_owner to struct mnt_namespace")
    https://virtuozzo.atlassian.net/browse/VSTOR-141545
    Feature: ve: ve generic structures
    Signed-off-by: Mirian Shilakadze <mirian.shilakadze@virtuozzo.com>
    Reviewed-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
    Reviewed-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
 fs/namespace.c | 24 +++++++++++++++++++-----
 1 file changed, 19 insertions(+), 5 deletions(-)

diff --git a/fs/namespace.c b/fs/namespace.c
index 4d4dc52903508..7e27537dcdaf9 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -4243,17 +4243,30 @@ static void dec_mnt_namespaces(struct ucounts *ucounts)
 static LIST_HEAD(all_mntns_list);
 static DEFINE_SPINLOCK(all_mntns_list_lock);
 
-static void free_mnt_ns(struct mnt_namespace *ns)
+/*
+ * Undo the bookkeeping alloc_mnt_ns() sets up beyond what upstream does: the
+ * entry on all_mntns_list and the reference on the owning VE.
+ *
+ * Kept separate from free_mnt_ns() because copy_mnt_ns() has to unwind a
+ * namespace that is not in mnt_ns_tree yet, so it cannot use free_mnt_ns()
+ * without rb_erase()ing a node that was never inserted.
+ */
+static void mnt_ns_unlink(struct mnt_namespace *ns)
 {
-	if (!is_anon_ns(ns))
-		ns_free_inum(&ns->ns);
-	dec_mnt_namespaces(ns->ucounts);
-
 	spin_lock(&all_mntns_list_lock);
 	list_del(&ns->mntns_list);
 	spin_unlock(&all_mntns_list_lock);
 
 	put_ve(ns->ve_owner);
+}
+
+static void free_mnt_ns(struct mnt_namespace *ns)
+{
+	if (!is_anon_ns(ns))
+		ns_free_inum(&ns->ns);
+	dec_mnt_namespaces(ns->ucounts);
+
+	mnt_ns_unlink(ns);
 
 	mnt_ns_tree_remove(ns);
 }
@@ -4347,6 +4360,7 @@ struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns,
 		namespace_unlock();
 		ns_free_inum(&new_ns->ns);
 		dec_mnt_namespaces(new_ns->ucounts);
+		mnt_ns_unlink(new_ns);
 		mnt_ns_release(new_ns);
 		return ERR_CAST(new);
 	}
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 5%]

* Re: [Devel] [PATCH RHEL10 COMMIT] selftests/ve: check that hiding an entry does not unmount it
  2026-08-26 11:04  6% ` [Devel] [PATCH vz10 v2 2/2] selftests/ve: check that hiding an entry does not unmount it Mirian Shilakadze
@ 2026-08-26 16:15  6%   ` Konstantin Khorenko
  0 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-26 16:15 UTC (permalink / raw)
  To: Mirian Shilakadze; +Cc: OpenVZ devel

The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git@bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.10.vz10
------>
commit e6d9a8ea97c1933241869f60ac5677865d848dab
Author: Mirian Shilakadze <mirian.shilakadze@virtuozzo.com>
Date:   Wed Aug 26 15:04:11 2026 +0400

    selftests/ve: check that hiding an entry does not unmount it
    
    kernfs_dop_revalidate() answered the per VE visibility check with the same
    "return 0" the staleness checks use, and the VFS reads 0 as a global fact:
    d_invalidate() hands every mountpoint under that dentry to
    __detach_mounts(), whose mountpoint hash is not scoped to a mount
    namespace.  A single lookup from inside a Container unmounted the host's
    bpffs, and libvzctl needs bpffs for the cgroup v2 device controller, so
    the whole node stopped being manageable.
    
    Mount a tmpfs on the entry the variant already keeps host only, look it up
    from inside a VE, and require both that the VE is told ENOENT and that the
    mount is still there afterwards.  The mount is made in the test's own
    mount namespace so the machine running the test cannot lose a mount it
    needs, while the dentry the mount hangs on is still the shared one the bug
    worked through.
    
    The mount check uses openat2() with RESOLVE_NO_XDEV, which fails with
    EXDEV when the final component is a mount point, rather than reading
    /proc/self/mountinfo.
    
    Fails without the preceding fix, on both the sysfs and the proc variant.
    
    Feature: kernfs: per-CT entries visibility and permissions configuration
    https://virtuozzo.atlassian.net/browse/VSTOR-142552
    Signed-off-by: Mirian Shilakadze <mirian.shilakadze@virtuozzo.com>
    Reviewed-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
    Reviewed-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
 tools/testing/selftests/ve/ve_perms_test.c | 52 ++++++++++++++++++++++++++++++
 tools/testing/selftests/ve/ve_selftest.h   | 40 +++++++++++++++++++++--
 2 files changed, 90 insertions(+), 2 deletions(-)

diff --git a/tools/testing/selftests/ve/ve_perms_test.c b/tools/testing/selftests/ve/ve_perms_test.c
index 4522950c17f2f..25ffbd42c3802 100644
--- a/tools/testing/selftests/ve/ve_perms_test.c
+++ b/tools/testing/selftests/ve/ve_perms_test.c
@@ -24,6 +24,7 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <limits.h>
+#include <sys/mount.h>
 #include <sys/wait.h>
 #include <errno.h>
 
@@ -412,4 +413,55 @@ TEST_F(ve_perms, enforce_denies)
 			      absent, O_RDONLY), EACCES);
 }
 
+/*
+ * Looking up an entry that a VE cannot see must not disturb a mount that
+ * sits on it.
+ *
+ * The lookup used to answer "this dentry is stale" where it meant "this name
+ * is not here for you", and the VFS acts on stale globally: d_invalidate()
+ * detaches every mount on that dentry in every mount namespace.  One lookup
+ * from inside a Container took the host's bpffs and tracefs with it.
+ *
+ * The tmpfs is mounted in the test's own mount namespace, so the machine
+ * running this cannot lose a mount it needs, while the dentry the mount hangs
+ * on is still the shared one the bug worked through.
+ */
+TEST_F(ve_perms, hidden_entry_keeps_its_mount)
+{
+	char path[PATH_MAX];
+	int status;
+	pid_t pid;
+
+	if (!entry_present(variant->dir_prefix, variant->dir))
+		SKIP(return, "%s/%s absent", variant->dir_prefix, variant->dir);
+	snprintf(path, sizeof(path), "%s/%s", variant->dir_prefix, variant->dir);
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0) {
+		if (unshare(CLONE_NEWNS) != 0 ||
+		    mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) != 0 ||
+		    mount("ve_selftest", path, "tmpfs", 0, NULL) != 0)
+			_exit(255);
+		if (is_mounted(path) != 1)
+			_exit(254);
+
+		/*
+		 * The lookup that used to unmount it.  What the VE is told
+		 * depends on the filesystem and on the mount now covering the
+		 * entry, and enforce_denies() already covers that.  Here only
+		 * the mount surviving the lookup is the point.
+		 */
+		ve_open_rel(self->cgv2_fd, self->ctid_a, variant->dir_prefix,
+			    variant->dir, O_RDONLY | O_DIRECTORY);
+
+		_exit(is_mounted(path) == 1 ? 0 : 2);
+	}
+	ASSERT_EQ(waitpid(pid, &status, 0), pid);
+	ASSERT_TRUE(WIFEXITED(status));
+	if (WEXITSTATUS(status) == 2)
+		TH_LOG("the VE lookup unmounted %s", path);
+	EXPECT_EQ(WEXITSTATUS(status), 0);
+}
+
 TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/ve/ve_selftest.h b/tools/testing/selftests/ve/ve_selftest.h
index 69c0a52dd7ef0..c53bf7900d208 100644
--- a/tools/testing/selftests/ve/ve_selftest.h
+++ b/tools/testing/selftests/ve/ve_selftest.h
@@ -1,8 +1,8 @@
 /* SPDX-License-Identifier: GPL-2.0 */
 /*
  * Shared helpers for the ve selftests: a private cgroup2 mount, small file and
- * cgroup helpers, and VE cgroup create and destroy, used across the tests in
- * this directory.
+ * cgroup helpers, VE cgroup create and destroy, and a mount point query, used
+ * across the tests in this directory.
  */
 #ifndef __SELFTESTS_VE_VE_SELFTEST_H
 #define __SELFTESTS_VE_VE_SELFTEST_H
@@ -16,6 +16,8 @@
 #include <limits.h>
 #include <sys/stat.h>
 #include <sys/mount.h>
+#include <sys/syscall.h>
+#include <linux/openat2.h>
 
 #ifndef CLONE_NEWVE
 #define CLONE_NEWVE		0x00000040
@@ -180,4 +182,38 @@ static inline void destroy_ve(int cgv2_fd, int id)
 		__func__, id, strerror(errno));
 }
 
+/*
+ * Is @path a mount point?  RESOLVE_NO_XDEV makes openat2() fail with EXDEV
+ * when the final component is a mount point, which answers the question
+ * without reading the mount table.
+ */
+static inline int is_mounted(const char *path)
+{
+	struct open_how how = {
+		.flags = O_PATH | O_CLOEXEC,
+		.resolve = RESOLVE_NO_XDEV,
+	};
+	char buf[PATH_MAX], *dir, *base;
+	int dfd, fd;
+
+	if (snprintf(buf, sizeof(buf), "%s", path) >= (int)sizeof(buf))
+		return -1;
+	base = strrchr(buf, '/');
+	if (!base)
+		return -1;
+	*base++ = '\0';
+	dir = buf[0] ? buf : "/";
+
+	dfd = open(dir, O_PATH | O_DIRECTORY | O_CLOEXEC);
+	if (dfd < 0)
+		return -1;
+	fd = syscall(__NR_openat2, dfd, base, &how, sizeof(how));
+	close(dfd);
+	if (fd >= 0) {
+		close(fd);
+		return 0;
+	}
+	return errno == EXDEV ? 1 : -1;
+}
+
 #endif /* __SELFTESTS_VE_VE_SELFTEST_H */
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 6%]

* Re: [Devel] [PATCH RHEL10 COMMIT] b4: ship project defaults in .b4-config
  2026-08-26 14:44  6% [Devel] [PATCH VZ10] b4: ship project defaults in .b4-config Vasileios Almpanis
  2026-08-26 15:15  0% ` Pavel Tikhomirov
@ 2026-08-26 16:42  6% ` Konstantin Khorenko
  1 sibling, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-26 16:42 UTC (permalink / raw)
  To: Vasileios Almpanis; +Cc: OpenVZ devel

The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git@bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.10.vz10
------>
commit 58367508a27f0be6127bf618fb1c974921d2b45e
Author: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
Date:   Wed Aug 26 14:44:10 2026 +0000

    b4: ship project defaults in .b4-config
    
    b4 fetches series from lore.kernel.org by default. To use it with our
    list archive at lore.virtuozzo.com every developer had to set the same
    handful of b4.* git config keys, plus two more to get the right
    recipients on "b4 send" and one for the subject prefix.
    
    b4 loads a .b4-config at the top of the tree as defaults for commands
    run inside it, so put the archive URL masks, the default recipients and
    the VZ10 subject prefix there. Add the file to the .gitignore dot-file
    exceptions so it is tracked.
    
    https://virtuozzo.atlassian.net/browse/VSTOR-142960
    Feature: internal
    Signed-off-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
    Reviewed-by: Pavel Tikhomirov <ptikhomirov@viruozzo.com>
---
 .b4-config | 7 +++++++
 .gitignore | 1 +
 2 files changed, 8 insertions(+)

diff --git a/.b4-config b/.b4-config
new file mode 100644
index 0000000000000..3f5aac9bb1cc4
--- /dev/null
+++ b/.b4-config
@@ -0,0 +1,7 @@
+[b4]
+	midmask = http://lore.virtuozzo.com/%s
+	searchmask = http://lore.virtuozzo.com/all/?x=m&t=1&q=%s
+	linkmask = http://lore.virtuozzo.com/%s
+	send-series-to = "Konstantin Khorenko <khorenko@virtuozzo.com>"
+	send-series-cc = devel@openvz.org
+	send-prefixes = VZ10
diff --git a/.gitignore b/.gitignore
index 0c7ad0516fc67..b64c07bd537ed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -103,6 +103,7 @@ /pacman/
 #
 # We don't want to ignore the following even if they are dot-files
 #
+!.b4-config
 !.clang-format
 !.cocciconfig
 !.editorconfig
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 6%]

* Re: [Devel] [PATCH vz10 v2] ve: downgrade the trusted exec/mmap denial from WARN to pr_warn
  2026-08-19 16:25 16% ` [Devel] [PATCH vz10 v2] ve: downgrade the trusted exec/mmap denial from WARN to pr_warn Konstantin Khorenko
@ 2026-08-26 16:49  0%   ` Konstantin Khorenko
  0 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-26 16:49 UTC (permalink / raw)
  To: Pavel Tikhomirov; +Cc: OpenVZ devel list

a kind ping

--
Best regards,

Konstantin Khorenko,
Virtuozzo Linux Kernel Team

On 8/19/26 18:25, Konstantin Khorenko wrote:
> ve_check_trusted_exec()/ve_check_trusted_mmap() protect the host, but
> the WARN(1, ...) is too much here:
> 
>  * it taints the kernel while this is not a kernel bug which we beed to
>    debug and fix
>  * the backtrace only shows the exec/mmap path, which is already known
>  * on a host booted with panic_on_warn a denied exec takes the host down
> 
> The SIGSEGV and the core dump of the offending process are enough to
> report and investigate such an attempt. Downgrade the WARN() to
> pr_warn(), keeping the message, the rate limiting and the signal.
> 
> Fixes: fc7157b84c32 ("trusted/ve/mmap: Protect from unsecure library load from CT image")
> Fixes: cc218b70c6b4 ("trusted/ve/fs/exec: Send SIGSEGV to a process trying to execute untrusted files")
> Feature: security/fs: tructed exec feature
> https://virtuozzo.atlassian.net/browse/VSTOR-137234
> Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
> ---
> v1 -> v2: commit message rewritten
> ---
>  kernel/ve/ve.c | 6 +++---
>  1 file changed, 3 insertions(+), 3 deletions(-)
> 
> diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
> index 73d1c3b4873e5..0d43dc3253ea3 100644
> --- a/kernel/ve/ve.c
> +++ b/kernel/ve/ve.c
> @@ -1745,8 +1745,8 @@ bool ve_check_trusted_mmap(struct file *file)
>  	if (file->f_path.dentry)
>  		filename = file->f_path.dentry->d_name.name;
>  
> -	WARN(1, "VE0 %s tried to map code from file '%s' from VEX\n",
> -			current->comm, filename);
> +	pr_warn("VE0 %s tried to map code from file '%s' from VEX\n",
> +		current->comm, filename);
>  	force_sigsegv(SIGSEGV);
>  	return false;
>  }
> @@ -1765,7 +1765,7 @@ bool ve_check_trusted_exec(struct file *file, struct filename *name)
>  	if (!__ratelimit(&sigsegv_rs))
>  		return false;
>  
> -	WARN(1, "VE0's %s tried to execute untrusted file %s from VEX\n",
> +	pr_warn("VE0's %s tried to execute untrusted file %s from VEX\n",
>  		current->comm, name->name);
>  	force_sigsegv(SIGSEGV);
>  	return false;

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 0%]

* Re: [Devel] [PATCH vz10 v2] selftests/uevent: do not fail on a netlink receive buffer overrun
  2026-08-24 13:09  4% ` [Devel] [PATCH vz10 v2] selftests/uevent: do not fail on a netlink receive buffer overrun Konstantin Khorenko
@ 2026-08-26 16:49  0%   ` Konstantin Khorenko
  2026-08-26 19:32  0%   ` Eva Kurchatova (Virtuozzo)
  2026-08-27 12:28  4%   ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
  2 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-26 16:49 UTC (permalink / raw)
  To: Vasileios Almpanis, Eva Kurchatova, OpenVZ devel list

a kind ping

--
Best regards,

Konstantin Khorenko,
Virtuozzo Linux Kernel Team

On 8/24/26 15:09, Konstantin Khorenko wrote:
> SO_RCVBUF is set to __UEVENT_BUFFER_SIZE, 4KB, which a busy machine
> overruns while the test is listening:
> 
>   No buffer space available - Failed to receive uevent
> 
> Two things are wrong here.
> 
> The socket queue is sized after a single message, while do_test()
> deliberately triggers ten uevents "to account for the case where the
> kernel might drop some", so the queue has to hold more than one.
> 
> Give it its own size and leave the message buffer alone: the kernel caps
> a single uevent at UEVENT_BUFFER_SIZE, 2048 bytes, so 4KB per message is
> already generous.
> 
> The receive loop then treats every error as fatal, ENOBUFS included,
> which defeats those ten uevents. Netlink clears the error after
> reporting it once, so the copies still queued, or still on their way,
> are perfectly receivable. Retry instead.
> 
> do_test() bounds the listener with a two second sigtimedwait(), so a
> retry cannot hang the test.
> 
> https://virtuozzo.atlassian.net/browse/VSTOR-139674
> Feature: fix selftests
> Reported-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
> Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
> ---
> Changes in v2:
> - keep __UEVENT_BUFFER_SIZE at 4KB and give SO_RCVBUF its own
>   __UEVENT_RCVBUF_SIZE.  v1 raised the shared macro, which sized the
>   socket queue correctly but also turned the per message buffer into a
>   128KB zero initialized array on the stack, while the kernel caps a
>   single uevent at UEVENT_BUFFER_SIZE, 2048 bytes.
> - retry recvmsg() on ENOBUFS instead of failing.  v1 only made the
>   overrun less likely; the test still died on the first one, even
>   though do_test() triggers ten uevents precisely so that drops are
>   tolerated.
> - subject and commit message updated accordingly.
> 
>  tools/testing/selftests/uevent/uevent_filtering.c | 13 ++++++++++++-
>  1 file changed, 12 insertions(+), 1 deletion(-)
> 
> diff --git a/tools/testing/selftests/uevent/uevent_filtering.c b/tools/testing/selftests/uevent/uevent_filtering.c
> index 8062804ff759..735eb8138c44 100644
> --- a/tools/testing/selftests/uevent/uevent_filtering.c
> +++ b/tools/testing/selftests/uevent/uevent_filtering.c
> @@ -23,6 +23,11 @@
>  
>  #define __DEV_FULL "/sys/devices/virtual/mem/full/uevent"
>  #define __UEVENT_BUFFER_SIZE (2048 * 2)
> +/*
> + * The socket queue has to hold more than a single message: the test
> + * triggers ten uevents and a busy machine overruns a small buffer.
> + */
> +#define __UEVENT_RCVBUF_SIZE (2048 * 64)
>  #define __UEVENT_HEADER "add@/devices/virtual/mem/full"
>  #define __UEVENT_HEADER_LEN sizeof("add@/devices/virtual/mem/full")
>  #define __UEVENT_LISTEN_ALL -1
> @@ -78,7 +83,7 @@ static int uevent_listener(unsigned long post_flags, bool expect_uevent,
>  {
>  	int sk_fd, ret;
>  	socklen_t sk_addr_len;
> -	int rcv_buf_sz = __UEVENT_BUFFER_SIZE;
> +	int rcv_buf_sz = __UEVENT_RCVBUF_SIZE;
>  	uint64_t sync_add = 1;
>  	struct sockaddr_nl sk_addr = { 0 }, rcv_addr = { 0 };
>  	char buf[__UEVENT_BUFFER_SIZE] = { 0 };
> @@ -158,6 +163,12 @@ static int uevent_listener(unsigned long post_flags, bool expect_uevent,
>  		ssize_t r;
>  
>  		r = recvmsg(sk_fd, &hdr, 0);
> +		/*
> +		 * The queue overran.  The kernel clears the error after
> +		 * reporting it once and more uevents are on their way.
> +		 */
> +		if (r < 0 && errno == ENOBUFS)
> +			continue;
>  		if (r <= 0) {
>  			fprintf(stderr, "%s - Failed to receive uevent\n", strerror(errno));
>  			ret = -1;

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 0%]

* Re: [Devel] [PATCH vz10 v2] selftests/uevent: do not fail on a netlink receive buffer overrun
  2026-08-24 13:09  4% ` [Devel] [PATCH vz10 v2] selftests/uevent: do not fail on a netlink receive buffer overrun Konstantin Khorenko
  2026-08-26 16:49  0%   ` Konstantin Khorenko
@ 2026-08-26 19:32  0%   ` Eva Kurchatova (Virtuozzo)
  2026-08-27 12:28  4%   ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
  2 siblings, 0 replies; 119+ results
From: Eva Kurchatova (Virtuozzo) @ 2026-08-26 19:32 UTC (permalink / raw)
  To: Konstantin Khorenko, Vasileios Almpanis, OpenVZ devel list


On 8/24/26 16:09, Konstantin Khorenko wrote:
> SO_RCVBUF is set to __UEVENT_BUFFER_SIZE, 4KB, which a busy machine
> overruns while the test is listening:
>
>    No buffer space available - Failed to receive uevent
>
> Two things are wrong here.
>
> The socket queue is sized after a single message, while do_test()
> deliberately triggers ten uevents "to account for the case where the
> kernel might drop some", so the queue has to hold more than one.
>
> Give it its own size and leave the message buffer alone: the kernel caps
> a single uevent at UEVENT_BUFFER_SIZE, 2048 bytes, so 4KB per message is
> already generous.
>
> The receive loop then treats every error as fatal, ENOBUFS included,
> which defeats those ten uevents. Netlink clears the error after
> reporting it once, so the copies still queued, or still on their way,
> are perfectly receivable. Retry instead.
>
> do_test() bounds the listener with a two second sigtimedwait(), so a
> retry cannot hang the test.
>
> https://virtuozzo.atlassian.net/browse/VSTOR-139674
> Feature: fix selftests
> Reported-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
> Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
> ---
> Changes in v2:
> - keep __UEVENT_BUFFER_SIZE at 4KB and give SO_RCVBUF its own
>    __UEVENT_RCVBUF_SIZE.  v1 raised the shared macro, which sized the
>    socket queue correctly but also turned the per message buffer into a
>    128KB zero initialized array on the stack, while the kernel caps a
>    single uevent at UEVENT_BUFFER_SIZE, 2048 bytes.
> - retry recvmsg() on ENOBUFS instead of failing.  v1 only made the
>    overrun less likely; the test still died on the first one, even
>    though do_test() triggers ten uevents precisely so that drops are
>    tolerated.
> - subject and commit message updated accordingly.
>
>   tools/testing/selftests/uevent/uevent_filtering.c | 13 ++++++++++++-
>   1 file changed, 12 insertions(+), 1 deletion(-)
>
> diff --git a/tools/testing/selftests/uevent/uevent_filtering.c b/tools/testing/selftests/uevent/uevent_filtering.c
> index 8062804ff759..735eb8138c44 100644
> --- a/tools/testing/selftests/uevent/uevent_filtering.c
> +++ b/tools/testing/selftests/uevent/uevent_filtering.c
> @@ -23,6 +23,11 @@
>   
>   #define __DEV_FULL "/sys/devices/virtual/mem/full/uevent"
>   #define __UEVENT_BUFFER_SIZE (2048 * 2)
> +/*
> + * The socket queue has to hold more than a single message: the test
> + * triggers ten uevents and a busy machine overruns a small buffer.
> + */
> +#define __UEVENT_RCVBUF_SIZE (2048 * 64)
>   #define __UEVENT_HEADER "add@/devices/virtual/mem/full"
>   #define __UEVENT_HEADER_LEN sizeof("add@/devices/virtual/mem/full")
>   #define __UEVENT_LISTEN_ALL -1
> @@ -78,7 +83,7 @@ static int uevent_listener(unsigned long post_flags, bool expect_uevent,
>   {
>   	int sk_fd, ret;
>   	socklen_t sk_addr_len;
> -	int rcv_buf_sz = __UEVENT_BUFFER_SIZE;
> +	int rcv_buf_sz = __UEVENT_RCVBUF_SIZE;
>   	uint64_t sync_add = 1;
>   	struct sockaddr_nl sk_addr = { 0 }, rcv_addr = { 0 };
>   	char buf[__UEVENT_BUFFER_SIZE] = { 0 };
> @@ -158,6 +163,12 @@ static int uevent_listener(unsigned long post_flags, bool expect_uevent,
>   		ssize_t r;
>   
>   		r = recvmsg(sk_fd, &hdr, 0);
> +		/*
> +		 * The queue overran.  The kernel clears the error after
> +		 * reporting it once and more uevents are on their way.
> +		 */
> +		if (r < 0 && errno == ENOBUFS)
> +			continue;
>   		if (r <= 0) {
>   			fprintf(stderr, "%s - Failed to receive uevent\n", strerror(errno));
>   			ret = -1;

I was originally concerned about spinning on recvmsg(), but considering 
the socket is blocking, and recvmsg() should consume the error once, 
this should be fine.

Otherwise, this is a good improvement over the v1.


Acknowledged-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 0%]

* Re: [Devel] [PATCH RHEL10 COMMIT] selftests/uevent: do not fail on a netlink receive buffer overrun
  2026-08-24 13:09  4% ` [Devel] [PATCH vz10 v2] selftests/uevent: do not fail on a netlink receive buffer overrun Konstantin Khorenko
  2026-08-26 16:49  0%   ` Konstantin Khorenko
  2026-08-26 19:32  0%   ` Eva Kurchatova (Virtuozzo)
@ 2026-08-27 12:28  4%   ` Konstantin Khorenko
  2 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-27 12:28 UTC (permalink / raw)
  To: Konstantin Khorenko; +Cc: OpenVZ devel

The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git@bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.11.vz10
------>
commit dd27dfa9341d66afa10abd7e082b2c32dfd160d0
Author: Konstantin Khorenko <khorenko@virtuozzo.com>
Date:   Mon Aug 24 15:09:22 2026 +0200

    selftests/uevent: do not fail on a netlink receive buffer overrun
    
    SO_RCVBUF is set to __UEVENT_BUFFER_SIZE, 4KB, which a busy machine
    overruns while the test is listening:
    
      No buffer space available - Failed to receive uevent
    
    Two things are wrong here.
    
    The socket queue is sized after a single message, while do_test()
    deliberately triggers ten uevents "to account for the case where the
    kernel might drop some", so the queue has to hold more than one.
    
    Give it its own size and leave the message buffer alone: the kernel caps
    a single uevent at UEVENT_BUFFER_SIZE, 2048 bytes, so 4KB per message is
    already generous.
    
    The receive loop then treats every error as fatal, ENOBUFS included,
    which defeats those ten uevents. Netlink clears the error after
    reporting it once, so the copies still queued, or still on their way,
    are perfectly receivable. Retry instead.
    
    do_test() bounds the listener with a two second sigtimedwait(), so a
    retry cannot hang the test.
    
    https://virtuozzo.atlassian.net/browse/VSTOR-139674
    Feature: fix selftests
    Reported-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
    Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
    Reviewed-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
---
 tools/testing/selftests/uevent/uevent_filtering.c | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/uevent/uevent_filtering.c b/tools/testing/selftests/uevent/uevent_filtering.c
index 8062804ff759a..735eb8138c448 100644
--- a/tools/testing/selftests/uevent/uevent_filtering.c
+++ b/tools/testing/selftests/uevent/uevent_filtering.c
@@ -23,6 +23,11 @@
 
 #define __DEV_FULL "/sys/devices/virtual/mem/full/uevent"
 #define __UEVENT_BUFFER_SIZE (2048 * 2)
+/*
+ * The socket queue has to hold more than a single message: the test
+ * triggers ten uevents and a busy machine overruns a small buffer.
+ */
+#define __UEVENT_RCVBUF_SIZE (2048 * 64)
 #define __UEVENT_HEADER "add@/devices/virtual/mem/full"
 #define __UEVENT_HEADER_LEN sizeof("add@/devices/virtual/mem/full")
 #define __UEVENT_LISTEN_ALL -1
@@ -78,7 +83,7 @@ static int uevent_listener(unsigned long post_flags, bool expect_uevent,
 {
 	int sk_fd, ret;
 	socklen_t sk_addr_len;
-	int rcv_buf_sz = __UEVENT_BUFFER_SIZE;
+	int rcv_buf_sz = __UEVENT_RCVBUF_SIZE;
 	uint64_t sync_add = 1;
 	struct sockaddr_nl sk_addr = { 0 }, rcv_addr = { 0 };
 	char buf[__UEVENT_BUFFER_SIZE] = { 0 };
@@ -158,6 +163,12 @@ static int uevent_listener(unsigned long post_flags, bool expect_uevent,
 		ssize_t r;
 
 		r = recvmsg(sk_fd, &hdr, 0);
+		/*
+		 * The queue overran.  The kernel clears the error after
+		 * reporting it once and more uevents are on their way.
+		 */
+		if (r < 0 && errno == ENOBUFS)
+			continue;
 		if (r <= 0) {
 			fprintf(stderr, "%s - Failed to receive uevent\n", strerror(errno));
 			ret = -1;
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 4%]

* [Devel] [PATCH VZ10] fs/fuse kio: track pending kRPC connect via state machine only
@ 2026-08-27 12:37  4% Liu Kui
  2026-08-28 17:13  0% ` Konstantin Khorenko
  2026-09-01 20:59  0% ` Konstantin Khorenko
  0 siblings, 2 replies; 119+ results
From: Liu Kui @ 2026-08-27 12:37 UTC (permalink / raw)
  To: devel; +Cc: azaitsev, Liu Kui

Rework the previous fix ("fs/fuse kio: fix kRPC connect issues") to not
require the new struct pcs_krpc member "connect_req", so the fix can be
shipped as a livepatch.

Both things connect_req was tracking are already derivable from the
existing state machine once PCS_KRPC_STATE_CONNECT is made to mean
exactly "a connect req is in flight":

 - krpc_connect_done() settles a failed connect back to UNCONN instead
   of leaving the state in CONNECT forever;

 - pcs_krpc_abort() no longer resets CONNECT to UNCONN: the req is
   still in flight, and only its completion settles the state;

 - pcs_krpc_connect() proceeds only from UNCONN or ABORTED, refusing
   new connects (-EPERM) while a req is in flight - at most one connect
   req exists at a time, same as with the connect_req check;

 - pcs_krpc_poll() reports EPOLLERR on UNCONN: poll bails out earlier
   unless ctx->gen == krpc->gen, and the current session can only be in
   UNCONN if its connect failed or was aborted, which is what the
   (CONNECT && !connect_req) test used to detect.

gen only advances in pcs_krpc_connect(), which is blocked during
CONNECT, so within that state the in-flight req always carries the
current gen and krpc_connect_done()'s existing staleness check is
sufficient.

Related to:
https://virtuozzo.atlassian.net/browse/VSTOR-135626

Signed-off-by: Liu Kui <kui.liu@virtuozzo.com>
---
 fs/fuse/kio/pcs/pcs_krpc.c | 39 ++++++++++++++++++++++++++++----------
 fs/fuse/kio/pcs/pcs_krpc.h |  2 --
 2 files changed, 29 insertions(+), 12 deletions(-)

diff --git a/fs/fuse/kio/pcs/pcs_krpc.c b/fs/fuse/kio/pcs/pcs_krpc.c
index b55c093c13d0..bcfbfc6d9304 100644
--- a/fs/fuse/kio/pcs/pcs_krpc.c
+++ b/fs/fuse/kio/pcs/pcs_krpc.c
@@ -738,8 +738,12 @@ static int pcs_krpc_abort(struct pcs_krpc *krpc)
 	spin_lock(&krpc->lock);
 
 	if (krpc->state != PCS_KRPC_STATE_CONNECTED) {
-		if (krpc->state == PCS_KRPC_STATE_CONNECT)
-			krpc->state = PCS_KRPC_STATE_UNCONN;
+		/*
+		 * A pending connect stays in CONNECT state: its connect req
+		 * is still in flight and krpc_connect_done() will settle the
+		 * state to UNCONN when it completes.  Until then new connects
+		 * are refused, so at most one connect req exists at a time.
+		 */
 		spin_unlock(&krpc->lock);
 		return 0;
 	}
@@ -907,8 +911,13 @@ static __poll_t pcs_krpc_poll(struct file *file, poll_table *wait)
 
 	spin_lock(&krpc->lock);
 
+	/*
+	 * ctx->gen == krpc->gen (checked above) means this is the current
+	 * session, so UNCONN here can only mean its connect attempt has
+	 * failed (see krpc_connect_done()) or the session was aborted.
+	 */
 	if (krpc->state == PCS_KRPC_STATE_ABORTED ||
-	    (krpc->state == PCS_KRPC_STATE_CONNECT && !krpc->connect_req)) {
+	    krpc->state == PCS_KRPC_STATE_UNCONN) {
 		pollflags |= EPOLLERR;
 	} else if (krpc->state == PCS_KRPC_STATE_CONNECTED) {
 		pollflags |= EPOLLOUT;
@@ -999,7 +1008,6 @@ int pcs_krpc_create(struct pcs_krpc_set *krpcs, PCS_NODE_ID_T *id,
 	krpc->gen = 0;
 	krpc->state = PCS_KRPC_STATE_UNCONN;
 	krpc->cs = NULL;
-	krpc->connect_req = NULL;
 
 	krpc->rpc = pcs_rpc_clnt_create(&cc_from_krpcset(krpcs)->eng, id, addr, cs_flags);
 	if (!krpc->rpc) {
@@ -1051,8 +1059,6 @@ static void krpc_connect_done(struct pcs_msg *msg)
 	}
 
 	spin_lock(&krpc->lock);
-	if (krpc->connect_req == req)
-		krpc->connect_req = NULL;
 	/* from a stale session, do nothing  */
 	if (req->gen != krpc->gen || krpc->state != PCS_KRPC_STATE_CONNECT) {
 		spin_unlock(&krpc->lock);
@@ -1062,6 +1068,14 @@ static void krpc_connect_done(struct pcs_msg *msg)
 	if (!pcs_if_error(&msg->error)) {
 		krpc->state = PCS_KRPC_STATE_CONNECTED;
 		pollflags = EPOLLOUT;
+	} else {
+		/*
+		 * Connect failed: settle back to UNCONN so that a new connect
+		 * is allowed again, and report the failure to poll().  Since
+		 * gen is unchanged, the current session's poll sees UNCONN
+		 * and returns EPOLLERR.
+		 */
+		krpc->state = PCS_KRPC_STATE_UNCONN;
 	}
 	spin_unlock(&krpc->lock);
 
@@ -1119,9 +1133,15 @@ int pcs_krpc_connect(struct pcs_krpc_set *krpcs, PCS_NODE_ID_T *id)
 	}
 
 	spin_lock(&krpc->lock);
-	if (krpc->state == PCS_KRPC_STATE_CONNECTED ||
-	    krpc->state == PCS_KRPC_STATE_DESTROYED ||
-	    krpc->connect_req) {
+	/*
+	 * A connect is allowed only when there is neither an established
+	 * session nor a connect req in flight (CONNECT state, see
+	 * krpc_connect_done()).  This limits connect reqs to one at a time:
+	 * if userspace gave up on a connect and retries, the new connect
+	 * fails immediately until the old req completes.
+	 */
+	if (krpc->state != PCS_KRPC_STATE_UNCONN &&
+	    krpc->state != PCS_KRPC_STATE_ABORTED) {
 		spin_unlock(&krpc->lock);
 		err = -EPERM;
 		/* fput() drops ctx and its krpc reference via pcs_krpc_release() */
@@ -1133,7 +1153,6 @@ int pcs_krpc_connect(struct pcs_krpc_set *krpcs, PCS_NODE_ID_T *id)
 	connect_req->gen = krpc->gen;
 	connect_req->krpc = pcs_krpc_get(krpc);
 	krpc->state = PCS_KRPC_STATE_CONNECT;
-	krpc->connect_req = connect_req;
 	spin_unlock(&krpc->lock);
 
 	/* publish the fd only after the connect is committed */
diff --git a/fs/fuse/kio/pcs/pcs_krpc.h b/fs/fuse/kio/pcs/pcs_krpc.h
index 3db9e9712019..6a090ef66185 100644
--- a/fs/fuse/kio/pcs/pcs_krpc.h
+++ b/fs/fuse/kio/pcs/pcs_krpc.h
@@ -82,8 +82,6 @@ struct pcs_krpc {
 	/** Wait queue head for poll */
 	wait_queue_head_t		poll_wait;
 	struct pcs_cs			*cs;
-
-	struct krpc_connect_req *connect_req;
 };
 
 struct pcs_krpc_context {
-- 
2.50.1 (Apple Git-155)

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 4%]

* [PATCH QEMU HCI-8.0] b4: ship project defaults in .b4-config
@ 2026-08-28 11:15 14% Vasileios Almpanis
  0 siblings, 0 replies; 119+ results
From: Vasileios Almpanis @ 2026-08-28 11:15 UTC (permalink / raw)
  To: Andrey Drobyshev; +Cc: svt-core, Vasileios Almpanis

b4 fetches series from lore.kernel.org by default. To use it with our
list archive at lore.virtuozzo.com every developer had to set the same
handful of b4.* git config keys, plus two more to get the right
recipients on "b4 send" and one for the subject prefix.

b4 loads a .b4-config at the top of the tree as defaults for commands
run inside it, so put the archive URL masks, the default recipients and
the default subject prefix there.

https://virtuozzo.atlassian.net/browse/VSTOR-142960

Signed-off-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
---
 .b4-config | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/.b4-config b/.b4-config
index 126f503ded7..c8afd7b3e61 100644
--- a/.b4-config
+++ b/.b4-config
@@ -4,10 +4,13 @@
 #
 
 [b4]
-    send-series-to = qemu-devel@nongnu.org
+    send-series-to = "Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>"
+    send-series-cc = svt-core@virtuozzo.com
+    send-prefixes = "QEMU HCI-8.0"
     send-auto-to-cmd = echo
     send-auto-cc-cmd = scripts/get_maintainer.pl --noroles --norolestats --nogit --nogit-fallback
     am-perpatch-check-cmd = scripts/checkpatch.pl -q --terse --no-summary --mailback -
     prep-perpatch-check-cmd = scripts/checkpatch.pl -q --terse --no-summary --mailback -
-    searchmask = https://lore.kernel.org/qemu-devel/?x=m&t=1&q=%s
-    linkmask = https://lore.kernel.org/qemu-devel/%s
+    midmask = http://lore.virtuozzo.com/%s
+    searchmask = http://lore.virtuozzo.com/all/?x=m&t=1&q=%s
+    linkmask = http://lore.virtuozzo.com/%s

---
base-commit: c8606682c5168c07badb55aebaa55a05db1d5376
change-id: 20260828-b4-a3aaa05eb6aa

-- 
Best regards, Vasileios Almpanis
Software Developer, Virtuozzo.


^ permalink raw reply	[relevance 14%]

* [PATCH QEMU HCI-8.0] b4: ship project defaults in .b4-config
@ 2026-08-28 11:16 14% Vasileios Almpanis
  2026-08-28 11:40  5% ` Andrey Drobyshev
  0 siblings, 1 reply; 119+ results
From: Vasileios Almpanis @ 2026-08-28 11:16 UTC (permalink / raw)
  To: Andrey Drobyshev; +Cc: svt-core, Vasileios Almpanis

b4 fetches series from lore.kernel.org by default. To use it with our
list archive at lore.virtuozzo.com every developer had to set the same
handful of b4.* git config keys, plus two more to get the right
recipients on "b4 send" and one for the subject prefix.

b4 loads a .b4-config at the top of the tree as defaults for commands
run inside it, so put the archive URL masks, the default recipients and
the default subject prefix there.

https://virtuozzo.atlassian.net/browse/VSTOR-142960

Signed-off-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
---
 .b4-config | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/.b4-config b/.b4-config
index 126f503ded7..c8afd7b3e61 100644
--- a/.b4-config
+++ b/.b4-config
@@ -4,10 +4,13 @@
 #
 
 [b4]
-    send-series-to = qemu-devel@nongnu.org
+    send-series-to = "Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>"
+    send-series-cc = svt-core@virtuozzo.com
+    send-prefixes = "QEMU HCI-8.0"
     send-auto-to-cmd = echo
     send-auto-cc-cmd = scripts/get_maintainer.pl --noroles --norolestats --nogit --nogit-fallback
     am-perpatch-check-cmd = scripts/checkpatch.pl -q --terse --no-summary --mailback -
     prep-perpatch-check-cmd = scripts/checkpatch.pl -q --terse --no-summary --mailback -
-    searchmask = https://lore.kernel.org/qemu-devel/?x=m&t=1&q=%s
-    linkmask = https://lore.kernel.org/qemu-devel/%s
+    midmask = http://lore.virtuozzo.com/%s
+    searchmask = http://lore.virtuozzo.com/all/?x=m&t=1&q=%s
+    linkmask = http://lore.virtuozzo.com/%s

---
base-commit: c8606682c5168c07badb55aebaa55a05db1d5376
change-id: 20260828-b4-a3aaa05eb6aa

-- 
Best regards, Vasileios Almpanis
Software Developer, Virtuozzo.


^ permalink raw reply	[relevance 14%]

* Re: [PATCH QEMU HCI-8.0] b4: ship project defaults in .b4-config
  2026-08-28 11:16 14% Vasileios Almpanis
@ 2026-08-28 11:40  5% ` Andrey Drobyshev
  0 siblings, 0 replies; 119+ results
From: Andrey Drobyshev @ 2026-08-28 11:40 UTC (permalink / raw)
  To: Vasileios Almpanis; +Cc: svt-core

On 8/28/26 2:16 PM, Vasileios Almpanis wrote:
> b4 fetches series from lore.kernel.org by default. To use it with our
> list archive at lore.virtuozzo.com every developer had to set the same
> handful of b4.* git config keys, plus two more to get the right
> recipients on "b4 send" and one for the subject prefix.
> 
> b4 loads a .b4-config at the top of the tree as defaults for commands
> run inside it, so put the archive URL masks, the default recipients and
> the default subject prefix there.
> 
> https://virtuozzo.atlassian.net/browse/VSTOR-142960
> 
> Signed-off-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
> ---
>  .b4-config | 9 ++++++---
>  1 file changed, 6 insertions(+), 3 deletions(-)
> 
> diff --git a/.b4-config b/.b4-config
> index 126f503ded7..c8afd7b3e61 100644
> --- a/.b4-config
> +++ b/.b4-config
> @@ -4,10 +4,13 @@
>  #
>  
>  [b4]
> -    send-series-to = qemu-devel@nongnu.org
> +    send-series-to = "Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>"
> +    send-series-cc = svt-core@virtuozzo.com

I like the default better, i.e. --to is the list, --cc is the
maintainer(s).  I'll adjust it myself.

> +    send-prefixes = "QEMU HCI-8.0"
>      send-auto-to-cmd = echo
>      send-auto-cc-cmd = scripts/get_maintainer.pl --noroles --norolestats --nogit --nogit-fallback
>      am-perpatch-check-cmd = scripts/checkpatch.pl -q --terse --no-summary --mailback -
>      prep-perpatch-check-cmd = scripts/checkpatch.pl -q --terse --no-summary --mailback -
> -    searchmask = https://lore.kernel.org/qemu-devel/?x=m&t=1&q=%s
> -    linkmask = https://lore.kernel.org/qemu-devel/%s
> +    midmask = http://lore.virtuozzo.com/%s
> +    searchmask = http://lore.virtuozzo.com/all/?x=m&t=1&q=%s

I'm wondering isn't it better to explicitly specify /qemu here?  It'll
probably make the search faster.

Andrey

> +    linkmask = http://lore.virtuozzo.com/%s
> 
> ---
> base-commit: c8606682c5168c07badb55aebaa55a05db1d5376
> change-id: 20260828-b4-a3aaa05eb6aa
> 


^ permalink raw reply	[relevance 5%]

* Re: [Devel] [PATCH VZ10 v7 7/9] ve: Introduce per-VE failcount
  2026-08-24 13:54  9% ` [Devel] [PATCH VZ10 v7 7/9] ve: Introduce per-VE failcount Vladimir Riabchun
@ 2026-08-28 16:52  5%   ` Pavel Tikhomirov
  2026-08-28 17:32  0%     ` Vladimir Riabchun
  0 siblings, 1 reply; 119+ results
From: Pavel Tikhomirov @ 2026-08-28 16:52 UTC (permalink / raw)
  To: Vladimir Riabchun, devel



On 8/24/26 15:54, Vladimir Riabchun wrote:
> It may be useful to have a history of resource limit hits for every VE,
> this may simplify debugging and provide some information about the
> resources usage.
> 
> This information is provided by ve.failcount file, any write to it
> resets all failcounts.
> 
> To add a new failcounter we need to create a new atomic_t field
> name_failcount in ve structure and add a new VE_FC_ENTRY in
> ve_failcounts array.
> 
> One change, unrelated to failcounts: aio fields are now initialized
> in ve0.
> 
> https://virtuozzo.atlassian.net/browse/VSTOR-135520
> 
> Feature: per-ve failcounters
> Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
> ---
>  fs/aio.c                 |  1 +
>  fs/namespace.c           |  2 ++
>  include/linux/ve.h       |  6 ++++
>  kernel/bpf/syscall.c     |  1 +
>  kernel/ve/ve.c           | 70 ++++++++++++++++++++++++++++++++++++++++
>  net/core/dev.c           |  2 ++
>  net/core/neighbour.c     |  1 +
>  net/core/net_namespace.c |  4 ++-
>  8 files changed, 86 insertions(+), 1 deletion(-)
> 
> diff --git a/fs/aio.c b/fs/aio.c
> index cb63416af135..3fa07cc626f8 100644
> --- a/fs/aio.c
> +++ b/fs/aio.c
> @@ -814,6 +814,7 @@ static struct kioctx *ioctx_alloc(unsigned nr_events)
>  	spin_lock(&ve->aio_nr_lock);
>  	if (ve->aio_nr + ctx->max_reqs > ve->aio_max_nr ||
>  	    ve->aio_nr + ctx->max_reqs < ve->aio_nr) {
> +		atomic_inc(&ve->aio_failcount);
>  		spin_unlock(&ve->aio_nr_lock);
>  		err = -EAGAIN;
>  		goto err_ctx;
> diff --git a/fs/namespace.c b/fs/namespace.c
> index e97f48204617..c30bbc370f2b 100644
> --- a/fs/namespace.c
> +++ b/fs/namespace.c
> @@ -3371,6 +3371,8 @@ static inline int ve_try_reserve_mount(struct ve_struct *ve)
>  
>  	if (ret)
>  		get_ve(ve);
> +	else
> +		atomic_inc(&ve->mnt_failcount);
>  	return ret;
>  }
>  
> diff --git a/include/linux/ve.h b/include/linux/ve.h
> index 5687faad46ff..9e73527e970e 100644
> --- a/include/linux/ve.h
> +++ b/include/linux/ve.h
> @@ -72,12 +72,15 @@ struct ve_struct {
>  	struct kmapset_key	proc_perms_key;
>  
>  	atomic_t		netns_avail_nr;
> +	atomic_t		netns_failcount;
>  	int			netns_max_nr;
>  
>  	atomic_t		netif_avail_nr;
> +	atomic_t		netif_failcount;
>  	int			netif_max_nr;
>  
>  	atomic_t		bpf_prog_avail_nr;
> +	atomic_t		bpf_prog_failcount;
>  	int			bpf_prog_max_nr;
>  
>  	atomic64_t		_uevent_seqnum;
> @@ -86,6 +89,7 @@ struct ve_struct {
>  
>  	atomic_t		arp_neigh_nr;
>  	atomic_t		nd_neigh_nr;
> +	atomic_t		neigh_tbl_failcount;
>  	unsigned long		meminfo_val;
>  
>  	/*
> @@ -94,6 +98,7 @@ struct ve_struct {
>  	 * other containers.
>  	 */
>  	atomic_t		mnt_avail_nr; /* number of available VE mounts */
> +	atomic_t		mnt_failcount;
>  	int			mnt_max_nr;
>  
>  #ifdef CONFIG_COREDUMP
> @@ -121,6 +126,7 @@ struct ve_struct {
>  	spinlock_t		aio_nr_lock;
>  	unsigned long		aio_nr;
>  	unsigned long		aio_max_nr;
> +	atomic_t		aio_failcount;
>  #endif
>  	struct vfsmount		*devtmpfs_mnt;
>  };
> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> index c94d4240e3d3..9d57e7999ae0 100644
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
> @@ -2891,6 +2891,7 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
>  	if (!bpf_cap && type == BPF_PROG_TYPE_CGROUP_DEVICE) {
>  		load_ve = get_exec_env();
>  		if (atomic_dec_if_positive(&load_ve->bpf_prog_avail_nr) < 0) {
> +			atomic_inc(&load_ve->bpf_prog_failcount);
>  			load_ve = NULL;
>  			err = -ENOSPC;
>  			goto put_token;
> diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
> index 0f02835765ff..826f72ad0a22 100644
> --- a/kernel/ve/ve.c
> +++ b/kernel/ve/ve.c
> @@ -99,10 +99,13 @@ struct ve_struct ve0 = {
>  	.features		= -1,
>  	.sched_lat_ve.cur	= &ve0_lat_stats,
>  	.netns_avail_nr		= ATOMIC_INIT(INT_MAX),
> +	.netns_failcount	= ATOMIC_INIT(0),
>  	.netns_max_nr		= INT_MAX,
>  	.netif_avail_nr		= ATOMIC_INIT(INT_MAX),
> +	.netif_failcount	= ATOMIC_INIT(0),
>  	.netif_max_nr		= INT_MAX,
>  	.bpf_prog_avail_nr	= ATOMIC_INIT(INT_MAX),
> +	.bpf_prog_failcount	= ATOMIC_INIT(0),
>  	.bpf_prog_max_nr	= INT_MAX,
>  	.fsync_enable		= FSYNC_FILTERED,
>  	._randomize_va_space	=
> @@ -114,8 +117,16 @@ struct ve_struct ve0 = {
>  
>  	.arp_neigh_nr		= ATOMIC_INIT(0),
>  	.nd_neigh_nr		= ATOMIC_INIT(0),
> +	.neigh_tbl_failcount	= ATOMIC_INIT(0),
>  	.mnt_avail_nr		= ATOMIC_INIT(INT_MAX),
>  	.mnt_max_nr		= INT_MAX,
> +	.mnt_failcount		= ATOMIC_INIT(0),
> +#ifdef CONFIG_AIO
> +	.aio_nr_lock		= __SPIN_LOCK_UNLOCKED(aio_nr_lock),
> +	.aio_nr			= 0,
> +	.aio_max_nr		= AIO_MAX_NR_DEFAULT,

This feature is not intended to limit host aio, right? Should it
be INT_MAX or something big here for ve0?

> +	.aio_failcount		= ATOMIC_INIT(0),
> +#endif
>  	.meminfo_val		= VE_MEMINFO_SYSTEM,
>  	.umh_running_helpers	= ATOMIC_INIT(0),
>  	.umh_helpers_waitq	= __WAIT_QUEUE_HEAD_INITIALIZER(ve0.umh_helpers_waitq),
> @@ -780,12 +791,15 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
>  	ve->fsync_enable = FSYNC_FILTERED;
>  
>  	atomic_set(&ve->netns_avail_nr, NETNS_MAX_NR_DEFAULT);
> +	atomic_set(&ve->netns_failcount, 0);
>  	ve->netns_max_nr = NETNS_MAX_NR_DEFAULT;
>  
>  	atomic_set(&ve->netif_avail_nr, NETIF_MAX_NR_DEFAULT);
> +	atomic_set(&ve->netif_failcount, 0);
>  	ve->netif_max_nr = NETIF_MAX_NR_DEFAULT;
>  
>  	atomic_set(&ve->bpf_prog_avail_nr, BPF_PROG_MAX_NR_DEFAULT);
> +	atomic_set(&ve->bpf_prog_failcount, 0);
>  	ve->bpf_prog_max_nr = BPF_PROG_MAX_NR_DEFAULT;
>  
>  	err = ve_log_init(ve);
> @@ -812,7 +826,9 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
>  
>  	atomic_set(&ve->arp_neigh_nr, 0);
>  	atomic_set(&ve->nd_neigh_nr, 0);
> +	atomic_set(&ve->neigh_tbl_failcount, 0);
>  	ve->mnt_max_nr = MNT_MAX_NR_DEFAULT;
> +	atomic_set(&ve->mnt_failcount, 0);
>  	atomic_set(&ve->mnt_avail_nr, MNT_MAX_NR_DEFAULT);
>  
>  #ifdef CONFIG_COREDUMP
> @@ -825,6 +841,7 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
>  	spin_lock_init(&ve->aio_nr_lock);
>  	ve->aio_nr = 0;
>  	ve->aio_max_nr = AIO_MAX_NR_DEFAULT;
> +	atomic_set(&ve->aio_failcount, 0);
>  #endif
>  
>  	return &ve->css;
> @@ -1065,6 +1082,53 @@ VE_RESOURCE(mnt);
>  VE_RESOURCE(netif);
>  VE_RESOURCE(bpf_prog);
>  
> +static const struct ve_failcount_entry {
> +	const char *name;
> +	size_t offset;
> +} ve_failcounts[] = {
> +#define VE_FC_ENTRY(name) { #name, offsetof(struct ve_struct, name##_failcount) }
> +	VE_FC_ENTRY(netns),
> +	VE_FC_ENTRY(mnt),
> +	VE_FC_ENTRY(netif),
> +	VE_FC_ENTRY(bpf_prog),
> +	VE_FC_ENTRY(neigh_tbl),
> +#ifdef CONFIG_AIO
> +	VE_FC_ENTRY(aio),
> +#endif
> +	{}
> +};
> +
> +static int ve_failcount_read(struct seq_file *sf, void *v)
> +{
> +	struct ve_struct *ve = css_to_ve(seq_css(sf));
> +	const struct ve_failcount_entry *entry;
> +	atomic_t *fc;
> +
> +	for (entry = ve_failcounts; entry->name; entry++) {
> +		fc = (void *)ve + entry->offset;
> +		seq_printf(sf, "%s: %d\n", entry->name, atomic_read(fc));
> +	}
> +	return 0;
> +}
> +
> +static ssize_t ve_failcount_write(struct kernfs_open_file *of, char *buf,
> +				  size_t nbytes, loff_t off)
> +{
> +	struct ve_struct *ve = css_to_ve(of_css(of));
> +	const struct ve_failcount_entry *entry;
> +	atomic_t *fc;
> +
> +	if (!ve_is_super(get_exec_env()) && !ve->is_pseudosuper)
> +		return -EPERM;
> +
> +	for (entry = ve_failcounts; entry->name; entry++) {
> +		fc = (void *)ve + entry->offset;
> +		atomic_set(fc, 0);
> +	}
> +
> +	return nbytes;
> +}
> +
>  static int ve_os_release_read(struct seq_file *sf, void *v)
>  {
>  	struct cgroup_subsys_state *css = seq_css(sf);
> @@ -1602,6 +1666,12 @@ static struct cftype ve_cftypes[] = {
>  		.flags			= CFTYPE_NOT_ON_ROOT,
>  		.write_u64		= ve_rpc_kill_write,
>  	},
> +	{
> +		.name			= "failcount",
> +		.flags			= CFTYPE_NOT_ON_ROOT,
> +		.seq_show		= ve_failcount_read,
> +		.write			= ve_failcount_write,
> +	},
>  	{ }
>  };
>  
> diff --git a/net/core/dev.c b/net/core/dev.c
> index c7dddb200489..05e0b9b6ba23 100644
> --- a/net/core/dev.c
> +++ b/net/core/dev.c
> @@ -10997,6 +10997,7 @@ int register_netdevice(struct net_device *dev)
>  
>  	ret = -ENOMEM;
>  	if (atomic_dec_if_positive(&net->owner_ve->netif_avail_nr) < 0) {
> +		atomic_inc(&net->owner_ve->netif_failcount);
>  		ve_pr_warn_ratelimited(VE_LOG_BOTH,
>  			"CT%s: hits max number of network devices, "
>  			"increase ve::netif_max_nr parameter\n",
> @@ -12211,6 +12212,7 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net,
>  
>  	err = -ENOMEM;
>  	if (atomic_dec_if_positive(&net->owner_ve->netif_avail_nr) < 0) {
> +		atomic_inc(&net->owner_ve->netif_failcount);
>  		ve_pr_warn_ratelimited(VE_LOG_BOTH,
>  			"CT%s: hits max number of network devices, "
>  			"increase ve::netif_max_nr parameter\n",
> diff --git a/net/core/neighbour.c b/net/core/neighbour.c
> index f90deb17fb25..57a49d9c98a7 100644
> --- a/net/core/neighbour.c
> +++ b/net/core/neighbour.c
> @@ -520,6 +520,7 @@ static struct neighbour *neigh_alloc(struct neigh_table *tbl,
>  	    (glob_entries >= READ_ONCE(tbl->gc_thresh2) &&
>  	     time_after(now, READ_ONCE(tbl->last_flush) + 5 * HZ))) {
>  		if (!neigh_forced_gc(tbl, ve) && entries >= gc_thresh3) {
> +			atomic_inc(&ve->neigh_tbl_failcount);
>  			net_info_ratelimited("%s: neighbor table overflow!\n",
>  					     tbl->id);
>  			NEIGH_CACHE_STAT_INC(tbl, table_fulls);
> diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
> index b3d54cad984a..9a3376d2682f 100644
> --- a/net/core/net_namespace.c
> +++ b/net/core/net_namespace.c
> @@ -486,8 +486,10 @@ void net_drop_ns(void *p)
>  #ifdef CONFIG_VE
>  static int dec_netns_avail(struct ve_struct *ve)
>  {
> -	if (atomic_dec_if_positive(&ve->netns_avail_nr) < 0)
> +	if (atomic_dec_if_positive(&ve->netns_avail_nr) < 0) {
> +		atomic_inc(&ve->netns_failcount);

Let's add a helper for incrementing our failcounts:

#define ve_failcount_inc(ve, name)                                    \
do {                                                                  \
      struct ve_struct *__ve = (ve);                                  \
                                                                      \
      if (atomic_inc_return(&__ve->name##_failcount) == 1)            \
              pr_warn("CT%s: hits the " #name " limit, see the '"     \
                      #name "' counter in ve.failcount of the "       \
                      "container's ve cgroup\n", ve_name(__ve));      \
} while (0)

Call sites become one-liners:

--- a/fs/aio.c
-             atomic_inc(&ve->aio_failcount);
+             ve_failcount_inc(ve, aio);
--- a/fs/namespace.c
-             atomic_inc(&ve->mnt_failcount);
+             ve_failcount_inc(ve, mnt);
--- a/kernel/bpf/syscall.c
-                     atomic_inc(&load_ve->bpf_prog_failcount);
+                     ve_failcount_inc(load_ve, bpf_prog);
--- a/net/core/dev.c          (both register_netdevice() and __dev_change_net_namespace())
-             atomic_inc(&net->owner_ve->netif_failcount);
+             ve_failcount_inc(net->owner_ve, netif);
--- a/net/core/neighbour.c
-                     atomic_inc(&ve->neigh_tbl_failcount);
+                     ve_failcount_inc(ve, neigh_tbl);
--- a/net/core/net_namespace.c
-             atomic_inc(&ve->netns_failcount);
+             ve_failcount_inc(ve, netns);

The idea behind it is to show in dmesg that failcount was reached, to simpify
the detection of problematic containers for us.

For reference http://lore.virtuozzo.com/kernel/ccc3cbc8-e225-41cf-b563-699254aeb57b@virtuozzo.com/T/#t

>  		return -ENOSPC;
> +	}
>  	return 0;
>  }
>  

-- 
Best regards, Pavel Tikhomirov
Senior Software Developer, Virtuozzo.

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 5%]

* Re: [Devel] [PATCH VZ10] fs/fuse kio: track pending kRPC connect via state machine only
  2026-08-27 12:37  4% [Devel] [PATCH VZ10] fs/fuse kio: track pending kRPC connect via state machine only Liu Kui
@ 2026-08-28 17:13  0% ` Konstantin Khorenko
  2026-09-01 20:59  0% ` Konstantin Khorenko
  1 sibling, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-08-28 17:13 UTC (permalink / raw)
  To: Liu Kui, devel; +Cc: azaitsev

Liu, please explain what do i do with that patch?
Should i revert ("fs/fuse kio: fix kRPC connect issues") and push this patch instead?

Should we release it as an RK? If yes - for which kernels and where are the corresponding bugs?
Currently i understand nothing, sorry.

--
Best regards,

Konstantin Khorenko,
Virtuozzo Linux Kernel Team

On 8/27/26 14:37, Liu Kui wrote:
> Rework the previous fix ("fs/fuse kio: fix kRPC connect issues") to not
> require the new struct pcs_krpc member "connect_req", so the fix can be
> shipped as a livepatch.
> 
> Both things connect_req was tracking are already derivable from the
> existing state machine once PCS_KRPC_STATE_CONNECT is made to mean
> exactly "a connect req is in flight":
> 
>  - krpc_connect_done() settles a failed connect back to UNCONN instead
>    of leaving the state in CONNECT forever;
> 
>  - pcs_krpc_abort() no longer resets CONNECT to UNCONN: the req is
>    still in flight, and only its completion settles the state;
> 
>  - pcs_krpc_connect() proceeds only from UNCONN or ABORTED, refusing
>    new connects (-EPERM) while a req is in flight - at most one connect
>    req exists at a time, same as with the connect_req check;
> 
>  - pcs_krpc_poll() reports EPOLLERR on UNCONN: poll bails out earlier
>    unless ctx->gen == krpc->gen, and the current session can only be in
>    UNCONN if its connect failed or was aborted, which is what the
>    (CONNECT && !connect_req) test used to detect.
> 
> gen only advances in pcs_krpc_connect(), which is blocked during
> CONNECT, so within that state the in-flight req always carries the
> current gen and krpc_connect_done()'s existing staleness check is
> sufficient.
> 
> Related to:
> https://virtuozzo.atlassian.net/browse/VSTOR-135626
> 
> Signed-off-by: Liu Kui <kui.liu@virtuozzo.com>
> ---
>  fs/fuse/kio/pcs/pcs_krpc.c | 39 ++++++++++++++++++++++++++++----------
>  fs/fuse/kio/pcs/pcs_krpc.h |  2 --
>  2 files changed, 29 insertions(+), 12 deletions(-)
> 
> diff --git a/fs/fuse/kio/pcs/pcs_krpc.c b/fs/fuse/kio/pcs/pcs_krpc.c
> index b55c093c13d0..bcfbfc6d9304 100644
> --- a/fs/fuse/kio/pcs/pcs_krpc.c
> +++ b/fs/fuse/kio/pcs/pcs_krpc.c
> @@ -738,8 +738,12 @@ static int pcs_krpc_abort(struct pcs_krpc *krpc)
>  	spin_lock(&krpc->lock);
>  
>  	if (krpc->state != PCS_KRPC_STATE_CONNECTED) {
> -		if (krpc->state == PCS_KRPC_STATE_CONNECT)
> -			krpc->state = PCS_KRPC_STATE_UNCONN;
> +		/*
> +		 * A pending connect stays in CONNECT state: its connect req
> +		 * is still in flight and krpc_connect_done() will settle the
> +		 * state to UNCONN when it completes.  Until then new connects
> +		 * are refused, so at most one connect req exists at a time.
> +		 */
>  		spin_unlock(&krpc->lock);
>  		return 0;
>  	}
> @@ -907,8 +911,13 @@ static __poll_t pcs_krpc_poll(struct file *file, poll_table *wait)
>  
>  	spin_lock(&krpc->lock);
>  
> +	/*
> +	 * ctx->gen == krpc->gen (checked above) means this is the current
> +	 * session, so UNCONN here can only mean its connect attempt has
> +	 * failed (see krpc_connect_done()) or the session was aborted.
> +	 */
>  	if (krpc->state == PCS_KRPC_STATE_ABORTED ||
> -	    (krpc->state == PCS_KRPC_STATE_CONNECT && !krpc->connect_req)) {
> +	    krpc->state == PCS_KRPC_STATE_UNCONN) {
>  		pollflags |= EPOLLERR;
>  	} else if (krpc->state == PCS_KRPC_STATE_CONNECTED) {
>  		pollflags |= EPOLLOUT;
> @@ -999,7 +1008,6 @@ int pcs_krpc_create(struct pcs_krpc_set *krpcs, PCS_NODE_ID_T *id,
>  	krpc->gen = 0;
>  	krpc->state = PCS_KRPC_STATE_UNCONN;
>  	krpc->cs = NULL;
> -	krpc->connect_req = NULL;
>  
>  	krpc->rpc = pcs_rpc_clnt_create(&cc_from_krpcset(krpcs)->eng, id, addr, cs_flags);
>  	if (!krpc->rpc) {
> @@ -1051,8 +1059,6 @@ static void krpc_connect_done(struct pcs_msg *msg)
>  	}
>  
>  	spin_lock(&krpc->lock);
> -	if (krpc->connect_req == req)
> -		krpc->connect_req = NULL;
>  	/* from a stale session, do nothing  */
>  	if (req->gen != krpc->gen || krpc->state != PCS_KRPC_STATE_CONNECT) {
>  		spin_unlock(&krpc->lock);
> @@ -1062,6 +1068,14 @@ static void krpc_connect_done(struct pcs_msg *msg)
>  	if (!pcs_if_error(&msg->error)) {
>  		krpc->state = PCS_KRPC_STATE_CONNECTED;
>  		pollflags = EPOLLOUT;
> +	} else {
> +		/*
> +		 * Connect failed: settle back to UNCONN so that a new connect
> +		 * is allowed again, and report the failure to poll().  Since
> +		 * gen is unchanged, the current session's poll sees UNCONN
> +		 * and returns EPOLLERR.
> +		 */
> +		krpc->state = PCS_KRPC_STATE_UNCONN;
>  	}
>  	spin_unlock(&krpc->lock);
>  
> @@ -1119,9 +1133,15 @@ int pcs_krpc_connect(struct pcs_krpc_set *krpcs, PCS_NODE_ID_T *id)
>  	}
>  
>  	spin_lock(&krpc->lock);
> -	if (krpc->state == PCS_KRPC_STATE_CONNECTED ||
> -	    krpc->state == PCS_KRPC_STATE_DESTROYED ||
> -	    krpc->connect_req) {
> +	/*
> +	 * A connect is allowed only when there is neither an established
> +	 * session nor a connect req in flight (CONNECT state, see
> +	 * krpc_connect_done()).  This limits connect reqs to one at a time:
> +	 * if userspace gave up on a connect and retries, the new connect
> +	 * fails immediately until the old req completes.
> +	 */
> +	if (krpc->state != PCS_KRPC_STATE_UNCONN &&
> +	    krpc->state != PCS_KRPC_STATE_ABORTED) {
>  		spin_unlock(&krpc->lock);
>  		err = -EPERM;
>  		/* fput() drops ctx and its krpc reference via pcs_krpc_release() */
> @@ -1133,7 +1153,6 @@ int pcs_krpc_connect(struct pcs_krpc_set *krpcs, PCS_NODE_ID_T *id)
>  	connect_req->gen = krpc->gen;
>  	connect_req->krpc = pcs_krpc_get(krpc);
>  	krpc->state = PCS_KRPC_STATE_CONNECT;
> -	krpc->connect_req = connect_req;
>  	spin_unlock(&krpc->lock);
>  
>  	/* publish the fd only after the connect is committed */
> diff --git a/fs/fuse/kio/pcs/pcs_krpc.h b/fs/fuse/kio/pcs/pcs_krpc.h
> index 3db9e9712019..6a090ef66185 100644
> --- a/fs/fuse/kio/pcs/pcs_krpc.h
> +++ b/fs/fuse/kio/pcs/pcs_krpc.h
> @@ -82,8 +82,6 @@ struct pcs_krpc {
>  	/** Wait queue head for poll */
>  	wait_queue_head_t		poll_wait;
>  	struct pcs_cs			*cs;
> -
> -	struct krpc_connect_req *connect_req;
>  };
>  
>  struct pcs_krpc_context {

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 0%]

* Re: [Devel] [PATCH VZ10 v7 7/9] ve: Introduce per-VE failcount
  2026-08-28 16:52  5%   ` Pavel Tikhomirov
@ 2026-08-28 17:32  0%     ` Vladimir Riabchun
  0 siblings, 0 replies; 119+ results
From: Vladimir Riabchun @ 2026-08-28 17:32 UTC (permalink / raw)
  To: Pavel Tikhomirov, devel



On 8/28/26 18:52, Pavel Tikhomirov wrote:
> 
> 
> On 8/24/26 15:54, Vladimir Riabchun wrote:
>> It may be useful to have a history of resource limit hits for every VE,
>> this may simplify debugging and provide some information about the
>> resources usage.
>>
>> This information is provided by ve.failcount file, any write to it
>> resets all failcounts.
>>
>> To add a new failcounter we need to create a new atomic_t field
>> name_failcount in ve structure and add a new VE_FC_ENTRY in
>> ve_failcounts array.
>>
>> One change, unrelated to failcounts: aio fields are now initialized
>> in ve0.
>>
>> https://virtuozzo.atlassian.net/browse/VSTOR-135520
>>
>> Feature: per-ve failcounters
>> Signed-off-by: Vladimir Riabchun <vladimir.riabchun@virtuozzo.com>
>> ---
>>   fs/aio.c                 |  1 +
>>   fs/namespace.c           |  2 ++
>>   include/linux/ve.h       |  6 ++++
>>   kernel/bpf/syscall.c     |  1 +
>>   kernel/ve/ve.c           | 70 ++++++++++++++++++++++++++++++++++++++++
>>   net/core/dev.c           |  2 ++
>>   net/core/neighbour.c     |  1 +
>>   net/core/net_namespace.c |  4 ++-
>>   8 files changed, 86 insertions(+), 1 deletion(-)
>>
>> diff --git a/fs/aio.c b/fs/aio.c
>> index cb63416af135..3fa07cc626f8 100644
>> --- a/fs/aio.c
>> +++ b/fs/aio.c
>> @@ -814,6 +814,7 @@ static struct kioctx *ioctx_alloc(unsigned nr_events)
>>   	spin_lock(&ve->aio_nr_lock);
>>   	if (ve->aio_nr + ctx->max_reqs > ve->aio_max_nr ||
>>   	    ve->aio_nr + ctx->max_reqs < ve->aio_nr) {
>> +		atomic_inc(&ve->aio_failcount);
>>   		spin_unlock(&ve->aio_nr_lock);
>>   		err = -EAGAIN;
>>   		goto err_ctx;
>> diff --git a/fs/namespace.c b/fs/namespace.c
>> index e97f48204617..c30bbc370f2b 100644
>> --- a/fs/namespace.c
>> +++ b/fs/namespace.c
>> @@ -3371,6 +3371,8 @@ static inline int ve_try_reserve_mount(struct ve_struct *ve)
>>   
>>   	if (ret)
>>   		get_ve(ve);
>> +	else
>> +		atomic_inc(&ve->mnt_failcount);
>>   	return ret;
>>   }
>>   
>> diff --git a/include/linux/ve.h b/include/linux/ve.h
>> index 5687faad46ff..9e73527e970e 100644
>> --- a/include/linux/ve.h
>> +++ b/include/linux/ve.h
>> @@ -72,12 +72,15 @@ struct ve_struct {
>>   	struct kmapset_key	proc_perms_key;
>>   
>>   	atomic_t		netns_avail_nr;
>> +	atomic_t		netns_failcount;
>>   	int			netns_max_nr;
>>   
>>   	atomic_t		netif_avail_nr;
>> +	atomic_t		netif_failcount;
>>   	int			netif_max_nr;
>>   
>>   	atomic_t		bpf_prog_avail_nr;
>> +	atomic_t		bpf_prog_failcount;
>>   	int			bpf_prog_max_nr;
>>   
>>   	atomic64_t		_uevent_seqnum;
>> @@ -86,6 +89,7 @@ struct ve_struct {
>>   
>>   	atomic_t		arp_neigh_nr;
>>   	atomic_t		nd_neigh_nr;
>> +	atomic_t		neigh_tbl_failcount;
>>   	unsigned long		meminfo_val;
>>   
>>   	/*
>> @@ -94,6 +98,7 @@ struct ve_struct {
>>   	 * other containers.
>>   	 */
>>   	atomic_t		mnt_avail_nr; /* number of available VE mounts */
>> +	atomic_t		mnt_failcount;
>>   	int			mnt_max_nr;
>>   
>>   #ifdef CONFIG_COREDUMP
>> @@ -121,6 +126,7 @@ struct ve_struct {
>>   	spinlock_t		aio_nr_lock;
>>   	unsigned long		aio_nr;
>>   	unsigned long		aio_max_nr;
>> +	atomic_t		aio_failcount;
>>   #endif
>>   	struct vfsmount		*devtmpfs_mnt;
>>   };
>> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
>> index c94d4240e3d3..9d57e7999ae0 100644
>> --- a/kernel/bpf/syscall.c
>> +++ b/kernel/bpf/syscall.c
>> @@ -2891,6 +2891,7 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
>>   	if (!bpf_cap && type == BPF_PROG_TYPE_CGROUP_DEVICE) {
>>   		load_ve = get_exec_env();
>>   		if (atomic_dec_if_positive(&load_ve->bpf_prog_avail_nr) < 0) {
>> +			atomic_inc(&load_ve->bpf_prog_failcount);
>>   			load_ve = NULL;
>>   			err = -ENOSPC;
>>   			goto put_token;
>> diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
>> index 0f02835765ff..826f72ad0a22 100644
>> --- a/kernel/ve/ve.c
>> +++ b/kernel/ve/ve.c
>> @@ -99,10 +99,13 @@ struct ve_struct ve0 = {
>>   	.features		= -1,
>>   	.sched_lat_ve.cur	= &ve0_lat_stats,
>>   	.netns_avail_nr		= ATOMIC_INIT(INT_MAX),
>> +	.netns_failcount	= ATOMIC_INIT(0),
>>   	.netns_max_nr		= INT_MAX,
>>   	.netif_avail_nr		= ATOMIC_INIT(INT_MAX),
>> +	.netif_failcount	= ATOMIC_INIT(0),
>>   	.netif_max_nr		= INT_MAX,
>>   	.bpf_prog_avail_nr	= ATOMIC_INIT(INT_MAX),
>> +	.bpf_prog_failcount	= ATOMIC_INIT(0),
>>   	.bpf_prog_max_nr	= INT_MAX,
>>   	.fsync_enable		= FSYNC_FILTERED,
>>   	._randomize_va_space	=
>> @@ -114,8 +117,16 @@ struct ve_struct ve0 = {
>>   
>>   	.arp_neigh_nr		= ATOMIC_INIT(0),
>>   	.nd_neigh_nr		= ATOMIC_INIT(0),
>> +	.neigh_tbl_failcount	= ATOMIC_INIT(0),
>>   	.mnt_avail_nr		= ATOMIC_INIT(INT_MAX),
>>   	.mnt_max_nr		= INT_MAX,
>> +	.mnt_failcount		= ATOMIC_INIT(0),
>> +#ifdef CONFIG_AIO
>> +	.aio_nr_lock		= __SPIN_LOCK_UNLOCKED(aio_nr_lock),
>> +	.aio_nr			= 0,
>> +	.aio_max_nr		= AIO_MAX_NR_DEFAULT,
> 
> This feature is not intended to limit host aio, right? Should it
> be INT_MAX or something big here for ve0?

It preserves original kernel behavior. Upstream kernel has aio_max_nr
sysctl variable with the same value as AIO_MAX_NR_DEFAULT.
Commit 1f6006388601 ("ve/fs/aio: aio_nr & aio_max_nr variables virtualization")
added aio_max_nr to ve structure, but it didn't initialize it in ve0.

Here I just put the correct value in it, so no functional changes.

> 
>> +	.aio_failcount		= ATOMIC_INIT(0),
>> +#endif
>>   	.meminfo_val		= VE_MEMINFO_SYSTEM,
>>   	.umh_running_helpers	= ATOMIC_INIT(0),
>>   	.umh_helpers_waitq	= __WAIT_QUEUE_HEAD_INITIALIZER(ve0.umh_helpers_waitq),
>> @@ -780,12 +791,15 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
>>   	ve->fsync_enable = FSYNC_FILTERED;
>>   
>>   	atomic_set(&ve->netns_avail_nr, NETNS_MAX_NR_DEFAULT);
>> +	atomic_set(&ve->netns_failcount, 0);
>>   	ve->netns_max_nr = NETNS_MAX_NR_DEFAULT;
>>   
>>   	atomic_set(&ve->netif_avail_nr, NETIF_MAX_NR_DEFAULT);
>> +	atomic_set(&ve->netif_failcount, 0);
>>   	ve->netif_max_nr = NETIF_MAX_NR_DEFAULT;
>>   
>>   	atomic_set(&ve->bpf_prog_avail_nr, BPF_PROG_MAX_NR_DEFAULT);
>> +	atomic_set(&ve->bpf_prog_failcount, 0);
>>   	ve->bpf_prog_max_nr = BPF_PROG_MAX_NR_DEFAULT;
>>   
>>   	err = ve_log_init(ve);
>> @@ -812,7 +826,9 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
>>   
>>   	atomic_set(&ve->arp_neigh_nr, 0);
>>   	atomic_set(&ve->nd_neigh_nr, 0);
>> +	atomic_set(&ve->neigh_tbl_failcount, 0);
>>   	ve->mnt_max_nr = MNT_MAX_NR_DEFAULT;
>> +	atomic_set(&ve->mnt_failcount, 0);
>>   	atomic_set(&ve->mnt_avail_nr, MNT_MAX_NR_DEFAULT);
>>   
>>   #ifdef CONFIG_COREDUMP
>> @@ -825,6 +841,7 @@ static struct cgroup_subsys_state *ve_create(struct cgroup_subsys_state *parent_
>>   	spin_lock_init(&ve->aio_nr_lock);
>>   	ve->aio_nr = 0;
>>   	ve->aio_max_nr = AIO_MAX_NR_DEFAULT;
>> +	atomic_set(&ve->aio_failcount, 0);
>>   #endif
>>   
>>   	return &ve->css;
>> @@ -1065,6 +1082,53 @@ VE_RESOURCE(mnt);
>>   VE_RESOURCE(netif);
>>   VE_RESOURCE(bpf_prog);
>>   
>> +static const struct ve_failcount_entry {
>> +	const char *name;
>> +	size_t offset;
>> +} ve_failcounts[] = {
>> +#define VE_FC_ENTRY(name) { #name, offsetof(struct ve_struct, name##_failcount) }
>> +	VE_FC_ENTRY(netns),
>> +	VE_FC_ENTRY(mnt),
>> +	VE_FC_ENTRY(netif),
>> +	VE_FC_ENTRY(bpf_prog),
>> +	VE_FC_ENTRY(neigh_tbl),
>> +#ifdef CONFIG_AIO
>> +	VE_FC_ENTRY(aio),
>> +#endif
>> +	{}
>> +};
>> +
>> +static int ve_failcount_read(struct seq_file *sf, void *v)
>> +{
>> +	struct ve_struct *ve = css_to_ve(seq_css(sf));
>> +	const struct ve_failcount_entry *entry;
>> +	atomic_t *fc;
>> +
>> +	for (entry = ve_failcounts; entry->name; entry++) {
>> +		fc = (void *)ve + entry->offset;
>> +		seq_printf(sf, "%s: %d\n", entry->name, atomic_read(fc));
>> +	}
>> +	return 0;
>> +}
>> +
>> +static ssize_t ve_failcount_write(struct kernfs_open_file *of, char *buf,
>> +				  size_t nbytes, loff_t off)
>> +{
>> +	struct ve_struct *ve = css_to_ve(of_css(of));
>> +	const struct ve_failcount_entry *entry;
>> +	atomic_t *fc;
>> +
>> +	if (!ve_is_super(get_exec_env()) && !ve->is_pseudosuper)
>> +		return -EPERM;
>> +
>> +	for (entry = ve_failcounts; entry->name; entry++) {
>> +		fc = (void *)ve + entry->offset;
>> +		atomic_set(fc, 0);
>> +	}
>> +
>> +	return nbytes;
>> +}
>> +
>>   static int ve_os_release_read(struct seq_file *sf, void *v)
>>   {
>>   	struct cgroup_subsys_state *css = seq_css(sf);
>> @@ -1602,6 +1666,12 @@ static struct cftype ve_cftypes[] = {
>>   		.flags			= CFTYPE_NOT_ON_ROOT,
>>   		.write_u64		= ve_rpc_kill_write,
>>   	},
>> +	{
>> +		.name			= "failcount",
>> +		.flags			= CFTYPE_NOT_ON_ROOT,
>> +		.seq_show		= ve_failcount_read,
>> +		.write			= ve_failcount_write,
>> +	},
>>   	{ }
>>   };
>>   
>> diff --git a/net/core/dev.c b/net/core/dev.c
>> index c7dddb200489..05e0b9b6ba23 100644
>> --- a/net/core/dev.c
>> +++ b/net/core/dev.c
>> @@ -10997,6 +10997,7 @@ int register_netdevice(struct net_device *dev)
>>   
>>   	ret = -ENOMEM;
>>   	if (atomic_dec_if_positive(&net->owner_ve->netif_avail_nr) < 0) {
>> +		atomic_inc(&net->owner_ve->netif_failcount);
>>   		ve_pr_warn_ratelimited(VE_LOG_BOTH,
>>   			"CT%s: hits max number of network devices, "
>>   			"increase ve::netif_max_nr parameter\n",
>> @@ -12211,6 +12212,7 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net,
>>   
>>   	err = -ENOMEM;
>>   	if (atomic_dec_if_positive(&net->owner_ve->netif_avail_nr) < 0) {
>> +		atomic_inc(&net->owner_ve->netif_failcount);
>>   		ve_pr_warn_ratelimited(VE_LOG_BOTH,
>>   			"CT%s: hits max number of network devices, "
>>   			"increase ve::netif_max_nr parameter\n",
>> diff --git a/net/core/neighbour.c b/net/core/neighbour.c
>> index f90deb17fb25..57a49d9c98a7 100644
>> --- a/net/core/neighbour.c
>> +++ b/net/core/neighbour.c
>> @@ -520,6 +520,7 @@ static struct neighbour *neigh_alloc(struct neigh_table *tbl,
>>   	    (glob_entries >= READ_ONCE(tbl->gc_thresh2) &&
>>   	     time_after(now, READ_ONCE(tbl->last_flush) + 5 * HZ))) {
>>   		if (!neigh_forced_gc(tbl, ve) && entries >= gc_thresh3) {
>> +			atomic_inc(&ve->neigh_tbl_failcount);
>>   			net_info_ratelimited("%s: neighbor table overflow!\n",
>>   					     tbl->id);
>>   			NEIGH_CACHE_STAT_INC(tbl, table_fulls);
>> diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
>> index b3d54cad984a..9a3376d2682f 100644
>> --- a/net/core/net_namespace.c
>> +++ b/net/core/net_namespace.c
>> @@ -486,8 +486,10 @@ void net_drop_ns(void *p)
>>   #ifdef CONFIG_VE
>>   static int dec_netns_avail(struct ve_struct *ve)
>>   {
>> -	if (atomic_dec_if_positive(&ve->netns_avail_nr) < 0)
>> +	if (atomic_dec_if_positive(&ve->netns_avail_nr) < 0) {
>> +		atomic_inc(&ve->netns_failcount);
> 
> Let's add a helper for incrementing our failcounts:
> 
> #define ve_failcount_inc(ve, name)                                    \
> do {                                                                  \
>        struct ve_struct *__ve = (ve);                                  \
>                                                                        \
>        if (atomic_inc_return(&__ve->name##_failcount) == 1)            \
>                pr_warn("CT%s: hits the " #name " limit, see the '"     \
>                        #name "' counter in ve.failcount of the "       \
>                        "container's ve cgroup\n", ve_name(__ve));      \
> } while (0)
> 
> Call sites become one-liners:
> 
> --- a/fs/aio.c
> -             atomic_inc(&ve->aio_failcount);
> +             ve_failcount_inc(ve, aio);
> --- a/fs/namespace.c
> -             atomic_inc(&ve->mnt_failcount);
> +             ve_failcount_inc(ve, mnt);
> --- a/kernel/bpf/syscall.c
> -                     atomic_inc(&load_ve->bpf_prog_failcount);
> +                     ve_failcount_inc(load_ve, bpf_prog);
> --- a/net/core/dev.c          (both register_netdevice() and __dev_change_net_namespace())
> -             atomic_inc(&net->owner_ve->netif_failcount);
> +             ve_failcount_inc(net->owner_ve, netif);
> --- a/net/core/neighbour.c
> -                     atomic_inc(&ve->neigh_tbl_failcount);
> +                     ve_failcount_inc(ve, neigh_tbl);
> --- a/net/core/net_namespace.c
> -             atomic_inc(&ve->netns_failcount);
> +             ve_failcount_inc(ve, netns);
> 
> The idea behind it is to show in dmesg that failcount was reached, to simpify
> the detection of problematic containers for us.
> 
> For reference http://lore.virtuozzo.com/kernel/ccc3cbc8-e225-41cf-b563-699254aeb57b@virtuozzo.com/T/#t
> 

Fair, I'll do it in next version or as a follow-up if everything else is fine here.
Just instead of pr_warn we should use pr_warn_ratelimited, as it is easy
to trigger warning from container and flood dmesg and serial console.

>>   		return -ENOSPC;
>> +	}
>>   	return 0;
>>   }
>>   
> 

-- 
Best regards, Riabchun Vladimir
Linux Kernel Developer, Virtuozzo

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 0%]

* [Devel] [PATCH 1/1] ms/virtio_ring: fix infinite loop in virtnet_poll_cleantx when device is broken
@ 2026-08-31 15:16  4% Denis V. Lunev
  0 siblings, 0 replies; 119+ results
From: Denis V. Lunev @ 2026-08-31 15:16 UTC (permalink / raw)
  To: devel

From: Jinqian Yang <yangjinqian1@huawei.com>

virtnet_poll_cleantx() contains a do-while loop that cleans up
transmitted TX buffers and calls virtqueue_enable_cb_delayed() to check
whether more buffers need processing. When the virtio backend stops
responding during guest reboot, used->idx is never updated, so
virtqueue_enable_cb_delayed() always returns false and the loop never
terminates. Then it will block reboot process, and the guest will hang.

The problem occurs during guest reboot under network traffic:

  1. kernel_restart() -> device_shutdown() traverses the device list
  2. virtio_dev_shutdown() calls virtio_break_device() which sets
     vq->broken = true
  3. virtio_dev_shutdown() then calls virtio_synchronize_cbs() to wait
     for in-flight callbacks to complete
  4. A virtio interrupt fires, softirq is deferred to ksoftirqd which
     calls net_rx_action() -> virtnet_poll() -> virtnet_poll_cleantx()
  5. virtnet_poll_cleantx() enters the do-while loop and never exits
     because the QEMU backend has stopped updating used->idx, despite
     vq->broken having been set to true in step 2.

Since the loop runs inside ksoftirqd (a SCHED_OTHER kthread), it is
visible to the scheduler and does not trigger a hard lockup. However,
the kthread never leaves the loop, so RCU detects it as a CPU stall
and reports it periodically. Meanwhile, the reboot process remains
blocked in device_shutdown() because virtio_dev_shutdown() cannot
complete its synchronization step, and the guest hangs permanently.

This can be reproduced on a guest with a virtio-net device: run iperf3
traffic in the guest, then trigger reboot. The reboot occasionally hangs
permanently with RCU stall on ksoftirqd.

Observed on ARM64 KVM guest:

  CPU#1 RCU stall (ksoftirqd/1), repeated periodically:
    virtqueue_enable_cb_delayed_split <- virtnet_poll <- __napi_poll <-
    net_rx_action <- handle_softirqs <- run_ksoftirqd <-
    smpboot_thread_fn <- kthread

Fix by adding a vq->broken check in virtqueue_enable_cb_delayed(), so
that the loop exits immediately when the device is broken, allowing
the device shutdown to proceed.

Signed-off-by: Jinqian Yang <yangjinqian1@huawei.com>
Reviewed-by: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Message-ID: <20260716115940.394832-1-yangjinqian1@huawei.com>

(cherry picked from commit 0d0eff39ceb3dcbf7847a6f4517086c207c60081)

Applied by hand: this tree has no data_race() annotation on
vq->event_triggered and dispatches the split and packed helpers
directly, so only the surrounding context differs. The added check is
identical.

This tree is hit harder than the kernel the fix was written against.
Upstream commit e13b6da7045f ("virtio-net: tweak for better TX
performance in NAPI mode") replaced the same do-while loop in
start_xmit() with a single check and is not here yet, so the livelock
is also reachable from a timer softirq: a delack timer transmitting on
a broken queue never returns from start_xmit(), and softlockup_panic
turns that into a panic rather than an RCU stall on ksoftirqd.

https://virtuozzo.atlassian.net/browse/VSTOR-143525
Feature: fix ms/virtio_ring
Signed-off-by: Denis V. Lunev <den@openvz.org>
---
 drivers/virtio/virtio_ring.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c
index 65674af37918..8097e49456d3 100644
--- a/drivers/virtio/virtio_ring.c
+++ b/drivers/virtio/virtio_ring.c
@@ -2529,6 +2529,14 @@ bool virtqueue_enable_cb_delayed(struct virtqueue *_vq)
 {
 	struct vring_virtqueue *vq = to_vvq(_vq);
 
+	/*
+	 * When the device is broken there is no point in polling used->idx,
+	 * the backend will never update it. Return true to let callers
+	 * exit their cleanup loops instead of spinning forever.
+	 */
+	if (unlikely(vq->broken))
+		return true;
+
 	if (vq->event_triggered)
 		vq->event_triggered = false;
 
-- 
2.53.0

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 4%]

* [Devel] [PATCH vz10] ms/selftests/sched_ext: flush stdout before test to avoid log spam
@ 2026-08-31 22:44  5% Eva Kurchatova
  0 siblings, 0 replies; 119+ results
From: Eva Kurchatova @ 2026-08-31 22:44 UTC (permalink / raw)
  To: khorenko; +Cc: devel

From: Emil Tsalapatis <etsal@meta.com>

The sched_ext selftests runner runs each test in the same process,
with each test possibly forking multiple times. When the main runner
has not flushed its stdout, the children inherit the buffered output
for previous tests and emit it during exit. This causes log spam.

Make sure stdout/stderr is fully flushed before each test.

Cc: Ihor Solodrai <ihor.solodrai@linux.dev>
Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
Signed-off-by: Tejun Heo <tj@kernel.org>
Started by hand, the runner writes 12 MB for its 23 tests, the preamble
of each repeated about five thousand times, against 8 KB with this
applied. run_kselftest.sh starts every test under stdbuf --output=L,
so a run through it never shows the duplication.

(cherry picked from commit 579a3297b268f0281644ead7ff574a2b4bc64d3c)

https://virtuozzo.atlassian.net/browse/VSTOR-143426
Feature: fix selftests
Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
---
 tools/testing/selftests/sched_ext/runner.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/tools/testing/selftests/sched_ext/runner.c b/tools/testing/selftests/sched_ext/runner.c
index aa2d7d32dda9..5748d2c69903 100644
--- a/tools/testing/selftests/sched_ext/runner.c
+++ b/tools/testing/selftests/sched_ext/runner.c
@@ -46,6 +46,14 @@ static void print_test_preamble(const struct scx_test *test, bool quiet)
 	if (!quiet)
 		printf("DESCRIPTION: %s\n", test->description);
 	printf("OUTPUT:\n");
+
+	/*
+	 * The tests may fork with the preamble buffered
+	 * in the children's stdout. Flush before the test
+	 * to avoid printing the message multiple times.
+	 */
+	fflush(stdout);
+	fflush(stderr);
 }
 
 static const char *status_to_result(enum scx_test_status status)
-- 
2.55.0

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 5%]

* [Devel] [PATCH vz10 1/3] ms/selftests/posix_timers: Use CLOCK_THREAD_CPUTIME_ID for ITIMER_PROF measurements
@ 2026-08-31 22:45  5% Eva Kurchatova
  0 siblings, 0 replies; 119+ results
From: Eva Kurchatova @ 2026-08-31 22:45 UTC (permalink / raw)
  To: khorenko; +Cc: devel

From: John Stultz <jstultz@google.com>

It was reported that the posix_timers test was at times seeing failures
with ITIMER_PROF timers, specifically in cases where the RCU_SOFTIRQ was
taking up significant amounts of time.

Analysis showed that as the time in softirq isn't included in the task
stime + utime accounting used to trigger the SIGPROF so delays from softirq
work could cause it to appear that the signal was incorrectly delayed.

Contributing to this is that the test uses gettimeofday() to measure
itimers, which also means any scheduling delay can also cause failures (as
the task may not be running the entire time).

To fix this, convert all the itimer measurements to use clock_gettime(),
tweaking the logic to use nsecs instead of usecs. Then for ITIMER_PROF
timers, utilize the CLOCK_THREAD_CPUTIME_ID clockid so that it is similarly
measuring the time the task was running.

Signed-off-by: John Stultz <jstultz@google.com>
Signed-off-by: Thomas Gleixner <tglx@kernel.org>
Link: https://patch.msgid.link/20260428173957.1394265-1-jstultz@google.com
The ITIMER_PROF case of it fails on the CI machines, which are two
cpu guests under load:

  # Diff too high: 2561992..not ok 3 ITIMER_PROF

2.56 seconds of wall time for the 2 seconds of cpu time the timer
counts, so the task had about four fifths of a cpu.

(cherry picked from commit b00385b8d081ce74f36ea178e04e1b106505fb36)

https://virtuozzo.atlassian.net/browse/VSTOR-143363
Feature: fix selftests
Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
---
 tools/testing/selftests/timers/posix_timers.c | 55 ++++++++++---------
 1 file changed, 28 insertions(+), 27 deletions(-)

diff --git a/tools/testing/selftests/timers/posix_timers.c b/tools/testing/selftests/timers/posix_timers.c
index f0eceb0faf34..077a98fb1b1d 100644
--- a/tools/testing/selftests/timers/posix_timers.c
+++ b/tools/testing/selftests/timers/posix_timers.c
@@ -77,19 +77,25 @@ static void sig_handler(int nr)
 	done = 1;
 }
 
+static inline int64_t calcdiff_ns(struct timespec t1, struct timespec t2)
+{
+	int64_t diff;
+
+	diff = NSEC_PER_SEC * (int64_t)((int) t1.tv_sec - (int) t2.tv_sec);
+	diff += ((int) t1.tv_nsec - (int) t2.tv_nsec);
+	return diff;
+}
+
 /*
  * Check the expected timer expiration matches the GTOD elapsed delta since
  * we armed the timer. Keep a 0.5 sec error margin due to various jitter.
  */
-static int check_diff(struct timeval start, struct timeval end)
+static int check_diff(struct timespec start, struct timespec end)
 {
-	long long diff;
-
-	diff = end.tv_usec - start.tv_usec;
-	diff += (end.tv_sec - start.tv_sec) * USEC_PER_SEC;
+	long long diff = calcdiff_ns(end, start);
 
-	if (llabs(diff - DELAY * USEC_PER_SEC) > USEC_PER_SEC / 2) {
-		printf("Diff too high: %lld..", diff);
+	if (llabs(diff - DELAY * NSEC_PER_SEC) > NSEC_PER_SEC / 2) {
+		printf("Diff too high: %lld ns..", diff);
 		return -1;
 	}
 
@@ -98,22 +104,25 @@ static int check_diff(struct timeval start, struct timeval end)
 
 static void check_itimer(int which, const char *name)
 {
-	struct timeval start, end;
+	struct timespec start, end;
 	struct itimerval val = {
 		.it_value.tv_sec = DELAY,
 	};
+	int clock_id = CLOCK_REALTIME;
 
 	done = 0;
 
 	if (which == ITIMER_VIRTUAL)
 		signal(SIGVTALRM, sig_handler);
-	else if (which == ITIMER_PROF)
+	else if (which == ITIMER_PROF) {
+		clock_id = CLOCK_THREAD_CPUTIME_ID;
 		signal(SIGPROF, sig_handler);
+	}
 	else if (which == ITIMER_REAL)
 		signal(SIGALRM, sig_handler);
 
-	if (gettimeofday(&start, NULL) < 0)
-		fatal_error(name, "gettimeofday()");
+	if (clock_gettime(clock_id, &start))
+		fatal_error(name, "clock_gettime()");
 
 	if (setitimer(which, &val, NULL) < 0)
 		fatal_error(name, "setitimer()");
@@ -125,18 +134,19 @@ static void check_itimer(int which, const char *name)
 	else if (which == ITIMER_REAL)
 		idle_loop();
 
-	if (gettimeofday(&end, NULL) < 0)
-		fatal_error(name, "gettimeofday()");
+	if (clock_gettime(clock_id, &end))
+		fatal_error(name, "clock_gettime()");
 
 	ksft_test_result(check_diff(start, end) == 0, "%s\n", name);
 }
 
 static void check_timer_create(int which, const char *name)
 {
-	struct timeval start, end;
+	struct timespec start, end;
 	struct itimerspec val = {
 		.it_value.tv_sec = DELAY,
 	};
+	int clock_id = CLOCK_REALTIME;
 	timer_t id;
 
 	done = 0;
@@ -147,16 +157,16 @@ static void check_timer_create(int which, const char *name)
 	if (signal(SIGALRM, sig_handler) == SIG_ERR)
 		fatal_error(name, "signal()");
 
-	if (gettimeofday(&start, NULL) < 0)
-		fatal_error(name, "gettimeofday()");
+	if (clock_gettime(clock_id, &start))
+		fatal_error(name, "clock_gettime()");
 
 	if (timer_settime(id, 0, &val, NULL) < 0)
 		fatal_error(name, "timer_settime()");
 
 	user_loop();
 
-	if (gettimeofday(&end, NULL) < 0)
-		fatal_error(name, "gettimeofday()");
+	if (clock_gettime(clock_id, &end))
+		fatal_error(name, "clock_gettime()");
 
 	ksft_test_result(check_diff(start, end) == 0,
 			 "timer_create() per %s\n", name);
@@ -444,15 +454,6 @@ static void check_delete(void)
 	ksft_test_result(!tsig.signals, "check_delete\n");
 }
 
-static inline int64_t calcdiff_ns(struct timespec t1, struct timespec t2)
-{
-	int64_t diff;
-
-	diff = NSEC_PER_SEC * (int64_t)((int) t1.tv_sec - (int) t2.tv_sec);
-	diff += ((int) t1.tv_nsec - (int) t2.tv_nsec);
-	return diff;
-}
-
 static void check_sigev_none(int which, const char *name)
 {
 	struct timespec start, now;
-- 
2.55.0

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 5%]

* [Devel] [PATCH vz10 3/7] selftests: net: skip what this kernel and iproute2 do not have
  @ 2026-08-31 22:48  3% ` Eva Kurchatova
  2026-08-31 22:48 19% ` [Devel] [PATCH vz10 7/7] selftests: net: let the bridged PMTU tests take the ICMP they ask for Eva Kurchatova
  1 sibling, 0 replies; 119+ results
From: Eva Kurchatova @ 2026-08-31 22:48 UTC (permalink / raw)
  To: khorenko; +Cc: devel

Five cases report a failure where a feature is simply not there.

tls asks for every cipher it knows and fails on the ones the kernel does
not implement, SM4 and ARIA, which answer ENOENT at setsockopt
time. Skip a cipher the kernel refuses that way; the run then reports
632 passed, 264 skipped and none failed.

rtnetlink.sh sets up an erspan tunnel, which this kernel does not have
and which fails with "Unknown device type", and checks a netconf dump
whose exit status iproute2 gets wrong: 6.11 exits 2 on a dump that
worked, 6.17 exits 0. Probe for both and skip when unusable.

rtnetlink.py asks for an IPv4 multicast address dump the kernel answers
with EOPNOTSUPP, and ip_local_port_range asks for protocols it does not
have. Skip those too.

https://virtuozzo.atlassian.net/browse/VSTOR-139651
Feature: fix selftests
Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
---
 .../selftests/net/ip_local_port_range.c       | 17 +++++++---
 tools/testing/selftests/net/rtnetlink.py      | 11 +++++--
 tools/testing/selftests/net/rtnetlink.sh      | 31 +++++++++++++++++++
 tools/testing/selftests/net/tls.c             |  2 ++
 4 files changed, 55 insertions(+), 6 deletions(-)

diff --git a/tools/testing/selftests/net/ip_local_port_range.c b/tools/testing/selftests/net/ip_local_port_range.c
index 29451d2244b7..2d7b9c852b2b 100644
--- a/tools/testing/selftests/net/ip_local_port_range.c
+++ b/tools/testing/selftests/net/ip_local_port_range.c
@@ -118,10 +118,6 @@ static int get_ip_local_port_range(int fd, __u32 *range)
 
 FIXTURE(ip_local_port_range) {};
 
-FIXTURE_SETUP(ip_local_port_range)
-{
-}
-
 FIXTURE_TEARDOWN(ip_local_port_range)
 {
 }
@@ -180,6 +176,19 @@ FIXTURE_VARIANT_ADD(ip_local_port_range, ip6_mptcp) {
 	.so_protocol	= IPPROTO_MPTCP,
 };
 
+FIXTURE_SETUP(ip_local_port_range)
+{
+	int fd;
+
+	/* Not every protocol under test is built into every kernel. */
+	fd = socket(variant->so_domain, variant->so_type, variant->so_protocol);
+	if (fd < 0 && (errno == EPROTONOSUPPORT || errno == ESOCKTNOSUPPORT ||
+		       errno == EAFNOSUPPORT))
+		SKIP(return, "%s", strerror(errno));
+	ASSERT_GE(fd, 0) TH_LOG("socket failed");
+	close(fd);
+}
+
 TEST_F(ip_local_port_range, invalid_option_value)
 {
 	__u16 val16;
diff --git a/tools/testing/selftests/net/rtnetlink.py b/tools/testing/selftests/net/rtnetlink.py
index baa7b33f313e..425fc7a3941b 100755
--- a/tools/testing/selftests/net/rtnetlink.py
+++ b/tools/testing/selftests/net/rtnetlink.py
@@ -1,7 +1,9 @@
 #! /usr/bin/python3 -sP
 # SPDX-License-Identifier: GPL-2.0
 
-from lib.py import ksft_exit, ksft_run, ksft_ge, RtnlAddrFamily
+from lib.py import ksft_exit, ksft_run, ksft_ge, KsftSkipEx
+from lib.py import NlError, RtnlAddrFamily
+import errno
 import socket
 
 IPV4_ALL_HOSTS_MULTICAST = b'\xe0\x00\x00\x01'
@@ -12,7 +14,12 @@ def dump_mcaddr_check(rtnl: RtnlAddrFamily) -> None:
     At least the loopback interface should have this address.
     """
 
-    addresses = rtnl.getmulticast({"ifa-family": socket.AF_INET}, dump=True)
+    try:
+        addresses = rtnl.getmulticast({"ifa-family": socket.AF_INET}, dump=True)
+    except NlError as e:
+        if e.nl_msg.error == -errno.EOPNOTSUPP:
+            raise KsftSkipEx("IPv4 multicast address dump is not supported")
+        raise
 
     all_host_multicasts = [
         addr for addr in addresses if addr['multicast'] == IPV4_ALL_HOSTS_MULTICAST
diff --git a/tools/testing/selftests/net/rtnetlink.sh b/tools/testing/selftests/net/rtnetlink.sh
index 5f72b447e940..cc4e94b90002 100755
--- a/tools/testing/selftests/net/rtnetlink.sh
+++ b/tools/testing/selftests/net/rtnetlink.sh
@@ -142,10 +142,30 @@ kci_del_dummy()
 	run_cmd ip link del dev "$devdummy"
 }
 
+# Some iproute2 versions exit non-zero from a netconf dump that worked,
+# printing the devconf and then failing anyway.  Nothing about the kernel
+# can be learned from the exit status of such an ip, so find out once.
+netconf_exit_status_usable()
+{
+	local out
+
+	out=$(ip -4 netconf show dev lo 2>/dev/null)
+	if [ $? -ne 0 ] && [ -n "$out" ]; then
+		return 1
+	fi
+	return 0
+}
+
 kci_test_netconf()
 {
 	dev="$1"
 	r=$ret
+
+	if ! netconf_exit_status_usable; then
+		end_test "SKIP: ip netconf $dev: iproute2 fails a dump that worked"
+		return $ksft_skip
+	fi
+
 	run_cmd ip netconf show dev "$dev"
 	for f in 4 6; do
 		run_cmd ip -$f netconf show dev "$dev"
@@ -920,6 +940,17 @@ kci_test_erspan()
 		return $ksft_skip
 	fi
 
+	# the kernel can be built without erspan, rtnetlink then has no ops
+	# for the type and says so.  A kernel that has it gets past this and
+	# fails on the attributes the probe leaves out.
+	if ip -netns "$testns" link add dev "$DEV_NS" type erspan 2>&1 | \
+	   grep -q "Unknown device type"; then
+		end_test "SKIP: erspan: not supported by the kernel"
+		ip netns del "$testns"
+		return $ksft_skip
+	fi
+	ip -netns "$testns" link del dev "$DEV_NS" 2>/dev/null
+
 	# test native tunnel erspan v1
 	run_cmd ip -netns "$testns" link add dev "$DEV_NS" type erspan seq \
 		key 102 local 172.16.1.100 remote 172.16.1.200 \
diff --git a/tools/testing/selftests/net/tls.c b/tools/testing/selftests/net/tls.c
index c9b0b4337e12..07f2b454d755 100644
--- a/tools/testing/selftests/net/tls.c
+++ b/tools/testing/selftests/net/tls.c
@@ -413,6 +413,8 @@ FIXTURE_SETUP(tls)
 		return;
 
 	ret = setsockopt(self->fd, SOL_TLS, TLS_TX, &tls12, tls12.len);
+	if (ret < 0 && errno == ENOENT)
+		SKIP(return, "Cipher not built into the kernel");
 	ASSERT_EQ(ret, 0);
 
 	ret = setsockopt(self->cfd, SOL_TLS, TLS_RX, &tls12, tls12.len);
-- 
2.55.0

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 3%]

* [Devel] [PATCH vz10 7/7] selftests: net: let the bridged PMTU tests take the ICMP they ask for
    2026-08-31 22:48  3% ` [Devel] [PATCH vz10 3/7] selftests: net: skip what this kernel and iproute2 do not have Eva Kurchatova
@ 2026-08-31 22:48 19% ` Eva Kurchatova
  1 sibling, 0 replies; 119+ results
From: Eva Kurchatova @ 2026-08-31 22:48 UTC (permalink / raw)
  To: khorenko; +Cc: devel

The bridged tunnel PMTU tests send a packet that is too big to make the
tunnel answer with "Frag needed", which is how the route exception they
check gets created. The ping that does it from the namespace holding
the bridge is given a deadline, and ping(8) says of it:

  In this case ping does not stop after count packet are sent, it waits
  either for deadline expire or until count probes are answered or for
  some error notification from network.

so it stops at that very ICMP and reports the loss:

  # ping -M want -i 0.1 -w 1 -s 4500 192.168.2.2
  From 192.168.2.2 icmp_seq=2 Frag needed and DF set (mtu = 3950)
  2 packets transmitted, 0 received, +1 errors, 100% packet loss

  TEST: IPv4, bridged vxlan4: PMTU exceptions                    [FAIL]

The exception is created all the same, and with the right value, the
test never gets to look at it. Ask for a count of pings instead, as
the line above this one already does for the other namespace, so the
error is counted and the run goes on.

All 16 bridged cases pass with this, vxlan and geneve, over IPv4 and
IPv6.

https://virtuozzo.atlassian.net/browse/VSTOR-139651
Feature: fix selftests
Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
---
 tools/testing/selftests/net/pmtu.sh | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/net/pmtu.sh b/tools/testing/selftests/net/pmtu.sh
index 6c651c880fe8..d607429840cf 100755
--- a/tools/testing/selftests/net/pmtu.sh
+++ b/tools/testing/selftests/net/pmtu.sh
@@ -1409,7 +1409,12 @@ test_pmtu_ipvX_over_bridged_vxlanY_or_geneveY_exception() {
 	mtu "${ns_b}" ${type}_b $((${ll_mtu} + 1000))
 
 	run_cmd ${ns_c} ${ping} -q -M want -i 0.1 -c 10 -s $((${ll_mtu} + 500)) ${dst} || return 1
-	run_cmd ${ns_a} ${ping} -q -M want -i 0.1 -w 1  -s $((${ll_mtu} + 500)) ${dst} || return 1
+	# This ping is meant to draw the ICMP that creates the exception,
+	# and ping stops on "some error notification from network" where a
+	# deadline is given, see ping(8), so it would always report the
+	# loss and fail here.  Send a count of them instead, as the ping
+	# above does.
+	run_cmd ${ns_a} ${ping} -q -M want -i 0.1 -c 10 -s $((${ll_mtu} + 500)) ${dst} || return 1
 
 	# Check that exceptions were created
 	pmtu="$(route_get_dst_pmtu_from_exception "${ns_c}" ${dst})"
-- 
2.55.0

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 19%]

* [Devel] [PATCH vz10 v2 2/2] ms/selftests/damon/damon_nr_regions: sort collected regiosn before checking with min/max boundaries
  @ 2026-08-31 23:13  5% ` Eva Kurchatova
  0 siblings, 0 replies; 119+ results
From: Eva Kurchatova @ 2026-08-31 23:13 UTC (permalink / raw)
  To: khorenko; +Cc: devel

From: SeongJae Park <sj@kernel.org>

damon_nr_regions.py starts DAMON, periodically collect number of regions
in snapshots, and see if it is in the requested range.  The check code
assumes the numbers are sorted on the collection list, but there is no
such guarantee.  Hence this can result in false positive test success.
Sort the list before doing the check.

Link: https://lkml.kernel.org/r/20250225222333.505646-4-sj@kernel.org
Fixes: 781497347d1b ("selftests/damon: implement test for min/max_nr_regions")
Signed-off-by: SeongJae Park <sj@kernel.org>
Cc: Shuah Khan <shuah@kernel.org>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
(cherry picked from commit 582ccf78f6090d88b1c7066b1e90b3d9ec952d08)

https://virtuozzo.atlassian.net/browse/VSTOR-132453
Feature: fix selftests
Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
---
 tools/testing/selftests/damon/damon_nr_regions.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/tools/testing/selftests/damon/damon_nr_regions.py b/tools/testing/selftests/damon/damon_nr_regions.py
index 2ed12281cea2..29523275c262 100755
--- a/tools/testing/selftests/damon/damon_nr_regions.py
+++ b/tools/testing/selftests/damon/damon_nr_regions.py
@@ -69,6 +69,7 @@ def test_nr_regions(real_nr_regions, min_nr_regions, max_nr_regions):
 
     test_name = 'nr_regions test with %d/%d/%d real/min/max nr_regions' % (
             real_nr_regions, min_nr_regions, max_nr_regions)
+    collected_nr_regions.sort()
     if (collected_nr_regions[0] < min_nr_regions or
         collected_nr_regions[-1] > max_nr_regions):
         print('fail %s' % test_name)
-- 
2.55.0

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 5%]

* Re: [Devel] [PATCH vz10] selftests/damon: wait for the merge to apply the new max_nr_regions
  2026-08-21 15:13 15% [Devel] [PATCH vz10] selftests/damon: wait for the merge to apply the new max_nr_regions Eva Kurchatova
@ 2026-08-31 23:22  0% ` Eva Kurchatova (Virtuozzo)
  0 siblings, 0 replies; 119+ results
From: Eva Kurchatova (Virtuozzo) @ 2026-08-31 23:22 UTC (permalink / raw)
  To: devel


On 8/21/26 18:13, Eva Kurchatova wrote:
> damon_nr_regions sleeps 0.3s after committing max_nr_regions and then
> reads the number of regions back, which assumes the merge has happened
> by then.  On a machine that is not idle it has not, and the test fails
> on a region count that is still the old one.
>
> Poll until the count settles instead of sleeping for a fixed time.
>
> https://virtuozzo.atlassian.net/browse/VSTOR-132453
> Feature: fix selftests
> Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
> ---
>   .../selftests/damon/damon_nr_regions.py       | 35 +++++++++++--------
>   1 file changed, 20 insertions(+), 15 deletions(-)
>
> diff --git a/tools/testing/selftests/damon/damon_nr_regions.py b/tools/testing/selftests/damon/damon_nr_regions.py
> index cb31cd140d22..39063ce5b27d 100755
> --- a/tools/testing/selftests/damon/damon_nr_regions.py
> +++ b/tools/testing/selftests/damon/damon_nr_regions.py
> @@ -4,6 +4,7 @@
>   import subprocess
>   import time
>   
> +
>   import _damon_sysfs
>   
>   def test_nr_regions(real_nr_regions, min_nr_regions, max_nr_regions):
> @@ -114,27 +115,31 @@ def main():
>           proc.terminate()
>           print('commit failed: %s' % err)
>           exit(1)
> -    # wait for next merge operation is executed
> -    time.sleep(0.3)
> +    # wait for the merge operations to apply the new max_nr_regions
> +    nr_tried_regions = 0
> +    for _ in range(50):
> +        time.sleep(0.1)
>   
> -    err = kdamonds.kdamonds[0].update_schemes_tried_regions()
> -    if err is not None:
> -        proc.terminate()
> -        print('tried regions update failed: %s' % err)
> -        exit(1)
> +        err = kdamonds.kdamonds[0].update_schemes_tried_regions()
> +        if err is not None:
> +            proc.terminate()
> +            print('tried regions update failed: %s' % err)
> +            exit(1)
>   
> -    scheme = kdamonds.kdamonds[0].contexts[0].schemes[0]
> -    if scheme.tried_regions is None:
> -        proc.terminate()
> -        print('tried regions is not collected')
> -        exit(1)
> +        scheme = kdamonds.kdamonds[0].contexts[0].schemes[0]
> +        if scheme.tried_regions is None:
> +            proc.terminate()
> +            print('tried regions is not collected')
> +            exit(1)
> +
> +        nr_tried_regions = len(scheme.tried_regions)
> +        if 0 < nr_tried_regions <= 7:
> +            break
> +    proc.terminate()
>   
> -    nr_tried_regions = len(scheme.tried_regions)
>       if nr_tried_regions <= 0:
> -        proc.terminate()
>           print('tried regions is not created')
>           exit(1)
> -    proc.terminate()
>   
>       if nr_tried_regions > 7:
>           print('fail online-tuned max_nr_regions: %d > 7' % nr_tried_regions)

Sent v2, replacing this commit with two upstream backports: 
https://lists.openvz.org/pipermail/devel/2026-September/084959.html

Verification: 
https://bitbucket.org/virtuozzocore/vzkernel.vzspecs/pipelines/results/609


_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 0%]

* [Devel] [PATCH vz10 3/3] selftests: drv-net: read the channel count over netlink
  @ 2026-08-31 23:43  9% ` Eva Kurchatova
  0 siblings, 0 replies; 119+ results
From: Eva Kurchatova @ 2026-08-31 23:43 UTC (permalink / raw)
  To: khorenko; +Cc: devel

napi_threaded needs the number of combined channels of the device and
asks ethtool to print it as JSON. Not every ethtool prints that one:
ethtool 6.11 does it for -k, -a, -c, -g and -x, but not for -l, and
the test ends in the first case:

  CmdExitFailure: Command failed: ethtool --json -l eth4
  STDERR: b'ethtool: bad command line argument(s)
  JSON output not available for this subcommand

  not ok 1 napi_threaded.napi_init

Ask the kernel for the count over netlink, which is where ethtool reads
it too, so the test no longer depends on the ethtool version installed.
Changing the count stays with ethtool -L, which needs no JSON. No other
test in the group asks ethtool for something it cannot print, so this is
the only one that needs it.

https://virtuozzo.atlassian.net/browse/VSTOR-139651
Feature: fix selftests
Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
---
 tools/testing/selftests/drivers/net/napi_threaded.py | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/tools/testing/selftests/drivers/net/napi_threaded.py b/tools/testing/selftests/drivers/net/napi_threaded.py
index 20f1a12552da..6662aee3442b 100755
--- a/tools/testing/selftests/drivers/net/napi_threaded.py
+++ b/tools/testing/selftests/drivers/net/napi_threaded.py
@@ -7,7 +7,7 @@ Test napi threaded states.
 
 from lib.py import ksft_run, ksft_exit
 from lib.py import ksft_eq, ksft_ne, ksft_ge
-from lib.py import NetDrvEnv, NetdevFamily
+from lib.py import EthtoolFamily, NetDrvEnv, NetdevFamily
 from lib.py import cmd, defer, ethtool
 
 
@@ -28,7 +28,13 @@ def _set_threaded_state(cfg, threaded) -> None:
 
 
 def _setup_deferred_cleanup(cfg) -> None:
-    combined = ethtool(f"-l {cfg.ifname}", json=True)[0].get("combined", 0)
+    # Not every ethtool prints the channel counts as JSON, the one on
+    # the test machine here does not:
+    #   ethtool: bad command line argument(s)
+    #   JSON output not available for this subcommand
+    # Ask the kernel for them instead, it is the same number.
+    chan = EthtoolFamily().channels_get({'header': {'dev-index': cfg.ifindex}})
+    combined = chan.get('combined-count', 0)
     ksft_ge(combined, 2)
     defer(ethtool, f"-L {cfg.ifname} combined {combined}")
 
-- 
2.55.0

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 9%]

* [Devel] [PATCH vz10] selftests: pstore: skip when no backend is registered
@ 2026-08-31 23:48  5% Eva Kurchatova
  2026-09-02 14:29  0% ` Konstantin Khorenko
  0 siblings, 1 reply; 119+ results
From: Eva Kurchatova @ 2026-08-31 23:48 UTC (permalink / raw)
  To: khorenko; +Cc: devel

Without a pstore backend the console, /dev/pmsg0 and every write check
fail.

https://virtuozzo.atlassian.net/browse/VSTOR-142447
Feature: fix selftests
Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
---
 tools/testing/selftests/pstore/common_tests | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/tools/testing/selftests/pstore/common_tests b/tools/testing/selftests/pstore/common_tests
index 4509f0cc9c91..e486a5a111d8 100755
--- a/tools/testing/selftests/pstore/common_tests
+++ b/tools/testing/selftests/pstore/common_tests
@@ -55,6 +55,7 @@ operate_files() { # tested value, files, operation
 
 # Parameters
 TEST_STRING_PATTERN="Testing pstore: uuid="
+ksft_skip=4
 UUID=`cat /proc/sys/kernel/random/uuid`
 TOP_DIR=`absdir $0`
 LOG_DIR=$TOP_DIR/logs/`date +%Y%m%d-%H%M%S`_${UUID}/
@@ -81,3 +82,7 @@ prlog -e "\tcmdline=`cat /proc/cmdline`"
 if [ $rc -ne 0 ]; then
     exit 1
 fi
+if [ -z "$backend" -o "$backend" = "(null)" ]; then
+    prlog "pstore backend is not registered, skipping"
+    exit $ksft_skip
+fi
-- 
2.55.0

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 5%]

* [Devel] [PATCH vz10] selftests: pci_endpoint: skip when the test device is absent
@ 2026-08-31 23:47  4% Eva Kurchatova
  2026-09-02 14:57  5% ` Konstantin Khorenko
  0 siblings, 1 reply; 119+ results
From: Eva Kurchatova @ 2026-08-31 23:47 UTC (permalink / raw)
  To: khorenko; +Cc: devel

Every fixture opens /dev/pci-endpoint-test.0 without checking, so on a
machine that has no PCI endpoint test device each case fails on the
first ioctl against fd -1 rather than saying the hardware is not there.

Check for the device once before handing over to the harness. Doing it
there rather than in each of the four fixtures keeps it to a single skip.

https://virtuozzo.atlassian.net/browse/VSTOR-142446
Feature: fix selftests
Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
---
 .../selftests/pci_endpoint/pci_endpoint_test.c      | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/pci_endpoint/pci_endpoint_test.c b/tools/testing/selftests/pci_endpoint/pci_endpoint_test.c
index da0db0e7c969..640c74fd5b78 100644
--- a/tools/testing/selftests/pci_endpoint/pci_endpoint_test.c
+++ b/tools/testing/selftests/pci_endpoint/pci_endpoint_test.c
@@ -257,4 +257,15 @@ TEST_F(pcie_ep_doorbell, DOORBELL_TEST)
 	pci_ep_ioctl(PCITEST_DOORBELL, 0);
 	EXPECT_FALSE(ret) TH_LOG("Test failed for Doorbell\n");
 }
-TEST_HARNESS_MAIN
+static bool test_device_available(void)
+{
+	return access(test_device, F_OK) == 0;
+}
+
+int main(int argc, char **argv)
+{
+	if (!test_device_available())
+		ksft_exit_skip("no PCI endpoint test device %s\n", test_device);
+
+	return test_harness_run(argc, argv);
+}
-- 
2.55.0

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 4%]

* [Devel] [PATCH vz10] selftests: dma-buf: skip the huge page test without huge pages
@ 2026-09-01  0:41  4% Eva Kurchatova
  0 siblings, 0 replies; 119+ results
From: Eva Kurchatova @ 2026-09-01  0:41 UTC (permalink / raw)
  To: khorenko; +Cc: devel

The last udmabuf test backs a memfd with MFD_HUGETLB. Where no huge
pages are reserved the mapping fails and the whole test is reported as
a failure.

https://virtuozzo.atlassian.net/browse/VSTOR-142448
Feature: fix selftests
Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
---
 .../selftests/drivers/dma-buf/udmabuf.c       | 20 +++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/tools/testing/selftests/drivers/dma-buf/udmabuf.c b/tools/testing/selftests/drivers/dma-buf/udmabuf.c
index 6062723a172e..a3bb26c1991a 100644
--- a/tools/testing/selftests/drivers/dma-buf/udmabuf.c
+++ b/tools/testing/selftests/drivers/dma-buf/udmabuf.c
@@ -25,6 +25,21 @@
 
 static unsigned int page_size;
 
+static bool hugepages_available(void)
+{
+	unsigned long nr = 0;
+	FILE *f;
+
+	f = fopen("/proc/sys/vm/nr_hugepages", "r");
+	if (!f)
+		return false;
+	if (fscanf(f, "%lu", &nr) != 1)
+		nr = 0;
+	fclose(f);
+
+	return nr > 0;
+}
+
 static int create_memfd_with_seals(off64_t size, bool hpage)
 {
 	int memfd, ret;
@@ -234,6 +249,11 @@ int main(int argc, char *argv[])
 	close(memfd);
 
 	/* should work (migration of 2MB size huge pages)*/
+	if (!hugepages_available()) {
+		ksft_test_result_skip("%s: [SKIP,test-6] no huge pages\n", TEST_PREFIX);
+		close(devfd);
+		ksft_finished();
+	}
 	page_size = getpagesize() * 512; /* 2 MB */
 	size = MEMFD_SIZE * page_size;
 	memfd = create_memfd_with_seals(size, true);
-- 
2.55.0

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 4%]

* Re: [Devel] [PATCH VZ10] fs/fuse kio: track pending kRPC connect via state machine only
  2026-08-27 12:37  4% [Devel] [PATCH VZ10] fs/fuse kio: track pending kRPC connect via state machine only Liu Kui
  2026-08-28 17:13  0% ` Konstantin Khorenko
@ 2026-09-01 20:59  0% ` Konstantin Khorenko
  1 sibling, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-09-01 20:59 UTC (permalink / raw)
  To: Liu Kui; +Cc: devel, azaitsev

> Rework the previous fix ("fs/fuse kio: fix kRPC connect issues") to not
> require the new struct pcs_krpc member "connect_req", so the fix can be
> shipped as a livepatch.
> 
> Both things connect_req was tracking are already derivable from the
> existing state machine once PCS_KRPC_STATE_CONNECT is made to mean
> exactly "a connect req is in flight":
> 
>  - krpc_connect_done() settles a failed connect back to UNCONN instead
>    of leaving the state in CONNECT forever;
> 
>  - pcs_krpc_abort() no longer resets CONNECT to UNCONN: the req is
>    still in flight, and only its completion settles the state;
> 
>  - pcs_krpc_connect() proceeds only from UNCONN or ABORTED, refusing
>    new connects (-EPERM) while a req is in flight - at most one connect
>    req exists at a time, same as with the connect_req check;
> 
>  - pcs_krpc_poll() reports EPOLLERR on UNCONN: poll bails out earlier
>    unless ctx->gen == krpc->gen, and the current session can only be in
>    UNCONN if its connect failed or was aborted, which is what the
>    (CONNECT && !connect_req) test used to detect.
> 
> gen only advances in pcs_krpc_connect(), which is blocked during
> CONNECT, so within that state the in-flight req always carries the
> current gen and krpc_connect_done()'s existing staleness check is
> sufficient.
> 
> Related to:
> https://virtuozzo.atlassian.net/browse/VSTOR-135626
> Fixes: d0d6034c36010 ("fs/fuse kio: fix kRPC connect issues")
> Feature: fuse: kRPC - single RPC for kernel and userspace
> 
> Signed-off-by: Liu Kui <kui.liu@virtuozzo.com>
>
> diff --git a/fs/fuse/kio/pcs/pcs_krpc.c b/fs/fuse/kio/pcs/pcs_krpc.c
> index 0930fb4adf125..f9fb6b3699062 100644
> --- a/fs/fuse/kio/pcs/pcs_krpc.c
> +++ b/fs/fuse/kio/pcs/pcs_krpc.c
> @@ -787,8 +787,12 @@ static int pcs_krpc_abort(struct pcs_krpc *krpc)
>       spin_lock(&krpc->lock);
>
>       if (krpc->state != PCS_KRPC_STATE_CONNECTED) {
> -             if (krpc->state == PCS_KRPC_STATE_CONNECT)
> -                     krpc->state = PCS_KRPC_STATE_UNCONN;
> +             /*
> +              * A pending connect stays in CONNECT state: its connect req
> +              * is still in flight and krpc_connect_done() will settle the
> +              * state to UNCONN when it completes.  Until then new connects
> +              * are refused, so at most one connect req exists at a time.
> +              */
>               spin_unlock(&krpc->lock);
>               return 0;
>       }

[Severity: High]
Can this change leave a krpc stuck in the connected state with no
session fd attached to it?

Consider a connect req in flight (state is CONNECT) whose fd is closed
by userspace after its connect timeout expires - the scenario the
original fix was written for:

pcs_krpc_release()
    if (ctx->gen == krpc->gen)
        pcs_krpc_abort(krpc);    /* state is CONNECT: does nothing now */

gen cannot advance while the state stays CONNECT, because
pcs_krpc_connect() refuses new connects with -EPERM in that state.  So
when the in-flight req later completes successfully (the peer became
reachable again), krpc_connect_done() passes its staleness check and
commits the dead session:

    if (req->gen != krpc->gen || krpc->state != PCS_KRPC_STATE_CONNECT) {
        spin_unlock(&krpc->lock);
        goto out;
    }

    if (!pcs_if_error(&msg->error)) {
        krpc->state = PCS_KRPC_STATE_CONNECTED;

Now the krpc is CONNECTED while its gen still names a session whose fd
is gone.  Every following PCS_IOC_KRPC_CONNECT returns -EPERM, there is
no fd left on which userspace could issue PCS_KRPC_IOC_ABORT, and no
kernel path resets the state, so the node stays unconnectable until the
krpc is destroyed.

The connect_req based code handled this case: pcs_krpc_abort() moved
CONNECT to UNCONN, a successful krpc_connect_done() then took the stale
path without transitioning to CONNECTED, and the next connect was
allowed as soon as the old req completed.

Does the abort/release path need to invalidate the pending connect, so
that a late successful completion settles the state to UNCONN instead
of resurrecting the closed session?

> @@ -956,8 +960,13 @@ static __poll_t pcs_krpc_poll(struct file *file, poll_table *wait)
>
>       spin_lock(&krpc->lock);
>
> +     /*
> +      * ctx->gen == krpc->gen (checked above) means this is the current
> +      * session, so UNCONN here can only mean its connect attempt has
> +      * failed (see krpc_connect_done()) or the session was aborted.
> +      */
>       if (krpc->state == PCS_KRPC_STATE_ABORTED ||
> -         (krpc->state == PCS_KRPC_STATE_CONNECT && !krpc->connect_req)) {
> +         krpc->state == PCS_KRPC_STATE_UNCONN) {
>               pollflags |= EPOLLERR;
>       } else if (krpc->state == PCS_KRPC_STATE_CONNECTED) {
>               pollflags |= EPOLLOUT;

[Severity: Low]
This isn't a bug, but after this change pcs_krpc_abort() never sets
UNCONN, and an aborted session is left in ABORTED, which the first
check already handles.

For a session with ctx->gen == krpc->gen, can UNCONN still be reached
through an abort as the comment says, or only through a failed
connect?  The commit message carries the same wording ("the current
session can only be in UNCONN if its connect failed or was aborted").

[ ... ]

-- 
Konstantin Khorenko <khorenko@virtuozzo.com>
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 0%]

* Re: [Devel] [PATCH vz10] selftests: pstore: skip when no backend is registered
  2026-08-31 23:48  5% [Devel] [PATCH vz10] selftests: pstore: skip when no backend is registered Eva Kurchatova
@ 2026-09-02 14:29  0% ` Konstantin Khorenko
  0 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-09-02 14:29 UTC (permalink / raw)
  To: Eva Kurchatova; +Cc: devel

> Without a pstore backend the console, /dev/pmsg0 and every write check
> fail.
> 
> https://virtuozzo.atlassian.net/browse/VSTOR-142447
> Feature: fix selftests
> Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
>
> diff --git a/tools/testing/selftests/pstore/common_tests b/tools/testing/selftests/pstore/common_tests
> index 4509f0cc9c918..e486a5a111d8d 100755
> --- a/tools/testing/selftests/pstore/common_tests
> +++ b/tools/testing/selftests/pstore/common_tests
> @@ -55,6 +55,7 @@ operate_files() { # tested value, files, operation
>  
>  # Parameters
>  TEST_STRING_PATTERN="Testing pstore: uuid="
> +ksft_skip=4
>  UUID=`cat /proc/sys/kernel/random/uuid`
>  TOP_DIR=`absdir $0`
>  LOG_DIR=$TOP_DIR/logs/`date +%Y%m%d-%H%M%S`_${UUID}/
> @@ -81,3 +82,7 @@ prlog -e "\tcmdline=`cat /proc/cmdline`"
>  if [ $rc -ne 0 ]; then
>      exit 1
>  fi
> +if [ -z "$backend" -o "$backend" = "(null)" ]; then
> +    prlog "pstore backend is not registered, skipping"
> +    exit $ksft_skip
> +fi

The goal is right, and it is actually what this test set has claimed to do
since 2015: pstore_crash_test:10 carries the comment

      # exit if pstore backend is not registered
      . ./common_tests

but common_tests never implemented it. The existing check at
common_tests:77-79 tests cat's exit status, i.e. only whether the sysfs file
is readable, not what it contains. So the patch finally implements what the
comment promises. Upstream common_tests is identical to ours, so this is
worth sending to mainline too.

================================================
And send to mainstream in parallel, do not wait!
================================================

1) The output now contradicts itself:

      Checking pstore backend is registered ... ok
              backend=(null)
              cmdline=...
      pstore backend is not registered, skipping

Since the patch is about exactly this check, please fix its verdict instead
of appending a second check at the end of the file. Keep the backend= and
cmdline= diagnostics - they are useful in the skip case too.

2) CONFIG_PSTORE=n (and pstore built as a module but not loaded) still FAILs
instead of skipping. CONFIG_PSTORE is tristate (fs/pstore/Kconfig:3), so
/sys/module/pstore/parameters/backend may not exist at all; then cat fails,
show_result reports FAIL, rc=1, and common_tests:82-84 exits 1 before the new
check is reached. That is the most direct "feature not built" case and it
should skip too. One extra condition.

3) More important in practice: on our config the patch is not enough once a
backend does register. We have

      CONFIG_PSTORE=y
      # CONFIG_PSTORE_CONSOLE is not set
      # CONFIG_PSTORE_PMSG is not set
      CONFIG_PSTORE_RAM=m
      CONFIG_EFI_VARS_PSTORE=y
      CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE=y

while pstore_tests checks precisely the frontends we do not have: the pstore
console (pstore_tests:13) and /dev/pmsg0 (:17, :21). The test's own config
file asks for CONFIG_PSTORE_PMSG=y and CONFIG_PSTORE_CONSOLE=y.

What saves us today is only that no backend registers by default:
efi_pstore is off (pstore_disable = IS_ENABLED(CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE),
drivers/firmware/efi/efi-pstore.c:22, early return at :261), ERST registers
only if the platform provides an ERST range (drivers/acpi/apei/erst.c:1266),
and ramoops is a module that is not autoloaded. Hence backend=(null) and the
skip fires.

But on hardware with ERST, or when booted with efivars.pstore_disable=0, or
after modprobe ramoops, pstore_tests fails again - for a reason unrelated to
the backend. If the goal is a green run under any conditions, the frontend
checks need their own skips: the console check only when a pstore console is
actually registered, the pmsg checks only when /dev/pmsg0 exists. That can be
a separate patch, but it should be decided now, otherwise the task gets
closed while the test still fails on some machines.

Nits:

- [ -z "$backend" -o "$backend" = "(null)" ]: -o inside [ is obsolescent in
  POSIX; prefer [ -z "$backend" ] || [ "$backend" = "(null)" ].
- pstore_post_reboot_tests:10-11 has the canonical comment above the same
  variable ("# Kselftest framework requirement - SKIP code is 4."); worth
  repeating it. After that the duplicate ksft_skip=4 in
  pstore_post_reboot_tests can be dropped, since it sources common_tests
  (line 13) before first using the variable (line 19).

-- 
Konstantin Khorenko <khorenko@virtuozzo.com>
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 0%]

* Re: [Devel] [PATCH vz10] selftests: pci_endpoint: skip when the test device is absent
  2026-08-31 23:47  4% [Devel] [PATCH vz10] selftests: pci_endpoint: skip when the test device is absent Eva Kurchatova
@ 2026-09-02 14:57  5% ` Konstantin Khorenko
  0 siblings, 0 replies; 119+ results
From: Konstantin Khorenko @ 2026-09-02 14:57 UTC (permalink / raw)
  To: Eva Kurchatova; +Cc: devel

> Every fixture opens /dev/pci-endpoint-test.0 without checking, so on a
> machine that has no PCI endpoint test device each case fails on the
> first ioctl against fd -1 rather than saying the hardware is not there.
> 

The commit message describes a different bug than the one that exists:

      Every fixture opens /dev/pci-endpoint-test.0 without checking, so on a
      machine that has no PCI endpoint test device each case fails on the
      first ioctl against fd -1

Every fixture does check:

      FIXTURE_SETUP(pci_ep_bar)
      {
              self->fd = open(test_device, O_RDWR);

              ASSERT_NE(-1, self->fd) TH_LOG("Can't open PCI Endpoint Test device");
      }

at pci_endpoint_test.c:43 (pci_ep_bar), :82 (pci_ep_basic), :153
(pci_ep_data_transfer) and :232 (pcie_ep_doorbell).

No ioctl is ever reached - what fails is the assertion in the fixture setup
("Test terminated by assertion" in the output above).

Please reword: each of the 17 cases fails on the fixture setup assertion.
Otherwise the next reader goes looking for an ioctl(-1, ...) bug that is not
there. The "four fixtures" part is correct.

> Check for the device once before handing over to the harness. Doing it
> there rather than in each of the four fixtures keeps it to a single skip.
> 
> https://virtuozzo.atlassian.net/browse/VSTOR-142446
> Feature: fix selftests
> Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
>
> diff --git a/tools/testing/selftests/pci_endpoint/pci_endpoint_test.c b/tools/testing/selftests/pci_endpoint/pci_endpoint_test.c
> index da0db0e7c9693..640c74fd5b780 100644
> --- a/tools/testing/selftests/pci_endpoint/pci_endpoint_test.c
> +++ b/tools/testing/selftests/pci_endpoint/pci_endpoint_test.c
> @@ -257,4 +257,15 @@ TEST_F(pcie_ep_doorbell, DOORBELL_TEST)
>  	pci_ep_ioctl(PCITEST_DOORBELL, 0);
>  	EXPECT_FALSE(ret) TH_LOG("Test failed for Doorbell\n");
>  }
> -TEST_HARNESS_MAIN

An empty line to be here.

> +static bool test_device_available(void)
> +{
> +	return access(test_device, F_OK) == 0;
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	if (!test_device_available())
> +		ksft_exit_skip("no PCI endpoint test device %s\n", test_device);
> +
> +	return test_harness_run(argc, argv);
> +}

The check runs before test_harness_run(), while argv parsing happens
inside it (test_harness_argv_check(), kselftest_harness.h:1119).

So on a machine without the device, "./pci_endpoint_test -l" (list tests) and
"-h" (usage) now print the skip message instead of their own output. Please
limit the skip to the plain invocation, which is the one kselftest uses:

      int main(int argc, char **argv)
      {
              /*
               * Only the plain invocation - the one kselftest uses - turns into a
               * skip, so that -l and -h keep working without the hardware.
               */
              if (argc == 1 && !test_device_available())
                      ksft_exit_skip("no PCI endpoint test device %s\n", test_device);

              return test_harness_run(argc, argv);
      }

-- 
Konstantin Khorenko <khorenko@virtuozzo.com>
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

^ permalink raw reply	[relevance 5%]

* [QEMU HCI-8.0 PATCH 3/5] vhost-blk: add read-only flag
    2026-09-03 12:32 29% ` [QEMU HCI-8.0 PATCH 2/5] vhost-blk: " Andrey Zhadchenko
@ 2026-09-03 12:32 31% ` Andrey Zhadchenko
  2026-09-03 14:56  8%   ` Andrey Drobyshev
  2026-09-03 12:32 23% ` [QEMU HCI-8.0 PATCH 4/5] vhost-blk: watch the device for resize events Andrey Zhadchenko
  2026-09-03 12:32  4% ` [QEMU HCI-8.0 PATCH 5/5] vhost-blk: filter uevents in the kernel Andrey Zhadchenko
  3 siblings, 1 reply; 119+ results
From: Andrey Zhadchenko @ 2026-09-03 12:32 UTC (permalink / raw)
  To: svt-core; +Cc: den, andrey.drobyshev

and set RO respectively. Also compare BLKROGET with the selected
mode and reject r/w if needed.

https://virtuozzo.atlassian.net/browse/VSTOR-143437
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
---
 hw/block/vhost-blk.c          | 23 ++++++++++++++++++++++-
 include/hw/virtio/vhost-blk.h |  1 +
 2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/hw/block/vhost-blk.c b/hw/block/vhost-blk.c
index a3e0010982..47b6e560d4 100644
--- a/hw/block/vhost-blk.c
+++ b/hw/block/vhost-blk.c
@@ -24,6 +24,7 @@
 #include "system/system.h"
 #include "linux-headers/linux/vhost.h"
 #include <sys/ioctl.h>
+#include <linux/fs.h>
 #include "system/runstate.h"
 
 static int vhost_blk_start(VirtIODevice *vdev)
@@ -233,8 +234,10 @@ static bool vhost_blk_open_backend(VHostBlk *s, Error **errp)
 {
     BlockConf *conf = &s->conf.conf;
     struct stat st;
+    int readonly;
+    int open_flags = s->conf.readonly ? O_RDONLY : O_RDWR;
 
-    s->backend_fd = qemu_open(s->conf.devpath, O_RDWR, errp);
+    s->backend_fd = qemu_open(s->conf.devpath, open_flags, errp);
     if (s->backend_fd < 0) {
         error_prepend(errp, "vhost-blk: unable to open backend: ");
         return false;
@@ -252,6 +255,19 @@ static bool vhost_blk_open_backend(VHostBlk *s, Error **errp)
         goto fail;
     }
 
+    if (ioctl(s->backend_fd, BLKROGET, &readonly) < 0) {
+        error_setg_errno(errp, errno,
+                         "vhost-blk: unable to get read-only status of '%s'",
+                         s->conf.devpath);
+        goto fail;
+    }
+
+    if (readonly && !s->conf.readonly) {
+        error_setg(errp, "vhost-blk: '%s' is read-only",
+                   s->conf.devpath);
+        goto fail;
+    }
+
     if (vhost_blk_update_size(s, errp) < 0) {
         goto fail;
     }
@@ -416,6 +432,10 @@ static uint64_t vhost_blk_get_features(VirtIODevice *vdev,
 
     virtio_add_feature(&features, VIRTIO_F_VERSION_1);
 
+    if (s->conf.readonly) {
+        virtio_add_feature(&features, VIRTIO_BLK_F_RO);
+    }
+
     if (s->conf.num_queues > 1) {
         virtio_add_feature(&features, VIRTIO_BLK_F_MQ);
     }
@@ -464,6 +484,7 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
 static const Property vhost_blk_properties[] = {
     DEFINE_BLOCK_PROPERTIES_BASE(VHostBlk, conf.conf),
     DEFINE_PROP_STRING("devpath", VHostBlk, conf.devpath),
+    DEFINE_PROP_BOOL("read-only", VHostBlk, conf.readonly, false),
     DEFINE_PROP_UINT16("num-queues", VHostBlk, conf.num_queues,
                        VHOST_BLK_AUTO_NUM_QUEUES),
     DEFINE_PROP_UINT16("queue-size", VHostBlk, conf.queue_size, 256),
diff --git a/include/hw/virtio/vhost-blk.h b/include/hw/virtio/vhost-blk.h
index c194b421d9..c6646f5845 100644
--- a/include/hw/virtio/vhost-blk.h
+++ b/include/hw/virtio/vhost-blk.h
@@ -25,6 +25,7 @@
 typedef struct VhostBlkConf {
     BlockConf conf;
     char *devpath;
+    bool readonly;
     uint16_t num_queues;
     uint16_t queue_size;
     uint16_t num_threads;
-- 
2.43.5


^ permalink raw reply	[relevance 31%]

* [QEMU HCI-8.0 PATCH 5/5] vhost-blk: filter uevents in the kernel
                     ` (2 preceding siblings ...)
  2026-09-03 12:32 23% ` [QEMU HCI-8.0 PATCH 4/5] vhost-blk: watch the device for resize events Andrey Zhadchenko
@ 2026-09-03 12:32  4% ` Andrey Zhadchenko
  3 siblings, 0 replies; 119+ results
From: Andrey Zhadchenko @ 2026-09-03 12:32 UTC (permalink / raw)
  To: svt-core; +Cc: den, andrey.drobyshev

The uevent socket receives every uevent broadcast on the host:
NETLINK_KOBJECT_UEVENT group 1 has no kernel-side subscription by
subsystem or device. On a dense node every add/remove/change event
of every device (mass container starts creating dm and loop
devices, SCSI rescans, udevadm trigger) wakes up the main loop of
every QEMU with a vhost-blk device just to parse and discard the
message, and a burst can overflow the socket receive buffer.

Attach a classic BPF socket filter which passes only messages
starting with "change@" and containing a "RES"-prefixed property
within the first 512 bytes. Messages longer than the scan window
are passed to userspace instead of being dropped, so the filter
can have false positives but never false negatives:
vhost_blk_uevent_read() remains the authoritative parser. Also
enlarge the receive buffer to 1M: with the filter attached even a
large backlog consists of relevant events only.

Matching the full "RESIZE=1" property or MAJOR=/MINOR= of the
watched devices kernel-side was considered and rejected: the
kernel converts classic BPF to eBPF on attach and the converted
program must fit in BPF_MAXINSNS, which allows only ~1900 classic
instructions of this shape (three per scanned offset). Classic BPF
also cannot loop, so device numbers (variable-length decimal
strings at variable offsets) would need unrolled matching code
regenerated and re-attached on every device plug/unplug. Resize
events are rare; the coarse kernel filter drops all of the heavy
traffic and userspace keeps doing the exact matching.

https://virtuozzo.atlassian.net/browse/VSTOR-143437
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
---
 hw/block/vhost-blk.c | 123 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 123 insertions(+)

diff --git a/hw/block/vhost-blk.c b/hw/block/vhost-blk.c
index f8eca4d58a..80b0e06b4c 100644
--- a/hw/block/vhost-blk.c
+++ b/hw/block/vhost-blk.c
@@ -28,6 +28,7 @@
 #include <sys/ioctl.h>
 #include <linux/fs.h>
 #include <linux/netlink.h>
+#include <linux/filter.h>
 #include "system/runstate.h"
 
 static int vhost_blk_uevent_fd = -1;
@@ -347,6 +348,126 @@ static void vhost_blk_uevent_read(void *opaque)
     }
 }
 
+/*
+ * The kernel broadcasts uevents of every device on the host to
+ * NETLINK_KOBJECT_UEVENT group 1 and provides no subscription by subsystem
+ * or device. Without a filter each uevent (device hotplug, SCSI rescan,
+ * udevadm trigger, ...) wakes up the main loop of every QEMU with a
+ * vhost-blk device just to parse and discard the message.
+ *
+ * Attach a classic BPF socket filter passing only what we are interested
+ * in: messages which start with "change@" and contain a property beginning
+ * with "RES" ("\0RES" match at every offset) within the first
+ * VHOST_BLK_UEVENT_SCAN_LEN bytes. Messages longer than the scan window
+ * are passed to userspace instead of being dropped. The filter can have
+ * false positives but never false negatives: vhost_blk_uevent_read()
+ * remains the authoritative parser.
+ *
+ * Matching the full "\0RESIZE=1\0" property would be nicer, but the kernel
+ * converts classic BPF to eBPF on attach and every packet load expands to
+ * several eBPF instructions; the converted program must fit in
+ * BPF_MAXINSNS (4096) instructions, which allows roughly 1900 classic
+ * instructions of this shape. Three instructions per scanned offset
+ * (load, match, accept-jump) fit with a good margin, seven do not.
+ *
+ * Filtering by MAJOR=/MINOR= of the watched devices is done in userspace
+ * only. Classic BPF cannot loop, so matching these variable-length decimal
+ * strings at variable offsets would require regenerating and re-attaching
+ * unrolled matching code on every device plug/unplug, and the instruction
+ * budget above does not allow anything close to that. Resize events are
+ * rare, all of the heavy traffic is already dropped by the "change@" and
+ * "\0RES" matches.
+ */
+#define VHOST_BLK_UEVENT_SCAN_LEN     512
+#define VHOST_BLK_UEVENT_FILTER_HEAD  10
+#define VHOST_BLK_UEVENT_FILTER_BLOCK 3
+#define VHOST_BLK_UEVENT_FILTER_INSNS (VHOST_BLK_UEVENT_FILTER_HEAD + \
+                                       VHOST_BLK_UEVENT_FILTER_BLOCK * \
+                                       VHOST_BLK_UEVENT_SCAN_LEN + 2)
+
+static void vhost_blk_uevent_apply_filter(int fd)
+{
+    g_autofree struct sock_filter *insns =
+        g_new0(struct sock_filter, VHOST_BLK_UEVENT_FILTER_INSNS);
+    const uint32_t accept = VHOST_BLK_UEVENT_FILTER_INSNS - 1;
+    struct sock_fprog prog = {
+        .len = VHOST_BLK_UEVENT_FILTER_INSNS,
+        .filter = insns,
+    };
+    int rcvbuf = 1024 * 1024;
+    uint32_t pc = 0;
+    uint32_t i;
+
+    QEMU_BUILD_BUG_ON(VHOST_BLK_UEVENT_FILTER_INSNS > BPF_MAXINSNS);
+
+    /* Drop everything which does not start with "change@" */
+    insns[pc++] = (struct sock_filter)
+        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 0);
+    insns[pc++] = (struct sock_filter)
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x6368616e /* "chan" */, 0, 4);
+    insns[pc++] = (struct sock_filter)
+        BPF_STMT(BPF_LD | BPF_H | BPF_ABS, 4);
+    insns[pc++] = (struct sock_filter)
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x6765 /* "ge" */, 0, 2);
+    insns[pc++] = (struct sock_filter)
+        BPF_STMT(BPF_LD | BPF_B | BPF_ABS, 6);
+    insns[pc++] = (struct sock_filter)
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, '@', 1, 0);
+    insns[pc++] = (struct sock_filter)
+        BPF_STMT(BPF_RET | BPF_K, 0);
+
+    /*
+     * A message longer than the scan window cannot be scanned completely:
+     * pass it to userspace instead of risking a lost resize event.
+     */
+    insns[pc++] = (struct sock_filter)
+        BPF_STMT(BPF_LD | BPF_W | BPF_LEN, 0);
+    insns[pc++] = (struct sock_filter)
+        BPF_JUMP(BPF_JMP | BPF_JGT | BPF_K, VHOST_BLK_UEVENT_SCAN_LEN, 0, 1);
+    insns[pc] = (struct sock_filter)
+        BPF_STMT(BPF_JMP | BPF_JA, accept - pc - 1);
+    pc++;
+
+    /*
+     * Scan for "\0RES" at every offset. A load beyond the end of the
+     * message terminates the program with a drop verdict, which is
+     * correct: had the message contained the pattern, it would have been
+     * matched at an earlier, in-bounds offset.
+     */
+    for (i = 0; i < VHOST_BLK_UEVENT_SCAN_LEN; i++) {
+        insns[pc++] = (struct sock_filter)
+            BPF_STMT(BPF_LD | BPF_W | BPF_ABS, i);
+        insns[pc++] = (struct sock_filter)
+            BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x00524553 /* "\0RES" */,
+                     0, 1);
+        insns[pc] = (struct sock_filter)
+            BPF_STMT(BPF_JMP | BPF_JA, accept - pc - 1);
+        pc++;
+    }
+
+    /* Drop */
+    insns[pc++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, 0);
+    /* Accept */
+    insns[pc++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, 0xffffffff);
+    assert(pc == VHOST_BLK_UEVENT_FILTER_INSNS);
+
+    if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog))) {
+        warn_report("vhost-blk: unable to attach uevent filter: %s",
+                    strerror(errno));
+    }
+
+    /*
+     * Make the socket resilient to main loop stalls. With the filter
+     * attached even a large backlog consists of relevant events only.
+     */
+    if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE,
+                   &rcvbuf, sizeof(rcvbuf)) &&
+        setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf))) {
+        warn_report("vhost-blk: unable to enlarge uevent socket buffer: %s",
+                    strerror(errno));
+    }
+}
+
 static bool vhost_blk_uevent_init(Error **errp)
 {
     struct sockaddr_nl address = {
@@ -367,6 +488,8 @@ static bool vhost_blk_uevent_init(Error **errp)
         return false;
     }
 
+    vhost_blk_uevent_apply_filter(vhost_blk_uevent_fd);
+
     if (bind(vhost_blk_uevent_fd, (struct sockaddr *)&address,
              sizeof(address)) < 0) {
         error_setg_errno(errp, errno,
-- 
2.43.5


^ permalink raw reply	[relevance 4%]

* [QEMU HCI-8.0 PATCH 2/5] vhost-blk: change backend setup
  @ 2026-09-03 12:32 29% ` Andrey Zhadchenko
  2026-09-03 14:56  0%   ` Andrey Drobyshev
  2026-09-03 12:32 31% ` [QEMU HCI-8.0 PATCH 3/5] vhost-blk: add read-only flag Andrey Zhadchenko
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 119+ results
From: Andrey Zhadchenko @ 2026-09-03 12:32 UTC (permalink / raw)
  To: svt-core; +Cc: den, andrey.drobyshev

Previously we used very ugly and incapsulation-breaking assignment
fd = blk_bs(s->conf.conf.blk)->file->bs->opaque;
It is wrong in a many ways, so let's rework this.

Patch changes default `drive` to new `devpath` option so device fd
is managed by vhost-blk itself. Unfortunately this way we need a
bit more preparational work: finding out disk length, block size,
etc. Don't be too broad and just do the minimal and set reasonable
default values. Validate with previously introduced
blkconf_validate_blocksizes().
Also we lose resize, as this is tied to the block node, which is
now have no place in the setup. We will add this in the next
patches as well as RO mode.

https://virtuozzo.atlassian.net/browse/VSTOR-143437
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
---
 hw/block/vhost-blk.c          | 144 ++++++++++++++++++++++------------
 include/hw/virtio/vhost-blk.h |   5 +-
 2 files changed, 95 insertions(+), 54 deletions(-)

diff --git a/hw/block/vhost-blk.c b/hw/block/vhost-blk.c
index c52851fcf8..a3e0010982 100644
--- a/hw/block/vhost-blk.c
+++ b/hw/block/vhost-blk.c
@@ -24,23 +24,17 @@
 #include "system/system.h"
 #include "linux-headers/linux/vhost.h"
 #include <sys/ioctl.h>
-#include <linux/fs.h>
-#include "include/block/block_int-common.h"
 #include "system/runstate.h"
 
 static int vhost_blk_start(VirtIODevice *vdev)
 {
     VHostBlk *s = VHOST_BLK(vdev);
     struct vhost_vring_file backend;
-    int ret, i, nworkers, *fd;
+    int ret, i, nworkers;
     BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
     VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
     char serial[VIRTIO_BLK_ID_BYTES] = {0};
 
-    bdrv_graph_rdlock_main_loop();
-    fd = blk_bs(s->conf.conf.blk)->file->bs->opaque;
-    bdrv_graph_rdunlock_main_loop();
-
     if (!k->set_guest_notifiers) {
         error_report("vhost-blk: binding does not support guest notifiers");
         return -ENOSYS;
@@ -92,7 +86,7 @@ static int vhost_blk_start(VirtIODevice *vdev)
 
     memset(&backend, 0, sizeof(backend));
     backend.index = 0;
-    backend.fd = *fd;
+    backend.fd = s->backend_fd;
     if (ioctl(s->vhostfd, VHOST_BLK_SET_BACKEND, &backend)) {
         error_report("vhost-blk: unable to set backend");
         ret = -errno;
@@ -208,29 +202,79 @@ static void vhost_blk_vm_state(void *opaque, bool running, RunState state)
     }
 }
 
-static void vhost_blk_resize_cb(void *opaque)
+static int vhost_blk_update_size(VHostBlk *s, Error **errp)
 {
-    VirtIODevice *vdev = opaque;
+    BlockConf *conf = &s->conf.conf;
+    off_t length;
+    bool changed;
+
+    length = lseek(s->backend_fd, 0, SEEK_END);
+    if (length < 0) {
+        int error = errno;
+
+        error_setg_errno(errp, error,
+                         "vhost-blk: unable to determine size of '%s'",
+                         s->conf.devpath);
+        return -error;
+    }
 
-    assert(qemu_get_current_aio_context() == qemu_get_aio_context());
-    virtio_notify_config(vdev);
+    changed = s->length != length;
+    s->length = length;
+    conf->heads = 16;
+    conf->secs = 63;
+    conf->cyls = s->length / BDRV_SECTOR_SIZE /
+                 (conf->heads * conf->secs);
+    conf->cyls = MIN(MAX(conf->cyls, 2U), 16383U);
+
+    return changed;
 }
 
-static void vhost_blk_resize(void *opaque)
+static bool vhost_blk_open_backend(VHostBlk *s, Error **errp)
 {
-    VirtIODevice *vdev = VIRTIO_DEVICE(opaque);
+    BlockConf *conf = &s->conf.conf;
+    struct stat st;
 
-    /*
-     * virtio_notify_config() needs to acquire the global mutex,
-     * so it can't be called from an iothread. Instead, schedule
-     * it to be run in the main context BH.
-     */
-    aio_bh_schedule_oneshot(qemu_get_aio_context(), vhost_blk_resize_cb, vdev);
-}
+    s->backend_fd = qemu_open(s->conf.devpath, O_RDWR, errp);
+    if (s->backend_fd < 0) {
+        error_prepend(errp, "vhost-blk: unable to open backend: ");
+        return false;
+    }
 
-static const BlockDevOps vhost_blk_block_ops = {
-    .resize_cb     = vhost_blk_resize,
-};
+    if (fstat(s->backend_fd, &st) < 0) {
+        error_setg_errno(errp, errno, "vhost-blk: unable to stat '%s'",
+                         s->conf.devpath);
+        goto fail;
+    }
+
+    if (!S_ISBLK(st.st_mode)) {
+        error_setg(errp, "vhost-blk: '%s' is not a block device",
+                   s->conf.devpath);
+        goto fail;
+    }
+
+    if (vhost_blk_update_size(s, errp) < 0) {
+        goto fail;
+    }
+
+    if (!conf->logical_block_size) {
+        conf->logical_block_size = BDRV_SECTOR_SIZE;
+    }
+
+    if (!conf->physical_block_size) {
+        conf->physical_block_size = BDRV_SECTOR_SIZE;
+    }
+
+    if (!blkconf_validate_blocksizes(conf, errp)) {
+        goto fail;
+    }
+
+    return true;
+
+fail:
+    qemu_close(s->backend_fd);
+    s->backend_fd = -1;
+    return false;
+}
 
 static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
 {
@@ -239,13 +283,8 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
     VhostBlkConf *conf = &s->conf;
     int i, ret;
 
-    if (!conf->conf.blk) {
-        error_setg(errp, "vhost-blk: drive property not set");
-        return;
-    }
-
-    if (!blk_is_inserted(conf->conf.blk)) {
-        error_setg(errp, "vhost-blk: device needs media, but drive is empty");
+    if (!conf->devpath) {
+        error_setg(errp, "vhost-blk: devpath property must be set");
         return;
     }
 
@@ -273,17 +312,7 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
         return;
     }
 
-    if (!blkconf_apply_backend_options(&conf->conf,
-                                       !blk_supports_write_perm(conf->conf.blk),
-                                       true, errp)) {
-        return;
-    }
-
-    if (!blkconf_geometry(&conf->conf, NULL, 65535, 255, 255, errp)) {
-        return;
-    }
-
-    if (!blkconf_blocksizes(&conf->conf, errp)) {
+    if (!vhost_blk_open_backend(s, errp)) {
         return;
     }
 
@@ -311,13 +340,13 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
         goto cleanup;
     }
 
-    blk_set_dev_ops(s->conf.conf.blk, &vhost_blk_block_ops, s);
-
     ret = vhost_dev_init(&s->dev, (void *)((size_t)s->vhostfd),
                          VHOST_BACKEND_TYPE_KERNEL, 0, NULL);
     if (ret < 0) {
         error_setg(errp, "vhost-blk: vhost initialization failed: %s",
                 strerror(-ret));
+        /* vhost_dev_init() closes vhostfd on failure */
+        s->vhostfd = -1;
         goto cleanup;
     }
 
@@ -328,7 +357,14 @@ cleanup:
             qemu_del_vm_change_state_handler(s->mighand);
     }
     g_free(s->dev.vqs);
-    close(s->vhostfd);
+    if (s->vhostfd >= 0) {
+        close(s->vhostfd);
+        s->vhostfd = -1;
+    }
+    if (s->backend_fd >= 0) {
+        qemu_close(s->backend_fd);
+        s->backend_fd = -1;
+    }
     for (i = 0; i < conf->num_queues; i++) {
         virtio_del_queue(vdev, i);
     }
@@ -344,6 +380,10 @@ static void vhost_blk_device_unrealize(DeviceState *dev)
     qemu_del_vm_change_state_handler(s->mighand);
     vhost_blk_set_status(vdev, 0);
     vhost_dev_cleanup(&s->dev);
+    if (s->backend_fd >= 0) {
+        qemu_close(s->backend_fd);
+        s->backend_fd = -1;
+    }
     g_free(s->dev.vqs);
     virtio_cleanup(vdev);
 }
@@ -376,10 +416,6 @@ static uint64_t vhost_blk_get_features(VirtIODevice *vdev,
 
     virtio_add_feature(&features, VIRTIO_F_VERSION_1);
 
-    if (!blk_is_writable(s->conf.conf.blk)) {
-        virtio_add_feature(&features, VIRTIO_BLK_F_RO);
-    }
-
     if (s->conf.num_queues > 1) {
         virtio_add_feature(&features, VIRTIO_BLK_F_MQ);
     }
@@ -398,7 +434,9 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
     int64_t length;
     int blk_size = conf->logical_block_size;
 
-    blk_get_geometry(s->conf.conf.blk, &capacity);
+    length = s->length;
+    capacity = length / BDRV_SECTOR_SIZE;
+
     memset(&blkcfg, 0, sizeof(blkcfg));
     virtio_stq_p(vdev, &blkcfg.capacity, capacity);
     virtio_stl_p(vdev, &blkcfg.seg_max, s->conf.queue_size - 2);
@@ -406,7 +444,6 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
     virtio_stl_p(vdev, &blkcfg.blk_size, blk_size);
     blkcfg.geometry.heads = conf->heads;
 
-    length = blk_getlength(s->conf.conf.blk);
     if (length > 0 && length / conf->heads / conf->secs % blk_size) {
         unsigned short mask;
 
@@ -425,7 +462,8 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
 }
 
 static const Property vhost_blk_properties[] = {
-    DEFINE_BLOCK_PROPERTIES(VHostBlk, conf.conf),
+    DEFINE_BLOCK_PROPERTIES_BASE(VHostBlk, conf.conf),
+    DEFINE_PROP_STRING("devpath", VHostBlk, conf.devpath),
     DEFINE_PROP_UINT16("num-queues", VHostBlk, conf.num_queues,
                        VHOST_BLK_AUTO_NUM_QUEUES),
     DEFINE_PROP_UINT16("queue-size", VHostBlk, conf.queue_size, 256),
@@ -470,6 +508,8 @@ static void vhost_blk_instance_init(Object *obj)
 {
     VHostBlk *s = VHOST_BLK(obj);
 
+    s->vhostfd = -1;
+    s->backend_fd = -1;
     device_add_bootindex_property(obj, &s->conf.conf.bootindex,
                                   "bootindex", "/disk@0,0",
                                   DEVICE(obj));
diff --git a/include/hw/virtio/vhost-blk.h b/include/hw/virtio/vhost-blk.h
index 0c7e212595..c194b421d9 100644
--- a/include/hw/virtio/vhost-blk.h
+++ b/include/hw/virtio/vhost-blk.h
@@ -14,7 +14,6 @@
 #include "standard-headers/linux/virtio_blk.h"
 #include "hw/block/block.h"
 #include "hw/virtio/vhost.h"
-#include "system/block-backend.h"
 
 #define TYPE_VHOST_BLK "vhost-blk"
 #define VHOST_BLK(obj) \
@@ -25,6 +24,7 @@
 
 typedef struct VhostBlkConf {
     BlockConf conf;
+    char *devpath;
     uint16_t num_queues;
     uint16_t queue_size;
     uint16_t num_threads;
@@ -37,10 +37,11 @@ typedef struct VHostBlk {
     VMChangeStateEntry *mighand;
     uint64_t host_features;
     uint64_t decided_features;
-    struct virtio_blk_config blkcfg;
     int vhostfd;
+    int backend_fd;
     struct vhost_dev dev;
     bool vhost_started;
+    uint64_t length;
 } VHostBlk;
 
 #endif
-- 
2.43.5


^ permalink raw reply	[relevance 29%]

* [QEMU HCI-8.0 PATCH 4/5] vhost-blk: watch the device for resize events
    2026-09-03 12:32 29% ` [QEMU HCI-8.0 PATCH 2/5] vhost-blk: " Andrey Zhadchenko
  2026-09-03 12:32 31% ` [QEMU HCI-8.0 PATCH 3/5] vhost-blk: add read-only flag Andrey Zhadchenko
@ 2026-09-03 12:32 23% ` Andrey Zhadchenko
  2026-09-03 12:32  4% ` [QEMU HCI-8.0 PATCH 5/5] vhost-blk: filter uevents in the kernel Andrey Zhadchenko
  3 siblings, 0 replies; 119+ results
From: Andrey Zhadchenko @ 2026-09-03 12:32 UTC (permalink / raw)
  To: svt-core; +Cc: den, andrey.drobyshev

Resize was tied to block node, which we removed some time ago.
Luckily we can make resize automated: watch netlink for relevant
events and call virtio_notify_config() if we detect capacity
change.
Failed netlink setup during creation leads to failure, but this
is a price we are ready to pay for consistency.

https://virtuozzo.atlassian.net/browse/VSTOR-143437
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
---
 hw/block/vhost-blk.c          | 193 ++++++++++++++++++++++++++++++++++
 include/hw/virtio/vhost-blk.h |   5 +
 2 files changed, 198 insertions(+)

diff --git a/hw/block/vhost-blk.c b/hw/block/vhost-blk.c
index 47b6e560d4..f8eca4d58a 100644
--- a/hw/block/vhost-blk.c
+++ b/hw/block/vhost-blk.c
@@ -10,7 +10,9 @@
 
 #include "qemu/osdep.h"
 #include "qapi/error.h"
+#include "qemu/cutils.h"
 #include "qemu/error-report.h"
+#include "qemu/main-loop.h"
 #include "qom/object.h"
 #include "hw/qdev-core.h"
 #include "hw/boards.h"
@@ -25,8 +27,13 @@
 #include "linux-headers/linux/vhost.h"
 #include <sys/ioctl.h>
 #include <linux/fs.h>
+#include <linux/netlink.h>
 #include "system/runstate.h"
 
+static int vhost_blk_uevent_fd = -1;
+static QLIST_HEAD(, VHostBlk) vhost_blk_uevent_watchers =
+    QLIST_HEAD_INITIALIZER(vhost_blk_uevent_watchers);
+
 static int vhost_blk_start(VirtIODevice *vdev)
 {
     VHostBlk *s = VHOST_BLK(vdev);
@@ -230,6 +237,182 @@ static int vhost_blk_update_size(VHostBlk *s, Error **errp)
     return changed;
 }
 
+static void vhost_blk_resize_bh(void *opaque)
+{
+    VHostBlk *s = opaque;
+    Error *local_err = NULL;
+    int ret;
+
+    ret = vhost_blk_update_size(s, &local_err);
+    if (ret < 0) {
+        error_report_err(local_err);
+        return;
+    }
+
+    if (ret) {
+        virtio_notify_config(VIRTIO_DEVICE(s));
+    }
+}
+
+static void vhost_blk_uevent_read(void *opaque)
+{
+    char buffer[64 * 1024 + 1];
+
+    for (;;) {
+        struct sockaddr_nl source;
+        socklen_t source_len = sizeof(source);
+        uint64_t event_major = UINT64_MAX;
+        uint64_t event_minor = UINT64_MAX;
+        bool action_change = false;
+        bool subsystem_block = false;
+        bool resize = false;
+        char *field;
+        char *end;
+        ssize_t len;
+
+        memset(&source, 0, sizeof(source));
+        len = recvfrom(vhost_blk_uevent_fd, buffer, sizeof(buffer) - 1,
+                       MSG_DONTWAIT, (struct sockaddr *)&source, &source_len);
+        if (len < 0) {
+            if (errno == EINTR) {
+                continue;
+            }
+            if (errno == ENOBUFS) {
+                VHostBlk *s;
+
+                /* uevents dropped. Re-check just to be sure */
+                QLIST_FOREACH(s, &vhost_blk_uevent_watchers, uevent_node) {
+                    qemu_bh_schedule(s->resize_bh);
+                }
+                continue;
+            }
+            if (errno != EAGAIN && errno != EWOULDBLOCK) {
+                error_report("vhost-blk: unable to receive uevent: %s",
+                             strerror(errno));
+            }
+            return;
+        }
+
+        if (source.nl_family != AF_NETLINK || source.nl_pid != 0) {
+            continue;
+        }
+
+        buffer[len] = '\0';
+        field = buffer;
+        end = buffer + len;
+        while (field < end) {
+            size_t field_len = strnlen(field, end - field);
+
+            if (!strcmp(field, "ACTION=change")) {
+                action_change = true;
+            } else if (!strcmp(field, "SUBSYSTEM=block")) {
+                subsystem_block = true;
+            } else if (!strcmp(field, "RESIZE=1")) {
+                resize = true;
+            } else if (g_str_has_prefix(field, "MAJOR=")) {
+                uint64_t value;
+
+                if (!qemu_strtou64(field + strlen("MAJOR="), NULL, 10,
+                                   &value)) {
+                    event_major = value;
+                }
+            } else if (g_str_has_prefix(field, "MINOR=")) {
+                uint64_t value;
+
+                if (!qemu_strtou64(field + strlen("MINOR="), NULL, 10,
+                                   &value)) {
+                    event_minor = value;
+                }
+            }
+
+            if (field_len == end - field) {
+                break;
+            }
+            field += field_len + 1;
+        }
+
+        if (action_change && subsystem_block && resize &&
+            event_major <= UINT_MAX && event_minor <= UINT_MAX) {
+            VHostBlk *s;
+
+            QLIST_FOREACH(s, &vhost_blk_uevent_watchers, uevent_node) {
+                dev_t rdev = s->backend_rdev;
+
+                if (major(rdev) == event_major &&
+                    minor(rdev) == event_minor) {
+                    qemu_bh_schedule(s->resize_bh);
+                }
+            }
+        }
+    }
+}
+
+static bool vhost_blk_uevent_init(Error **errp)
+{
+    struct sockaddr_nl address = {
+        .nl_family = AF_NETLINK,
+        .nl_groups = 1,
+    };
+
+    if (vhost_blk_uevent_fd >= 0) {
+        return true;
+    }
+
+    vhost_blk_uevent_fd = socket(AF_NETLINK,
+                                 SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
+                                 NETLINK_KOBJECT_UEVENT);
+    if (vhost_blk_uevent_fd < 0) {
+        error_setg_errno(errp, errno,
+                         "vhost-blk: unable to create uevent socket");
+        return false;
+    }
+
+    if (bind(vhost_blk_uevent_fd, (struct sockaddr *)&address,
+             sizeof(address)) < 0) {
+        error_setg_errno(errp, errno,
+                         "vhost-blk: unable to bind uevent socket");
+        qemu_close(vhost_blk_uevent_fd);
+        vhost_blk_uevent_fd = -1;
+        return false;
+    }
+
+    qemu_set_fd_handler(vhost_blk_uevent_fd, vhost_blk_uevent_read,
+                        NULL, NULL);
+    return true;
+}
+
+static void vhost_blk_uevent_cleanup_if_unused(void)
+{
+    if (vhost_blk_uevent_fd < 0 ||
+        !QLIST_EMPTY(&vhost_blk_uevent_watchers)) {
+        return;
+    }
+
+    qemu_set_fd_handler(vhost_blk_uevent_fd, NULL, NULL, NULL);
+    qemu_close(vhost_blk_uevent_fd);
+    vhost_blk_uevent_fd = -1;
+}
+
+static void vhost_blk_uevent_register(VHostBlk *s)
+{
+    s->resize_bh = qemu_bh_new(vhost_blk_resize_bh, s);
+    QLIST_INSERT_HEAD(&vhost_blk_uevent_watchers, s, uevent_node);
+    s->uevent_registered = true;
+}
+
+static void vhost_blk_uevent_unregister(VHostBlk *s)
+{
+    if (!s->uevent_registered) {
+        return;
+    }
+
+    QLIST_REMOVE(s, uevent_node);
+    s->uevent_registered = false;
+    qemu_bh_delete(s->resize_bh);
+    s->resize_bh = NULL;
+    vhost_blk_uevent_cleanup_if_unused();
+}
+
 static bool vhost_blk_open_backend(VHostBlk *s, Error **errp)
 {
     BlockConf *conf = &s->conf.conf;
@@ -254,6 +437,7 @@ static bool vhost_blk_open_backend(VHostBlk *s, Error **errp)
                    s->conf.devpath);
         goto fail;
     }
+    s->backend_rdev = st.st_rdev;
 
     if (ioctl(s->backend_fd, BLKROGET, &readonly) < 0) {
         error_setg_errno(errp, errno,
@@ -332,6 +516,12 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
         return;
     }
 
+    if (!vhost_blk_uevent_init(errp)) {
+        qemu_close(s->backend_fd);
+        s->backend_fd = -1;
+        return;
+    }
+
     s->dev.nvqs = conf->num_queues;
     s->dev.max_queues = conf->num_queues;
     s->dev.vqs = g_new0(struct vhost_virtqueue, s->dev.nvqs);
@@ -366,6 +556,7 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
         goto cleanup;
     }
 
+    vhost_blk_uevent_register(s);
     return;
 
 cleanup:
@@ -385,6 +576,7 @@ cleanup:
         virtio_del_queue(vdev, i);
     }
     virtio_cleanup(vdev);
+    vhost_blk_uevent_cleanup_if_unused();
     return;
 }
 
@@ -393,6 +585,7 @@ static void vhost_blk_device_unrealize(DeviceState *dev)
     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
     VHostBlk *s = VHOST_BLK(dev);
 
+    vhost_blk_uevent_unregister(s);
     qemu_del_vm_change_state_handler(s->mighand);
     vhost_blk_set_status(vdev, 0);
     vhost_dev_cleanup(&s->dev);
diff --git a/include/hw/virtio/vhost-blk.h b/include/hw/virtio/vhost-blk.h
index c6646f5845..815939419f 100644
--- a/include/hw/virtio/vhost-blk.h
+++ b/include/hw/virtio/vhost-blk.h
@@ -14,6 +14,7 @@
 #include "standard-headers/linux/virtio_blk.h"
 #include "hw/block/block.h"
 #include "hw/virtio/vhost.h"
+#include "qemu/queue.h"
 
 #define TYPE_VHOST_BLK "vhost-blk"
 #define VHOST_BLK(obj) \
@@ -43,6 +44,10 @@ typedef struct VHostBlk {
     struct vhost_dev dev;
     bool vhost_started;
     uint64_t length;
+    uint64_t backend_rdev;
+    QEMUBH *resize_bh;
+    QLIST_ENTRY(VHostBlk) uevent_node;
+    bool uevent_registered;
 } VHostBlk;
 
 #endif
-- 
2.43.5


^ permalink raw reply	[relevance 23%]

* Re: [QEMU HCI-8.0 PATCH 3/5] vhost-blk: add read-only flag
  2026-09-03 12:32 31% ` [QEMU HCI-8.0 PATCH 3/5] vhost-blk: add read-only flag Andrey Zhadchenko
@ 2026-09-03 14:56  8%   ` Andrey Drobyshev
  0 siblings, 0 replies; 119+ results
From: Andrey Drobyshev @ 2026-09-03 14:56 UTC (permalink / raw)
  To: Andrey Zhadchenko; +Cc: svt-core, den, andrey.drobyshev

> and set RO respectively. Also compare BLKROGET with the selected

Nit: make commit message start with their own sentence.

> mode and reject r/w if needed.
> 
> https://virtuozzo.atlassian.net/browse/VSTOR-143437
> Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
>
> diff --git a/hw/block/vhost-blk.c b/hw/block/vhost-blk.c
> index a3e0010982f..47b6e560d47 100644
> --- a/hw/block/vhost-blk.c
> +++ b/hw/block/vhost-blk.c
> @@ -24,6 +24,7 @@
>  #include "system/system.h"
>  #include "linux-headers/linux/vhost.h"
>  #include <sys/ioctl.h>
> +#include <linux/fs.h>

Previous patch removes the header, now we add it back.  Let's just not
touch it.

>  #include "system/runstate.h"
>  
>  static int vhost_blk_start(VirtIODevice *vdev)
> @@ -233,8 +234,10 @@ static bool vhost_blk_open_backend(VHostBlk *s, Error **errp)
>  {
>      BlockConf *conf = &s->conf.conf;
>      struct stat st;
> +    int readonly;
> +    int open_flags = s->conf.readonly ? O_RDONLY : O_RDWR;
>  
> -    s->backend_fd = qemu_open(s->conf.devpath, O_RDWR, errp);
> +    s->backend_fd = qemu_open(s->conf.devpath, open_flags, errp);
>      if (s->backend_fd < 0) {
>          error_prepend(errp, "vhost-blk: unable to open backend: ");
>          return false;
> @@ -252,6 +255,19 @@ static bool vhost_blk_open_backend(VHostBlk *s, Error **errp)
>          goto fail;
>      }
>  
> +    if (ioctl(s->backend_fd, BLKROGET, &readonly) < 0) {
> +        error_setg_errno(errp, errno,
> +                         "vhost-blk: unable to get read-only status of '%s'",
> +                         s->conf.devpath);
> +        goto fail;
> +    }
> +
> +    if (readonly && !s->conf.readonly) {
> +        error_setg(errp, "vhost-blk: '%s' is read-only",
> +                   s->conf.devpath);
> +        goto fail;
> +    }
> +

How about doing it similarly to file-posix:

    if (!s->conf.readonly) {
        if (ioctl(s->backend_fd, BLKROGET, &readonly) < 0) {
            error_setg_errno(errp, errno,
                             "vhost-blk: unable to get read-only status of "
                             "'%s'", s->conf.devpath);
            goto fail;
         }

         if (readonly) {
             error_setg_errno(errp, EROFS, "The device is not writable");
             goto fail;
         }
    }

In addition: conf.readonly value comes from libvirt.  conf.devpath is
also provided by libvirt.  Shouldn't we check BLKROGET early on and fail
in libvirt instead of waiting till here?  I'd prefer having both checks.
Leave the check here, but fail early on in libvirt.

Andrey

-- 
Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>

^ permalink raw reply	[relevance 8%]

* Re: [QEMU HCI-8.0 PATCH 2/5] vhost-blk: change backend setup
  2026-09-03 12:32 29% ` [QEMU HCI-8.0 PATCH 2/5] vhost-blk: " Andrey Zhadchenko
@ 2026-09-03 14:56  0%   ` Andrey Drobyshev
  2026-09-03 15:27  0%     ` Andrey Zhadchenko
  0 siblings, 1 reply; 119+ results
From: Andrey Drobyshev @ 2026-09-03 14:56 UTC (permalink / raw)
  To: Andrey Zhadchenko; +Cc: svt-core, den, andrey.drobyshev

> Previously we used very ugly and incapsulation-breaking assignment
> fd = blk_bs(s->conf.conf.blk)->file->bs->opaque;
> It is wrong in a many ways, so let's rework this.
> 
> Patch changes default `drive` to new `devpath` option so device fd
> is managed by vhost-blk itself. Unfortunately this way we need a
> bit more preparational work: finding out disk length, block size,
> etc. Don't be too broad and just do the minimal and set reasonable
> default values. Validate with previously introduced
> blkconf_validate_blocksizes().
> Also we lose resize, as this is tied to the block node, which is
> now have no place in the setup. We will add this in the next
> patches as well as RO mode.
> 
> https://virtuozzo.atlassian.net/browse/VSTOR-143437
> Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
>
> diff --git a/hw/block/vhost-blk.c b/hw/block/vhost-blk.c
> index c52851fcf8b..a3e0010982f 100644
> --- a/hw/block/vhost-blk.c
> +++ b/hw/block/vhost-blk.c
> @@ -24,23 +24,17 @@
>  #include "system/system.h"
>  #include "linux-headers/linux/vhost.h"
>  #include <sys/ioctl.h>
> -#include <linux/fs.h>
> -#include "include/block/block_int-common.h"
>  #include "system/runstate.h"
>  
>  static int vhost_blk_start(VirtIODevice *vdev)
>  {
>      VHostBlk *s = VHOST_BLK(vdev);
>      struct vhost_vring_file backend;
> -    int ret, i, nworkers, *fd;
> +    int ret, i, nworkers;
>      BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
>      VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
>      char serial[VIRTIO_BLK_ID_BYTES] = {0};
>  
> -    bdrv_graph_rdlock_main_loop();
> -    fd = blk_bs(s->conf.conf.blk)->file->bs->opaque;
> -    bdrv_graph_rdunlock_main_loop();
> -
>      if (!k->set_guest_notifiers) {
>          error_report("vhost-blk: binding does not support guest notifiers");
>          return -ENOSYS;
> @@ -92,7 +86,7 @@ static int vhost_blk_start(VirtIODevice *vdev)
>  
>      memset(&backend, 0, sizeof(backend));
>      backend.index = 0;
> -    backend.fd = *fd;
> +    backend.fd = s->backend_fd;
>      if (ioctl(s->vhostfd, VHOST_BLK_SET_BACKEND, &backend)) {
>          error_report("vhost-blk: unable to set backend");
>          ret = -errno;
> @@ -208,29 +202,79 @@ static void vhost_blk_vm_state(void *opaque, bool running, RunState state)
>      }
>  }
>  
> -static void vhost_blk_resize_cb(void *opaque)
> +static int vhost_blk_update_size(VHostBlk *s, Error **errp)
>  {
> -    VirtIODevice *vdev = opaque;
> +    BlockConf *conf = &s->conf.conf;
> +    off_t length;
> +    bool changed;
> +
> +    length = lseek(s->backend_fd, 0, SEEK_END);
> +    if (length < 0) {
> +        int error = errno;
> +
> +        error_setg_errno(errp, error,
> +                         "vhost-blk: unable to determine size of '%s'",
> +                         s->conf.devpath);
> +        return -error;
> +    }
>  
> -    assert(qemu_get_current_aio_context() == qemu_get_aio_context());
> -    virtio_notify_config(vdev);
> +    changed = s->length != length;
> +    s->length = length;
> +    conf->heads = 16;
> +    conf->secs = 63;
> +    conf->cyls = s->length / BDRV_SECTOR_SIZE /
> +                 (conf->heads * conf->secs);
> +    conf->cyls = MIN(MAX(conf->cyls, 2U), 16383U);
> +
> +    return changed;

This function should return int, but here we return bool.  And then
we do 'if (vhost_blk_update_size() < 0) ...', which never fires.

>  }
>  
> -static void vhost_blk_resize(void *opaque)
> +static bool vhost_blk_open_backend(VHostBlk *s, Error **errp)
>  {
> -    VirtIODevice *vdev = VIRTIO_DEVICE(opaque);
> +    BlockConf *conf = &s->conf.conf;
> +    struct stat st;
>  
> -    /*
> -     * virtio_notify_config() needs to acquire the global mutex,
> -     * so it can't be called from an iothread. Instead, schedule
> -     * it to be run in the main context BH.
> -     */
> -    aio_bh_schedule_oneshot(qemu_get_aio_context(), vhost_blk_resize_cb, vdev);
> -}
> +    s->backend_fd = qemu_open(s->conf.devpath, O_RDWR, errp);
> +    if (s->backend_fd < 0) {
> +        error_prepend(errp, "vhost-blk: unable to open backend: ");
> +        return false;
> +    }

Suggestion: how about also checking BLKSSZGET value of the device at this
point and comparing it against conf->logical_block_size?

>  
> -static const BlockDevOps vhost_blk_block_ops = {
> -    .resize_cb     = vhost_blk_resize,
> -};
> +    if (fstat(s->backend_fd, &st) < 0) {
> +        error_setg_errno(errp, errno, "vhost-blk: unable to stat '%s'",
> +                         s->conf.devpath);
> +        goto fail;
> +    }
> +
> +    if (!S_ISBLK(st.st_mode)) {
> +        error_setg(errp, "vhost-blk: '%s' is not a block device",
> +                   s->conf.devpath);
> +        goto fail;
> +    }
> +
> +    if (vhost_blk_update_size(s, errp) < 0) {
> +        goto fail;
> +    }
> +
> +    if (!conf->logical_block_size) {
> +        conf->logical_block_size = BDRV_SECTOR_SIZE;
> +    }
> +
> +    if (!conf->physical_block_size) {
> +        conf->physical_block_size = BDRV_SECTOR_SIZE;
> +    }
> +
> +    if (!blkconf_validate_blocksizes(conf, errp)) {
> +        goto fail;
> +    }
> +
> +    return true;
> +
> +fail:
> +    qemu_close(s->backend_fd);
> +    s->backend_fd = -1;
> +    return false;
> +}
>  
>  static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
>  {
> @@ -239,13 +283,8 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
>      VhostBlkConf *conf = &s->conf;
>      int i, ret;
>  
> -    if (!conf->conf.blk) {
> -        error_setg(errp, "vhost-blk: drive property not set");
> -        return;
> -    }
> -
> -    if (!blk_is_inserted(conf->conf.blk)) {
> -        error_setg(errp, "vhost-blk: device needs media, but drive is empty");
> +    if (!conf->devpath) {
> +        error_setg(errp, "vhost-blk: devpath property must be set");
>          return;
>      }
>  
> @@ -273,17 +312,7 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
>          return;
>      }
>  
> -    if (!blkconf_apply_backend_options(&conf->conf,
> -                                       !blk_supports_write_perm(conf->conf.blk),
> -                                       true, errp)) {
> -        return;
> -    }
> -
> -    if (!blkconf_geometry(&conf->conf, NULL, 65535, 255, 255, errp)) {
> -        return;
> -    }
> -
> -    if (!blkconf_blocksizes(&conf->conf, errp)) {
> +    if (!vhost_blk_open_backend(s, errp)) {
>          return;
>      }
>  
> @@ -311,13 +340,13 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
>          goto cleanup;
>      }
>  
> -    blk_set_dev_ops(s->conf.conf.blk, &vhost_blk_block_ops, s);
> -
>      ret = vhost_dev_init(&s->dev, (void *)((size_t)s->vhostfd),
>                           VHOST_BACKEND_TYPE_KERNEL, 0, NULL);
>      if (ret < 0) {
>          error_setg(errp, "vhost-blk: vhost initialization failed: %s",
>                  strerror(-ret));
> +        /* vhost_dev_init() closes vhostfd on failure */
> +        s->vhostfd = -1;

Before this patch we were doing double close(vhostfd) after vhost_dev_init()
failure.  I'd make it a separate commit with a "Fixes:" tag.

>          goto cleanup;
>      }
>  
> @@ -328,7 +357,14 @@ cleanup:
>              qemu_del_vm_change_state_handler(s->mighand);
>      }
>      g_free(s->dev.vqs);
> -    close(s->vhostfd);
> +    if (s->vhostfd >= 0) {
> +        close(s->vhostfd);
> +        s->vhostfd = -1;
> +    }
> +    if (s->backend_fd >= 0) {
> +        qemu_close(s->backend_fd);
> +        s->backend_fd = -1;
> +    }
>      for (i = 0; i < conf->num_queues; i++) {
>          virtio_del_queue(vdev, i);
>      }
> @@ -344,6 +380,10 @@ static void vhost_blk_device_unrealize(DeviceState *dev)
>      qemu_del_vm_change_state_handler(s->mighand);
>      vhost_blk_set_status(vdev, 0);
>      vhost_dev_cleanup(&s->dev);
> +    if (s->backend_fd >= 0) {
> +        qemu_close(s->backend_fd);
> +        s->backend_fd = -1;
> +    }
>      g_free(s->dev.vqs);
>      virtio_cleanup(vdev);
>  }
> @@ -376,10 +416,6 @@ static uint64_t vhost_blk_get_features(VirtIODevice *vdev,
>  
>      virtio_add_feature(&features, VIRTIO_F_VERSION_1);
>  
> -    if (!blk_is_writable(s->conf.conf.blk)) {
> -        virtio_add_feature(&features, VIRTIO_BLK_F_RO);
> -    }
> -
>      if (s->conf.num_queues > 1) {
>          virtio_add_feature(&features, VIRTIO_BLK_F_MQ);
>      }
> @@ -398,7 +434,9 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
>      int64_t length;
>      int blk_size = conf->logical_block_size;
>  
> -    blk_get_geometry(s->conf.conf.blk, &capacity);
> +    length = s->length;
> +    capacity = length / BDRV_SECTOR_SIZE;
> +
>      memset(&blkcfg, 0, sizeof(blkcfg));
>      virtio_stq_p(vdev, &blkcfg.capacity, capacity);
>      virtio_stl_p(vdev, &blkcfg.seg_max, s->conf.queue_size - 2);
> @@ -406,7 +444,6 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
>      virtio_stl_p(vdev, &blkcfg.blk_size, blk_size);
>      blkcfg.geometry.heads = conf->heads;
>  
> -    length = blk_getlength(s->conf.conf.blk);
>      if (length > 0 && length / conf->heads / conf->secs % blk_size) {
>          unsigned short mask;
>  
> @@ -425,7 +462,8 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
>  }
>  
>  static const Property vhost_blk_properties[] = {
> -    DEFINE_BLOCK_PROPERTIES(VHostBlk, conf.conf),
> +    DEFINE_BLOCK_PROPERTIES_BASE(VHostBlk, conf.conf),

DEFINE_BLOCK_PROPERTIES_BASE() macro defines lots of properties that
make no sense without BlockBackend.  E.g. backend_defaults, write-cache,
share-rw, account-invalid, account-failed, stats-intervals.  We should
consider limiting the list of config properties to the ones which really
matter to us.  Ideally as a separate commit.

Andrey

-- 
Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>

^ permalink raw reply	[relevance 0%]

* Re: [QEMU HCI-8.0 PATCH 2/5] vhost-blk: change backend setup
  2026-09-03 14:56  0%   ` Andrey Drobyshev
@ 2026-09-03 15:27  0%     ` Andrey Zhadchenko
  2026-09-03 15:34  0%       ` Andrey Drobyshev
  0 siblings, 1 reply; 119+ results
From: Andrey Zhadchenko @ 2026-09-03 15:27 UTC (permalink / raw)
  To: Andrey Drobyshev; +Cc: svt-core, den



On 9/3/26 16:56, Andrey Drobyshev wrote:
>> Previously we used very ugly and incapsulation-breaking assignment
>> fd = blk_bs(s->conf.conf.blk)->file->bs->opaque;
>> It is wrong in a many ways, so let's rework this.
>>
>> Patch changes default `drive` to new `devpath` option so device fd
>> is managed by vhost-blk itself. Unfortunately this way we need a
>> bit more preparational work: finding out disk length, block size,
>> etc. Don't be too broad and just do the minimal and set reasonable
>> default values. Validate with previously introduced
>> blkconf_validate_blocksizes().
>> Also we lose resize, as this is tied to the block node, which is
>> now have no place in the setup. We will add this in the next
>> patches as well as RO mode.
>>
>> https://virtuozzo.atlassian.net/browse/VSTOR-143437
>> Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
>>
>> diff --git a/hw/block/vhost-blk.c b/hw/block/vhost-blk.c
>> index c52851fcf8b..a3e0010982f 100644
>> --- a/hw/block/vhost-blk.c
>> +++ b/hw/block/vhost-blk.c
>> @@ -24,23 +24,17 @@
>>   #include "system/system.h"
>>   #include "linux-headers/linux/vhost.h"
>>   #include <sys/ioctl.h>
>> -#include <linux/fs.h>
>> -#include "include/block/block_int-common.h"
>>   #include "system/runstate.h"
>>   
>>   static int vhost_blk_start(VirtIODevice *vdev)
>>   {
>>       VHostBlk *s = VHOST_BLK(vdev);
>>       struct vhost_vring_file backend;
>> -    int ret, i, nworkers, *fd;
>> +    int ret, i, nworkers;
>>       BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
>>       VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
>>       char serial[VIRTIO_BLK_ID_BYTES] = {0};
>>   
>> -    bdrv_graph_rdlock_main_loop();
>> -    fd = blk_bs(s->conf.conf.blk)->file->bs->opaque;
>> -    bdrv_graph_rdunlock_main_loop();
>> -
>>       if (!k->set_guest_notifiers) {
>>           error_report("vhost-blk: binding does not support guest notifiers");
>>           return -ENOSYS;
>> @@ -92,7 +86,7 @@ static int vhost_blk_start(VirtIODevice *vdev)
>>   
>>       memset(&backend, 0, sizeof(backend));
>>       backend.index = 0;
>> -    backend.fd = *fd;
>> +    backend.fd = s->backend_fd;
>>       if (ioctl(s->vhostfd, VHOST_BLK_SET_BACKEND, &backend)) {
>>           error_report("vhost-blk: unable to set backend");
>>           ret = -errno;
>> @@ -208,29 +202,79 @@ static void vhost_blk_vm_state(void *opaque, bool running, RunState state)
>>       }
>>   }
>>   
>> -static void vhost_blk_resize_cb(void *opaque)
>> +static int vhost_blk_update_size(VHostBlk *s, Error **errp)
>>   {
>> -    VirtIODevice *vdev = opaque;
>> +    BlockConf *conf = &s->conf.conf;
>> +    off_t length;
>> +    bool changed;
>> +
>> +    length = lseek(s->backend_fd, 0, SEEK_END);
>> +    if (length < 0) {
>> +        int error = errno;
>> +
>> +        error_setg_errno(errp, error,
>> +                         "vhost-blk: unable to determine size of '%s'",
>> +                         s->conf.devpath);
>> +        return -error;
>> +    }
>>   
>> -    assert(qemu_get_current_aio_context() == qemu_get_aio_context());
>> -    virtio_notify_config(vdev);
>> +    changed = s->length != length;
>> +    s->length = length;
>> +    conf->heads = 16;
>> +    conf->secs = 63;
>> +    conf->cyls = s->length / BDRV_SECTOR_SIZE /
>> +                 (conf->heads * conf->secs);
>> +    conf->cyls = MIN(MAX(conf->cyls, 2U), 16383U);
>> +
>> +    return changed;
> 
> This function should return int, but here we return bool.  And then
> we do 'if (vhost_blk_update_size() < 0) ...', which never fires.
> 
>>   }
>>   
>> -static void vhost_blk_resize(void *opaque)
>> +static bool vhost_blk_open_backend(VHostBlk *s, Error **errp)
>>   {
>> -    VirtIODevice *vdev = VIRTIO_DEVICE(opaque);
>> +    BlockConf *conf = &s->conf.conf;
>> +    struct stat st;
>>   
>> -    /*
>> -     * virtio_notify_config() needs to acquire the global mutex,
>> -     * so it can't be called from an iothread. Instead, schedule
>> -     * it to be run in the main context BH.
>> -     */
>> -    aio_bh_schedule_oneshot(qemu_get_aio_context(), vhost_blk_resize_cb, vdev);
>> -}
>> +    s->backend_fd = qemu_open(s->conf.devpath, O_RDWR, errp);
>> +    if (s->backend_fd < 0) {
>> +        error_prepend(errp, "vhost-blk: unable to open backend: ");
>> +        return false;
>> +    }
> 
> Suggestion: how about also checking BLKSSZGET value of the device at this
> point and comparing it against conf->logical_block_size?

I would personally avoid this for now. First of all BLKSSZGET (and other 
things) may be undefined (failed this for BLKROGET btw) and proper ifdef
decoration are not so important for 'change backend setup' patch. We can
always add it later.

> 
>>   
>> -static const BlockDevOps vhost_blk_block_ops = {
>> -    .resize_cb     = vhost_blk_resize,
>> -};
>> +    if (fstat(s->backend_fd, &st) < 0) {
>> +        error_setg_errno(errp, errno, "vhost-blk: unable to stat '%s'",
>> +                         s->conf.devpath);
>> +        goto fail;
>> +    }
>> +
>> +    if (!S_ISBLK(st.st_mode)) {
>> +        error_setg(errp, "vhost-blk: '%s' is not a block device",
>> +                   s->conf.devpath);
>> +        goto fail;
>> +    }
>> +
>> +    if (vhost_blk_update_size(s, errp) < 0) {
>> +        goto fail;
>> +    }
>> +
>> +    if (!conf->logical_block_size) {
>> +        conf->logical_block_size = BDRV_SECTOR_SIZE;
>> +    }
>> +
>> +    if (!conf->physical_block_size) {
>> +        conf->physical_block_size = BDRV_SECTOR_SIZE;
>> +    }
>> +
>> +    if (!blkconf_validate_blocksizes(conf, errp)) {
>> +        goto fail;
>> +    }
>> +
>> +    return true;
>> +
>> +fail:
>> +    qemu_close(s->backend_fd);
>> +    s->backend_fd = -1;
>> +    return false;
>> +}
>>   
>>   static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
>>   {
>> @@ -239,13 +283,8 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
>>       VhostBlkConf *conf = &s->conf;
>>       int i, ret;
>>   
>> -    if (!conf->conf.blk) {
>> -        error_setg(errp, "vhost-blk: drive property not set");
>> -        return;
>> -    }
>> -
>> -    if (!blk_is_inserted(conf->conf.blk)) {
>> -        error_setg(errp, "vhost-blk: device needs media, but drive is empty");
>> +    if (!conf->devpath) {
>> +        error_setg(errp, "vhost-blk: devpath property must be set");
>>           return;
>>       }
>>   
>> @@ -273,17 +312,7 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
>>           return;
>>       }
>>   
>> -    if (!blkconf_apply_backend_options(&conf->conf,
>> -                                       !blk_supports_write_perm(conf->conf.blk),
>> -                                       true, errp)) {
>> -        return;
>> -    }
>> -
>> -    if (!blkconf_geometry(&conf->conf, NULL, 65535, 255, 255, errp)) {
>> -        return;
>> -    }
>> -
>> -    if (!blkconf_blocksizes(&conf->conf, errp)) {
>> +    if (!vhost_blk_open_backend(s, errp)) {
>>           return;
>>       }
>>   
>> @@ -311,13 +340,13 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
>>           goto cleanup;
>>       }
>>   
>> -    blk_set_dev_ops(s->conf.conf.blk, &vhost_blk_block_ops, s);
>> -
>>       ret = vhost_dev_init(&s->dev, (void *)((size_t)s->vhostfd),
>>                            VHOST_BACKEND_TYPE_KERNEL, 0, NULL);
>>       if (ret < 0) {
>>           error_setg(errp, "vhost-blk: vhost initialization failed: %s",
>>                   strerror(-ret));
>> +        /* vhost_dev_init() closes vhostfd on failure */
>> +        s->vhostfd = -1;
> 
> Before this patch we were doing double close(vhostfd) after vhost_dev_init()
> failure.  I'd make it a separate commit with a "Fixes:" tag.
> 
>>           goto cleanup;
>>       }
>>   
>> @@ -328,7 +357,14 @@ cleanup:
>>               qemu_del_vm_change_state_handler(s->mighand);
>>       }
>>       g_free(s->dev.vqs);
>> -    close(s->vhostfd);
>> +    if (s->vhostfd >= 0) {
>> +        close(s->vhostfd);
>> +        s->vhostfd = -1;
>> +    }
>> +    if (s->backend_fd >= 0) {
>> +        qemu_close(s->backend_fd);
>> +        s->backend_fd = -1;
>> +    }
>>       for (i = 0; i < conf->num_queues; i++) {
>>           virtio_del_queue(vdev, i);
>>       }
>> @@ -344,6 +380,10 @@ static void vhost_blk_device_unrealize(DeviceState *dev)
>>       qemu_del_vm_change_state_handler(s->mighand);
>>       vhost_blk_set_status(vdev, 0);
>>       vhost_dev_cleanup(&s->dev);
>> +    if (s->backend_fd >= 0) {
>> +        qemu_close(s->backend_fd);
>> +        s->backend_fd = -1;
>> +    }
>>       g_free(s->dev.vqs);
>>       virtio_cleanup(vdev);
>>   }
>> @@ -376,10 +416,6 @@ static uint64_t vhost_blk_get_features(VirtIODevice *vdev,
>>   
>>       virtio_add_feature(&features, VIRTIO_F_VERSION_1);
>>   
>> -    if (!blk_is_writable(s->conf.conf.blk)) {
>> -        virtio_add_feature(&features, VIRTIO_BLK_F_RO);
>> -    }
>> -
>>       if (s->conf.num_queues > 1) {
>>           virtio_add_feature(&features, VIRTIO_BLK_F_MQ);
>>       }
>> @@ -398,7 +434,9 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
>>       int64_t length;
>>       int blk_size = conf->logical_block_size;
>>   
>> -    blk_get_geometry(s->conf.conf.blk, &capacity);
>> +    length = s->length;
>> +    capacity = length / BDRV_SECTOR_SIZE;
>> +
>>       memset(&blkcfg, 0, sizeof(blkcfg));
>>       virtio_stq_p(vdev, &blkcfg.capacity, capacity);
>>       virtio_stl_p(vdev, &blkcfg.seg_max, s->conf.queue_size - 2);
>> @@ -406,7 +444,6 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
>>       virtio_stl_p(vdev, &blkcfg.blk_size, blk_size);
>>       blkcfg.geometry.heads = conf->heads;
>>   
>> -    length = blk_getlength(s->conf.conf.blk);
>>       if (length > 0 && length / conf->heads / conf->secs % blk_size) {
>>           unsigned short mask;
>>   
>> @@ -425,7 +462,8 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
>>   }
>>   
>>   static const Property vhost_blk_properties[] = {
>> -    DEFINE_BLOCK_PROPERTIES(VHostBlk, conf.conf),
>> +    DEFINE_BLOCK_PROPERTIES_BASE(VHostBlk, conf.conf),
> 
> DEFINE_BLOCK_PROPERTIES_BASE() macro defines lots of properties that
> make no sense without BlockBackend.  E.g. backend_defaults, write-cache,
> share-rw, account-invalid, account-failed, stats-intervals.  We should
> consider limiting the list of config properties to the ones which really
> matter to us.  Ideally as a separate commit.

 From one point of view yes, from another point of view a lot other make 
sense for virtio-device. I thought it was better to leave it as is and 
use it later.
But maybe remove it altogether (also along with logical/physical block 
size) and better add it later as separate options if we feel tuning 
these values brings any impact?

> 
> Andrey
> 


^ permalink raw reply	[relevance 0%]

* Re: [QEMU HCI-8.0 PATCH 2/5] vhost-blk: change backend setup
  2026-09-03 15:27  0%     ` Andrey Zhadchenko
@ 2026-09-03 15:34  0%       ` Andrey Drobyshev
  0 siblings, 0 replies; 119+ results
From: Andrey Drobyshev @ 2026-09-03 15:34 UTC (permalink / raw)
  To: Andrey Zhadchenko; +Cc: svt-core, den

On 9/3/26 6:27 PM, Andrey Zhadchenko wrote:
> 
> 
> On 9/3/26 16:56, Andrey Drobyshev wrote:
>>> Previously we used very ugly and incapsulation-breaking assignment
>>> fd = blk_bs(s->conf.conf.blk)->file->bs->opaque;
>>> It is wrong in a many ways, so let's rework this.
>>>
>>> Patch changes default `drive` to new `devpath` option so device fd
>>> is managed by vhost-blk itself. Unfortunately this way we need a
>>> bit more preparational work: finding out disk length, block size,
>>> etc. Don't be too broad and just do the minimal and set reasonable
>>> default values. Validate with previously introduced
>>> blkconf_validate_blocksizes().
>>> Also we lose resize, as this is tied to the block node, which is
>>> now have no place in the setup. We will add this in the next
>>> patches as well as RO mode.
>>>
>>> https://virtuozzo.atlassian.net/browse/VSTOR-143437
>>> Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
>>>
>>> diff --git a/hw/block/vhost-blk.c b/hw/block/vhost-blk.c
>>> index c52851fcf8b..a3e0010982f 100644
>>> --- a/hw/block/vhost-blk.c
>>> +++ b/hw/block/vhost-blk.c
>>> @@ -24,23 +24,17 @@
>>>   #include "system/system.h"
>>>   #include "linux-headers/linux/vhost.h"
>>>   #include <sys/ioctl.h>
>>> -#include <linux/fs.h>
>>> -#include "include/block/block_int-common.h"
>>>   #include "system/runstate.h"
>>>   
>>>   static int vhost_blk_start(VirtIODevice *vdev)
>>>   {
>>>       VHostBlk *s = VHOST_BLK(vdev);
>>>       struct vhost_vring_file backend;
>>> -    int ret, i, nworkers, *fd;
>>> +    int ret, i, nworkers;
>>>       BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
>>>       VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
>>>       char serial[VIRTIO_BLK_ID_BYTES] = {0};
>>>   
>>> -    bdrv_graph_rdlock_main_loop();
>>> -    fd = blk_bs(s->conf.conf.blk)->file->bs->opaque;
>>> -    bdrv_graph_rdunlock_main_loop();
>>> -
>>>       if (!k->set_guest_notifiers) {
>>>           error_report("vhost-blk: binding does not support guest notifiers");
>>>           return -ENOSYS;
>>> @@ -92,7 +86,7 @@ static int vhost_blk_start(VirtIODevice *vdev)
>>>   
>>>       memset(&backend, 0, sizeof(backend));
>>>       backend.index = 0;
>>> -    backend.fd = *fd;
>>> +    backend.fd = s->backend_fd;
>>>       if (ioctl(s->vhostfd, VHOST_BLK_SET_BACKEND, &backend)) {
>>>           error_report("vhost-blk: unable to set backend");
>>>           ret = -errno;
>>> @@ -208,29 +202,79 @@ static void vhost_blk_vm_state(void *opaque, bool running, RunState state)
>>>       }
>>>   }
>>>   
>>> -static void vhost_blk_resize_cb(void *opaque)
>>> +static int vhost_blk_update_size(VHostBlk *s, Error **errp)
>>>   {
>>> -    VirtIODevice *vdev = opaque;
>>> +    BlockConf *conf = &s->conf.conf;
>>> +    off_t length;
>>> +    bool changed;
>>> +
>>> +    length = lseek(s->backend_fd, 0, SEEK_END);
>>> +    if (length < 0) {
>>> +        int error = errno;
>>> +
>>> +        error_setg_errno(errp, error,
>>> +                         "vhost-blk: unable to determine size of '%s'",
>>> +                         s->conf.devpath);
>>> +        return -error;
>>> +    }
>>>   
>>> -    assert(qemu_get_current_aio_context() == qemu_get_aio_context());
>>> -    virtio_notify_config(vdev);
>>> +    changed = s->length != length;
>>> +    s->length = length;
>>> +    conf->heads = 16;
>>> +    conf->secs = 63;
>>> +    conf->cyls = s->length / BDRV_SECTOR_SIZE /
>>> +                 (conf->heads * conf->secs);
>>> +    conf->cyls = MIN(MAX(conf->cyls, 2U), 16383U);
>>> +
>>> +    return changed;
>>
>> This function should return int, but here we return bool.  And then
>> we do 'if (vhost_blk_update_size() < 0) ...', which never fires.
>>
>>>   }
>>>   
>>> -static void vhost_blk_resize(void *opaque)
>>> +static bool vhost_blk_open_backend(VHostBlk *s, Error **errp)
>>>   {
>>> -    VirtIODevice *vdev = VIRTIO_DEVICE(opaque);
>>> +    BlockConf *conf = &s->conf.conf;
>>> +    struct stat st;
>>>   
>>> -    /*
>>> -     * virtio_notify_config() needs to acquire the global mutex,
>>> -     * so it can't be called from an iothread. Instead, schedule
>>> -     * it to be run in the main context BH.
>>> -     */
>>> -    aio_bh_schedule_oneshot(qemu_get_aio_context(), vhost_blk_resize_cb, vdev);
>>> -}
>>> +    s->backend_fd = qemu_open(s->conf.devpath, O_RDWR, errp);
>>> +    if (s->backend_fd < 0) {
>>> +        error_prepend(errp, "vhost-blk: unable to open backend: ");
>>> +        return false;
>>> +    }
>>
>> Suggestion: how about also checking BLKSSZGET value of the device at this
>> point and comparing it against conf->logical_block_size?
> 
> I would personally avoid this for now. First of all BLKSSZGET (and other 
> things) may be undefined (failed this for BLKROGET btw) and proper ifdef
> decoration are not so important for 'change backend setup' patch. We can
> always add it later.
>

At least in our headers BLKROGET and BLKSSZGET are defined together.
Both conf.readonly and conf.logical_block_size are set by the user (i.e.
libvirt) and might mismatch with the actual device state.  So IMHO
they're symmetrical in this regard.

>>
>>>   
>>> -static const BlockDevOps vhost_blk_block_ops = {
>>> -    .resize_cb     = vhost_blk_resize,
>>> -};
>>> +    if (fstat(s->backend_fd, &st) < 0) {
>>> +        error_setg_errno(errp, errno, "vhost-blk: unable to stat '%s'",
>>> +                         s->conf.devpath);
>>> +        goto fail;
>>> +    }
>>> +
>>> +    if (!S_ISBLK(st.st_mode)) {
>>> +        error_setg(errp, "vhost-blk: '%s' is not a block device",
>>> +                   s->conf.devpath);
>>> +        goto fail;
>>> +    }
>>> +
>>> +    if (vhost_blk_update_size(s, errp) < 0) {
>>> +        goto fail;
>>> +    }
>>> +
>>> +    if (!conf->logical_block_size) {
>>> +        conf->logical_block_size = BDRV_SECTOR_SIZE;
>>> +    }
>>> +
>>> +    if (!conf->physical_block_size) {
>>> +        conf->physical_block_size = BDRV_SECTOR_SIZE;
>>> +    }
>>> +
>>> +    if (!blkconf_validate_blocksizes(conf, errp)) {
>>> +        goto fail;
>>> +    }
>>> +
>>> +    return true;
>>> +
>>> +fail:
>>> +    qemu_close(s->backend_fd);
>>> +    s->backend_fd = -1;
>>> +    return false;
>>> +}
>>>   
>>>   static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
>>>   {
>>> @@ -239,13 +283,8 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
>>>       VhostBlkConf *conf = &s->conf;
>>>       int i, ret;
>>>   
>>> -    if (!conf->conf.blk) {
>>> -        error_setg(errp, "vhost-blk: drive property not set");
>>> -        return;
>>> -    }
>>> -
>>> -    if (!blk_is_inserted(conf->conf.blk)) {
>>> -        error_setg(errp, "vhost-blk: device needs media, but drive is empty");
>>> +    if (!conf->devpath) {
>>> +        error_setg(errp, "vhost-blk: devpath property must be set");
>>>           return;
>>>       }
>>>   
>>> @@ -273,17 +312,7 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
>>>           return;
>>>       }
>>>   
>>> -    if (!blkconf_apply_backend_options(&conf->conf,
>>> -                                       !blk_supports_write_perm(conf->conf.blk),
>>> -                                       true, errp)) {
>>> -        return;
>>> -    }
>>> -
>>> -    if (!blkconf_geometry(&conf->conf, NULL, 65535, 255, 255, errp)) {
>>> -        return;
>>> -    }
>>> -
>>> -    if (!blkconf_blocksizes(&conf->conf, errp)) {
>>> +    if (!vhost_blk_open_backend(s, errp)) {
>>>           return;
>>>       }
>>>   
>>> @@ -311,13 +340,13 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
>>>           goto cleanup;
>>>       }
>>>   
>>> -    blk_set_dev_ops(s->conf.conf.blk, &vhost_blk_block_ops, s);
>>> -
>>>       ret = vhost_dev_init(&s->dev, (void *)((size_t)s->vhostfd),
>>>                            VHOST_BACKEND_TYPE_KERNEL, 0, NULL);
>>>       if (ret < 0) {
>>>           error_setg(errp, "vhost-blk: vhost initialization failed: %s",
>>>                   strerror(-ret));
>>> +        /* vhost_dev_init() closes vhostfd on failure */
>>> +        s->vhostfd = -1;
>>
>> Before this patch we were doing double close(vhostfd) after vhost_dev_init()
>> failure.  I'd make it a separate commit with a "Fixes:" tag.
>>
>>>           goto cleanup;
>>>       }
>>>   
>>> @@ -328,7 +357,14 @@ cleanup:
>>>               qemu_del_vm_change_state_handler(s->mighand);
>>>       }
>>>       g_free(s->dev.vqs);
>>> -    close(s->vhostfd);
>>> +    if (s->vhostfd >= 0) {
>>> +        close(s->vhostfd);
>>> +        s->vhostfd = -1;
>>> +    }
>>> +    if (s->backend_fd >= 0) {
>>> +        qemu_close(s->backend_fd);
>>> +        s->backend_fd = -1;
>>> +    }
>>>       for (i = 0; i < conf->num_queues; i++) {
>>>           virtio_del_queue(vdev, i);
>>>       }
>>> @@ -344,6 +380,10 @@ static void vhost_blk_device_unrealize(DeviceState *dev)
>>>       qemu_del_vm_change_state_handler(s->mighand);
>>>       vhost_blk_set_status(vdev, 0);
>>>       vhost_dev_cleanup(&s->dev);
>>> +    if (s->backend_fd >= 0) {
>>> +        qemu_close(s->backend_fd);
>>> +        s->backend_fd = -1;
>>> +    }
>>>       g_free(s->dev.vqs);
>>>       virtio_cleanup(vdev);
>>>   }
>>> @@ -376,10 +416,6 @@ static uint64_t vhost_blk_get_features(VirtIODevice *vdev,
>>>   
>>>       virtio_add_feature(&features, VIRTIO_F_VERSION_1);
>>>   
>>> -    if (!blk_is_writable(s->conf.conf.blk)) {
>>> -        virtio_add_feature(&features, VIRTIO_BLK_F_RO);
>>> -    }
>>> -
>>>       if (s->conf.num_queues > 1) {
>>>           virtio_add_feature(&features, VIRTIO_BLK_F_MQ);
>>>       }
>>> @@ -398,7 +434,9 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
>>>       int64_t length;
>>>       int blk_size = conf->logical_block_size;
>>>   
>>> -    blk_get_geometry(s->conf.conf.blk, &capacity);
>>> +    length = s->length;
>>> +    capacity = length / BDRV_SECTOR_SIZE;
>>> +
>>>       memset(&blkcfg, 0, sizeof(blkcfg));
>>>       virtio_stq_p(vdev, &blkcfg.capacity, capacity);
>>>       virtio_stl_p(vdev, &blkcfg.seg_max, s->conf.queue_size - 2);
>>> @@ -406,7 +444,6 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
>>>       virtio_stl_p(vdev, &blkcfg.blk_size, blk_size);
>>>       blkcfg.geometry.heads = conf->heads;
>>>   
>>> -    length = blk_getlength(s->conf.conf.blk);
>>>       if (length > 0 && length / conf->heads / conf->secs % blk_size) {
>>>           unsigned short mask;
>>>   
>>> @@ -425,7 +462,8 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
>>>   }
>>>   
>>>   static const Property vhost_blk_properties[] = {
>>> -    DEFINE_BLOCK_PROPERTIES(VHostBlk, conf.conf),
>>> +    DEFINE_BLOCK_PROPERTIES_BASE(VHostBlk, conf.conf),
>>
>> DEFINE_BLOCK_PROPERTIES_BASE() macro defines lots of properties that
>> make no sense without BlockBackend.  E.g. backend_defaults, write-cache,
>> share-rw, account-invalid, account-failed, stats-intervals.  We should
>> consider limiting the list of config properties to the ones which really
>> matter to us.  Ideally as a separate commit.
> 
>  From one point of view yes, from another point of view a lot other make 
> sense for virtio-device. I thought it was better to leave it as is and 
> use it later.
> But maybe remove it altogether (also along with logical/physical block 
> size) and better add it later as separate options if we feel tuning 
> these values brings any impact?


In general I'd just vote for limiting that list to the properties that
we're actually using and that matter to us.  Whether it's done via a new
list or via limiting existing ones is technical details.

Andrey

^ permalink raw reply	[relevance 0%]

* [QEMU HCI-8.0 PATCH 4/7] hw/display/qxl: fix TOCTOU in cursor chunk data_size handling #VSTOR-144000
    2026-09-03 20:25 10% ` [QEMU HCI-8.0 PATCH 3/7] hw/display/qxl: Fix mono cursor validation that can read past a cursor chunk #VSTOR-144000 Denis V. Lunev
@ 2026-09-03 20:25  4% ` Denis V. Lunev
  2026-09-03 20:25  9% ` [QEMU HCI-8.0 PATCH 7/7] hw/display/qxl: validate primary surface stride against width #VSTOR-144000 Denis V. Lunev
  2 siblings, 0 replies; 119+ results
From: Denis V. Lunev @ 2026-09-03 20:25 UTC (permalink / raw)
  To: svt-core; +Cc: den

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Snapshot chunk.data_size into a host-local variable before passing it to
qxl_phys2virt() for validation, and pass it through qxl_cursor() and
qxl_unpack_chunks() so that no subsequent code re-reads the field.

Without this, a racing vCPU can inflate data_size between the
qxl_phys2virt() validation and the memcpy in qxl_unpack_chunks(),
causing a source read past the validated region. In practice the read
stays within the guest's own VRAM mmap, so the impact is limited.

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3757
Reported-by: Feifan Qian <bea1e@proton.me>
Signed-off-by: Marc-Andre Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Message-ID: <20260710134352.2313675-1-marcandre.lureau@redhat.com>
Signed-off-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
(cherry picked from commit a3cc0069e151e5eb5db57bb4e86b00861d5b97ab)
---
 hw/display/qxl-render.c | 28 +++++++++++++++++-----------
 1 file changed, 17 insertions(+), 11 deletions(-)

diff --git a/hw/display/qxl-render.c b/hw/display/qxl-render.c
index 1fe63b6f5ca..cf0f6849cd0 100644
--- a/hw/display/qxl-render.c
+++ b/hw/display/qxl-render.c
@@ -217,7 +217,8 @@ void qxl_render_update_area_done(PCIQXLDevice *qxl, QXLCookie *cookie)
 }
 
 static void qxl_unpack_chunks(void *dest, size_t size, PCIQXLDevice *qxl,
-                              QXLDataChunk *chunk, uint32_t group_id)
+                              QXLDataChunk *chunk, uint32_t group_id,
+                              uint32_t chunk_data_size)
 {
     uint32_t max_chunks = 32;
     size_t offset = 0;
@@ -225,22 +226,21 @@ static void qxl_unpack_chunks(void *dest, size_t size, PCIQXLDevice *qxl,
     QXLPHYSICAL next_chunk_phys = 0;
 
     for (;;) {
-        bytes = MIN(size - offset, chunk->data_size);
+        bytes = MIN(size - offset, chunk_data_size);
         memcpy(dest + offset, chunk->data, bytes);
         offset += bytes;
         if (offset == size) {
             return;
         }
         next_chunk_phys = chunk->next_chunk;
-        /* fist time, only get the next chunk's data size */
         chunk = qxl_phys2virt(qxl, next_chunk_phys, group_id,
                               sizeof(QXLDataChunk));
         if (!chunk) {
             return;
         }
-        /* second time, check data size and get data */
+        chunk_data_size = chunk->data_size;
         chunk = qxl_phys2virt(qxl, next_chunk_phys, group_id,
-                              sizeof(QXLDataChunk) + chunk->data_size);
+                              sizeof(QXLDataChunk) + chunk_data_size);
         if (!chunk) {
             return;
         }
@@ -252,7 +252,7 @@ static void qxl_unpack_chunks(void *dest, size_t size, PCIQXLDevice *qxl,
 }
 
 static QEMUCursor *qxl_cursor(PCIQXLDevice *qxl, QXLCursor *cursor,
-                              uint32_t group_id)
+                              uint32_t group_id, uint32_t chunk_data_size)
 {
     QEMUCursor *c;
     uint8_t *and_mask, *xor_mask;
@@ -272,11 +272,11 @@ static QEMUCursor *qxl_cursor(PCIQXLDevice *qxl, QXLCursor *cursor,
     case SPICE_CURSOR_TYPE_MONO:
         /* Assume that the full cursor is available in a single chunk. */
         size = 2 * cursor_get_mono_bpl(c) * c->height;
-        if (size != cursor->data_size || cursor->chunk.data_size < size) {
+        if (size != cursor->data_size || chunk_data_size < size) {
             qxl_set_guest_bug(qxl, "%s: bad monochrome cursor %ux%u"
                               " data_size %u chunk_size %u",
                               __func__, c->width, c->height,
-                              cursor->data_size, cursor->chunk.data_size);
+                              cursor->data_size, chunk_data_size);
             goto fail;
         }
         and_mask = cursor->chunk.data;
@@ -288,7 +288,8 @@ static QEMUCursor *qxl_cursor(PCIQXLDevice *qxl, QXLCursor *cursor,
         break;
     case SPICE_CURSOR_TYPE_ALPHA:
         size = sizeof(uint32_t) * c->width * c->height;
-        qxl_unpack_chunks(c->data, size, qxl, &cursor->chunk, group_id);
+        qxl_unpack_chunks(c->data, size, qxl, &cursor->chunk, group_id,
+                          chunk_data_size);
         if (qxl->debug > 2) {
             cursor_print_ascii_art(c, "qxl/alpha");
         }
@@ -325,19 +326,23 @@ int qxl_render_cursor(PCIQXLDevice *qxl, QXLCommandExt *ext)
     }
     switch (cmd->type) {
     case QXL_CURSOR_SET:
+    {
+        uint32_t chunk_data_size;
+
         /* First read the QXLCursor to get QXLDataChunk::data_size ... */
         cursor = qxl_phys2virt(qxl, cmd->u.set.shape, ext->group_id,
                                sizeof(QXLCursor));
         if (!cursor) {
             return 1;
         }
+        chunk_data_size = cursor->chunk.data_size;
         /* Then read including the chunked data following QXLCursor. */
         cursor = qxl_phys2virt(qxl, cmd->u.set.shape, ext->group_id,
-                               sizeof(QXLCursor) + cursor->chunk.data_size);
+                               sizeof(QXLCursor) + chunk_data_size);
         if (!cursor) {
             return 1;
         }
-        c = qxl_cursor(qxl, cursor, ext->group_id);
+        c = qxl_cursor(qxl, cursor, ext->group_id, chunk_data_size);
         if (c == NULL) {
             c = cursor_builtin_left_ptr();
         }
@@ -351,6 +356,7 @@ int qxl_render_cursor(PCIQXLDevice *qxl, QXLCommandExt *ext)
         qemu_mutex_unlock(&qxl->ssd.lock);
         qemu_bh_schedule(qxl->ssd.cursor_bh);
         break;
+    }
     case QXL_CURSOR_MOVE:
         qemu_mutex_lock(&qxl->ssd.lock);
         qxl->ssd.mouse_x = cmd->u.position.x;
-- 
2.53.0


^ permalink raw reply	[relevance 4%]

* [QEMU HCI-8.0 PATCH 3/7] hw/display/qxl: Fix mono cursor validation that can read past a cursor chunk #VSTOR-144000
  @ 2026-09-03 20:25 10% ` Denis V. Lunev
  2026-09-03 20:25  4% ` [QEMU HCI-8.0 PATCH 4/7] hw/display/qxl: fix TOCTOU in cursor chunk data_size handling #VSTOR-144000 Denis V. Lunev
  2026-09-03 20:25  9% ` [QEMU HCI-8.0 PATCH 7/7] hw/display/qxl: validate primary surface stride against width #VSTOR-144000 Denis V. Lunev
  2 siblings, 0 replies; 119+ results
From: Denis V. Lunev @ 2026-09-03 20:25 UTC (permalink / raw)
  To: svt-core; +Cc: den

From: Thomas Huth <thuth@redhat.com>

qxl_render_cursor() maps the guest-provided QXLCursor object using the
guest-controlled cursor->chunk.data_size.
For a mono cursor, qxl_cursor() then validates the expected bitmap size
against cursor->data_size, but it does not validate that the first chunk
actually contains that many bytes.
A guest could set cursor->data_size to the correct full mono cursor size
while setting cursor->chunk.data_size to zero. In that case, cursor_set_mono()
reads the AND/XOR masks starting at cursor->chunk.data. If the cursor object
is placed at the end of the QXL RAM BAR, those reads cross the mapped RAM
region and could crash the QEMU process (e.g. under ASan).

Fix it by double-checking cursor->chunk.data_size for the correct size.

This patch is based on the suggested changes by the reporter in the bug
ticket.

Reported-by: huntr bubble
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3646
Signed-off-by: Thomas Huth <thuth@redhat.com>
Acked-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-ID: <20260630101022.379057-1-thuth@redhat.com>
Signed-off-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
(cherry picked from commit 0e51b71c7b7706923536c1f7923cace82877932b)
---
 hw/display/qxl-render.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/hw/display/qxl-render.c b/hw/display/qxl-render.c
index c6a9ac1da10..1fe63b6f5ca 100644
--- a/hw/display/qxl-render.c
+++ b/hw/display/qxl-render.c
@@ -272,9 +272,11 @@ static QEMUCursor *qxl_cursor(PCIQXLDevice *qxl, QXLCursor *cursor,
     case SPICE_CURSOR_TYPE_MONO:
         /* Assume that the full cursor is available in a single chunk. */
         size = 2 * cursor_get_mono_bpl(c) * c->height;
-        if (size != cursor->data_size) {
-            fprintf(stderr, "%s: bad monochrome cursor %ux%u with size %u\n",
-                    __func__, c->width, c->height, cursor->data_size);
+        if (size != cursor->data_size || cursor->chunk.data_size < size) {
+            qxl_set_guest_bug(qxl, "%s: bad monochrome cursor %ux%u"
+                              " data_size %u chunk_size %u",
+                              __func__, c->width, c->height,
+                              cursor->data_size, cursor->chunk.data_size);
             goto fail;
         }
         and_mask = cursor->chunk.data;
-- 
2.53.0


^ permalink raw reply	[relevance 10%]

* [QEMU HCI-8.0 PATCH 7/7] hw/display/qxl: validate primary surface stride against width #VSTOR-144000
    2026-09-03 20:25 10% ` [QEMU HCI-8.0 PATCH 3/7] hw/display/qxl: Fix mono cursor validation that can read past a cursor chunk #VSTOR-144000 Denis V. Lunev
  2026-09-03 20:25  4% ` [QEMU HCI-8.0 PATCH 4/7] hw/display/qxl: fix TOCTOU in cursor chunk data_size handling #VSTOR-144000 Denis V. Lunev
@ 2026-09-03 20:25  9% ` Denis V. Lunev
  2 siblings, 0 replies; 119+ results
From: Denis V. Lunev @ 2026-09-03 20:25 UTC (permalink / raw)
  To: svt-core; +Cc: den

From: Marc-André Lureau <marcandre.lureau@redhat.com>

The existing validation in qxl_create_guest_primary() checks that
abs(stride) * height fits in vgamem_size and that stride is 4-byte
aligned, but never checks that abs(stride) is large enough to hold one
row of pixels for the declared width and format.

A malicious guest can create a primary surface with a stride much
smaller than width * bytes_per_pixel (e.g. stride=4 for a 64-wide 32bpp
surface). The spice server rejects this via red_validate_surface(), but
the return is void and QEMU unconditionally proceeds to set up the local
rendering state. On the next display refresh, VNC or SDL reads width *
bytes_pp per scanline from a region backed by only stride bytes per
row, causing a host-side out-of-bounds read.

Add three checks in qxl_create_guest_primary() before creating the
surface:
 - reject unknown surface formats
 - reject zero width or height
 - reject surfaces where abs(stride) < width * bytes_per_pixel

Also fix three related issues in qxl-render.c:
 - qxl_blit() used abs_stride to advance the dst pointer into the
   DisplaySurface, but when stride is negative the DisplaySurface is a
   packed buffer whose stride may be smaller. Use surface_stride()
   instead.
 - qxl_render_update_area_unlocked() uses guest_head0_width (set via
   QXL_IO_MONITORS_CONFIG_ASYNC) without validating it against
   abs_stride, bypassing the new validation. Clamp the effective width
   to abs_stride / bytes_pp to prevent out-of-bounds access while
   tolerating the normal transient where the monitor config arrives
   before the primary surface is resized to match.
 - Similarly, guest_head0_height bypasses qxl_create_guest_primary()
   validation. Without clamping, abs_stride * height can overrun
   vgamem_size, and the product can also overflow 32 bits (e.g.
   abs_stride=16 MiB, height=256 wraps to zero), defeating the
   qxl_phys2virt() bounds check. Clamp height to
   vgamem_size / abs_stride to prevent both.

While touch it, fix some endianness issues.

Fixes: CVE-2026-16271
Fixes: a19cbfb34642 ("spice: add qxl device")
Fixes: 979f7ef8966b ("qxl: use guest_monitor_config for local renderer.")
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3637
Reported-by: huntr bubble
Signed-off-by: Marc-Andre Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
Message-ID: <20260806094028.640676-1-marcandre.lureau@redhat.com>
(cherry picked from commit ab7183ed4eecb4727532e3ffe5953d127e102c72)
---
 hw/display/qxl-render.c | 66 +++++++++++++++++++++++++----------------
 hw/display/qxl.c        | 59 ++++++++++++++++++++++++++++++++++++
 hw/display/qxl.h        |  2 ++
 3 files changed, 101 insertions(+), 26 deletions(-)

diff --git a/hw/display/qxl-render.c b/hw/display/qxl-render.c
index cf0f6849cd0..f2bc25f03ad 100644
--- a/hw/display/qxl-render.c
+++ b/hw/display/qxl-render.c
@@ -27,6 +27,7 @@
 static void qxl_blit(PCIQXLDevice *qxl, QXLRect *rect)
 {
     DisplaySurface *surface = qemu_console_surface(qxl->vga.con);
+    int dst_stride = surface_stride(surface);
     uint8_t *dst = surface_data(surface);
     uint8_t *src;
     int len, i;
@@ -45,14 +46,14 @@ static void qxl_blit(PCIQXLDevice *qxl, QXLRect *rect)
     } else {
         src += rect->top * qxl->guest_primary.abs_stride;
     }
-    dst += rect->top  * qxl->guest_primary.abs_stride;
+    dst += rect->top  * dst_stride;
     src += rect->left * qxl->guest_primary.bytes_pp;
     dst += rect->left * qxl->guest_primary.bytes_pp;
     len  = (rect->right - rect->left) * qxl->guest_primary.bytes_pp;
 
     for (i = rect->top; i < rect->bottom; i++) {
         memcpy(dst, src, len);
-        dst += qxl->guest_primary.abs_stride;
+        dst += dst_stride;
         src += qxl->guest_primary.qxl_stride;
     }
 }
@@ -61,30 +62,13 @@ void qxl_render_resize(PCIQXLDevice *qxl)
 {
     QXLSurfaceCreate *sc = &qxl->guest_primary.surface;
 
-    qxl->guest_primary.qxl_stride = sc->stride;
-    qxl->guest_primary.abs_stride = abs(sc->stride);
+    qxl->guest_primary.qxl_stride = le32_to_cpu(sc->stride);
+    qxl->guest_primary.abs_stride = abs(qxl->guest_primary.qxl_stride);
     qxl->guest_primary.resized++;
-    switch (sc->format) {
-    case SPICE_SURFACE_FMT_16_555:
-        qxl->guest_primary.bytes_pp = 2;
-        qxl->guest_primary.bits_pp = 15;
-        break;
-    case SPICE_SURFACE_FMT_16_565:
-        qxl->guest_primary.bytes_pp = 2;
-        qxl->guest_primary.bits_pp = 16;
-        break;
-    case SPICE_SURFACE_FMT_32_xRGB:
-    case SPICE_SURFACE_FMT_32_ARGB:
-        qxl->guest_primary.bytes_pp = 4;
-        qxl->guest_primary.bits_pp = 32;
-        break;
-    default:
-        fprintf(stderr, "%s: unhandled format: %x\n", __func__,
-                qxl->guest_primary.surface.format);
-        qxl->guest_primary.bytes_pp = 4;
-        qxl->guest_primary.bits_pp = 32;
-        break;
-    }
+    /* fallback to default bpp if format is unknown */
+    qxl_format_bpp(qxl, le32_to_cpu(sc->format),
+                   &qxl->guest_primary.bytes_pp,
+                   &qxl->guest_primary.bits_pp);
 }
 
 static void qxl_set_rect_to_surface(PCIQXLDevice *qxl, QXLRect *area)
@@ -101,15 +85,45 @@ static void qxl_render_update_area_unlocked(PCIQXLDevice *qxl)
     DisplaySurface *surface;
     int width = qxl->guest_head0_width ?: qxl->guest_primary.surface.width;
     int height = qxl->guest_head0_height ?: qxl->guest_primary.surface.height;
+    uint64_t map_height;
     int i;
 
+    if (width <= 0 || height <= 0) {
+        goto end;
+    }
+
+    if (qxl->guest_primary.bytes_pp > 0) {
+        int max_width = qxl->guest_primary.abs_stride
+                        / qxl->guest_primary.bytes_pp;
+        width = MIN(width, max_width);
+    }
+
+    if (qxl->guest_primary.qxl_stride < 0) {
+        /* qxl_blit() uses the primary height to find the first scanline. */
+        height = MIN(height, (int)qxl->guest_primary.surface.height);
+    }
+
+    if (qxl->guest_primary.abs_stride > 0) {
+        int max_height = qxl->vgamem_size / qxl->guest_primary.abs_stride;
+        height = MIN(height, max_height);
+    }
+
+    /*
+     * height limits the visible update, while map_height is the guest memory
+     * span validated by qxl_phys2virt().  With a negative stride qxl_blit()
+     * addresses scanlines from the declared primary height, so a shorter
+     * monitor still requires validating the full primary surface.
+     */
+    map_height = qxl->guest_primary.qxl_stride < 0 ?
+                 qxl->guest_primary.surface.height : height;
+
     if (qxl->guest_primary.resized) {
         qxl->guest_primary.resized = 0;
         qxl->guest_primary.data = qxl_phys2virt(qxl,
                                                 qxl->guest_primary.surface.mem,
                                                 MEMSLOT_GROUP_GUEST,
                                                 qxl->guest_primary.abs_stride
-                                                * height);
+                                                * map_height);
         if (!qxl->guest_primary.data) {
             goto end;
         }
diff --git a/hw/display/qxl.c b/hw/display/qxl.c
index 994bfcaa522..f0cf346c430 100644
--- a/hw/display/qxl.c
+++ b/hw/display/qxl.c
@@ -1509,6 +1509,47 @@ static void qxl_create_guest_primary_complete(PCIQXLDevice *qxl)
     qxl_render_resize(qxl);
 }
 
+/*
+ * Convert a SpiceSurfaceFormat to bytes per pixel and bits per pixel.
+ *
+ * Only valid for surface suitable for rendering.
+ */
+bool qxl_format_bpp(PCIQXLDevice *qxl, SpiceSurfaceFmt format,
+                    uint32_t *bytes_pp, uint32_t *bits_pp)
+{
+    uint32_t bypp = 4;
+    uint32_t bipp = 32;
+    bool ret = true;
+
+    switch (format) {
+    case SPICE_SURFACE_FMT_16_555:
+        bypp = 2;
+        bipp = 15;
+        break;
+    case SPICE_SURFACE_FMT_16_565:
+        bypp = 2;
+        bipp = 16;
+        break;
+    case SPICE_SURFACE_FMT_32_xRGB:
+    case SPICE_SURFACE_FMT_32_ARGB:
+        bypp = 4;
+        bipp = 32;
+        break;
+    default:
+        ret = false;
+        qxl_set_guest_bug(qxl, "%s: unhandled format: %x", __func__, format);
+    }
+
+    if (bytes_pp != NULL) {
+        *bytes_pp = bypp;
+    }
+    if (bits_pp != NULL) {
+        *bits_pp = bipp;
+    }
+
+    return ret;
+}
+
 static void qxl_create_guest_primary(PCIQXLDevice *qxl, int loadvm,
                                      qxl_async_io async)
 {
@@ -1516,6 +1557,7 @@ static void qxl_create_guest_primary(PCIQXLDevice *qxl, int loadvm,
     QXLSurfaceCreate *sc = &qxl->guest_primary.surface;
     uint32_t requested_height = le32_to_cpu(sc->height);
     int requested_stride = le32_to_cpu(sc->stride);
+    uint32_t bytes_pp;
 
     if (requested_stride == INT32_MIN ||
         abs(requested_stride) * (uint64_t)requested_height
@@ -1552,6 +1594,23 @@ static void qxl_create_guest_primary(PCIQXLDevice *qxl, int loadvm,
         return;
     }
 
+    if (!qxl_format_bpp(qxl, surface.format, &bytes_pp, NULL)) {
+        return;
+    }
+
+    if (surface.width == 0 || surface.height == 0) {
+        qxl_set_guest_bug(qxl, "%s: zero dimension %ux%u",
+                          __func__, surface.width, surface.height);
+        return;
+    }
+
+    if ((uint64_t)surface.width * bytes_pp > abs(surface.stride)) {
+        qxl_set_guest_bug(qxl, "%s: stride too small for width:"
+                          " stride %d width %u bpp %u",
+                          __func__, surface.stride, surface.width, bytes_pp);
+        return;
+    }
+
     surface.mouse_mode = true;
     surface.group_id   = MEMSLOT_GROUP_GUEST;
     if (loadvm) {
diff --git a/hw/display/qxl.h b/hw/display/qxl.h
index a25d9865453..ed5f71c0a3a 100644
--- a/hw/display/qxl.h
+++ b/hw/display/qxl.h
@@ -182,6 +182,8 @@ void qxl_spice_oom(PCIQXLDevice *qxl);
 void qxl_spice_reset_memslots(PCIQXLDevice *qxl);
 void qxl_spice_reset_image_cache(PCIQXLDevice *qxl);
 void qxl_spice_reset_cursor(PCIQXLDevice *qxl);
+bool qxl_format_bpp(PCIQXLDevice *qxl, SpiceSurfaceFmt format,
+                    uint32_t *bytes_pp, uint32_t *bits_pp);
 
 /* qxl-logger.c */
 int qxl_log_cmd_cursor(PCIQXLDevice *qxl, QXLCursorCmd *cmd, int group_id);
-- 
2.53.0


^ permalink raw reply	[relevance 9%]

* [QEMU HCI-8.0 PATCH v2 5/5] vhost-blk: preserve the uevent socket across cpr-exec
                     ` (3 preceding siblings ...)
  2026-09-04 13:21 23% ` [QEMU HCI-8.0 PATCH v2 4/5] vhost-blk: watch the device for resize events Andrey Zhadchenko
@ 2026-09-04 13:21 32% ` Andrey Zhadchenko
  2026-09-04 15:33  0%   ` Andrey Drobyshev
  4 siblings, 1 reply; 119+ results
From: Andrey Zhadchenko @ 2026-09-04 13:21 UTC (permalink / raw)
  To: svt-core; +Cc: den, andrey.drobyshev

qemu-update uses cpr-exec migration: QEMU re-execs itself in place.
A plain monitor fdset descriptor like our uevent socket does not
survive that (unlike the cpr_save_fd()'d tap/vhost fds), so without
help the resized-device notifications would silently stop until the
next full VM start.

Preserve it the same way the net backends do: on cold boot
cpr_save_fd() the socket under the device's canonical path, and on
the re-exec'd (incoming) side adopt it with cpr_find_fd() instead of
reopening a command line fd that no longer resolves. The management
layer passes a placeholder for the "ueventfd" property in that case.
Drop the saved descriptor on teardown so it does not outlive the
device.

https://virtuozzo.atlassian.net/browse/VSTOR-143437
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
---
 hw/block/vhost-blk.c | 39 ++++++++++++++++++++++++++++++---------
 1 file changed, 30 insertions(+), 9 deletions(-)

diff --git a/hw/block/vhost-blk.c b/hw/block/vhost-blk.c
index 7cb842a859..eca12356ae 100644
--- a/hw/block/vhost-blk.c
+++ b/hw/block/vhost-blk.c
@@ -28,6 +28,7 @@
 #include <sys/ioctl.h>
 #include <linux/fs.h>
 #include <linux/netlink.h>
+#include "migration/cpr.h"
 #include "system/runstate.h"
 
 static int vhost_blk_start(VirtIODevice *vdev)
@@ -364,20 +365,36 @@ static bool vhost_blk_uevent_check(int fd, const char *src, Error **errp)
 
 static bool vhost_blk_uevent_attach(VHostBlk *s, Error **errp)
 {
+    g_autofree char *cpr_name = NULL;
+
     if (!s->conf.ueventfd) {
         return true;
     }
 
-    s->uevent_fd = qemu_open(s->conf.ueventfd, O_RDWR, errp);
-    if (s->uevent_fd < 0) {
-        error_prepend(errp, "vhost-blk: unable to open uevent socket: ");
-        return false;
-    }
+    cpr_name = object_get_canonical_path(OBJECT(s));
 
-    if (!vhost_blk_uevent_check(s->uevent_fd, s->conf.ueventfd, errp)) {
-        qemu_close(s->uevent_fd);
-        s->uevent_fd = -1;
-        return false;
+    if (cpr_is_incoming()) {
+        s->uevent_fd = cpr_find_fd(cpr_name, 0);
+        if (s->uevent_fd < 0) {
+            error_setg(errp,
+                       "vhost-blk: no preserved uevent socket to restore");
+            return false;
+        }
+    } else {
+        s->uevent_fd = qemu_open(s->conf.ueventfd, O_RDWR, errp);
+        if (s->uevent_fd < 0) {
+            error_prepend(errp, "vhost-blk: unable to open uevent socket: ");
+            return false;
+        }
+
+        if (!vhost_blk_uevent_check(s->uevent_fd, s->conf.ueventfd, errp)) {
+            qemu_close(s->uevent_fd);
+            s->uevent_fd = -1;
+            return false;
+        }
+
+        /* Preserve the socket across a future cpr-exec qemu-update. */
+        cpr_save_fd(cpr_name, 0, s->uevent_fd);
     }
 
     s->resize_bh = qemu_bh_new(vhost_blk_resize_bh, s);
@@ -387,10 +404,14 @@ static bool vhost_blk_uevent_attach(VHostBlk *s, Error **errp)
 
 static void vhost_blk_uevent_detach(VHostBlk *s)
 {
+    g_autofree char *cpr_name = NULL;
+
     if (s->uevent_fd < 0) {
         return;
     }
 
+    cpr_name = object_get_canonical_path(OBJECT(s));
+    cpr_delete_fd(cpr_name, 0);
     qemu_set_fd_handler(s->uevent_fd, NULL, NULL, NULL);
     qemu_bh_delete(s->resize_bh);
     s->resize_bh = NULL;
-- 
2.43.5


^ permalink raw reply	[relevance 32%]

* [QEMU HCI-8.0 PATCH v2 2/5] vhost-blk: change backend setup
    2026-09-04 13:21 21% ` [QEMU HCI-8.0 PATCH v2 1/5] vhost-blk: do not double close vhostfd Andrey Zhadchenko
@ 2026-09-04 13:21 28% ` Andrey Zhadchenko
  2026-09-04 15:33  0%   ` Andrey Drobyshev
  2026-09-04 13:21 31% ` [QEMU HCI-8.0 PATCH v2 3/5] vhost-blk: add read-only flag Andrey Zhadchenko
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 119+ results
From: Andrey Zhadchenko @ 2026-09-04 13:21 UTC (permalink / raw)
  To: svt-core; +Cc: den, andrey.drobyshev

Previously we used very ugly and incapsulation-breaking assignment
fd = blk_bs(s->conf.conf.blk)->file->bs->opaque;
It is wrong in a many ways, so let's rework this.

Patch changes default `drive` to new `devpath` option so device fd
is managed by vhost-blk itself. Unfortunately this way we need a
bit more preparational work: finding out disk length, etc. Don't
be too broad and just do the minimal work. Drop the generic block
device properties along with the block node: the kernel module
does all IO in terms of 512 sectors, so simply report 512 byte
logical/physical block size to the guest.
Also we lose resize, as this is tied to the block node, which is
now have no place in the setup. We will add this in the next
patches as well as RO mode.

https://virtuozzo.atlassian.net/browse/VSTOR-143437
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
---
v2:
 - moved double close fix to  'do not double close vhostfd' patch
 - dropped DEFINE_BLOCK_PROPERTIES_BASE exposition to the outside. Let's
just use defaults for now. We can later check if tweaking this values
makes any difference and add them back.
 - change vhost_blk_update_size() to return zero or error and move 'changed'
to a separate argument
 - changed lseek to BLKGETSIZE64 (we already use BLKROGET anyway)

 hw/block/vhost-blk.c          | 126 ++++++++++++++++++++--------------
 include/hw/virtio/vhost-blk.h |   5 +-
 2 files changed, 78 insertions(+), 53 deletions(-)

diff --git a/hw/block/vhost-blk.c b/hw/block/vhost-blk.c
index 9bd49fef2d..24f4fbe2b6 100644
--- a/hw/block/vhost-blk.c
+++ b/hw/block/vhost-blk.c
@@ -24,23 +24,17 @@
 #include "system/system.h"
 #include "linux-headers/linux/vhost.h"
 #include <sys/ioctl.h>
-#include <linux/fs.h>
-#include "include/block/block_int-common.h"
 #include "system/runstate.h"
 
 static int vhost_blk_start(VirtIODevice *vdev)
 {
     VHostBlk *s = VHOST_BLK(vdev);
     struct vhost_vring_file backend;
-    int ret, i, nworkers, *fd;
+    int ret, i, nworkers;
     BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
     VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
     char serial[VIRTIO_BLK_ID_BYTES] = {0};
 
-    bdrv_graph_rdlock_main_loop();
-    fd = blk_bs(s->conf.conf.blk)->file->bs->opaque;
-    bdrv_graph_rdunlock_main_loop();
-
     if (!k->set_guest_notifiers) {
         error_report("vhost-blk: binding does not support guest notifiers");
         return -ENOSYS;
@@ -92,7 +86,7 @@ static int vhost_blk_start(VirtIODevice *vdev)
 
     memset(&backend, 0, sizeof(backend));
     backend.index = 0;
-    backend.fd = *fd;
+    backend.fd = s->backend_fd;
     if (ioctl(s->vhostfd, VHOST_BLK_SET_BACKEND, &backend)) {
         error_report("vhost-blk: unable to set backend");
         ret = -errno;
@@ -208,29 +202,69 @@ static void vhost_blk_vm_state(void *opaque, bool running, RunState state)
     }
 }
 
-static void vhost_blk_resize_cb(void *opaque)
+static int vhost_blk_update_size(VHostBlk *s, bool *changed, Error **errp)
 {
-    VirtIODevice *vdev = opaque;
+    BlockConf *conf = &s->conf.conf;
+    uint64_t length;
+
+    if (ioctl(s->backend_fd, BLKGETSIZE64, &length) < 0) {
+        int error = errno;
+
+        error_setg_errno(errp, error,
+                         "vhost-blk: unable to determine size of '%s'",
+                         s->conf.devpath);
+        return -error;
+    }
+
+    *changed = s->length != length;
+    s->length = length;
+    conf->heads = 16;
+    conf->secs = 63;
+    conf->cyls = s->length / BDRV_SECTOR_SIZE /
+                 (conf->heads * conf->secs);
+    conf->cyls = MIN(MAX(conf->cyls, 2U), 16383U);
 
-    assert(qemu_get_current_aio_context() == qemu_get_aio_context());
-    virtio_notify_config(vdev);
+    return 0;
 }
 
-static void vhost_blk_resize(void *opaque)
+static bool vhost_blk_open_backend(VHostBlk *s, Error **errp)
 {
-    VirtIODevice *vdev = VIRTIO_DEVICE(opaque);
+    BlockConf *conf = &s->conf.conf;
+    struct stat st;
+    bool changed;
 
-    /*
-     * virtio_notify_config() needs to acquire the global mutex,
-     * so it can't be called from an iothread. Instead, schedule
-     * it to be run in the main context BH.
-     */
-    aio_bh_schedule_oneshot(qemu_get_aio_context(), vhost_blk_resize_cb, vdev);
-}
+    s->backend_fd = qemu_open(s->conf.devpath, O_RDWR, errp);
+    if (s->backend_fd < 0) {
+        error_prepend(errp, "vhost-blk: unable to open backend: ");
+        return false;
+    }
 
-static const BlockDevOps vhost_blk_block_ops = {
-    .resize_cb     = vhost_blk_resize,
-};
+    if (fstat(s->backend_fd, &st) < 0) {
+        error_setg_errno(errp, errno, "vhost-blk: unable to stat '%s'",
+                         s->conf.devpath);
+        goto fail;
+    }
+
+    if (!S_ISBLK(st.st_mode)) {
+        error_setg(errp, "vhost-blk: '%s' is not a block device",
+                   s->conf.devpath);
+        goto fail;
+    }
+
+    if (vhost_blk_update_size(s, &changed, errp) < 0) {
+        goto fail;
+    }
+
+    conf->logical_block_size = BDRV_SECTOR_SIZE;
+    conf->physical_block_size = BDRV_SECTOR_SIZE;
+
+    return true;
+
+fail:
+    qemu_close(s->backend_fd);
+    s->backend_fd = -1;
+    return false;
+}
 
 static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
 {
@@ -239,13 +273,8 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
     VhostBlkConf *conf = &s->conf;
     int i, ret;
 
-    if (!conf->conf.blk) {
-        error_setg(errp, "vhost-blk: drive property not set");
-        return;
-    }
-
-    if (!blk_is_inserted(conf->conf.blk)) {
-        error_setg(errp, "vhost-blk: device needs media, but drive is empty");
+    if (!conf->devpath) {
+        error_setg(errp, "vhost-blk: devpath property must be set");
         return;
     }
 
@@ -273,17 +302,7 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
         return;
     }
 
-    if (!blkconf_apply_backend_options(&conf->conf,
-                                       !blk_supports_write_perm(conf->conf.blk),
-                                       true, errp)) {
-        return;
-    }
-
-    if (!blkconf_geometry(&conf->conf, NULL, 65535, 255, 255, errp)) {
-        return;
-    }
-
-    if (!blkconf_blocksizes(&conf->conf, errp)) {
+    if (!vhost_blk_open_backend(s, errp)) {
         return;
     }
 
@@ -311,8 +330,6 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
         goto cleanup;
     }
 
-    blk_set_dev_ops(s->conf.conf.blk, &vhost_blk_block_ops, s);
-
     ret = vhost_dev_init(&s->dev, (void *)((size_t)s->vhostfd),
                          VHOST_BACKEND_TYPE_KERNEL, 0, NULL);
     if (ret < 0) {
@@ -334,6 +351,10 @@ cleanup:
         close(s->vhostfd);
         s->vhostfd = -1;
     }
+    if (s->backend_fd >= 0) {
+        qemu_close(s->backend_fd);
+        s->backend_fd = -1;
+    }
     for (i = 0; i < conf->num_queues; i++) {
         virtio_del_queue(vdev, i);
     }
@@ -349,6 +370,10 @@ static void vhost_blk_device_unrealize(DeviceState *dev)
     qemu_del_vm_change_state_handler(s->mighand);
     vhost_blk_set_status(vdev, 0);
     vhost_dev_cleanup(&s->dev);
+    if (s->backend_fd >= 0) {
+        qemu_close(s->backend_fd);
+        s->backend_fd = -1;
+    }
     g_free(s->dev.vqs);
     virtio_cleanup(vdev);
 }
@@ -381,10 +406,6 @@ static uint64_t vhost_blk_get_features(VirtIODevice *vdev,
 
     virtio_add_feature(&features, VIRTIO_F_VERSION_1);
 
-    if (!blk_is_writable(s->conf.conf.blk)) {
-        virtio_add_feature(&features, VIRTIO_BLK_F_RO);
-    }
-
     if (s->conf.num_queues > 1) {
         virtio_add_feature(&features, VIRTIO_BLK_F_MQ);
     }
@@ -403,7 +424,9 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
     int64_t length;
     int blk_size = conf->logical_block_size;
 
-    blk_get_geometry(s->conf.conf.blk, &capacity);
+    length = s->length;
+    capacity = length / BDRV_SECTOR_SIZE;
+
     memset(&blkcfg, 0, sizeof(blkcfg));
     virtio_stq_p(vdev, &blkcfg.capacity, capacity);
     virtio_stl_p(vdev, &blkcfg.seg_max, s->conf.queue_size - 2);
@@ -411,7 +434,6 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
     virtio_stl_p(vdev, &blkcfg.blk_size, blk_size);
     blkcfg.geometry.heads = conf->heads;
 
-    length = blk_getlength(s->conf.conf.blk);
     if (length > 0 && length / conf->heads / conf->secs % blk_size) {
         unsigned short mask;
 
@@ -430,7 +452,7 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
 }
 
 static const Property vhost_blk_properties[] = {
-    DEFINE_BLOCK_PROPERTIES(VHostBlk, conf.conf),
+    DEFINE_PROP_STRING("devpath", VHostBlk, conf.devpath),
     DEFINE_PROP_UINT16("num-queues", VHostBlk, conf.num_queues,
                        VHOST_BLK_AUTO_NUM_QUEUES),
     DEFINE_PROP_UINT16("queue-size", VHostBlk, conf.queue_size, 256),
@@ -475,6 +497,8 @@ static void vhost_blk_instance_init(Object *obj)
 {
     VHostBlk *s = VHOST_BLK(obj);
 
+    s->vhostfd = -1;
+    s->backend_fd = -1;
     device_add_bootindex_property(obj, &s->conf.conf.bootindex,
                                   "bootindex", "/disk@0,0",
                                   DEVICE(obj));
diff --git a/include/hw/virtio/vhost-blk.h b/include/hw/virtio/vhost-blk.h
index 0c7e212595..c194b421d9 100644
--- a/include/hw/virtio/vhost-blk.h
+++ b/include/hw/virtio/vhost-blk.h
@@ -14,7 +14,6 @@
 #include "standard-headers/linux/virtio_blk.h"
 #include "hw/block/block.h"
 #include "hw/virtio/vhost.h"
-#include "system/block-backend.h"
 
 #define TYPE_VHOST_BLK "vhost-blk"
 #define VHOST_BLK(obj) \
@@ -25,6 +24,7 @@
 
 typedef struct VhostBlkConf {
     BlockConf conf;
+    char *devpath;
     uint16_t num_queues;
     uint16_t queue_size;
     uint16_t num_threads;
@@ -37,10 +37,11 @@ typedef struct VHostBlk {
     VMChangeStateEntry *mighand;
     uint64_t host_features;
     uint64_t decided_features;
-    struct virtio_blk_config blkcfg;
     int vhostfd;
+    int backend_fd;
     struct vhost_dev dev;
     bool vhost_started;
+    uint64_t length;
 } VHostBlk;
 
 #endif
-- 
2.43.5


^ permalink raw reply	[relevance 28%]

* [QEMU HCI-8.0 PATCH v2 1/5] vhost-blk: do not double close vhostfd
  @ 2026-09-04 13:21 21% ` Andrey Zhadchenko
  2026-09-04 15:33  8%   ` Andrey Drobyshev
  2026-09-04 13:21 28% ` [QEMU HCI-8.0 PATCH v2 2/5] vhost-blk: change backend setup Andrey Zhadchenko
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 119+ results
From: Andrey Zhadchenko @ 2026-09-04 13:21 UTC (permalink / raw)
  To: svt-core; +Cc: den, andrey.drobyshev

vhost_dev_init() closes the backend fd on failure, so the cleanup
path of vhost_blk_device_realize() closed vhostfd for the second
time. The cleanup path is also reachable with vhostfd never opened,
in which case we called close(-1).

Fixes: 0ff3cb5ff2 ("block: add vhost-blk backend")
https://virtuozzo.atlassian.net/browse/VSTOR-143437
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
---
 hw/block/vhost-blk.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/hw/block/vhost-blk.c b/hw/block/vhost-blk.c
index c52851fcf8..9bd49fef2d 100644
--- a/hw/block/vhost-blk.c
+++ b/hw/block/vhost-blk.c
@@ -318,6 +318,8 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
     if (ret < 0) {
         error_setg(errp, "vhost-blk: vhost initialization failed: %s",
                 strerror(-ret));
+        /* vhost_dev_init() closes vhostfd on failure */
+        s->vhostfd = -1;
         goto cleanup;
     }
 
@@ -328,7 +330,10 @@ cleanup:
             qemu_del_vm_change_state_handler(s->mighand);
     }
     g_free(s->dev.vqs);
-    close(s->vhostfd);
+    if (s->vhostfd >= 0) {
+        close(s->vhostfd);
+        s->vhostfd = -1;
+    }
     for (i = 0; i < conf->num_queues; i++) {
         virtio_del_queue(vdev, i);
     }
-- 
2.43.5


^ permalink raw reply	[relevance 21%]

* [QEMU HCI-8.0 PATCH v2 3/5] vhost-blk: add read-only flag
    2026-09-04 13:21 21% ` [QEMU HCI-8.0 PATCH v2 1/5] vhost-blk: do not double close vhostfd Andrey Zhadchenko
  2026-09-04 13:21 28% ` [QEMU HCI-8.0 PATCH v2 2/5] vhost-blk: change backend setup Andrey Zhadchenko
@ 2026-09-04 13:21 31% ` Andrey Zhadchenko
  2026-09-04 15:33  0%   ` Andrey Drobyshev
  2026-09-04 13:21 23% ` [QEMU HCI-8.0 PATCH v2 4/5] vhost-blk: watch the device for resize events Andrey Zhadchenko
  2026-09-04 13:21 32% ` [QEMU HCI-8.0 PATCH v2 5/5] vhost-blk: preserve the uevent socket across cpr-exec Andrey Zhadchenko
  4 siblings, 1 reply; 119+ results
From: Andrey Zhadchenko @ 2026-09-04 13:21 UTC (permalink / raw)
  To: svt-core; +Cc: den, andrey.drobyshev

and set RO respectively. Also compare BLKROGET with the selected
mode and reject r/w if needed.

https://virtuozzo.atlassian.net/browse/VSTOR-143437
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
---
v2:
 - check BLKROGET only if rw was requested like posix_file

 hw/block/vhost-blk.c          | 26 +++++++++++++++++++++++++-
 include/hw/virtio/vhost-blk.h |  1 +
 2 files changed, 26 insertions(+), 1 deletion(-)

diff --git a/hw/block/vhost-blk.c b/hw/block/vhost-blk.c
index 24f4fbe2b6..04a7013e52 100644
--- a/hw/block/vhost-blk.c
+++ b/hw/block/vhost-blk.c
@@ -24,6 +24,7 @@
 #include "system/system.h"
 #include "linux-headers/linux/vhost.h"
 #include <sys/ioctl.h>
+#include <linux/fs.h>
 #include "system/runstate.h"
 
 static int vhost_blk_start(VirtIODevice *vdev)
@@ -232,8 +233,9 @@ static bool vhost_blk_open_backend(VHostBlk *s, Error **errp)
     BlockConf *conf = &s->conf.conf;
     struct stat st;
     bool changed;
+    int open_flags = s->conf.readonly ? O_RDONLY : O_RDWR;
 
-    s->backend_fd = qemu_open(s->conf.devpath, O_RDWR, errp);
+    s->backend_fd = qemu_open(s->conf.devpath, open_flags, errp);
     if (s->backend_fd < 0) {
         error_prepend(errp, "vhost-blk: unable to open backend: ");
         return false;
@@ -251,6 +253,23 @@ static bool vhost_blk_open_backend(VHostBlk *s, Error **errp)
         goto fail;
     }
 
+    if (!s->conf.readonly) {
+        int readonly;
+
+        if (ioctl(s->backend_fd, BLKROGET, &readonly) < 0) {
+            error_setg_errno(errp, errno,
+                             "vhost-blk: unable to get read-only status of "
+                             "'%s'", s->conf.devpath);
+            goto fail;
+        }
+
+        if (readonly) {
+            error_setg_errno(errp, EROFS, "vhost-blk: '%s' is not writable",
+                             s->conf.devpath);
+            goto fail;
+        }
+    }
+
     if (vhost_blk_update_size(s, &changed, errp) < 0) {
         goto fail;
     }
@@ -406,6 +425,10 @@ static uint64_t vhost_blk_get_features(VirtIODevice *vdev,
 
     virtio_add_feature(&features, VIRTIO_F_VERSION_1);
 
+    if (s->conf.readonly) {
+        virtio_add_feature(&features, VIRTIO_BLK_F_RO);
+    }
+
     if (s->conf.num_queues > 1) {
         virtio_add_feature(&features, VIRTIO_BLK_F_MQ);
     }
@@ -453,6 +476,7 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
 
 static const Property vhost_blk_properties[] = {
     DEFINE_PROP_STRING("devpath", VHostBlk, conf.devpath),
+    DEFINE_PROP_BOOL("read-only", VHostBlk, conf.readonly, false),
     DEFINE_PROP_UINT16("num-queues", VHostBlk, conf.num_queues,
                        VHOST_BLK_AUTO_NUM_QUEUES),
     DEFINE_PROP_UINT16("queue-size", VHostBlk, conf.queue_size, 256),
diff --git a/include/hw/virtio/vhost-blk.h b/include/hw/virtio/vhost-blk.h
index c194b421d9..c6646f5845 100644
--- a/include/hw/virtio/vhost-blk.h
+++ b/include/hw/virtio/vhost-blk.h
@@ -25,6 +25,7 @@
 typedef struct VhostBlkConf {
     BlockConf conf;
     char *devpath;
+    bool readonly;
     uint16_t num_queues;
     uint16_t queue_size;
     uint16_t num_threads;
-- 
2.43.5


^ permalink raw reply	[relevance 31%]

* [QEMU HCI-8.0 PATCH v2 4/5] vhost-blk: watch the device for resize events
                     ` (2 preceding siblings ...)
  2026-09-04 13:21 31% ` [QEMU HCI-8.0 PATCH v2 3/5] vhost-blk: add read-only flag Andrey Zhadchenko
@ 2026-09-04 13:21 23% ` Andrey Zhadchenko
  2026-09-04 15:33  7%   ` Andrey Drobyshev
  2026-09-04 13:21 32% ` [QEMU HCI-8.0 PATCH v2 5/5] vhost-blk: preserve the uevent socket across cpr-exec Andrey Zhadchenko
  4 siblings, 1 reply; 119+ results
From: Andrey Zhadchenko @ 2026-09-04 13:21 UTC (permalink / raw)
  To: svt-core; +Cc: den, andrey.drobyshev

Resize was tied to block node, which we removed a few patches ago.
Luckily we can make resize automated: receive an uevent socket
from the management layer via the new "ueventfd" property (e.g. a
/dev/fdset/N path), watch it for relevant netlink messages and
call virtio_notify_config() if we detect a capacity change.

The socket is set up (possibly with filter) by management layer.
We only need to check that it is netlink and do some message
filtering.

When the property is not set, capacity changes are not detected.

https://virtuozzo.atlassian.net/browse/VSTOR-143437
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
---
v2:
 - rework patch a bit: now we expect opened and filtered fd from
libvirt.

 hw/block/vhost-blk.c          | 181 ++++++++++++++++++++++++++++++++++
 include/hw/virtio/vhost-blk.h |   4 +
 2 files changed, 185 insertions(+)

diff --git a/hw/block/vhost-blk.c b/hw/block/vhost-blk.c
index 04a7013e52..7cb842a859 100644
--- a/hw/block/vhost-blk.c
+++ b/hw/block/vhost-blk.c
@@ -10,7 +10,9 @@
 
 #include "qemu/osdep.h"
 #include "qapi/error.h"
+#include "qemu/cutils.h"
 #include "qemu/error-report.h"
+#include "qemu/main-loop.h"
 #include "qom/object.h"
 #include "hw/qdev-core.h"
 #include "hw/boards.h"
@@ -25,6 +27,7 @@
 #include "linux-headers/linux/vhost.h"
 #include <sys/ioctl.h>
 #include <linux/fs.h>
+#include <linux/netlink.h>
 #include "system/runstate.h"
 
 static int vhost_blk_start(VirtIODevice *vdev)
@@ -228,6 +231,173 @@ static int vhost_blk_update_size(VHostBlk *s, bool *changed, Error **errp)
     return 0;
 }
 
+static void vhost_blk_resize_bh(void *opaque)
+{
+    VHostBlk *s = opaque;
+    Error *local_err = NULL;
+    bool changed;
+
+    if (vhost_blk_update_size(s, &changed, &local_err) < 0) {
+        error_report_err(local_err);
+        return;
+    }
+
+    if (changed) {
+        virtio_notify_config(VIRTIO_DEVICE(s));
+    }
+}
+
+/*
+ * The uevent socket is created, bound and filtered by the management
+ * layer and passed to us via the "ueventfd" property.
+ */
+static void vhost_blk_uevent_read(void *opaque)
+{
+    VHostBlk *s = opaque;
+    char buffer[64 * 1024 + 1];
+
+    for (;;) {
+        struct sockaddr_nl source;
+        socklen_t source_len = sizeof(source);
+        uint64_t event_major = UINT64_MAX;
+        uint64_t event_minor = UINT64_MAX;
+        bool action_change = false;
+        bool subsystem_block = false;
+        bool resize = false;
+        char *field;
+        char *end;
+        ssize_t len;
+
+        memset(&source, 0, sizeof(source));
+        len = recvfrom(s->uevent_fd, buffer, sizeof(buffer) - 1,
+                       MSG_DONTWAIT, (struct sockaddr *)&source, &source_len);
+        if (len < 0) {
+            if (errno == EINTR) {
+                continue;
+            }
+            if (errno == ENOBUFS) {
+                /* Some events may be dropped, just re-check */
+                qemu_bh_schedule(s->resize_bh);
+                continue;
+            }
+            if (errno != EAGAIN && errno != EWOULDBLOCK) {
+                error_report("vhost-blk: unable to receive uevent: %s",
+                             strerror(errno));
+            }
+            return;
+        }
+
+        if (source.nl_family != AF_NETLINK || source.nl_pid != 0) {
+            continue;
+        }
+
+        buffer[len] = '\0';
+        field = buffer;
+        end = buffer + len;
+        while (field < end) {
+            size_t field_len = strnlen(field, end - field);
+
+            if (!strcmp(field, "ACTION=change")) {
+                action_change = true;
+            } else if (!strcmp(field, "SUBSYSTEM=block")) {
+                subsystem_block = true;
+            } else if (!strcmp(field, "RESIZE=1")) {
+                resize = true;
+            } else if (g_str_has_prefix(field, "MAJOR=")) {
+                uint64_t value;
+
+                if (!qemu_strtou64(field + strlen("MAJOR="), NULL, 10,
+                                   &value)) {
+                    event_major = value;
+                }
+            } else if (g_str_has_prefix(field, "MINOR=")) {
+                uint64_t value;
+
+                if (!qemu_strtou64(field + strlen("MINOR="), NULL, 10,
+                                   &value)) {
+                    event_minor = value;
+                }
+            }
+
+            if (field_len == end - field) {
+                break;
+            }
+            field += field_len + 1;
+        }
+
+        if (action_change && subsystem_block && resize &&
+            event_major == major(s->backend_rdev) &&
+            event_minor == minor(s->backend_rdev)) {
+            qemu_bh_schedule(s->resize_bh);
+        }
+    }
+}
+
+static bool vhost_blk_uevent_check(int fd, const char *src, Error **errp)
+{
+    socklen_t optlen;
+    int domain;
+    int protocol;
+
+    optlen = sizeof(domain);
+    if (getsockopt(fd, SOL_SOCKET, SO_DOMAIN, &domain, &optlen) < 0) {
+        error_setg_errno(errp, errno, "vhost-blk: '%s' is not a socket", src);
+        return false;
+    }
+
+    optlen = sizeof(protocol);
+    if (getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, &protocol, &optlen) < 0) {
+        error_setg_errno(errp, errno,
+                         "vhost-blk: unable to get protocol of '%s'", src);
+        return false;
+    }
+
+    if (domain != AF_NETLINK || protocol != NETLINK_KOBJECT_UEVENT) {
+        error_setg(errp,
+                   "vhost-blk: '%s' is not a NETLINK_KOBJECT_UEVENT socket",
+                   src);
+        return false;
+    }
+
+    return true;
+}
+
+static bool vhost_blk_uevent_attach(VHostBlk *s, Error **errp)
+{
+    if (!s->conf.ueventfd) {
+        return true;
+    }
+
+    s->uevent_fd = qemu_open(s->conf.ueventfd, O_RDWR, errp);
+    if (s->uevent_fd < 0) {
+        error_prepend(errp, "vhost-blk: unable to open uevent socket: ");
+        return false;
+    }
+
+    if (!vhost_blk_uevent_check(s->uevent_fd, s->conf.ueventfd, errp)) {
+        qemu_close(s->uevent_fd);
+        s->uevent_fd = -1;
+        return false;
+    }
+
+    s->resize_bh = qemu_bh_new(vhost_blk_resize_bh, s);
+    qemu_set_fd_handler(s->uevent_fd, vhost_blk_uevent_read, NULL, s);
+    return true;
+}
+
+static void vhost_blk_uevent_detach(VHostBlk *s)
+{
+    if (s->uevent_fd < 0) {
+        return;
+    }
+
+    qemu_set_fd_handler(s->uevent_fd, NULL, NULL, NULL);
+    qemu_bh_delete(s->resize_bh);
+    s->resize_bh = NULL;
+    qemu_close(s->uevent_fd);
+    s->uevent_fd = -1;
+}
+
 static bool vhost_blk_open_backend(VHostBlk *s, Error **errp)
 {
     BlockConf *conf = &s->conf.conf;
@@ -252,6 +422,7 @@ static bool vhost_blk_open_backend(VHostBlk *s, Error **errp)
                    s->conf.devpath);
         goto fail;
     }
+    s->backend_rdev = st.st_rdev;
 
     if (!s->conf.readonly) {
         int readonly;
@@ -325,6 +496,12 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
         return;
     }
 
+    if (!vhost_blk_uevent_attach(s, errp)) {
+        qemu_close(s->backend_fd);
+        s->backend_fd = -1;
+        return;
+    }
+
     s->dev.nvqs = conf->num_queues;
     s->dev.max_queues = conf->num_queues;
     s->dev.vqs = g_new0(struct vhost_virtqueue, s->dev.nvqs);
@@ -374,6 +551,7 @@ cleanup:
         qemu_close(s->backend_fd);
         s->backend_fd = -1;
     }
+    vhost_blk_uevent_detach(s);
     for (i = 0; i < conf->num_queues; i++) {
         virtio_del_queue(vdev, i);
     }
@@ -386,6 +564,7 @@ static void vhost_blk_device_unrealize(DeviceState *dev)
     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
     VHostBlk *s = VHOST_BLK(dev);
 
+    vhost_blk_uevent_detach(s);
     qemu_del_vm_change_state_handler(s->mighand);
     vhost_blk_set_status(vdev, 0);
     vhost_dev_cleanup(&s->dev);
@@ -476,6 +655,7 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
 
 static const Property vhost_blk_properties[] = {
     DEFINE_PROP_STRING("devpath", VHostBlk, conf.devpath),
+    DEFINE_PROP_STRING("ueventfd", VHostBlk, conf.ueventfd),
     DEFINE_PROP_BOOL("read-only", VHostBlk, conf.readonly, false),
     DEFINE_PROP_UINT16("num-queues", VHostBlk, conf.num_queues,
                        VHOST_BLK_AUTO_NUM_QUEUES),
@@ -523,6 +703,7 @@ static void vhost_blk_instance_init(Object *obj)
 
     s->vhostfd = -1;
     s->backend_fd = -1;
+    s->uevent_fd = -1;
     device_add_bootindex_property(obj, &s->conf.conf.bootindex,
                                   "bootindex", "/disk@0,0",
                                   DEVICE(obj));
diff --git a/include/hw/virtio/vhost-blk.h b/include/hw/virtio/vhost-blk.h
index c6646f5845..e3ad92bc6e 100644
--- a/include/hw/virtio/vhost-blk.h
+++ b/include/hw/virtio/vhost-blk.h
@@ -25,6 +25,7 @@
 typedef struct VhostBlkConf {
     BlockConf conf;
     char *devpath;
+    char *ueventfd;
     bool readonly;
     uint16_t num_queues;
     uint16_t queue_size;
@@ -40,9 +41,12 @@ typedef struct VHostBlk {
     uint64_t decided_features;
     int vhostfd;
     int backend_fd;
+    int uevent_fd;
     struct vhost_dev dev;
     bool vhost_started;
     uint64_t length;
+    uint64_t backend_rdev;
+    QEMUBH *resize_bh;
 } VHostBlk;
 
 #endif
-- 
2.43.5


^ permalink raw reply	[relevance 23%]

* Re: [QEMU HCI-8.0 PATCH v2 1/5] vhost-blk: do not double close vhostfd
  2026-09-04 13:21 21% ` [QEMU HCI-8.0 PATCH v2 1/5] vhost-blk: do not double close vhostfd Andrey Zhadchenko
@ 2026-09-04 15:33  8%   ` Andrey Drobyshev
  0 siblings, 0 replies; 119+ results
From: Andrey Drobyshev @ 2026-09-04 15:33 UTC (permalink / raw)
  To: Andrey Zhadchenko; +Cc: svt-core, den, andrey.drobyshev

> vhost_dev_init() closes the backend fd on failure, so the cleanup
> path of vhost_blk_device_realize() closed vhostfd for the second
> time. The cleanup path is also reachable with vhostfd never opened,
> in which case we called close(-1).
> 
> Fixes: 0ff3cb5ff2 ("block: add vhost-blk backend")
> https://virtuozzo.atlassian.net/browse/VSTOR-143437
> Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
>
> diff --git a/hw/block/vhost-blk.c b/hw/block/vhost-blk.c
> index c52851fcf8b..9bd49fef2da 100644
> --- a/hw/block/vhost-blk.c
> +++ b/hw/block/vhost-blk.c
> @@ -318,6 +318,8 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
>      if (ret < 0) {
>          error_setg(errp, "vhost-blk: vhost initialization failed: %s",
>                  strerror(-ret));
> +        /* vhost_dev_init() closes vhostfd on failure */
> +        s->vhostfd = -1;
>          goto cleanup;
>      }
>  
> @@ -328,7 +330,10 @@ cleanup:
>              qemu_del_vm_change_state_handler(s->mighand);
>      }
>      g_free(s->dev.vqs);

Not related, but while we're here:
  vhost_blk_device_realize()
    s->dev.vqs = g_new0()
    vhost_dev_init() fails ->
      vhost_dev_cleanup(s->dev)
        memset(s->dev, 0)
    g_free(s->dev.vqs)

Result: g_free(NULL) does nothing, array allocated with g_new0() leaks.
Worth another patch.  Or if it "Fixes:" the same commit - might fold
into the same patch.

Andrey

-- 
Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>

^ permalink raw reply	[relevance 8%]

* Re: [QEMU HCI-8.0 PATCH v2 3/5] vhost-blk: add read-only flag
  2026-09-04 13:21 31% ` [QEMU HCI-8.0 PATCH v2 3/5] vhost-blk: add read-only flag Andrey Zhadchenko
@ 2026-09-04 15:33  0%   ` Andrey Drobyshev
  0 siblings, 0 replies; 119+ results
From: Andrey Drobyshev @ 2026-09-04 15:33 UTC (permalink / raw)
  To: Andrey Zhadchenko; +Cc: svt-core, den, andrey.drobyshev

> and set RO respectively. Also compare BLKROGET with the selected

Please have commit message start their own sentences.

> mode and reject r/w if needed.
> 
> https://virtuozzo.atlassian.net/browse/VSTOR-143437
> Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
>
> diff --git a/hw/block/vhost-blk.c b/hw/block/vhost-blk.c
> index 24f4fbe2b68..04a7013e521 100644
> --- a/hw/block/vhost-blk.c
> +++ b/hw/block/vhost-blk.c
> @@ -24,6 +24,7 @@
>  #include "system/system.h"
>  #include "linux-headers/linux/vhost.h"
>  #include <sys/ioctl.h>
> +#include <linux/fs.h>

Header belongs to patch 2.

>  #include "system/runstate.h"
>  
>  static int vhost_blk_start(VirtIODevice *vdev)
> @@ -232,8 +233,9 @@ static bool vhost_blk_open_backend(VHostBlk *s, Error **errp)
>      BlockConf *conf = &s->conf.conf;
>      struct stat st;
>      bool changed;
> +    int open_flags = s->conf.readonly ? O_RDONLY : O_RDWR;
>  
> -    s->backend_fd = qemu_open(s->conf.devpath, O_RDWR, errp);
> +    s->backend_fd = qemu_open(s->conf.devpath, open_flags, errp);
>      if (s->backend_fd < 0) {
>          error_prepend(errp, "vhost-blk: unable to open backend: ");
>          return false;
> @@ -251,6 +253,23 @@ static bool vhost_blk_open_backend(VHostBlk *s, Error **errp)
>          goto fail;
>      }
>  
> +    if (!s->conf.readonly) {
> +        int readonly;
> +
> +        if (ioctl(s->backend_fd, BLKROGET, &readonly) < 0) {
> +            error_setg_errno(errp, errno,
> +                             "vhost-blk: unable to get read-only status of "
> +                             "'%s'", s->conf.devpath);
> +            goto fail;
> +        }
> +
> +        if (readonly) {
> +            error_setg_errno(errp, EROFS, "vhost-blk: '%s' is not writable",
> +                             s->conf.devpath);
> +            goto fail;
> +        }
> +    }
> +
>      if (vhost_blk_update_size(s, &changed, errp) < 0) {
>          goto fail;
>      }
> @@ -406,6 +425,10 @@ static uint64_t vhost_blk_get_features(VirtIODevice *vdev,
>  
>      virtio_add_feature(&features, VIRTIO_F_VERSION_1);
>  
> +    if (s->conf.readonly) {
> +        virtio_add_feature(&features, VIRTIO_BLK_F_RO);
> +    }
> +
>      if (s->conf.num_queues > 1) {
>          virtio_add_feature(&features, VIRTIO_BLK_F_MQ);
>      }
> @@ -453,6 +476,7 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
>  
>  static const Property vhost_blk_properties[] = {
>      DEFINE_PROP_STRING("devpath", VHostBlk, conf.devpath),
> +    DEFINE_PROP_BOOL("read-only", VHostBlk, conf.readonly, false),

Not for this patch, but related: AFAIU currently "<readonly/>" in domain
XML is applied by libvirt to the block node.  Correct?  If so - we also
need to patch libvirt making sure it applies to the device.

Andrey

-- 
Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>

^ permalink raw reply	[relevance 0%]

* Re: [QEMU HCI-8.0 PATCH v2 4/5] vhost-blk: watch the device for resize events
  2026-09-04 13:21 23% ` [QEMU HCI-8.0 PATCH v2 4/5] vhost-blk: watch the device for resize events Andrey Zhadchenko
@ 2026-09-04 15:33  7%   ` Andrey Drobyshev
  0 siblings, 0 replies; 119+ results
From: Andrey Drobyshev @ 2026-09-04 15:33 UTC (permalink / raw)
  To: Andrey Zhadchenko; +Cc: svt-core, den, andrey.drobyshev

> Resize was tied to block node, which we removed a few patches ago.
> Luckily we can make resize automated: receive an uevent socket
> from the management layer via the new "ueventfd" property (e.g. a
> /dev/fdset/N path), watch it for relevant netlink messages and
> call virtio_notify_config() if we detect a capacity change.
> 
> The socket is set up (possibly with filter) by management layer.
> We only need to check that it is netlink and do some message
> filtering.
> 
> When the property is not set, capacity changes are not detected.

So libvirt must ALWAYS set it, and we fail in .realize() if it's not
set.  Correct?  Let's mention it.

> 
> https://virtuozzo.atlassian.net/browse/VSTOR-143437
> Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
>
> diff --git a/hw/block/vhost-blk.c b/hw/block/vhost-blk.c
> index 04a7013e521..7cb842a859e 100644
> --- a/hw/block/vhost-blk.c
> +++ b/hw/block/vhost-blk.c
> @@ -10,7 +10,9 @@
>  
>  #include "qemu/osdep.h"
>  #include "qapi/error.h"
> +#include "qemu/cutils.h"
>  #include "qemu/error-report.h"
> +#include "qemu/main-loop.h"
>  #include "qom/object.h"
>  #include "hw/qdev-core.h"
>  #include "hw/boards.h"
> @@ -25,6 +27,7 @@
>  #include "linux-headers/linux/vhost.h"
>  #include <sys/ioctl.h>
>  #include <linux/fs.h>
> +#include <linux/netlink.h>
>  #include "system/runstate.h"
>  
>  static int vhost_blk_start(VirtIODevice *vdev)
> @@ -228,6 +231,173 @@ static int vhost_blk_update_size(VHostBlk *s, bool *changed, Error **errp)
>      return 0;
>  }
>  
> +static void vhost_blk_resize_bh(void *opaque)
> +{
> +    VHostBlk *s = opaque;
> +    Error *local_err = NULL;
> +    bool changed;
> +
> +    if (vhost_blk_update_size(s, &changed, &local_err) < 0) {
> +        error_report_err(local_err);
> +        return;
> +    }
> +
> +    if (changed) {
> +        virtio_notify_config(VIRTIO_DEVICE(s));
> +    }
> +}
> +
> +/*
> + * The uevent socket is created, bound and filtered by the management
> + * layer and passed to us via the "ueventfd" property.
> + */
> +static void vhost_blk_uevent_read(void *opaque)
> +{
> +    VHostBlk *s = opaque;
> +    char buffer[64 * 1024 + 1];

include/linux/kobject.h
33:#define UEVENT_BUFFER_SIZE           2048    /* buffer for the variables */

Looks like 64K is overkill, maybe 4K?

> +
> +    for (;;) {
> +        struct sockaddr_nl source;
> +        socklen_t source_len = sizeof(source);
> +        uint64_t event_major = UINT64_MAX;
> +        uint64_t event_minor = UINT64_MAX;
> +        bool action_change = false;
> +        bool subsystem_block = false;
> +        bool resize = false;
> +        char *field;
> +        char *end;
> +        ssize_t len;
> +
> +        memset(&source, 0, sizeof(source));
> +        len = recvfrom(s->uevent_fd, buffer, sizeof(buffer) - 1,
> +                       MSG_DONTWAIT, (struct sockaddr *)&source, &source_len);
> +        if (len < 0) {
> +            if (errno == EINTR) {
> +                continue;
> +            }
> +            if (errno == ENOBUFS) {
> +                /* Some events may be dropped, just re-check */
> +                qemu_bh_schedule(s->resize_bh);
> +                continue;
> +            }
> +            if (errno != EAGAIN && errno != EWOULDBLOCK) {

Should ENOMEM go here to?  And if it's an error that breaks the socket,
like EBADF or smth else - maybe spit the error and detach the handler?
What's the point of keeping it afterwards?

> +                error_report("vhost-blk: unable to receive uevent: %s",
> +                             strerror(errno));
> +            }
> +            return;
> +        }
> +
> +        if (source.nl_family != AF_NETLINK || source.nl_pid != 0) {
> +            continue;
> +        }
> +
> +        buffer[len] = '\0';
> +        field = buffer;
> +        end = buffer + len;
> +        while (field < end) {
> +            size_t field_len = strnlen(field, end - field);
> +
> +            if (!strcmp(field, "ACTION=change")) {
> +                action_change = true;
> +            } else if (!strcmp(field, "SUBSYSTEM=block")) {
> +                subsystem_block = true;
> +            } else if (!strcmp(field, "RESIZE=1")) {
> +                resize = true;
> +            } else if (g_str_has_prefix(field, "MAJOR=")) {
> +                uint64_t value;
> +
> +                if (!qemu_strtou64(field + strlen("MAJOR="), NULL, 10,
> +                                   &value)) {
> +                    event_major = value;
> +                }
> +            } else if (g_str_has_prefix(field, "MINOR=")) {
> +                uint64_t value;
> +
> +                if (!qemu_strtou64(field + strlen("MINOR="), NULL, 10,
> +                                   &value)) {
> +                    event_minor = value;
> +                }
> +            }
> +
> +            if (field_len == end - field) {
> +                break;
> +            }
> +            field += field_len + 1;
> +        }
> +
> +        if (action_change && subsystem_block && resize &&
> +            event_major == major(s->backend_rdev) &&
> +            event_minor == minor(s->backend_rdev)) {
> +            qemu_bh_schedule(s->resize_bh);
> +        }
> +    }
> +}
> +
> +static bool vhost_blk_uevent_check(int fd, const char *src, Error **errp)
> +{
> +    socklen_t optlen;
> +    int domain;
> +    int protocol;
> +
> +    optlen = sizeof(domain);
> +    if (getsockopt(fd, SOL_SOCKET, SO_DOMAIN, &domain, &optlen) < 0) {
> +        error_setg_errno(errp, errno, "vhost-blk: '%s' is not a socket", src);
> +        return false;
> +    }
> +
> +    optlen = sizeof(protocol);
> +    if (getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, &protocol, &optlen) < 0) {
> +        error_setg_errno(errp, errno,
> +                         "vhost-blk: unable to get protocol of '%s'", src);
> +        return false;
> +    }
> +
> +    if (domain != AF_NETLINK || protocol != NETLINK_KOBJECT_UEVENT) {
> +        error_setg(errp,
> +                   "vhost-blk: '%s' is not a NETLINK_KOBJECT_UEVENT socket",
> +                   src);
> +        return false;
> +    }

How about also checking nl_groups? Should be 1 for multicast.  I.e.

  if (getsockname(fd, (struct sockaddr *)&addr, &addrlen) < 0) {
      error_setg_errno(...);
      return false;
  }
  if (addr.nl_groups != 1) {
      error_setg(errp, "vhost-blk: '%s' is not subscribed to kernel uevents",
                 src);
      return false;
  }

> +
> +    return true;
> +}
> +
> +static bool vhost_blk_uevent_attach(VHostBlk *s, Error **errp)
> +{
> +    if (!s->conf.ueventfd) {
> +        return true;
> +    }
> +
> +    s->uevent_fd = qemu_open(s->conf.ueventfd, O_RDWR, errp);

For conf values monitor_fd_param() is usually used, so how about

  s->uevent_fd = monitor_fd_param(monitor_cur(), s->conf.ueventfd, errp);

Andrey

-- 
Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>

^ permalink raw reply	[relevance 7%]

* Re: [QEMU HCI-8.0 PATCH v2 2/5] vhost-blk: change backend setup
  2026-09-04 13:21 28% ` [QEMU HCI-8.0 PATCH v2 2/5] vhost-blk: change backend setup Andrey Zhadchenko
@ 2026-09-04 15:33  0%   ` Andrey Drobyshev
  0 siblings, 0 replies; 119+ results
From: Andrey Drobyshev @ 2026-09-04 15:33 UTC (permalink / raw)
  To: Andrey Zhadchenko; +Cc: svt-core, den, andrey.drobyshev

> Previously we used very ugly and incapsulation-breaking assignment
> fd = blk_bs(s->conf.conf.blk)->file->bs->opaque;
> It is wrong in a many ways, so let's rework this.
> 
> Patch changes default `drive` to new `devpath` option so device fd
> is managed by vhost-blk itself. Unfortunately this way we need a
> bit more preparational work: finding out disk length, etc. Don't
> be too broad and just do the minimal work. Drop the generic block
> device properties along with the block node: the kernel module
> does all IO in terms of 512 sectors, so simply report 512 byte
> logical/physical block size to the guest.
> Also we lose resize, as this is tied to the block node, which is
> now have no place in the setup. We will add this in the next
> patches as well as RO mode.
> 
> https://virtuozzo.atlassian.net/browse/VSTOR-143437
> Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
>
> diff --git a/hw/block/vhost-blk.c b/hw/block/vhost-blk.c
> index 9bd49fef2da..24f4fbe2b68 100644
> --- a/hw/block/vhost-blk.c
> +++ b/hw/block/vhost-blk.c
> @@ -24,23 +24,17 @@
>  #include "system/system.h"
>  #include "linux-headers/linux/vhost.h"
>  #include <sys/ioctl.h>
> -#include <linux/fs.h>

Nit: next patch brings this header back, just keep it here.

> -#include "include/block/block_int-common.h"
>  #include "system/runstate.h"
>  
>  static int vhost_blk_start(VirtIODevice *vdev)
>  {
>      VHostBlk *s = VHOST_BLK(vdev);
>      struct vhost_vring_file backend;
> -    int ret, i, nworkers, *fd;
> +    int ret, i, nworkers;
>      BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
>      VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
>      char serial[VIRTIO_BLK_ID_BYTES] = {0};
>  
> -    bdrv_graph_rdlock_main_loop();
> -    fd = blk_bs(s->conf.conf.blk)->file->bs->opaque;
> -    bdrv_graph_rdunlock_main_loop();
> -
>      if (!k->set_guest_notifiers) {
>          error_report("vhost-blk: binding does not support guest notifiers");
>          return -ENOSYS;
> @@ -92,7 +86,7 @@ static int vhost_blk_start(VirtIODevice *vdev)
>  
>      memset(&backend, 0, sizeof(backend));
>      backend.index = 0;
> -    backend.fd = *fd;
> +    backend.fd = s->backend_fd;
>      if (ioctl(s->vhostfd, VHOST_BLK_SET_BACKEND, &backend)) {
>          error_report("vhost-blk: unable to set backend");
>          ret = -errno;
> @@ -208,29 +202,69 @@ static void vhost_blk_vm_state(void *opaque, bool running, RunState state)
>      }
>  }
>  
> -static void vhost_blk_resize_cb(void *opaque)
> +static int vhost_blk_update_size(VHostBlk *s, bool *changed, Error **errp)
>  {
> -    VirtIODevice *vdev = opaque;
> +    BlockConf *conf = &s->conf.conf;
> +    uint64_t length;
> +
> +    if (ioctl(s->backend_fd, BLKGETSIZE64, &length) < 0) {
> +        int error = errno;

This is redundant, error_setg_errno() preserves errno value.

> +
> +        error_setg_errno(errp, error,
> +                         "vhost-blk: unable to determine size of '%s'",
> +                         s->conf.devpath);
> +        return -error;

This is wrong error handling.  Let this function return 'bool changed'.
Then callers of vhost_blk_update_size() should check whether Error **errp
was set to smth, and either process the error themselves or propagate it
further.  That's how it's usually done in QEMU codebase.

> +    }
> +
> +    *changed = s->length != length;
> +    s->length = length;
> +    conf->heads = 16;
> +    conf->secs = 63;
> +    conf->cyls = s->length / BDRV_SECTOR_SIZE /
> +                 (conf->heads * conf->secs);
> +    conf->cyls = MIN(MAX(conf->cyls, 2U), 16383U);
>  
> -    assert(qemu_get_current_aio_context() == qemu_get_aio_context());
> -    virtio_notify_config(vdev);
> +    return 0;
>  }
>  
> -static void vhost_blk_resize(void *opaque)
> +static bool vhost_blk_open_backend(VHostBlk *s, Error **errp)
>  {
> -    VirtIODevice *vdev = VIRTIO_DEVICE(opaque);
> +    BlockConf *conf = &s->conf.conf;
> +    struct stat st;
> +    bool changed;
>  
> -    /*
> -     * virtio_notify_config() needs to acquire the global mutex,
> -     * so it can't be called from an iothread. Instead, schedule
> -     * it to be run in the main context BH.
> -     */
> -    aio_bh_schedule_oneshot(qemu_get_aio_context(), vhost_blk_resize_cb, vdev);
> -}
> +    s->backend_fd = qemu_open(s->conf.devpath, O_RDWR, errp);
> +    if (s->backend_fd < 0) {
> +        error_prepend(errp, "vhost-blk: unable to open backend: ");
> +        return false;
> +    }
>  
> -static const BlockDevOps vhost_blk_block_ops = {
> -    .resize_cb     = vhost_blk_resize,
> -};
> +    if (fstat(s->backend_fd, &st) < 0) {
> +        error_setg_errno(errp, errno, "vhost-blk: unable to stat '%s'",
> +                         s->conf.devpath);
> +        goto fail;
> +    }
> +
> +    if (!S_ISBLK(st.st_mode)) {
> +        error_setg(errp, "vhost-blk: '%s' is not a block device",
> +                   s->conf.devpath);
> +        goto fail;
> +    }
> +
> +    if (vhost_blk_update_size(s, &changed, errp) < 0) {
> +        goto fail;
> +    }
> +
> +    conf->logical_block_size = BDRV_SECTOR_SIZE;
> +    conf->physical_block_size = BDRV_SECTOR_SIZE;
> +
> +    return true;
> +
> +fail:
> +    qemu_close(s->backend_fd);
> +    s->backend_fd = -1;
> +    return false;
> +}
>  
>  static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
>  {
> @@ -239,13 +273,8 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
>      VhostBlkConf *conf = &s->conf;
>      int i, ret;
>  
> -    if (!conf->conf.blk) {
> -        error_setg(errp, "vhost-blk: drive property not set");
> -        return;
> -    }
> -
> -    if (!blk_is_inserted(conf->conf.blk)) {
> -        error_setg(errp, "vhost-blk: device needs media, but drive is empty");
> +    if (!conf->devpath) {
> +        error_setg(errp, "vhost-blk: devpath property must be set");
>          return;
>      }
>  
> @@ -273,17 +302,7 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
>          return;
>      }
>  
> -    if (!blkconf_apply_backend_options(&conf->conf,
> -                                       !blk_supports_write_perm(conf->conf.blk),
> -                                       true, errp)) {
> -        return;
> -    }
> -
> -    if (!blkconf_geometry(&conf->conf, NULL, 65535, 255, 255, errp)) {
> -        return;
> -    }
> -
> -    if (!blkconf_blocksizes(&conf->conf, errp)) {
> +    if (!vhost_blk_open_backend(s, errp)) {
>          return;
>      }
>  
> @@ -311,8 +330,6 @@ static void vhost_blk_device_realize(DeviceState *dev, Error **errp)
>          goto cleanup;
>      }
>  
> -    blk_set_dev_ops(s->conf.conf.blk, &vhost_blk_block_ops, s);
> -
>      ret = vhost_dev_init(&s->dev, (void *)((size_t)s->vhostfd),
>                           VHOST_BACKEND_TYPE_KERNEL, 0, NULL);
>      if (ret < 0) {
> @@ -334,6 +351,10 @@ cleanup:
>          close(s->vhostfd);
>          s->vhostfd = -1;
>      }
> +    if (s->backend_fd >= 0) {
> +        qemu_close(s->backend_fd);
> +        s->backend_fd = -1;
> +    }
>      for (i = 0; i < conf->num_queues; i++) {
>          virtio_del_queue(vdev, i);
>      }
> @@ -349,6 +370,10 @@ static void vhost_blk_device_unrealize(DeviceState *dev)
>      qemu_del_vm_change_state_handler(s->mighand);
>      vhost_blk_set_status(vdev, 0);
>      vhost_dev_cleanup(&s->dev);
> +    if (s->backend_fd >= 0) {
> +        qemu_close(s->backend_fd);
> +        s->backend_fd = -1;
> +    }
>      g_free(s->dev.vqs);
>      virtio_cleanup(vdev);
>  }
> @@ -381,10 +406,6 @@ static uint64_t vhost_blk_get_features(VirtIODevice *vdev,
>  
>      virtio_add_feature(&features, VIRTIO_F_VERSION_1);
>  
> -    if (!blk_is_writable(s->conf.conf.blk)) {
> -        virtio_add_feature(&features, VIRTIO_BLK_F_RO);
> -    }
> -
>      if (s->conf.num_queues > 1) {
>          virtio_add_feature(&features, VIRTIO_BLK_F_MQ);
>      }
> @@ -403,7 +424,9 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
>      int64_t length;
>      int blk_size = conf->logical_block_size;
>  
> -    blk_get_geometry(s->conf.conf.blk, &capacity);
> +    length = s->length;
> +    capacity = length / BDRV_SECTOR_SIZE;
> +
>      memset(&blkcfg, 0, sizeof(blkcfg));
>      virtio_stq_p(vdev, &blkcfg.capacity, capacity);
>      virtio_stl_p(vdev, &blkcfg.seg_max, s->conf.queue_size - 2);
> @@ -411,7 +434,6 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
>      virtio_stl_p(vdev, &blkcfg.blk_size, blk_size);
>      blkcfg.geometry.heads = conf->heads;
>  
> -    length = blk_getlength(s->conf.conf.blk);
>      if (length > 0 && length / conf->heads / conf->secs % blk_size) {
>          unsigned short mask;
>  
> @@ -430,7 +452,7 @@ static void vhost_blk_update_config(VirtIODevice *vdev, uint8_t *config)
>  }
>  
>  static const Property vhost_blk_properties[] = {
> -    DEFINE_BLOCK_PROPERTIES(VHostBlk, conf.conf),

Removing these props currently results into:

  error: Failed to start domain
  Property 'vhost-blk-pci.physical_block_size' not found

I understand we also patch libvirt so that it doesn't send those
block_size props, as well as write-cache etc.  Let's mention that in
commit message.

> +    DEFINE_PROP_STRING("devpath", VHostBlk, conf.devpath),
>      DEFINE_PROP_UINT16("num-queues", VHostBlk, conf.num_queues,
>                         VHOST_BLK_AUTO_NUM_QUEUES),
>      DEFINE_PROP_UINT16("queue-size", VHostBlk, conf.queue_size, 256),
> @@ -475,6 +497,8 @@ static void vhost_blk_instance_init(Object *obj)
>  {
>      VHostBlk *s = VHOST_BLK(obj);
>  
> +    s->vhostfd = -1;

Nit: ideally belongs to patch #1.  Not a big deal, but if you do a respin -
put it there.

Andrey

-- 
Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>

^ permalink raw reply	[relevance 0%]

* Re: [QEMU HCI-8.0 PATCH v2 5/5] vhost-blk: preserve the uevent socket across cpr-exec
  2026-09-04 13:21 32% ` [QEMU HCI-8.0 PATCH v2 5/5] vhost-blk: preserve the uevent socket across cpr-exec Andrey Zhadchenko
@ 2026-09-04 15:33  0%   ` Andrey Drobyshev
  0 siblings, 0 replies; 119+ results
From: Andrey Drobyshev @ 2026-09-04 15:33 UTC (permalink / raw)
  To: Andrey Zhadchenko; +Cc: svt-core, den, andrey.drobyshev

> qemu-update uses cpr-exec migration: QEMU re-execs itself in place.
> A plain monitor fdset descriptor like our uevent socket does not
> survive that (unlike the cpr_save_fd()'d tap/vhost fds), so without
> help the resized-device notifications would silently stop until the
> next full VM start.
> 
> Preserve it the same way the net backends do: on cold boot
> cpr_save_fd() the socket under the device's canonical path, and on
> the re-exec'd (incoming) side adopt it with cpr_find_fd() instead of
> reopening a command line fd that no longer resolves. The management
> layer passes a placeholder for the "ueventfd" property in that case.
> Drop the saved descriptor on teardown so it does not outlive the
> device.
> 
> https://virtuozzo.atlassian.net/browse/VSTOR-143437
> Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
>
> diff --git a/hw/block/vhost-blk.c b/hw/block/vhost-blk.c
> index 7cb842a859e..eca12356ae5 100644
> --- a/hw/block/vhost-blk.c
> +++ b/hw/block/vhost-blk.c
> @@ -28,6 +28,7 @@
>  #include <sys/ioctl.h>
>  #include <linux/fs.h>
>  #include <linux/netlink.h>
> +#include "migration/cpr.h"
>  #include "system/runstate.h"
>  
>  static int vhost_blk_start(VirtIODevice *vdev)
> @@ -364,20 +365,36 @@ static bool vhost_blk_uevent_check(int fd, const char *src, Error **errp)
>  
>  static bool vhost_blk_uevent_attach(VHostBlk *s, Error **errp)
>  {
> +    g_autofree char *cpr_name = NULL;
> +
>      if (!s->conf.ueventfd) {
>          return true;
>      }
>  
> -    s->uevent_fd = qemu_open(s->conf.ueventfd, O_RDWR, errp);
> -    if (s->uevent_fd < 0) {
> -        error_prepend(errp, "vhost-blk: unable to open uevent socket: ");
> -        return false;
> -    }
> +    cpr_name = object_get_canonical_path(OBJECT(s));

That name is gonna be "/machine/peripheral/...".  Let's do CPR
consistently with other devices.  E.g. see how it's done in
vhost_vsock_device_realize():

  DeviceState *proxy = qdev_get_parent_bus(DEVICE(vsock))->parent;
  ...
  /* Add migration blockers if proxy->id isn't present */

For vhost-blk proxy->id is likely gonna be "virtio-disk0".

Also, in this case, since it's not a vhostfd or backend FD, but an
ueventfd, I'd prefer adding it as a suffix, as it's done for other
devices.  So CPR key should end up looking like "virtio-disk0_ueventfd".

>  
> -    if (!vhost_blk_uevent_check(s->uevent_fd, s->conf.ueventfd, errp)) {
> -        qemu_close(s->uevent_fd);
> -        s->uevent_fd = -1;
> -        return false;
> +    if (cpr_is_incoming()) {
> +        s->uevent_fd = cpr_find_fd(cpr_name, 0);
> +        if (s->uevent_fd < 0) {
> +            error_setg(errp,
> +                       "vhost-blk: no preserved uevent socket to restore");
> +            return false;
> +        }
> +    } else {
> +        s->uevent_fd = qemu_open(s->conf.ueventfd, O_RDWR, errp);
> +        if (s->uevent_fd < 0) {
> +            error_prepend(errp, "vhost-blk: unable to open uevent socket: ");
> +            return false;
> +        }
> +
> +        if (!vhost_blk_uevent_check(s->uevent_fd, s->conf.ueventfd, errp)) {

Don't we want the same validation for cpr_is_incoming() case?

Andrey

-- 
Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>

^ permalink raw reply	[relevance 0%]

* [QEMU HCI-8.0 PATCH v2 02/13] usb-host: don't leak hostdev FD on open failure #VSTOR-137800
  @ 2026-09-04 19:04 35% ` Andrey Drobyshev
  2026-09-04 19:04 15% ` [QEMU HCI-8.0 PATCH v2 03/13] usb-host: add migration blocker for CPR modes #VSTOR-137800 Andrey Drobyshev
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 119+ results
From: Andrey Drobyshev @ 2026-09-04 19:04 UTC (permalink / raw)
  To: svt-core; +Cc: andrey.drobyshev, den

The FD passed into usb_host_open() for a wrapped device is only stored in
s->hostfd after libusb_wrap_sys_device() succeeds, and the failure path
doesn't close it.  if wrapping fails, or a later step of the open sequence
fails (e.g. usb_device_attach), the FD is leaked, as usb_host_close() only
runs for fully opened devices, and the realize error path doesn't clean it
up either.

Store the FD in s->hostfd before wrapping and close it on the failure
path.

Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
---
 hw/usb/host-libusb.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c
index b74670ae256..a8e6f142ec0 100644
--- a/hw/usb/host-libusb.c
+++ b/hw/usb/host-libusb.c
@@ -974,11 +974,11 @@ static int usb_host_open(USBHostDevice *s, libusb_device *dev, int hostfd)
 #if LIBUSB_API_VERSION >= 0x01000107 && !defined(CONFIG_WIN32)
         trace_usb_host_open_hostfd(hostfd);
 
+        s->hostfd = hostfd;
         rc = libusb_wrap_sys_device(ctx, hostfd, &s->dh);
         if (rc != 0) {
             goto fail;
         }
-        s->hostfd  = hostfd;
         dev = libusb_get_device(s->dh);
         bus_num = libusb_get_bus_number(dev);
         addr = libusb_get_device_address(dev);
@@ -1066,6 +1066,10 @@ fail:
         s->dh = NULL;
         s->dev = NULL;
     }
+    if (s->hostfd != -1) {
+        close(s->hostfd);
+        s->hostfd = -1;
+    }
     return -1;
 }
 
-- 
2.47.1


^ permalink raw reply	[relevance 35%]

* [QEMU HCI-8.0 PATCH v2 03/13] usb-host: add migration blocker for CPR modes #VSTOR-137800
    2026-09-04 19:04 35% ` [QEMU HCI-8.0 PATCH v2 02/13] usb-host: don't leak hostdev FD on open failure #VSTOR-137800 Andrey Drobyshev
@ 2026-09-04 19:04 15% ` Andrey Drobyshev
  2026-09-04 19:04 30% ` [QEMU HCI-8.0 PATCH v2 04/13] usb-host: preserve hostdev FD during CPR migration #VSTOR-137800 Andrey Drobyshev
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 119+ results
From: Andrey Drobyshev @ 2026-09-04 19:04 UTC (permalink / raw)
  To: svt-core; +Cc: andrey.drobyshev, den

We're about to add support for CPR migration to usb-host in the upcoming
commits.  Right now CPR is crashing with usb-host devices.  Let's add
migration blocker upfront, for both cpr-exec and cpr-transfer.  The
blocker is added unconditionally, so that we resuse CPR migration instead
of crashing on it.  That is for the sake of bisectability of the upcoming
commits.  It is going to be lifted (conditioned) in a following patch
once CPR support is added.  Also, blocker is added at the end of
usb_host_realize(), so we have to cleanup all the side effects done
earlier in .realize() in case migrate_add_blocker_modes() fails.

Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
---
 hw/usb/host-libusb.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c
index a8e6f142ec0..86f96087085 100644
--- a/hw/usb/host-libusb.c
+++ b/hw/usb/host-libusb.c
@@ -46,6 +46,7 @@
 #endif
 
 #include "qapi/error.h"
+#include "migration/blocker.h"
 #include "migration/vmstate.h"
 #include "monitor/monitor.h"
 #include "qemu/error-report.h"
@@ -103,6 +104,7 @@ struct USBHostDevice {
     char                             port[16];
 
     int                              hostfd;
+    Error                            *cpr_blocker;
     libusb_device                    *dev;
     libusb_device_handle             *dh;
     struct libusb_device_descriptor  ddesc;
@@ -1254,6 +1256,18 @@ static void usb_host_realize(USBDevice *udev, Error **errp)
 
     s->exit.notify = usb_host_exit_notifier;
     qemu_add_exit_notifier(&s->exit);
+
+    error_setg(&s->cpr_blocker, "usb-host device %s does not support CPR: ",
+               DEVICE(s)->id ?: "(anonymous)");
+    if (migrate_add_blocker_modes(&s->cpr_blocker, errp,
+                                  MIG_MODE_CPR_TRANSFER,
+                                  MIG_MODE_CPR_EXEC, -1) < 0) {
+        qemu_remove_exit_notifier(&s->exit);
+        if (s->needs_autoscan) {
+            QTAILQ_REMOVE(&hostdevs, s, next);
+        }
+        usb_host_close(s);
+    }
 }
 
 static void usb_host_instance_init(Object *obj)
@@ -1275,6 +1289,7 @@ static void usb_host_unrealize(USBDevice *udev)
         QTAILQ_REMOVE(&hostdevs, s, next);
     }
     usb_host_close(s);
+    migrate_del_blocker(&s->cpr_blocker);
 }
 
 static void usb_host_cancel_packet(USBDevice *udev, USBPacket *p)
-- 
2.47.1


^ permalink raw reply	[relevance 15%]

* [QEMU HCI-8.0 PATCH v2 04/13] usb-host: preserve hostdev FD during CPR migration #VSTOR-137800
    2026-09-04 19:04 35% ` [QEMU HCI-8.0 PATCH v2 02/13] usb-host: don't leak hostdev FD on open failure #VSTOR-137800 Andrey Drobyshev
  2026-09-04 19:04 15% ` [QEMU HCI-8.0 PATCH v2 03/13] usb-host: add migration blocker for CPR modes #VSTOR-137800 Andrey Drobyshev
@ 2026-09-04 19:04 30% ` Andrey Drobyshev
  2026-09-04 19:04 30% ` [QEMU HCI-8.0 PATCH v2 05/13] usb-host: factor out usb_host_reap_xfers() #VSTOR-137800 Andrey Drobyshev
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 119+ results
From: Andrey Drobyshev @ 2026-09-04 19:04 UTC (permalink / raw)
  To: svt-core; +Cc: andrey.drobyshev, den

In .realize(), save freshly opened host device FD into the CPR registry,
and then reuse it on CPR target.  Also, add deletion of that FD from
registry to usb_host_open() / usb_host_close() cleanup paths.

For this to work, we also need to skip the .post-load() code which
closes, detaches the device, and then rescans the host bus to reopen
matching hostdevs.  For the CPR-case migration we don't want any of that
as the hostdev FD stays preserved, and the guest shouldn't notice the
switchover.

Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
---
 hw/usb/host-libusb.c | 53 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 51 insertions(+), 2 deletions(-)

diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c
index 86f96087085..eaacfd34c54 100644
--- a/hw/usb/host-libusb.c
+++ b/hw/usb/host-libusb.c
@@ -47,6 +47,7 @@
 
 #include "qapi/error.h"
 #include "migration/blocker.h"
+#include "migration/cpr.h"
 #include "migration/vmstate.h"
 #include "monitor/monitor.h"
 #include "qemu/error-report.h"
@@ -947,6 +948,38 @@ static void usb_host_ep_update(USBHostDevice *s)
     libusb_free_config_descriptor(conf);
 }
 
+static char *usb_host_cpr_fd_name(USBHostDevice *s)
+{
+    /*
+     * Prefix the CPR fd registry key so it can't collide with a chardev
+     * label or netdev id in the same flat namespace.
+     */
+    return DEVICE(s)->id ?
+        g_strdup_printf("usb-host/%s", DEVICE(s)->id) : NULL;
+}
+
+static int usb_host_cpr_find_fd(USBHostDevice *s)
+{
+    g_autofree char *name = usb_host_cpr_fd_name(s);
+    return name ? cpr_find_fd(name, 0) : -1;
+}
+
+static void usb_host_cpr_save_fd(USBHostDevice *s, int fd)
+{
+    g_autofree char *name = usb_host_cpr_fd_name(s);
+    if (name) {
+        cpr_save_fd(name, 0, fd);
+    }
+}
+
+static void usb_host_cpr_delete_fd(USBHostDevice *s)
+{
+    g_autofree char *name = usb_host_cpr_fd_name(s);
+    if (name) {
+        cpr_delete_fd(name, 0);
+    }
+}
+
 static int usb_host_open(USBHostDevice *s, libusb_device *dev, int hostfd)
 {
     USBDevice *udev = USB_DEVICE(s);
@@ -1069,6 +1102,7 @@ fail:
         s->dev = NULL;
     }
     if (s->hostfd != -1) {
+        usb_host_cpr_delete_fd(s);
         close(s->hostfd);
         s->hostfd = -1;
     }
@@ -1130,6 +1164,7 @@ static int usb_host_close(USBHostDevice *s)
     s->dev = NULL;
 
     if (s->hostfd != -1) {
+        usb_host_cpr_delete_fd(s);
         close(s->hostfd);
         s->hostfd = -1;
     }
@@ -1218,9 +1253,13 @@ static void usb_host_realize(USBDevice *udev, Error **errp)
     if (s->hostdevice) {
         int fd;
         s->needs_autoscan = false;
-        fd = qemu_open(s->hostdevice, O_RDWR, errp);
+        fd = usb_host_cpr_find_fd(s);
         if (fd < 0) {
-            return;
+            fd = qemu_open(s->hostdevice, O_RDWR, errp);
+            if (fd < 0) {
+                return;
+            }
+            usb_host_cpr_save_fd(s, fd);
         }
         rc = usb_host_open(s, NULL, fd);
         if (rc < 0) {
@@ -1757,6 +1796,16 @@ static int usb_host_post_load(void *opaque, int version_id)
 {
     USBHostDevice *dev = opaque;
 
+    /*
+     * For CPR migration, device wasn't released/reset, and the guest
+     * is unaware of the switchover.  The detach/rescan performed in
+     * usb_host_post_load_bh() only exists for cross-host migration.
+     * Skip it for CPR.
+     */
+    if (cpr_is_incoming()) {
+        return 0;
+    }
+
     if (!dev->bh_postld) {
         dev->bh_postld = qemu_bh_new_guarded(usb_host_post_load_bh, dev,
                                              &DEVICE(dev)->mem_reentrancy_guard);
-- 
2.47.1


^ permalink raw reply	[relevance 30%]

* [QEMU HCI-8.0 PATCH v2 05/13] usb-host: factor out usb_host_reap_xfers() #VSTOR-137800
                     ` (2 preceding siblings ...)
  2026-09-04 19:04 30% ` [QEMU HCI-8.0 PATCH v2 04/13] usb-host: preserve hostdev FD during CPR migration #VSTOR-137800 Andrey Drobyshev
@ 2026-09-04 19:04 30% ` Andrey Drobyshev
  2026-09-04 19:04 20% ` [QEMU HCI-8.0 PATCH v2 06/13] usb-host: drain in-flight URBs across CPR #VSTOR-137800 Andrey Drobyshev
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 119+ results
From: Andrey Drobyshev @ 2026-09-04 19:04 UTC (permalink / raw)
  To: svt-core; +Cc: andrey.drobyshev, den

usb_host_abort_xfers() cancels every pending request and then pumps
libusb events until all of them are reaped, with a bounded wait.
Split the reap-wait loop into usb_host_reap_xfers(), so that the
following commit can reuse it to reap canceled transfers without going
through the abort path.  No functional change.

Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
---
 hw/usb/host-libusb.c | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c
index eaacfd34c54..d695ba3170f 100644
--- a/hw/usb/host-libusb.c
+++ b/hw/usb/host-libusb.c
@@ -1109,15 +1109,11 @@ fail:
     return -1;
 }
 
-static void usb_host_abort_xfers(USBHostDevice *s)
+static void usb_host_reap_xfers(USBHostDevice *s)
 {
     USBHostRequest *r, *rtmp;
     int limit = 100;
 
-    QTAILQ_FOREACH_SAFE(r, &s->requests, next, rtmp) {
-        usb_host_req_abort(r);
-    }
-
     while (QTAILQ_FIRST(&s->requests) != NULL) {
         struct timeval tv;
         memset(&tv, 0, sizeof(tv));
@@ -1139,6 +1135,16 @@ static void usb_host_abort_xfers(USBHostDevice *s)
     }
 }
 
+static void usb_host_abort_xfers(USBHostDevice *s)
+{
+    USBHostRequest *r, *rtmp;
+
+    QTAILQ_FOREACH_SAFE(r, &s->requests, next, rtmp) {
+        usb_host_req_abort(r);
+    }
+    usb_host_reap_xfers(s);
+}
+
 static int usb_host_close(USBHostDevice *s)
 {
     USBDevice *udev = USB_DEVICE(s);
-- 
2.47.1


^ permalink raw reply	[relevance 30%]

* [QEMU HCI-8.0 PATCH v2 07/13] usb-host: re-issue drained URBs if CPR is aborted #VSTOR-137800
                     ` (4 preceding siblings ...)
  2026-09-04 19:04 20% ` [QEMU HCI-8.0 PATCH v2 06/13] usb-host: drain in-flight URBs across CPR #VSTOR-137800 Andrey Drobyshev
@ 2026-09-04 19:04 25% ` Andrey Drobyshev
  2026-09-04 19:04 26% ` [QEMU HCI-8.0 PATCH v2 08/13] usb-host: skip product-string read on CPR incoming #VSTOR-137800 Andrey Drobyshev
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 119+ results
From: Andrey Drobyshev @ 2026-09-04 19:04 UTC (permalink / raw)
  To: svt-core; +Cc: andrey.drobyshev, den

The previous commit drains the in-flight URBs in .pre_save() and leaves
their packets ASYNC for the target to replay.  If migration fails, the
source resumes instead, with those packets still owned by the
controller and nothing left to complete them.

Remember the drained packets in a list, and re-issue them on the
preserved FD from a MIG_EVENT_PRECOPY_FAILED notifier.  Control
transfers can't be reconstructed from the packet alone, so complete
those as errors and let the guest driver retry.

Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
---
 hw/usb/host-libusb.c | 75 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 73 insertions(+), 2 deletions(-)

diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c
index c8a893073a6..8483df0d12c 100644
--- a/hw/usb/host-libusb.c
+++ b/hw/usb/host-libusb.c
@@ -107,6 +107,9 @@ struct USBHostDevice {
 
     int                              hostfd;
     Error                            *cpr_blocker;
+    NotifierWithReturn               cpr_notifier;
+    GSList                           *cpr_inflight;
+    bool                             cpr_drained;
     libusb_device                    *dev;
     libusb_device_handle             *dh;
     struct libusb_device_descriptor  ddesc;
@@ -1159,8 +1162,13 @@ static bool usb_host_iso_inflight(USBHostDevice *s)
 }
 
 /*
- * Quiesce the device for a CPR switchover.  Cancel and reap all URBs,
- * so that no URB submitted by this process can outlive it.
+ * Quiesce the device for a CPR switchover.  Cancel and reap all URBs
+ * without completing their packets, so that no URB submitted by this
+ * process can outlive it.  CPR target re-executes them in
+ * usb_xhci_post_load() kicking the running endpoints.
+ *
+ * Keep the list of packets so that they can be re-issued on this side
+ * instead if MIG_EVENT_PRECOPY_FAILED fires.
  */
 static int usb_host_cpr_drain_xfers(USBHostDevice *s)
 {
@@ -1171,6 +1179,10 @@ static int usb_host_cpr_drain_xfers(USBHostDevice *s)
 
     QTAILQ_FOREACH_SAFE(r, &s->requests, next, rtmp) {
         if (r->p) {
+            if (r->p->state == USB_PACKET_ASYNC) {
+                /* In-flight req, in submission order, for the failure path */
+                s->cpr_inflight = g_slist_append(s->cpr_inflight, r->p);
+            }
             /* Clear r->p so the reap callback early-outs on it: the
              * callback must still run (it frees the request), but it
              * must not complete the packet. */
@@ -1178,6 +1190,7 @@ static int usb_host_cpr_drain_xfers(USBHostDevice *s)
         }
         libusb_cancel_transfer(r->xfer);
     }
+    s->cpr_drained = true;
 
     usb_host_reap_xfers(s);
 
@@ -1288,6 +1301,53 @@ static libusb_device *usb_host_find_ref(int bus, int addr)
     return ret;
 }
 
+static void usb_host_handle_data(USBDevice *udev, USBPacket *p);
+
+/*
+ * A failed CPR migration resumes the source VM with the drained
+ * packets still owned by the host controller as in-flight.  Re-issue
+ * them on the preserved fd.  Control transfers cannot be reconstructed
+ * from the packet alone; complete them as errors and let the guest
+ * driver retry.
+ */
+static int usb_host_cpr_notifier(NotifierWithReturn *notifier,
+                                 MigrationEvent *e, Error **errp)
+{
+    USBHostDevice *s = container_of(notifier, USBHostDevice, cpr_notifier);
+    USBDevice *udev = USB_DEVICE(s);
+    GSList *it;
+    USBPacket *p;
+
+    if (e->type != MIG_EVENT_PRECOPY_FAILED || !s->cpr_drained) {
+        return 0;
+    }
+
+    for (it = s->cpr_inflight; it; it = it->next) {
+        p = it->data;
+        /* Replay from the start of the TD */
+        p->actual_length = 0;
+        if (p->ep->nr == 0) {
+            /*
+             * p->ep[0] is control endpoint.  Control transfers can't
+             * be reconstructed reliably, so complete them as errors
+             * and let the guest retry.
+             */
+            p->status = USB_RET_IOERROR;
+            usb_generic_async_ctrl_complete(udev, p);
+        } else {
+            usb_host_handle_data(udev, p);
+            if (p->status != USB_RET_ASYNC) {
+                /* Completed synchronously with error */
+                usb_packet_complete(udev, p);
+            }
+        }
+    }
+    g_slist_free(s->cpr_inflight);
+    s->cpr_inflight = NULL;
+    s->cpr_drained = false;
+    return 0;
+}
+
 static void usb_host_realize(USBDevice *udev, Error **errp)
 {
     USBHostDevice *s = USB_HOST_DEVICE(udev);
@@ -1365,6 +1425,14 @@ static void usb_host_realize(USBDevice *udev, Error **errp)
     s->exit.notify = usb_host_exit_notifier;
     qemu_add_exit_notifier(&s->exit);
 
+#if LIBUSB_API_VERSION >= 0x01000107 && !defined(CONFIG_WIN32)
+    if (s->hostdevice && DEVICE(s)->id) {
+        migration_add_notifier_modes(&s->cpr_notifier, usb_host_cpr_notifier,
+                                     MIG_MODE_CPR_TRANSFER,
+                                     MIG_MODE_CPR_EXEC, -1);
+    }
+#endif
+
     error_setg(&s->cpr_blocker, "usb-host device %s does not support CPR: ",
                DEVICE(s)->id ?: "(anonymous)");
     if (migrate_add_blocker_modes(&s->cpr_blocker, errp,
@@ -1398,6 +1466,9 @@ static void usb_host_unrealize(USBDevice *udev)
     }
     usb_host_close(s);
     migrate_del_blocker(&s->cpr_blocker);
+    migration_remove_notifier(&s->cpr_notifier);
+    g_slist_free(s->cpr_inflight);
+    s->cpr_inflight = NULL;
 }
 
 static void usb_host_cancel_packet(USBDevice *udev, USBPacket *p)
-- 
2.47.1


^ permalink raw reply	[relevance 25%]

* [QEMU HCI-8.0 PATCH v2 06/13] usb-host: drain in-flight URBs across CPR #VSTOR-137800
                     ` (3 preceding siblings ...)
  2026-09-04 19:04 30% ` [QEMU HCI-8.0 PATCH v2 05/13] usb-host: factor out usb_host_reap_xfers() #VSTOR-137800 Andrey Drobyshev
@ 2026-09-04 19:04 20% ` Andrey Drobyshev
  2026-09-04 19:04 25% ` [QEMU HCI-8.0 PATCH v2 07/13] usb-host: re-issue drained URBs if CPR is aborted #VSTOR-137800 Andrey Drobyshev
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 119+ results
From: Andrey Drobyshev @ 2026-09-04 19:04 UTC (permalink / raw)
  To: svt-core; +Cc: andrey.drobyshev, den

URBs submitted by the old QEMU must not outlive it across the
switchover: the kernel would complete them into buffers of an address
space which is gone after exec, and the new libusb context would reap
URB pointers it never submitted.

So, in .pre_save() for the CPR modes, cancel and reap every pending
transfer, without completing its packet.  The packets stay ASYNC and
their TDs remain on the transfer rings, so the target re-executes them
when usb_xhci_post_load() kicks the endpoints - the same in-flight
replay as on regular live migration.  A partially executed transfer is
simply re-run from the start of the TD, which is safe for mass storage.

Isochronous URBs are not on the request list but on their own rings,
and freeing them while in-flight would leave them queued on the
preserved FD.  Cancel them so they are unlinked and reaped: an iso URB
in flight when the guest stops may never complete on its own.  The
target reconstructs the iso stream from the guest's transfer ring.

Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
---
 hw/usb/host-libusb.c | 79 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 79 insertions(+)

diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c
index d695ba3170f..c8a893073a6 100644
--- a/hw/usb/host-libusb.c
+++ b/hw/usb/host-libusb.c
@@ -48,6 +48,7 @@
 #include "qapi/error.h"
 #include "migration/blocker.h"
 #include "migration/cpr.h"
+#include "migration/misc.h"
 #include "migration/vmstate.h"
 #include "monitor/monitor.h"
 #include "qemu/error-report.h"
@@ -1145,6 +1146,68 @@ static void usb_host_abort_xfers(USBHostDevice *s)
     usb_host_reap_xfers(s);
 }
 
+static bool usb_host_iso_inflight(USBHostDevice *s)
+{
+    USBHostIsoRing *ring;
+
+    QTAILQ_FOREACH(ring, &s->isorings, next) {
+        if (!QTAILQ_EMPTY(&ring->inflight)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/*
+ * Quiesce the device for a CPR switchover.  Cancel and reap all URBs,
+ * so that no URB submitted by this process can outlive it.
+ */
+static int usb_host_cpr_drain_xfers(USBHostDevice *s)
+{
+    USBHostRequest *r, *rtmp;
+    USBHostIsoRing *ring;
+    USBHostIsoXfer *xfer;
+    int limit;
+
+    QTAILQ_FOREACH_SAFE(r, &s->requests, next, rtmp) {
+        if (r->p) {
+            /* Clear r->p so the reap callback early-outs on it: the
+             * callback must still run (it frees the request), but it
+             * must not complete the packet. */
+            r->p = NULL;
+        }
+        libusb_cancel_transfer(r->xfer);
+    }
+
+    usb_host_reap_xfers(s);
+
+    /*
+     * Iso URBs are on the rings, not s->requests.  Cancel them: one in
+     * flight when the guest stops may never complete on its own, and none
+     * may be left on the preserved fd.  The target replays from the ring.
+     */
+    QTAILQ_FOREACH(ring, &s->isorings, next) {
+        QTAILQ_FOREACH(xfer, &ring->inflight, next) {
+            libusb_cancel_transfer(xfer->xfer);
+        }
+    }
+
+    /*
+     * Cap the reap at 2x a full ring:
+     * iso_urb_count URBs x iso_urb_frames packets each
+     */
+    limit = 2 * s->iso_urb_count * s->iso_urb_frames;
+    while (usb_host_iso_inflight(s)) {
+        struct timeval tv = { .tv_usec = 1000 };
+        libusb_handle_events_timeout(ctx, &tv);
+        if (limit-- == 0) {
+            return -1;
+        }
+    }
+    usb_host_iso_free_all(s);
+    return 0;
+}
+
 static int usb_host_close(USBHostDevice *s)
 {
     USBDevice *udev = USB_DEVICE(s);
@@ -1821,10 +1884,26 @@ static int usb_host_post_load(void *opaque, int version_id)
     return 0;
 }
 
+static int usb_host_pre_save(void *opaque)
+{
+    USBHostDevice *s = opaque;
+    MigMode mode = migrate_mode();
+
+    /* URBs submitted by this process must not outlive CPR migration */
+    if ((mode == MIG_MODE_CPR_EXEC || mode == MIG_MODE_CPR_TRANSFER) &&
+        s->dh) {
+        if (usb_host_cpr_drain_xfers(s) < 0) {
+            return -1;
+        }
+    }
+    return 0;
+}
+
 static const VMStateDescription vmstate_usb_host = {
     .name = "usb-host",
     .version_id = 1,
     .minimum_version_id = 1,
+    .pre_save = usb_host_pre_save,
     .post_load = usb_host_post_load,
     .fields = (const VMStateField[]) {
         VMSTATE_USB_DEVICE(parent_obj, USBHostDevice),
-- 
2.47.1


^ permalink raw reply	[relevance 20%]

* [QEMU HCI-8.0 PATCH v2 08/13] usb-host: skip product-string read on CPR incoming #VSTOR-137800
                     ` (5 preceding siblings ...)
  2026-09-04 19:04 25% ` [QEMU HCI-8.0 PATCH v2 07/13] usb-host: re-issue drained URBs if CPR is aborted #VSTOR-137800 Andrey Drobyshev
@ 2026-09-04 19:04 26% ` Andrey Drobyshev
  2026-09-04 19:04 24% ` [QEMU HCI-8.0 PATCH v2 09/13] usb-host: hand off the device across cpr-transfer #VSTOR-137800 Andrey Drobyshev
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 119+ results
From: Andrey Drobyshev @ 2026-09-04 19:04 UTC (permalink / raw)
  To: svt-core; +Cc: andrey.drobyshev, den

usb_host_open() reads the product string descriptor from the device
with a synchronous control transfer.  On the CPR target this runs while
the device is being handed over, and the transfer can block there,
hanging the incoming migration.

The string only fills the cosmetic product_desc, so skip the read when
we're the CPR target and fall back to the synthetic "host:bus.addr"
name.

Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
---
 hw/usb/host-libusb.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c
index 8483df0d12c..3c760e9f8f1 100644
--- a/hw/usb/host-libusb.c
+++ b/hw/usb/host-libusb.c
@@ -1077,7 +1077,7 @@ static int usb_host_open(USBHostDevice *s, libusb_device *dev, int hostfd)
     udev->speed = speed_map[libusb_speed];
     usb_host_speed_compat(s);
 
-    if (s->ddesc.iProduct) {
+    if (s->ddesc.iProduct && !cpr_is_incoming()) {
         libusb_get_string_descriptor_ascii(s->dh, s->ddesc.iProduct,
                                            (unsigned char *)udev->product_desc,
                                            sizeof(udev->product_desc));
-- 
2.47.1


^ permalink raw reply	[relevance 26%]

* [QEMU HCI-8.0 PATCH v2 09/13] usb-host: hand off the device across cpr-transfer #VSTOR-137800
                     ` (6 preceding siblings ...)
  2026-09-04 19:04 26% ` [QEMU HCI-8.0 PATCH v2 08/13] usb-host: skip product-string read on CPR incoming #VSTOR-137800 Andrey Drobyshev
@ 2026-09-04 19:04 24% ` Andrey Drobyshev
  2026-09-04 19:04  7% ` [QEMU HCI-8.0 PATCH v2 11/13] usb-host: reconfigure endpoints on CPR incoming #VSTOR-137800 Andrey Drobyshev
  2026-09-04 19:04 24% ` [QEMU HCI-8.0 PATCH v2 13/13] usb-host: make CPR migration blocker conditional #VSTOR-137800 Andrey Drobyshev
  9 siblings, 0 replies; 119+ results
From: Andrey Drobyshev @ 2026-09-04 19:04 UTC (permalink / raw)
  To: svt-core; +Cc: andrey.drobyshev, den

Unlike cpr-exec, cpr-transfer keeps the source QEMU alive: it hands the
usbfs FD to the new QEMU over the migration channel and only exits once
the target is up.  While the handoff is in flight both processes hold the
same FD, and only one may reap URB events on it - if both do, one reaps
the other's in-flight completions and wedges an isochronous transfer that
cannot be retried.  So reaping is handed off along with the FD:

  * The target doesn't reap until it takes over: it stops events right
    after opening the FD in .realize() and restarts in .post_load().
  * The source stops reaping in .pre_save() and, since it stays alive,
    restarts from the MIG_EVENT_PRECOPY_FAILED notifier on failure.
  * On success the source's .exit() leaves the device untouched - no
    reset, interface release or host-driver rebind - and just closes its
    own copy of the FD.

Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
---
 hw/usb/host-libusb.c | 78 +++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 74 insertions(+), 4 deletions(-)

diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c
index 3c760e9f8f1..59f2b151cd2 100644
--- a/hw/usb/host-libusb.c
+++ b/hw/usb/host-libusb.c
@@ -110,6 +110,7 @@ struct USBHostDevice {
     NotifierWithReturn               cpr_notifier;
     GSList                           *cpr_inflight;
     bool                             cpr_drained;
+    bool                             cpr_handed_off;
     libusb_device                    *dev;
     libusb_device_handle             *dh;
     struct libusb_device_descriptor  ddesc;
@@ -1275,10 +1276,17 @@ static void usb_host_exit_notifier(struct Notifier *n, void *data)
     USBHostDevice *s = container_of(n, USBHostDevice, exit);
 
     if (s->dh) {
-        usb_host_abort_xfers(s);
-        usb_host_release_interfaces(s);
-        libusb_reset_device(s->dh);
-        usb_host_attach_kernel(s);
+        /*
+         * Handed to new QEMU across cpr-transfer: do not reset, release
+         * interfaces or rebind the host driver underneath it; just close
+         * our handle.
+         */
+        if (!s->cpr_handed_off) {
+            usb_host_abort_xfers(s);
+            usb_host_release_interfaces(s);
+            libusb_reset_device(s->dh);
+            usb_host_attach_kernel(s);
+        }
         libusb_close(s->dh);
     }
 }
@@ -1310,6 +1318,9 @@ static void usb_host_handle_data(USBDevice *udev, USBPacket *p);
  * from the packet alone; complete them as errors and let the guest
  * driver retry.
  */
+static void usb_host_cpr_stop_events(void);
+static void usb_host_cpr_restart_events(void);
+
 static int usb_host_cpr_notifier(NotifierWithReturn *notifier,
                                  MigrationEvent *e, Error **errp)
 {
@@ -1322,6 +1333,11 @@ static int usb_host_cpr_notifier(NotifierWithReturn *notifier,
         return 0;
     }
 
+    if (s->cpr_handed_off) {
+        usb_host_cpr_restart_events();
+        s->cpr_handed_off = false;
+    }
+
     for (it = s->cpr_inflight; it; it = it->next) {
         p = it->data;
         /* Replay from the start of the TD */
@@ -1395,6 +1411,14 @@ static void usb_host_realize(USBDevice *udev, Error **errp)
             error_setg(errp, "failed to open host usb device %s", s->hostdevice);
             return;
         }
+        if (cpr_is_incoming()) {
+            /*
+             * The source still owns the shared fd until it hands off.  Don't
+             * reap URBs on it yet, or we would steal the source's in-flight
+             * completions; .post_load() restarts events once we take over.
+             */
+            usb_host_cpr_stop_events();
+        }
     } else
 #endif
     if (s->match.addr && s->match.bus_num &&
@@ -1943,6 +1967,7 @@ static int usb_host_post_load(void *opaque, int version_id)
      * Skip it for CPR.
      */
     if (cpr_is_incoming()) {
+        usb_host_cpr_restart_events();
         return 0;
     }
 
@@ -1955,6 +1980,47 @@ static int usb_host_post_load(void *opaque, int version_id)
     return 0;
 }
 
+#ifndef CONFIG_WIN32
+
+/*
+ * cpr-transfer: the usbfs fd is shared with the new QEMU via SCM_RIGHTS.
+ * Stop this (source) process from reaping URBs on it, so the new QEMU can
+ * drive the device without both processes racing on the same fd.  Restart
+ * on migration failure, when the source resumes.
+ */
+static void usb_host_cpr_stop_events(void)
+{
+    const struct libusb_pollfd **poll = libusb_get_pollfds(ctx);
+
+    libusb_set_pollfd_notifiers(ctx, NULL, NULL, NULL);
+    if (poll) {
+        for (int i = 0; poll[i] != NULL; i++) {
+            usb_host_del_fd(poll[i]->fd, ctx);
+        }
+        free(poll);
+    }
+}
+
+static void usb_host_cpr_restart_events(void)
+{
+    const struct libusb_pollfd **poll = libusb_get_pollfds(ctx);
+
+    libusb_set_pollfd_notifiers(ctx, usb_host_add_fd, usb_host_del_fd, ctx);
+    if (poll) {
+        for (int i = 0; poll[i] != NULL; i++) {
+            usb_host_add_fd(poll[i]->fd, poll[i]->events, ctx);
+        }
+        free(poll);
+    }
+}
+
+#else
+
+static void usb_host_cpr_stop_events(void) {}
+static void usb_host_cpr_restart_events(void) {}
+
+#endif
+
 static int usb_host_pre_save(void *opaque)
 {
     USBHostDevice *s = opaque;
@@ -1966,6 +2032,10 @@ static int usb_host_pre_save(void *opaque)
         if (usb_host_cpr_drain_xfers(s) < 0) {
             return -1;
         }
+        if (mode == MIG_MODE_CPR_TRANSFER) {
+            usb_host_cpr_stop_events();
+            s->cpr_handed_off = true;
+        }
     }
     return 0;
 }
-- 
2.47.1


^ permalink raw reply	[relevance 24%]

* [QEMU HCI-8.0 PATCH v2 13/13] usb-host: make CPR migration blocker conditional #VSTOR-137800
                     ` (8 preceding siblings ...)
  2026-09-04 19:04  7% ` [QEMU HCI-8.0 PATCH v2 11/13] usb-host: reconfigure endpoints on CPR incoming #VSTOR-137800 Andrey Drobyshev
@ 2026-09-04 19:04 24% ` Andrey Drobyshev
  9 siblings, 0 replies; 119+ results
From: Andrey Drobyshev @ 2026-09-04 19:04 UTC (permalink / raw)
  To: svt-core; +Cc: andrey.drobyshev, den

A previous commit added an unconditional blocker for CPR migrations.
That is to refuse CPR gracefully instead of crashing and for the previous
commits to remain bisectable.

Now the restore path is complete: the FD is preserved, in-flight URBs
are drained and replayed, the device is handed off across cpr-transfer,
and the endpoints and isochronous streams are reconstructed on the
target.  So let's lift the CPR blocker for the devices which satisfy the
preconditions.

Still, the remaining preconditions for CPR to work are:

  * We must open host device FD ourselves, so that libusb owns the FD.
    This way we're able to preserve the FD during CPR;
  * Device ID must be provided as a unique stable key for CPR FD registry.

Use them to guard the blocker addition.  Apart from that, prohibit CPR on
WIN32 platform and on hosts with older libusb versions.

Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
---
 hw/usb/host-libusb.c | 50 +++++++++++++++++++++++++++++++-------------
 1 file changed, 35 insertions(+), 15 deletions(-)

diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c
index d12e04c2768..da0ff801661 100644
--- a/hw/usb/host-libusb.c
+++ b/hw/usb/host-libusb.c
@@ -1449,24 +1449,44 @@ static void usb_host_realize(USBDevice *udev, Error **errp)
     s->exit.notify = usb_host_exit_notifier;
     qemu_add_exit_notifier(&s->exit);
 
-#if LIBUSB_API_VERSION >= 0x01000107 && !defined(CONFIG_WIN32)
-    if (s->hostdevice && DEVICE(s)->id) {
-        migration_add_notifier_modes(&s->cpr_notifier, usb_host_cpr_notifier,
-                                     MIG_MODE_CPR_TRANSFER,
-                                     MIG_MODE_CPR_EXEC, -1);
+#ifdef CONFIG_WIN32
+    error_setg(&s->cpr_blocker,
+               "usb-host device %s does not support CPR: "
+               "WIN32 platform is not supported",
+               DEVICE(s)->id ?: "(anonymous)");
+#elif LIBUSB_API_VERSION < 0x01000107
+    error_setg(&s->cpr_blocker,
+               "usb-host device %s does not support CPR: "
+               "libusb API version 0x%08x is too old",
+               DEVICE(s)->id ?: "(anonymous)", (unsigned) LIBUSB_API_VERSION);
+#else
+    if (!(s->hostdevice && DEVICE(s)->id)) {
+        /*
+         * Host FD must be owned by libusb to support CPR migration.  That's
+         * equivalent to hostdevice= property being present.   Also device ID
+         * is required as stable key for CPR FD registry.
+         */
+        error_setg(&s->cpr_blocker,
+                   "usb-host device %s does not support CPR: "
+                   "hostdevice= and a device id are required",
+                   DEVICE(s)->id ?: "(anonymous)");
     }
 #endif
 
-    error_setg(&s->cpr_blocker, "usb-host device %s does not support CPR: ",
-               DEVICE(s)->id ?: "(anonymous)");
-    if (migrate_add_blocker_modes(&s->cpr_blocker, errp,
-                                  MIG_MODE_CPR_TRANSFER,
-                                  MIG_MODE_CPR_EXEC, -1) < 0) {
-        qemu_remove_exit_notifier(&s->exit);
-        if (s->needs_autoscan) {
-            QTAILQ_REMOVE(&hostdevs, s, next);
-        }
-        usb_host_close(s);
+    if (s->cpr_blocker) {
+        if (migrate_add_blocker_modes(&s->cpr_blocker, errp,
+                                      MIG_MODE_CPR_TRANSFER,
+                                      MIG_MODE_CPR_EXEC, -1) < 0) {
+            qemu_remove_exit_notifier(&s->exit);
+            if (s->needs_autoscan) {
+                QTAILQ_REMOVE(&hostdevs, s, next);
+            }
+            usb_host_close(s);
+        }
+    } else {
+        migration_add_notifier_modes(&s->cpr_notifier, usb_host_cpr_notifier,
+                                     MIG_MODE_CPR_TRANSFER,
+                                     MIG_MODE_CPR_EXEC, -1);
     }
 }
 
-- 
2.47.1


^ permalink raw reply	[relevance 24%]

* [QEMU HCI-8.0 PATCH v2 11/13] usb-host: reconfigure endpoints on CPR incoming #VSTOR-137800
                     ` (7 preceding siblings ...)
  2026-09-04 19:04 24% ` [QEMU HCI-8.0 PATCH v2 09/13] usb-host: hand off the device across cpr-transfer #VSTOR-137800 Andrey Drobyshev
@ 2026-09-04 19:04  7% ` Andrey Drobyshev
  2026-09-04 19:04 24% ` [QEMU HCI-8.0 PATCH v2 13/13] usb-host: make CPR migration blocker conditional #VSTOR-137800 Andrey Drobyshev
  9 siblings, 0 replies; 119+ results
From: Andrey Drobyshev @ 2026-09-04 19:04 UTC (permalink / raw)
  To: svt-core; +Cc: andrey.drobyshev, den

On CPR incoming we skip the usb_host_open() rescan, so libusb only sets
up the endpoints from the device's default configuration.  If the guest
had enabled an endpoint by selecting a non-zero altsetting (e.g. an
isochronous streaming endpoint), that endpoint is now missing, and the
guest's transfers to it simply stall.

The altsetting itself is restored by vmload now, so let's call
usb_host_ep_update() on the CPR target to rebuild the endpoints out of
it.

The rescan is also the only place where we claim the interfaces in
libusb.  The kernel claims are still there on the preserved FD, but
libusb's own handle knows nothing about them, so a later SET_INTERFACE
would fail with LIBUSB_ERROR_NOT_FOUND.  Let's re-claim the interfaces on
the target as well to bring libusb's view back in sync - on the
preserved FD this is a no-op at the usbfs level anyway.

Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
---
 hw/usb/host-libusb.c | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c
index 59f2b151cd2..d12e04c2768 100644
--- a/hw/usb/host-libusb.c
+++ b/hw/usb/host-libusb.c
@@ -1956,6 +1956,34 @@ static void usb_host_post_load_bh(void *opaque)
     usb_host_auto_check(NULL);
 }
 
+/*
+ * On the CPR target libusb knows nothing about the interface claims the
+ * preserved fd still holds, so a later SET_INTERFACE would fail with
+ * NOT_FOUND.  Re-claim them to bring libusb's view back in sync; on the
+ * preserved fd it's a no-op at the usbfs level anyway.
+ */
+static void usb_host_cpr_reclaim_interfaces(USBHostDevice *s)
+{
+    USBDevice *udev = USB_DEVICE(s);
+    struct libusb_config_descriptor *conf;
+    int i, n;
+
+    if (libusb_get_active_config_descriptor(s->dev, &conf) != 0) {
+        return;
+    }
+
+    n = MIN(conf->bNumInterfaces, USB_MAX_INTERFACES);
+    for (i = 0; i < n; i++) {
+        if (libusb_claim_interface(s->dh, i) == 0) {
+            s->ifs[i].claimed = true;
+        }
+    }
+
+    udev->ninterfaces = conf->bNumInterfaces;
+    udev->configuration = conf->bConfigurationValue;
+    libusb_free_config_descriptor(conf);
+}
+
 static int usb_host_post_load(void *opaque, int version_id)
 {
     USBHostDevice *dev = opaque;
@@ -1967,6 +1995,13 @@ static int usb_host_post_load(void *opaque, int version_id)
      * Skip it for CPR.
      */
     if (cpr_is_incoming()) {
+        /*
+         * We kept the device open across CPR, so libusb still needs its
+         * interface claims and endpoints rebuilt here.  And we only start
+         * reaping now, once the source has handed the fd off to us.
+         */
+        usb_host_cpr_reclaim_interfaces(dev);
+        usb_host_ep_update(dev);
         usb_host_cpr_restart_events();
         return 0;
     }
-- 
2.47.1


^ permalink raw reply	[relevance 7%]

Results 1-119 of 119 | reverse | sort options + mbox downloads above
-- links below jump to the message on this page --
2026-08-02 11:40     [Devel] [PATCH VZ10 v5 0/9] Add per-VE failcount support Vladimir Riabchun
2026-08-02 11:40  3% ` [Devel] [PATCH VZ10 v5 3/9] ve/fs: Rework per-ve mount count Vladimir Riabchun
2026-08-02 11:40  9% ` [Devel] [PATCH VZ10 v5 7/9] ve: Introduce per-VE failcount Vladimir Riabchun
2026-08-07  9:25  0%   ` Vasileios Almpanis
2026-08-02 11:40  7% ` [Devel] [PATCH VZ10 v5 8/9] selftests/ve: Add more helpers Vladimir Riabchun
2026-08-02 11:40  6% ` [Devel] [PATCH VZ10 v5 9/9] selftests/ve: Add mount accounting selftest Vladimir Riabchun
2026-08-07 10:01  0%   ` Vasileios Almpanis
     [not found]     <20260630160639.3043321-1-eva.kurchatova@virtuozzo.com>
2026-08-04 11:15  9% ` [Devel] [PATCH vz10 v2] ve/vtty: fix use-after-free on concurrent tty close and reopen Vasileios Almpanis
2026-08-04 12:53  3% [Devel] [PATCH vz10 v8 1/1] fs: enforce container device-mount policy in the common mount path Vasileios Almpanis
2026-08-05 16:41  0% ` Pavel Tikhomirov
2026-08-06 15:49  4% ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
     [not found]     <20260731155337.1209007-1-den@openvz.org>
2026-08-05 11:20  4% ` [Devel] [PATCH RHEL10 COMMIT] ms/qede: sync udp_tunnel ports outside qede_lock in the recovery path Konstantin Khorenko
     [not found]     <20260706110002.1024515-1-khorenko@virtuozzo.com>
     [not found]     ` <20260706110002.1024515-8-khorenko@virtuozzo.com>
     [not found]       ` <970b9d91-59c9-4a6d-8494-2d58afcab4b8@virtuozzo.com>
2026-08-05 19:26  0%     ` [Devel] [PATCH vz10 07/24] blk-cbt: don't WARN on a user-supplied ABI version mismatch Konstantin Khorenko
     [not found]     <20260706110002.1024515-17-khorenko@virtuozzo.com>
2026-08-05 20:05  7% ` [Devel] [PATCH RHEL10 COMMIT] drivers/base/cpu: fix cpu/offline content inside a ve Konstantin Khorenko
     [not found]     <20260706110002.1024515-19-khorenko@virtuozzo.com>
2026-08-05 20:09  5% ` [Devel] [PATCH RHEL10 COMMIT] ve: fix NULL-deref / use-after-free in ve_create() error unwind Konstantin Khorenko
2026-08-09 18:18     [Devel] [PATCH vz10] selftests: build test modules against the kernel tree Eva Kurchatova
2026-08-10 16:29  4% ` Konstantin Khorenko
2026-08-11 11:49 18% [Devel] [PATCH VZ10] x86/bugs: Make Safe-RET robust against interrupt injection Pavel Tikhomirov
2026-08-11 14:48 17% ` [Devel] [PATCH RHEL10 COMMIT] ms/x86/bugs: " Konstantin Khorenko
2026-08-12 13:03     [Devel] [PATCH DRAFT vz10 0/5] Enable GRE ERSPAN inside Containers Konstantin Khorenko
2026-08-12 13:03  4% ` [Devel] [PATCH DRAFT vz10 1/5] Revert "ve/net/gre: Disable ERSPAN support in ip_gre module" Konstantin Khorenko
2026-08-12 20:19     [Devel] [PATCH VZ10 v2 00/10] dm-qcow2: improve discard and read-only merge handling Andrey Zhadchenko
2026-08-12 20:19     ` [Devel] [PATCH VZ10 v2 01/10] drivers/md/dm-qcow2: fix revert_cluster_alloc() for ext_l2 case Andrey Zhadchenko
2026-08-19 10:27  5%   ` Pavel Tikhomirov
2026-08-17  7:16  3% [Devel] [PATCH vz10 0/3] ve/fs: make mount ownership follow the mount namespace Mirian Shilakadze
2026-08-17  7:16  6% ` [Devel] [PATCH vz10 1/3] ve/fs: unlink the mount namespace on the copy_mnt_ns() error path Mirian Shilakadze
2026-08-26 15:38  5%   ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
2026-08-18 10:40  0% ` [Devel] [PATCH vz10 0/3] ve/fs: make mount ownership follow the mount namespace Vasileios Almpanis
2026-08-18 15:09     [Devel] [PATCH VZ10 0/5] vhost-blk: fix protocol handling and backend setup Andrey Zhadchenko
2026-08-18 15:09  9% ` [Devel] [PATCH VZ10 5/5] drivers/vhost/blk: rework queue/backend setup Andrey Zhadchenko
     [not found]     <20260625181637.1555685-1-eva.kurchatova@virtuozzo.com>
     [not found]     ` <20260625181637.1555685-3-eva.kurchatova@virtuozzo.com>
2026-08-18 16:53  5%   ` [Devel] [PATCH vz10 3/5] fixup! vhost/vsock: only refuse connection when guest has never been ready Konstantin Khorenko
2026-08-19  9:07     [Devel] [PATCH VZ10 v6 0/9] Add per-VE failcount support Vladimir Riabchun
2026-08-19  9:07  3% ` [Devel] [PATCH VZ10 v6 3/9] ve/fs: Rework per-ve mount count Vladimir Riabchun
2026-08-19 13:16  0%   ` Vasileios Almpanis
2026-08-19  9:07  9% ` [Devel] [PATCH VZ10 v6 7/9] ve: Introduce per-VE failcount Vladimir Riabchun
2026-08-19 13:16  0%   ` Vasileios Almpanis
2026-08-19  9:07  7% ` [Devel] [PATCH VZ10 v6 8/9] selftests/ve: Add more helpers Vladimir Riabchun
2026-08-19  9:07  6% ` [Devel] [PATCH VZ10 v6 9/9] selftests/ve: Add mount accounting selftest Vladimir Riabchun
2026-08-19 13:16  0%   ` Vasileios Almpanis
     [not found]     <20260626113435.2210877-1-eva.kurchatova@virtuozzo.com>
     [not found]     ` <4492431c-87f8-4de6-9221-4e57107113bc@virtuozzo.com>
2026-08-19 14:12  0%   ` [Devel] [PATCH vz10] selftests/damon: add script dir to sys.path for PYTHONSAFEPATH compatibility Konstantin Khorenko
2026-08-19 14:16  4% ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
     [not found]     <20260706110002.1024515-9-khorenko@virtuozzo.com>
2026-08-19 16:25 16% ` [Devel] [PATCH vz10 v2] ve: downgrade the trusted exec/mmap denial from WARN to pr_warn Konstantin Khorenko
2026-08-26 16:49  0%   ` Konstantin Khorenko
2026-08-21 15:03     [Devel] [PATCH vz10] selftests/uevent: give the netlink socket a usable receive buffer Eva Kurchatova
2026-08-24 13:09  4% ` [Devel] [PATCH vz10 v2] selftests/uevent: do not fail on a netlink receive buffer overrun Konstantin Khorenko
2026-08-26 16:49  0%   ` Konstantin Khorenko
2026-08-26 19:32  0%   ` Eva Kurchatova (Virtuozzo)
2026-08-27 12:28  4%   ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
2026-08-21 15:13 15% [Devel] [PATCH vz10] selftests/damon: wait for the merge to apply the new max_nr_regions Eva Kurchatova
2026-08-31 23:22  0% ` Eva Kurchatova (Virtuozzo)
2026-08-21 15:32     [Devel] [PATCH vz10] selftests: ve_printk: match the conntrack overflow message Eva Kurchatova
2026-08-24 14:53  4% ` [Devel] [PATCH vz10 v2] " Konstantin Khorenko
2026-08-24 14:54  4%   ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
2026-08-21 16:36     [Devel] [PATCH vz10 00/32] Fix the VZ kernel build so that KUnit can run Konstantin Khorenko
2026-08-21 16:36  6% ` [Devel] [PATCH vz10 10/32] mm, proc: build the /proc/meminfo virtualization only with CONFIG_VE Konstantin Khorenko
2026-08-21 16:42  5%   ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
2026-08-21 16:37 11% ` [Devel] [PATCH vz10 29/32] ms/pcmcia: cistpl: Constify 'struct bin_attribute' Konstantin Khorenko
2026-08-21 16:42 11%   ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
2026-08-21 16:37  5% ` [Devel] [PATCH vz10 32/32] kunit: add the script dir to sys.path for PYTHONSAFEPATH compatibility Konstantin Khorenko
2026-08-21 16:42  4%   ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
2026-08-21 16:41  5% [Devel] [PATCH vz10 1/3] selftests: net: Adapt ethtool mq tests to fix in qdisc graft Eva Kurchatova
2026-08-24 13:36  0% ` Konstantin Khorenko
2026-08-24 14:36  5% ` [Devel] [PATCH RHEL10 COMMIT] ms/selftests: " Konstantin Khorenko
2026-08-24 13:54     [Devel] [PATCH VZ10 v7 0/9] Add per-VE failcount support Vladimir Riabchun
2026-08-24 13:54  3% ` [Devel] [PATCH VZ10 v7 3/9] ve/fs: Rework per-ve mount count Vladimir Riabchun
2026-08-24 13:54  9% ` [Devel] [PATCH VZ10 v7 7/9] ve: Introduce per-VE failcount Vladimir Riabchun
2026-08-28 16:52  5%   ` Pavel Tikhomirov
2026-08-28 17:32  0%     ` Vladimir Riabchun
2026-08-24 13:54  7% ` [Devel] [PATCH VZ10 v7 8/9] selftests/ve: Add more helpers Vladimir Riabchun
2026-08-24 13:54  6% ` [Devel] [PATCH VZ10 v7 9/9] selftests/ve: Add mount accounting selftest Vladimir Riabchun
2026-08-24 14:36  5% [Devel] [PATCH RHEL10 COMMIT] ms/selftests: net: Adapt ethtool mq tests to fix in qdisc graft Konstantin Khorenko
2026-08-24 15:59     [Devel] [PATCH VZ10 v2 0/5] vhost-blk: fix protocol handling and backend setup Andrey Zhadchenko
2026-08-24 15:59  9% ` [Devel] [PATCH VZ10 v2 5/5] drivers/vhost/blk: rework queue/backend setup Andrey Zhadchenko
2026-08-25 12:51     [Devel] [PATCH VZ10 v3 0/5] vhost-blk: fix protocol handling and backend setup Andrey Zhadchenko
2026-08-25 12:51  9% ` [Devel] [PATCH VZ10 v3 5/5] drivers/vhost/blk: rework queue/backend setup Andrey Zhadchenko
2026-08-25 13:57  9%   ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
2026-08-25 13:29     [Devel] [PATCH vz10 0/2] fs/kernfs, ve: stop a container lookup unmounting host filesystems Mirian Shilakadze
2026-08-25 13:29  7% ` [Devel] [PATCH vz10 2/2] selftests/ve: check that hiding an entry does not unmount it Mirian Shilakadze
2026-08-25 15:40  0%   ` Pavel Tikhomirov
2026-08-26 11:04     [Devel] [PATCH vz10 v2 0/2] fs/kernfs, ve: stop a container lookup unmounting host filesystems Mirian Shilakadze
2026-08-26 11:04  6% ` [Devel] [PATCH vz10 v2 2/2] selftests/ve: check that hiding an entry does not unmount it Mirian Shilakadze
2026-08-26 16:15  6%   ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
2026-08-26 14:44  6% [Devel] [PATCH VZ10] b4: ship project defaults in .b4-config Vasileios Almpanis
2026-08-26 15:15  0% ` Pavel Tikhomirov
2026-08-26 16:42  6% ` [Devel] [PATCH RHEL10 COMMIT] " Konstantin Khorenko
2026-08-27 12:37  4% [Devel] [PATCH VZ10] fs/fuse kio: track pending kRPC connect via state machine only Liu Kui
2026-08-28 17:13  0% ` Konstantin Khorenko
2026-09-01 20:59  0% ` Konstantin Khorenko
2026-08-28 11:15 14% [PATCH QEMU HCI-8.0] b4: ship project defaults in .b4-config Vasileios Almpanis
2026-08-28 11:16 14% Vasileios Almpanis
2026-08-28 11:40  5% ` Andrey Drobyshev
2026-08-31 15:16  4% [Devel] [PATCH 1/1] ms/virtio_ring: fix infinite loop in virtnet_poll_cleantx when device is broken Denis V. Lunev
2026-08-31 22:44  5% [Devel] [PATCH vz10] ms/selftests/sched_ext: flush stdout before test to avoid log spam Eva Kurchatova
2026-08-31 22:45  5% [Devel] [PATCH vz10 1/3] ms/selftests/posix_timers: Use CLOCK_THREAD_CPUTIME_ID for ITIMER_PROF measurements Eva Kurchatova
2026-08-31 22:48     [Devel] [PATCH vz10 1/7] ms/vxlan: do not reuse cached ip_hdr() value after skb_tunnel_check_pmtu() Eva Kurchatova
2026-08-31 22:48  3% ` [Devel] [PATCH vz10 3/7] selftests: net: skip what this kernel and iproute2 do not have Eva Kurchatova
2026-08-31 22:48 19% ` [Devel] [PATCH vz10 7/7] selftests: net: let the bridged PMTU tests take the ICMP they ask for Eva Kurchatova
2026-08-31 23:13     [Devel] [PATCH vz10 v2 1/2] ms/selftests/damon/damon_nr_regions: set ops update for merge results check to 100ms Eva Kurchatova
2026-08-31 23:13  5% ` [Devel] [PATCH vz10 v2 2/2] ms/selftests/damon/damon_nr_regions: sort collected regiosn before checking with min/max boundaries Eva Kurchatova
2026-08-31 23:43     [Devel] [PATCH vz10 1/3] selftests: drv-net: let NetDrvEnv take nsim_test Eva Kurchatova
2026-08-31 23:43  9% ` [Devel] [PATCH vz10 3/3] selftests: drv-net: read the channel count over netlink Eva Kurchatova
2026-08-31 23:47  4% [Devel] [PATCH vz10] selftests: pci_endpoint: skip when the test device is absent Eva Kurchatova
2026-09-02 14:57  5% ` Konstantin Khorenko
2026-08-31 23:48  5% [Devel] [PATCH vz10] selftests: pstore: skip when no backend is registered Eva Kurchatova
2026-09-02 14:29  0% ` Konstantin Khorenko
2026-09-01  0:41  4% [Devel] [PATCH vz10] selftests: dma-buf: skip the huge page test without huge pages Eva Kurchatova
2026-09-03 12:31     [QEMU HCI-8.0 PATCH 0/5] vhost-blk change backend setup Andrey Zhadchenko
2026-09-03 12:32 29% ` [QEMU HCI-8.0 PATCH 2/5] vhost-blk: " Andrey Zhadchenko
2026-09-03 14:56  0%   ` Andrey Drobyshev
2026-09-03 15:27  0%     ` Andrey Zhadchenko
2026-09-03 15:34  0%       ` Andrey Drobyshev
2026-09-03 12:32 31% ` [QEMU HCI-8.0 PATCH 3/5] vhost-blk: add read-only flag Andrey Zhadchenko
2026-09-03 14:56  8%   ` Andrey Drobyshev
2026-09-03 12:32 23% ` [QEMU HCI-8.0 PATCH 4/5] vhost-blk: watch the device for resize events Andrey Zhadchenko
2026-09-03 12:32  4% ` [QEMU HCI-8.0 PATCH 5/5] vhost-blk: filter uevents in the kernel Andrey Zhadchenko
2026-09-03 20:25     [QEMU HCI-8.0 PATCH 0/7] qxl cursor use-after-free plus stability backports #VSTOR-144000 Denis V. Lunev
2026-09-03 20:25 10% ` [QEMU HCI-8.0 PATCH 3/7] hw/display/qxl: Fix mono cursor validation that can read past a cursor chunk #VSTOR-144000 Denis V. Lunev
2026-09-03 20:25  4% ` [QEMU HCI-8.0 PATCH 4/7] hw/display/qxl: fix TOCTOU in cursor chunk data_size handling #VSTOR-144000 Denis V. Lunev
2026-09-03 20:25  9% ` [QEMU HCI-8.0 PATCH 7/7] hw/display/qxl: validate primary surface stride against width #VSTOR-144000 Denis V. Lunev
2026-09-04 13:21     [QEMU HCI-8.0 PATCH v2 0/5] vhost-blk change backend setup Andrey Zhadchenko
2026-09-04 13:21 21% ` [QEMU HCI-8.0 PATCH v2 1/5] vhost-blk: do not double close vhostfd Andrey Zhadchenko
2026-09-04 15:33  8%   ` Andrey Drobyshev
2026-09-04 13:21 28% ` [QEMU HCI-8.0 PATCH v2 2/5] vhost-blk: change backend setup Andrey Zhadchenko
2026-09-04 15:33  0%   ` Andrey Drobyshev
2026-09-04 13:21 31% ` [QEMU HCI-8.0 PATCH v2 3/5] vhost-blk: add read-only flag Andrey Zhadchenko
2026-09-04 15:33  0%   ` Andrey Drobyshev
2026-09-04 13:21 23% ` [QEMU HCI-8.0 PATCH v2 4/5] vhost-blk: watch the device for resize events Andrey Zhadchenko
2026-09-04 15:33  7%   ` Andrey Drobyshev
2026-09-04 13:21 32% ` [QEMU HCI-8.0 PATCH v2 5/5] vhost-blk: preserve the uevent socket across cpr-exec Andrey Zhadchenko
2026-09-04 15:33  0%   ` Andrey Drobyshev
2026-09-04 19:04     [QEMU HCI-8.0 PATCH v2 00/13] usb-host: support CPR migration Andrey Drobyshev
2026-09-04 19:04 35% ` [QEMU HCI-8.0 PATCH v2 02/13] usb-host: don't leak hostdev FD on open failure #VSTOR-137800 Andrey Drobyshev
2026-09-04 19:04 15% ` [QEMU HCI-8.0 PATCH v2 03/13] usb-host: add migration blocker for CPR modes #VSTOR-137800 Andrey Drobyshev
2026-09-04 19:04 30% ` [QEMU HCI-8.0 PATCH v2 04/13] usb-host: preserve hostdev FD during CPR migration #VSTOR-137800 Andrey Drobyshev
2026-09-04 19:04 30% ` [QEMU HCI-8.0 PATCH v2 05/13] usb-host: factor out usb_host_reap_xfers() #VSTOR-137800 Andrey Drobyshev
2026-09-04 19:04 20% ` [QEMU HCI-8.0 PATCH v2 06/13] usb-host: drain in-flight URBs across CPR #VSTOR-137800 Andrey Drobyshev
2026-09-04 19:04 25% ` [QEMU HCI-8.0 PATCH v2 07/13] usb-host: re-issue drained URBs if CPR is aborted #VSTOR-137800 Andrey Drobyshev
2026-09-04 19:04 26% ` [QEMU HCI-8.0 PATCH v2 08/13] usb-host: skip product-string read on CPR incoming #VSTOR-137800 Andrey Drobyshev
2026-09-04 19:04 24% ` [QEMU HCI-8.0 PATCH v2 09/13] usb-host: hand off the device across cpr-transfer #VSTOR-137800 Andrey Drobyshev
2026-09-04 19:04  7% ` [QEMU HCI-8.0 PATCH v2 11/13] usb-host: reconfigure endpoints on CPR incoming #VSTOR-137800 Andrey Drobyshev
2026-09-04 19:04 24% ` [QEMU HCI-8.0 PATCH v2 13/13] usb-host: make CPR migration blocker conditional #VSTOR-137800 Andrey Drobyshev

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.