All Virtuozzo development lists (kernel + QEMU)
 help / color / mirror / Atom feed
* [Devel] [PATCH hci-8.1 v6 0/2] selftests: ve: fix ve_perms, introduce vtty_stress
@ 2026-10-02  4:43 Eva Kurchatova
  2026-10-02  4:43 ` [Devel] [PATCH hci-8.1 v6 1/2] selftests: ve: grant the directories the files sit in Eva Kurchatova
  2026-10-02  4:43 ` [Devel] [PATCH hci-8.1 v6 2/2] selftests: ve: introduce vtty pair teardown stress test Eva Kurchatova
  0 siblings, 2 replies; 5+ messages in thread
From: Eva Kurchatova @ 2026-10-02  4:43 UTC (permalink / raw)
  To: khorenko; +Cc: devel

Patchset for tasks:
https://virtuozzo.atlassian.net/browse/VSTOR-144955
https://virtuozzo.atlassian.net/browse/VSTOR-144771

Should be merged in this order, since without the ve fix, the testsuite
will not pass at all.
Changes in v6:

2/2:
- Ask the kernel whether the veid is taken before writing ve.veid, and
  skip when it is, instead of assuming the 108..200 range is free.
- Skip when the slave node lands on a nodev mount, where mknod()
  succeeds and every open fails with EACCES.
- kmsg_check() still fails on any WARNING, BUG or Oops record, as the
  v2 review asked: the node runs nothing else during the test.

Link to v5: http://lore.virtuozzo.com/kernel/20261001032531.1307025-1-eva.kurchatova@virtuozzo.com/T/#t

Changes in v5:

2/2:
- Rebased.
- Drop the vendored uapi structure definition.
- Use newly introduced VE test helpers instead of hand-rolled ones.
- Check subtree_control write, skip if needed.
- Report child exit status alongside "The VE side never came up".

Link to v4: http://lore.virtuozzo.com/kernel/5fc7cab9-c486-4568-8034-693d756b1050@virtuozzo.com/T/#t

Changes in v4:

1/2:
- Check the snprintf() in set_entry() against sizeof() and give the
  buffer PATH_MAX, similar to how ve_open_rel() already operates.
- Correct comments.
- Fix the Feature tag.

2/2:
- Move the double release_tty() history out of the file header into
  the message. The header keeps what the test does, how it reaches
  both ends, and the usage line.
- Match "general protection fault" records as well, they carry no
  "Oops:" prefix.
- Reason about the test reproduction time in the commit message.
- Document that the test assumes its veid is not in use by a
  running container in the commit message.
- Default to 30 seconds and drop the settings file, the run now fits
  the timeout the suite already has.

Link to v3: https://lists.openvz.org/pipermail/devel/2026-September/085277.html

Changes in v3:

1/2:
- Code unchanged from v2. Only the test count and the sentence about
  the proc variant were dropped from the message; the rest of the v2
  review is addressed in v4.

2/2:
- Corrections in commit message and comments.
- Bail out on the first open errno that is not EBUSY, EIO or ENXIO,
  on both ends, and report it, instead of spinning erroneously.
- Match /dev/kmsg one record at a time.
- Keep reading /dev/kmsg after EPIPE instead of stopping on it.
- Put the slave node under a private mkdtemp() directory instead of a
  fixed name in /tmp.
- Kill the slave side when the master side gives up, with
  PR_SET_PDEATHSIG as the backstop, so it does not hold the cgroup
  open against destroy_ve().
- Call destroy_ve() on the one exit path that skipped it.
- Default to 60 seconds, from 600.

Link to v2: https://lists.openvz.org/pipermail/devel/2026-September/085125.html

Changes in v2:

1/2:
- Add the Fixes: tag, the code is unchanged.

2/2:
- Add the binary to .gitignore.
- Formatting: single spaces, one initialiser per line.

Link to v1: https://lists.openvz.org/pipermail/devel/2026-September/085081.html

Eva Kurchatova (2):
  selftests: ve: grant the directories the files sit in
  selftests: ve: introduce vtty pair teardown stress test

 tools/testing/selftests/ve/.gitignore       |   1 +
 tools/testing/selftests/ve/Makefile         |   3 +-
 tools/testing/selftests/ve/ve_perms_test.c  |  34 +-
 tools/testing/selftests/ve/ve_vtty_stress.c | 343 ++++++++++++++++++++
 4 files changed, 378 insertions(+), 3 deletions(-)
 create mode 100644 tools/testing/selftests/ve/ve_vtty_stress.c

-- 
2.55.0

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

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [Devel] [PATCH hci-8.1 v6 1/2] selftests: ve: grant the directories the files sit in
  2026-10-02  4:43 [Devel] [PATCH hci-8.1 v6 0/2] selftests: ve: fix ve_perms, introduce vtty_stress Eva Kurchatova
@ 2026-10-02  4:43 ` Eva Kurchatova
  2026-10-02  4:48   ` no-reply
  2026-10-02  4:43 ` [Devel] [PATCH hci-8.1 v6 2/2] selftests: ve: introduce vtty pair teardown stress test Eva Kurchatova
  1 sibling, 1 reply; 5+ messages in thread
From: Eva Kurchatova @ 2026-10-02  4:43 UTC (permalink / raw)
  To: khorenko; +Cc: devel

The sysfs variant names nested leaves, kernel/vmcoreinfo and
kernel/notes, while the proc one names files at the top of the
filesystem. Both enforcement tests grant the leaves and nothing else,
so in the sysfs case the lookup stops at /sys/kernel, which a fresh VE
cannot traverse, and every open answers ENOENT.

Grant x on each directory along the way, taken from the path the
variant already gives.

Fixes: 303ce49b7446 ("selftests/ve: add ve_perms test for proc and sysfs")

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

Feature: fix selftests
Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
---
 tools/testing/selftests/ve/ve_perms_test.c | 34 ++++++++++++++++++++--
 1 file changed, 32 insertions(+), 2 deletions(-)

diff --git a/tools/testing/selftests/ve/ve_perms_test.c b/tools/testing/selftests/ve/ve_perms_test.c
index 25ffbd42c380..8096b1fa3b43 100644
--- a/tools/testing/selftests/ve/ve_perms_test.c
+++ b/tools/testing/selftests/ve/ve_perms_test.c
@@ -114,9 +114,11 @@ static int ve_open_rel(int cgv2_fd, int ctid, const char *prefix,
 static int set_entry(int cgv2_fd, const char *path, const char *entry,
 		     const char *mask)
 {
-	char line[256];
+	char line[PATH_MAX];
 
-	snprintf(line, sizeof(line), "%s %s", entry, mask);
+	if (snprintf(line, sizeof(line), "%s %s", entry, mask) >=
+	    (int)sizeof(line))
+		return -1;
 	return write_file_at(cgv2_fd, path, line);
 }
 
@@ -343,6 +345,32 @@ TEST_F(ve_perms, write_parser)
 	EXPECT_LT(write_file_at(self->cgv2_fd, path, file), 0);
 }
 
+/* A leaf is reachable only once every directory above it can be traversed */
+static int allow_path(int cgv2_fd, const char *path, const char *file)
+{
+	char dir[PATH_MAX];
+	const char *sep;
+
+	for (sep = strchr(file, '/'); sep; sep = strchr(sep + 1, '/')) {
+		if (snprintf(dir, sizeof(dir), "%.*s", (int)(sep - file),
+			     file) >= (int)sizeof(dir))
+			return -1;
+		if (set_entry(cgv2_fd, path, dir, "x") < 0)
+			return -1;
+	}
+	return 0;
+}
+
+static int allow_paths(int cgv2_fd, const char *path, const char *const files[])
+{
+	int i;
+
+	for (i = 0; i < NR_FILES; i++)
+		if (allow_path(cgv2_fd, path, files[i]) < 0)
+			return -1;
+	return 0;
+}
+
 /*
  * Enforcement, allowed side: from inside a VE, granting r lets each file be
  * read, and granting x on the directory lets it be traversed (the lookup then
@@ -358,6 +386,7 @@ TEST_F(ve_perms, enforce_allows)
 
 	ve_file(path, sizeof(path), self->ctid_a, variant->fs);
 
+	ASSERT_EQ(allow_paths(self->cgv2_fd, path, variant->files), 0);
 	ASSERT_EQ(set_entries(self->cgv2_fd, path, variant->files, "r"), 0);
 	for (i = 0; i < NR_FILES; i++)
 		EXPECT_EQ(ve_open_rel(self->cgv2_fd, self->ctid_a,
@@ -400,6 +429,7 @@ TEST_F(ve_perms, enforce_denies)
 				      O_RDONLY), 0);
 
 	/* Granted w only: visible but not readable. */
+	ASSERT_EQ(allow_paths(self->cgv2_fd, path, variant->files), 0);
 	ASSERT_EQ(set_entries(self->cgv2_fd, path, variant->files, "w"), 0);
 	for (i = 0; i < NR_FILES; i++)
 		EXPECT_EQ(ve_open_rel(self->cgv2_fd, self->ctid_a,
-- 
2.55.0

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

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [Devel] [PATCH hci-8.1 v6 2/2] selftests: ve: introduce vtty pair teardown stress test
  2026-10-02  4:43 [Devel] [PATCH hci-8.1 v6 0/2] selftests: ve: fix ve_perms, introduce vtty_stress Eva Kurchatova
  2026-10-02  4:43 ` [Devel] [PATCH hci-8.1 v6 1/2] selftests: ve: grant the directories the files sit in Eva Kurchatova
@ 2026-10-02  4:43 ` Eva Kurchatova
  2026-10-02  4:54   ` no-reply
  1 sibling, 1 reply; 5+ messages in thread
From: Eva Kurchatova @ 2026-10-02  4:43 UTC (permalink / raw)
  To: khorenko; +Cc: devel

Either end of a vtty pair creates it, and the last close tears it down.

Race condition in older VZKernel vtty implementation allowed both sides
to decide they were the final one and call release_tty() twice, and a
master opening again while a teardown was in flight used to hand out a
reference to a tty_struct about to be freed. Either way, this caused
use-after-free. That issue was fixed in c5028cd61a69:
"ve/vtty: fix use-after-free on concurrent tty close and reopen"

Hammer both ends for ownership correctness & thread safety verification.

The original reproducer needed a few tens of minutes across a dozen
containers, where vzctl interaction with vtty subsystem was a small part
of the overall runtime, therefore it did not fire on every run. This test
opens and closes nothing but the pair, from both ends and without pause,
so the default stays in the tens of seconds and under the timeout of the
suite, and VE_VTTY_SECS or argv[1] allows to control this.

ve_id_write() does not check that a veid is unique and the vtty map is
keyed by veid, so a clash would hand the test a running container's
pair. Before writing ve.veid the test asks the kernel, through the
vzctl ioctl, whether the veid is taken, and skips when it is. It also
skips where /tmp is mounted nodev: mknod() succeeds there and every
open of the slave node fails.

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

Feature: ve selftest
Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
---
 tools/testing/selftests/ve/.gitignore       |   1 +
 tools/testing/selftests/ve/Makefile         |   3 +-
 tools/testing/selftests/ve/ve_vtty_stress.c | 343 ++++++++++++++++++++
 3 files changed, 346 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/ve/ve_vtty_stress.c

diff --git a/tools/testing/selftests/ve/.gitignore b/tools/testing/selftests/ve/.gitignore
index 3df4d05888dc..d8eb5c346c6e 100644
--- a/tools/testing/selftests/ve/.gitignore
+++ b/tools/testing/selftests/ve/.gitignore
@@ -1,3 +1,4 @@
 ve_ns_owner_test
 ve_perms_test
 ve_mount_accounting_test
+ve_vtty_stress
diff --git a/tools/testing/selftests/ve/Makefile b/tools/testing/selftests/ve/Makefile
index c6efe7c4b4fb..d287a73c5e97 100644
--- a/tools/testing/selftests/ve/Makefile
+++ b/tools/testing/selftests/ve/Makefile
@@ -1,9 +1,10 @@
 # SPDX-License-Identifier: GPL-2.0
 # Makefile for ve selftests.
-CFLAGS += -g -Wall -O2
+CFLAGS += -g -Wall -O2 $(KHDR_INCLUDES)
 
 TEST_GEN_PROGS += ve_ns_owner_test
 TEST_GEN_PROGS += ve_perms_test
 TEST_GEN_PROGS += ve_mount_accounting_test
+TEST_GEN_PROGS += ve_vtty_stress
 
 include ../lib.mk
diff --git a/tools/testing/selftests/ve/ve_vtty_stress.c b/tools/testing/selftests/ve/ve_vtty_stress.c
new file mode 100644
index 000000000000..8be6955d1dbc
--- /dev/null
+++ b/tools/testing/selftests/ve/ve_vtty_stress.c
@@ -0,0 +1,343 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ve_vtty_stress - open and close a vtty pair from both ends at once.
+ *
+ * Either end of a vtty pair creates it, and the last close tears it down.
+ *
+ * Hammer both ends. The master comes from the vzctl ioctl, which does
+ * not look the VE up. The slave is reachable only from inside the VE, as
+ * TTY_MAJOR minor 1..MAX_NR_VTTY_CONSOLES, which vtty_driver() maps to
+ * vttys index minor - 1, so the test builds a VE with cgroup2, sets
+ * ve.veid to the cgroup it made, and runs the slave side there.
+ *
+ * A warning does not stop the host, so the run reads the kernel log too.
+ * One read() of /dev/kmsg is one record, so the test matches vtty driver
+ * splats, as well as generic kernel warning/oops/kasan reports.
+ *
+ * Usage: ve_vtty_stress [seconds] (default 30, or $VE_VTTY_SECS)
+ */
+#define _GNU_SOURCE
+#include <sched.h>
+#include <stdbool.h>
+#include <sys/ioctl.h>
+#include <sys/prctl.h>
+#include <sys/statvfs.h>
+#include <linux/major.h>
+#include <linux/vzcalluser.h>
+#include <sys/sysmacros.h>
+#include <sys/wait.h>
+#include <time.h>
+
+#include "ve_selftest.h"
+#include "../kselftest.h"
+
+#define VTTY_IDX 0 /* The pair both ends work on */
+#define SLAVE_MINOR (VTTY_IDX + 1) /* vtty_driver(): index = minor - 1 */
+#define DEFAULT_SECS 30
+
+/*
+ * Expected open errno codes: the pair may be closing, or gone already,
+ * when an open lands on it.
+ * Anything else is an environment issue or a bug.
+ */
+static bool expected_errno(int err)
+{
+	return err == EBUSY || err == EIO || err == ENXIO;
+}
+
+static int open_master(int ctl_fd, unsigned int veid)
+{
+	struct vzctl_ve_configure c = {
+		.veid = veid,
+		.key = VE_CONFIGURE_OPEN_TTY,
+		.val = VTTY_IDX,
+	};
+
+	return ioctl(ctl_fd, VZCTL_VE_CONFIGURE, &c);
+}
+
+/* Any key but the tty one makes ve_configure() look a running VE up */
+static bool veid_in_use(int ctl_fd, unsigned int veid)
+{
+	struct vzctl_ve_configure c = {
+		.veid = veid,
+		.key = VE_CONFIGURE_OS_RELEASE,
+	};
+
+	return ioctl(ctl_fd, VZCTL_VE_CONFIGURE, &c) < 0 && errno == ENOKEY;
+}
+
+/* The slave end, inside the VE. Returns the errno that stopped it, or 0. */
+static int hammer_slave(const char *node, time_t deadline,
+			unsigned long *opened)
+{
+	int err = 0;
+
+	while (time(NULL) < deadline) {
+		int fd = open(node, O_RDWR | O_NOCTTY | O_NONBLOCK);
+
+		if (fd >= 0) {
+			(*opened)++;
+			close(fd);
+		} else if (!expected_errno(errno)) {
+			err = errno;
+			break;
+		}
+	}
+
+	return err;
+}
+
+/* The kernel log, since a warning fails nothing by itself. */
+static int kmsg_open(void)
+{
+	int fd = open("/dev/kmsg", O_RDONLY | O_NONBLOCK);
+
+	if (fd >= 0)
+		lseek(fd, 0, SEEK_END);
+	return fd;
+}
+
+/*
+ * Check for kernel log warnings, one read() per record, hence
+ * the function name is not checked.
+ */
+static int kmsg_check(int fd)
+{
+	char buf[8192];
+	int found = 0;
+	ssize_t r;
+
+	if (fd < 0)
+		return 0;
+
+	for (;;) {
+		r = read(fd, buf, sizeof(buf) - 1);
+		if (r < 0) {
+			/* Ring overrun, continue with the oldest existing record */
+			if (errno == EPIPE)
+				continue;
+			break;
+		}
+		if (r == 0)
+			break;
+		buf[r] = '\0';
+		/* Known symptoms of a buggy kernel */
+		if (strstr(buf, "vtty") && (strstr(buf, "tty->count") ||
+					    strstr(buf, "bad slave count"))) {
+			ksft_print_msg("vtty mismatched count log: %s", buf);
+			found++;
+		}
+		if (strstr(buf, "WARNING: ") || strstr(buf, "Oops: ") ||
+		    strstr(buf, ";BUG: ") || strstr(buf, "soft lockup - ") ||
+		    strstr(buf, "general protection fault")) {
+			ksft_print_msg("kernel log: %s", buf);
+			found++;
+		}
+	}
+	return found;
+}
+
+int main(int argc, char **argv)
+{
+	unsigned long master_opened = 0, slave_opened = 0;
+	int cg, ctid, ctl, kmsg, ready[2], count[2], status;
+	int master_err = 0, slave_err;
+	const char *env = getenv("VE_VTTY_SECS");
+	char path[PATH_MAX], val[16], c;
+	char dir[] = "/tmp/ve_vtty_XXXXXX";
+	char node[sizeof(dir) + 6];
+	struct namespace_ctx ns;
+	struct statvfs st;
+	time_t deadline;
+	long secs;
+	pid_t pid;
+
+	ksft_print_header();
+	secs = argc > 1 ? atol(argv[1]) : (env ? atol(env) : DEFAULT_SECS);
+	if (secs <= 0)
+		secs = DEFAULT_SECS;
+
+	ctl = open("/dev/vzctl", O_RDWR);
+	if (ctl < 0)
+		ksft_exit_skip("cannot open /dev/vzctl: %s, the vz modules "
+			       "(vzdev, vzmon) are needed\n",
+			       strerror(errno));
+
+	cg = mount_cg2_fd();
+	if (cg < 0)
+		ksft_exit_skip("cannot mount cgroup2: %s\n", strerror(errno));
+	if (write_file_at(cg, "cgroup.subtree_control", VE_CONTROLLERS) < 0)
+		ksft_exit_skip("cannot enable the ve controller: %s\n",
+			       strerror(errno));
+
+	ctid = make_ve(cg, CTID_MIN);
+	if (ctid < 0)
+		ksft_exit_skip("cannot make a VE cgroup\n");
+
+	/* The vtty map is keyed by veid, and ve.veid takes a taken one */
+	if (veid_in_use(ctl, ctid)) {
+		destroy_ve(cg, ctid);
+		ksft_exit_skip("veid %d is in use by a running container\n",
+			       ctid);
+	}
+
+	/* The ve.veid must match between master / slave */
+	snprintf(path, sizeof(path), "%d/ve.veid", ctid);
+	snprintf(val, sizeof(val), "%d", ctid);
+	if (write_file_at(cg, path, val) < 0) {
+		destroy_ve(cg, ctid);
+		ksft_exit_skip("cannot set ve.veid: %s\n", strerror(errno));
+	}
+
+	/* Do not report a failure where the vtty pair cannot be had at all */
+	status = open_master(ctl, ctid);
+	if (status < 0 && errno != EBUSY) {
+		destroy_ve(cg, ctid);
+		ksft_exit_skip("no vtty master: %s\n", strerror(errno));
+	}
+	if (status >= 0)
+		close(status);
+
+	/*
+	 * Use temp directory for the node the slave end opens.
+	 * Cleaned up by the master side instead of relying on the slave.
+	 */
+	if (!mkdtemp(dir)) {
+		destroy_ve(cg, ctid);
+		ksft_exit_fail_msg("mkdtemp: %s\n", strerror(errno));
+	}
+	/* A nodev mount takes the mknod() and refuses every open */
+	if (statvfs(dir, &st) == 0 && (st.f_flag & ST_NODEV)) {
+		rmdir(dir);
+		destroy_ve(cg, ctid);
+		ksft_exit_skip("%s is on a nodev filesystem\n", dir);
+	}
+	snprintf(node, sizeof(node), "%s/slave", dir);
+	if (mknod(node, S_IFCHR | 0600, makedev(TTY_MAJOR, SLAVE_MINOR)) < 0) {
+		status = errno;
+		rmdir(dir);
+		destroy_ve(cg, ctid);
+		ksft_exit_fail_msg("mknod: %s\n", strerror(status));
+	}
+
+	ksft_set_plan(1);
+	kmsg = kmsg_open();
+	if (kmsg < 0)
+		ksft_print_msg("cannot read /dev/kmsg: %s, only a crash will "
+			       "be noticed\n",
+			       strerror(errno));
+
+	if (pipe(ready) || pipe(count)) {
+		unlink(node);
+		rmdir(dir);
+		destroy_ve(cg, ctid);
+		ksft_exit_fail_msg("pipe: %s\n", strerror(errno));
+	}
+
+	/* The VE of the cgroup, which the slave side enters */
+	if (prepare_namespaces(&ns, CLONE_NEWVE, cg, ctid) < 0) {
+		unlink(node);
+		rmdir(dir);
+		destroy_ve(cg, ctid);
+		ksft_exit_fail_msg("cannot prepare the VE namespace\n");
+	}
+
+	deadline = time(NULL) + secs;
+	ksft_print_msg("VE %d, pair %d, %ld seconds\n", ctid, VTTY_IDX, secs);
+
+	pid = fork();
+	if (pid == 0) {
+		unsigned long n = 0;
+		int err;
+
+		close(ready[0]);
+		close(count[0]);
+		/*
+		 * Hammering on after the run has given up would hold
+		 * the cgroup open against destroy_ve().
+		 */
+		prctl(PR_SET_PDEATHSIG, SIGKILL);
+		if (enter_cgroup(cg, ctid) < 0 ||
+		    enter_namespaces(&ns, CLONE_NEWVE) < 0)
+			_exit(errno);
+
+		c = 'r';
+		if (write(ready[1], &c, 1) != 1)
+			_exit(errno);
+		err = hammer_slave(node, deadline, &n);
+		/* Send the count either way: it says how far it got. */
+		if (write(count[1], &n, sizeof(n)) != sizeof(n))
+			_exit(errno);
+		_exit(err);
+	} else if (pid < 0) {
+		close_namespace_fds(&ns);
+		unlink(node);
+		rmdir(dir);
+		destroy_ve(cg, ctid);
+		ksft_exit_fail_msg("fork: %s\n", strerror(errno));
+	}
+	close(ready[1]);
+	close(count[1]);
+
+	if (read(ready[0], &c, 1) != 1) {
+		waitpid(pid, &status, 0);
+		close_namespace_fds(&ns);
+		unlink(node);
+		rmdir(dir);
+		destroy_ve(cg, ctid);
+		ksft_exit_fail_msg("the VE side never came up: %s\n",
+				   strerror(WIFEXITED(status) ?
+					    WEXITSTATUS(status) : EINTR));
+	}
+
+	while (time(NULL) < deadline) {
+		int fd = open_master(ctl, ctid);
+
+		if (fd >= 0) {
+			master_opened++;
+			close(fd);
+		} else if (!expected_errno(errno)) {
+			master_err = errno;
+			break;
+		}
+	}
+
+	if (master_err) {
+		/* The other end runs to its own deadline, stop it */
+		kill(pid, SIGKILL);
+		waitpid(pid, &status, 0);
+		slave_err = 0;
+	} else {
+		if (read(count[0], &slave_opened, sizeof(slave_opened)) !=
+		    sizeof(slave_opened))
+			slave_opened = 0;
+		waitpid(pid, &status, 0);
+		slave_err = WIFEXITED(status) ? WEXITSTATUS(status) : EINTR;
+	}
+
+	ksft_print_msg("%lu master opens, %lu slave opens\n", master_opened,
+		       slave_opened);
+
+	status = kmsg_check(kmsg);
+	close_namespace_fds(&ns);
+	unlink(node);
+	rmdir(dir);
+	destroy_ve(cg, ctid);
+
+	if (master_err)
+		ksft_exit_fail_msg("the master end stopped: %s\n",
+				   strerror(master_err));
+	if (slave_err)
+		ksft_exit_fail_msg("the slave end stopped: %s\n",
+				   strerror(slave_err));
+	if (!master_opened)
+		ksft_exit_fail_msg("no master was ever opened\n");
+	if (!slave_opened)
+		ksft_exit_fail_msg("no slave was ever opened\n");
+	if (status)
+		ksft_exit_fail_msg("kernel complained %d time(s)\n", status);
+
+	ksft_test_result_pass("vtty pair closed from both ends\n");
+	ksft_finished();
+}
-- 
2.55.0

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

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [Devel] [PATCH hci-8.1 v6 1/2] selftests: ve: grant the directories the files sit in
  2026-10-02  4:43 ` [Devel] [PATCH hci-8.1 v6 1/2] selftests: ve: grant the directories the files sit in Eva Kurchatova
@ 2026-10-02  4:48   ` no-reply
  0 siblings, 0 replies; 5+ messages in thread
From: no-reply @ 2026-10-02  4:48 UTC (permalink / raw)
  To: khorenko, Eva Kurchatova; +Cc: devel

> The sysfs variant names nested leaves, kernel/vmcoreinfo and
> kernel/notes, while the proc one names files at the top of the
> filesystem. Both enforcement tests grant the leaves and nothing else,
> so in the sysfs case the lookup stops at /sys/kernel, which a fresh VE
> cannot traverse, and every open answers ENOENT.
> 
> Grant x on each directory along the way, taken from the path the
> variant already gives.
> 
> Fixes: 303ce49b7446 ("selftests/ve: add ve_perms test for proc and sysfs")
> 
> https://virtuozzo.atlassian.net/browse/VSTOR-144955
> 
> Feature: fix selftests
> Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261002044431.1632888-1-eva.kurchatova@virtuozzo.com?part=1


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

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [Devel] [PATCH hci-8.1 v6 2/2] selftests: ve: introduce vtty pair teardown stress test
  2026-10-02  4:43 ` [Devel] [PATCH hci-8.1 v6 2/2] selftests: ve: introduce vtty pair teardown stress test Eva Kurchatova
@ 2026-10-02  4:54   ` no-reply
  0 siblings, 0 replies; 5+ messages in thread
From: no-reply @ 2026-10-02  4:54 UTC (permalink / raw)
  To: Eva Kurchatova, khorenko; +Cc: devel

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] selftests/ve: kmsg_check() ignores EPIPE ring overrun and can falsely PASS
- [Low] selftests/ve: kmsg_check() fails on any unrelated kernel WARNING/Oops/BUG
- [Low] selftests/ve: veid uniqueness check is a TOCTOU, so the stress may hit a live container
--

commit e116830e8cd9eabf2049859ae877c3c8c485e837
Author: Eva Kurchatova <eva.kurchatova@virtuozzo.com>

selftests: ve: introduce vtty pair teardown stress test

This adds a selftest that opens and closes a vtty pair from both the
master end (vzctl ioctl) and the slave end (inside a VE) for a
configurable time. It then scans /dev/kmsg for vtty count mismatches and
generic kernel warnings, to catch the use-after-free fixed in c5028cd61a69.

Link: https://virtuozzo.atlassian.net/browse/VSTOR-144771

> diff --git a/tools/testing/selftests/ve/ve_vtty_stress.c b/tools/testing/selftests/ve/ve_vtty_stress.c
> new file mode 100644
> index 000000000000..8be6955d1dbc
> --- /dev/null
> +++ b/tools/testing/selftests/ve/ve_vtty_stress.c

[ ... ]

> +static int kmsg_check(int fd)
> +{
> +	char buf[8192];
> +	int found = 0;
> +	ssize_t r;
> +
> +	if (fd < 0)
> +		return 0;
> +
> +	for (;;) {
> +		r = read(fd, buf, sizeof(buf) - 1);
> +		if (r < 0) {
> +			/* Ring overrun, continue with the oldest existing record */
> +			if (errno == EPIPE)
> +				continue;
> +			break;
> +		}

[Severity: Medium]
Can this test report PASS even though the kernel logged the bug it is
meant to detect?

kmsg_check() is called only once, after the whole run, so /dev/kmsg is
drained after up to 30 seconds of logging. If the log ring wraps during
that time, the next read() returns EPIPE, and the branch below silently
resumes from the oldest surviving record:

    if (errno == EPIPE)
            continue;

Records emitted early in the run, such as a "bad slave count" message or
a WARNING from a double release_tty(), would be dropped without being
counted. Nothing tells the user that records were lost.

Should the EPIPE case at least print a message that records were lost, or
make the test fail or skip?

> +		if (r == 0)
> +			break;
> +		buf[r] = '\0';
> +		/* Known symptoms of a buggy kernel */
> +		if (strstr(buf, "vtty") && (strstr(buf, "tty->count") ||
> +					    strstr(buf, "bad slave count"))) {
> +			ksft_print_msg("vtty mismatched count log: %s", buf);
> +			found++;
> +		}
> +		if (strstr(buf, "WARNING: ") || strstr(buf, "Oops: ") ||
> +		    strstr(buf, ";BUG: ") || strstr(buf, "soft lockup - ") ||
> +		    strstr(buf, "general protection fault")) {

[Severity: Low]
Can an unrelated kernel message fail this test?

kmsg_open() seeks to the end of /dev/kmsg, and kmsg_check() then reads
every record logged until the end of the run. Any record from another
subsystem that contains "WARNING: ", "Oops: ", ";BUG: ", "soft lockup - "
or "general protection fault" is counted in found.

main() then calls ksft_exit_fail_msg("kernel complained %d time(s)\n", ...).
Only the "tty->count" and "bad slave count" checks are restricted to vtty.

On a busy host, wouldn't this make the result depend on unrelated activity?
Would it be possible to filter by something related to the vtty or tty
code, such as the call trace?

> +			ksft_print_msg("kernel log: %s", buf);
> +			found++;
> +		}
> +	}
> +	return found;
> +}

[ ... ]

> +	ctid = make_ve(cg, CTID_MIN);
> +	if (ctid < 0)
> +		ksft_exit_skip("cannot make a VE cgroup\n");
> +
> +	/* The vtty map is keyed by veid, and ve.veid takes a taken one */
> +	if (veid_in_use(ctl, ctid)) {
> +		destroy_ve(cg, ctid);
> +		ksft_exit_skip("veid %d is in use by a running container\n",
> +			       ctid);
> +	}
> +
> +	/* The ve.veid must match between master / slave */
> +	snprintf(path, sizeof(path), "%d/ve.veid", ctid);
> +	snprintf(val, sizeof(val), "%d", ctid);
> +	if (write_file_at(cg, path, val) < 0) {

[Severity: Low]
Is the veid uniqueness check in main() racy?

veid_in_use() queries only running VEs, and ve.veid is written afterwards.
ve_id_write() in kernel/ve/ve.c carries "/* FIXME: check veid is uniqul */"
and does not reserve the id. The test's own VE enters the idr only later,
after the namespace switch.

If a container with the same veid is started between veid_in_use() and the
write of ve.veid, would the test not detect it? It could then drive that
container's vtty pair 0 from both ends.

The commit message says "Before writing ve.veid the test asks the kernel
... whether the veid is taken, and skips when it is." This describes the
check as a full safeguard, but it only narrows the window. Could the
commit message or a code comment say that the check is best-effort?

[ ... ]

-- 
Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261002044431.1632888-1-eva.kurchatova@virtuozzo.com?part=2

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

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-10-02  4:55 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-02  4:43 [Devel] [PATCH hci-8.1 v6 0/2] selftests: ve: fix ve_perms, introduce vtty_stress Eva Kurchatova
2026-10-02  4:43 ` [Devel] [PATCH hci-8.1 v6 1/2] selftests: ve: grant the directories the files sit in Eva Kurchatova
2026-10-02  4:48   ` no-reply
2026-10-02  4:43 ` [Devel] [PATCH hci-8.1 v6 2/2] selftests: ve: introduce vtty pair teardown stress test Eva Kurchatova
2026-10-02  4:54   ` no-reply

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.