* [Devel] [PATCH hci-8.1 v8 0/2] selftests: ve: fix ve_perms, introduce vtty_stress
@ 2026-10-08 3:52 Eva Kurchatova
2026-10-08 3:52 ` [Devel] [PATCH hci-8.1 v8 1/2] selftests: ve: grant the directories the files sit in Eva Kurchatova
` (3 more replies)
0 siblings, 4 replies; 8+ messages in thread
From: Eva Kurchatova @ 2026-10-08 3:52 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 v8:
2/2:
- Take a high random veid instead of asking the kernel if one is in use.
- A master that cannot be opened fails the test unless the device is
absent or denied.
- Scan the kernel log one last time after the cleanup, the last report
could land after the scan.
- A signal that lands while the master waits for the slave's count no
longer leaves the slave running to its own deadline.
- Correct feature trailer to follow other ve selftests.
Link to v7: http://lore.virtuozzo.com/kernel/20261006221704.2507107-1-eva.kurchatova@virtuozzo.com/T/#t
Changes in v7:
2/2:
- Read the kernel log every second, match more warning patterns, count
a ring overrun or kmsg read error as a complaint.
- Try the next cgroup id when a running container holds the veid.
- Skip when mknod()/mkdtemp() is refused or /dev/kmsg cannot be opened.
instead of skipping on the first one for good.
- The slave side exits when its parent is already gone.
- Clarify that the veid check is best-effort and subject to TOCTOU.
- Correct the referenced vtty fix commit hash.
- Minor cleanup / skip fixes.
1/2:
- Mention the set_entry() change in the message.
Link to v6: http://lore.virtuozzo.com/kernel/20261002044431.1632888-1-eva.kurchatova@virtuozzo.com/T/#t
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 | 415 ++++++++++++++++++++
4 files changed, 450 insertions(+), 3 deletions(-)
create mode 100644 tools/testing/selftests/ve/ve_vtty_stress.c
--
2.56.0
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 8+ messages in thread
* [Devel] [PATCH hci-8.1 v8 1/2] selftests: ve: grant the directories the files sit in
2026-10-08 3:52 [Devel] [PATCH hci-8.1 v8 0/2] selftests: ve: fix ve_perms, introduce vtty_stress Eva Kurchatova
@ 2026-10-08 3:52 ` Eva Kurchatova
2026-10-08 3:56 ` no-reply
2026-10-08 3:52 ` [Devel] [PATCH hci-8.1 v8 2/2] selftests: ve: introduce vtty pair teardown stress test Eva Kurchatova
` (2 subsequent siblings)
3 siblings, 1 reply; 8+ messages in thread
From: Eva Kurchatova @ 2026-10-08 3:52 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:
not ok 14 ve_perms.sysfs.enforce_allows
not ok 15 ve_perms.sysfs.enforce_denies
Grant x on each directory along the way, taken from the path the
variant already gives. set_entry() gets a PATH_MAX buffer and fails on
truncation instead of writing a cut entry, as ve_open_rel() does.
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.56.0
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 8+ messages in thread
* [Devel] [PATCH hci-8.1 v8 2/2] selftests: ve: introduce vtty pair teardown stress test
2026-10-08 3:52 [Devel] [PATCH hci-8.1 v8 0/2] selftests: ve: fix ve_perms, introduce vtty_stress Eva Kurchatova
2026-10-08 3:52 ` [Devel] [PATCH hci-8.1 v8 1/2] selftests: ve: grant the directories the files sit in Eva Kurchatova
@ 2026-10-08 3:52 ` Eva Kurchatova
2026-10-08 4:03 ` no-reply
2026-10-08 16:24 ` [Devel] [PATCH hci-8.1 v8 0/2] selftests: ve: fix ve_perms, introduce vtty_stress Konstantin Khorenko
2026-10-08 16:32 ` Konstantin Khorenko
3 siblings, 1 reply; 8+ messages in thread
From: Eva Kurchatova @ 2026-10-08 3:52 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 23abb203a41a ("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.
Nothing keeps veids unique and the vtty map is keyed by veid, so the
test takes a random one high above the hand-given range, the way a
container gets its id from a uuid.
It also skips where /tmp is mounted nodev, where mknod() succeeds and
no open does. The log is read every second, and a ring overrun, which
loses records, counts as a complaint.
https://virtuozzo.atlassian.net/browse/VSTOR-144771
Feature: ve selftests
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 | 415 ++++++++++++++++++++
3 files changed, 418 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..f4bdd81133b8
--- /dev/null
+++ b/tools/testing/selftests/ve/ve_vtty_stress.c
@@ -0,0 +1,415 @@
+// 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 <signal.h>
+#include <stdbool.h>
+#include <sys/ioctl.h>
+#include <sys/prctl.h>
+#include <sys/random.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;
+}
+
+/* A runner timeout must still reach the cleanup */
+static volatile sig_atomic_t stop;
+
+static void on_signal(int sig)
+{
+ stop = sig;
+}
+
+/* The handler interrupts a wait, the child is still to be reaped */
+static void wait_child(pid_t pid, int *status)
+{
+ *status = 0;
+ do {
+ if (stop)
+ kill(pid, SIGKILL);
+ } while (waitpid(pid, status, 0) < 0 && errno == EINTR);
+}
+
+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);
+}
+
+/* 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)
+{
+ /* How a warning, a splat or a sanitizer report starts in a record */
+ static const char *const reports[] = {
+ "WARNING: ", "Oops", ";BUG", "kernel BUG at", "UBSAN: ",
+ "soft lockup - ", "general protection fault", "INFO: task ",
+ "rcu: INFO: ",
+ };
+ char buf[8192];
+ int found = 0;
+ ssize_t r;
+ size_t i;
+
+ if (fd < 0)
+ return 0;
+
+ for (;;) {
+ r = read(fd, buf, sizeof(buf) - 1);
+ if (r < 0) {
+ /* The ring wrapped since the last read, records are gone */
+ if (errno == EPIPE) {
+ ksft_print_msg("kernel log overrun, records lost\n");
+ found++;
+ continue;
+ }
+ /* Anything but the end of the log is a scan that stopped */
+ if (errno != EAGAIN) {
+ ksft_print_msg("kernel log read: %s\n",
+ strerror(errno));
+ found++;
+ }
+ 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++;
+ }
+ for (i = 0; i < ARRAY_SIZE(reports); i++) {
+ if (!strstr(buf, reports[i]))
+ continue;
+ ksft_print_msg("kernel log: %s", buf);
+ found++;
+ break;
+ }
+ }
+ 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;
+ unsigned int veid;
+ int master_err = 0, slave_err, complaints = 0;
+ 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 sigaction sa;
+ struct statvfs st;
+ time_t deadline, now, last = 0;
+ long secs;
+ pid_t pid, parent;
+
+ 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));
+
+ /* Nothing is made yet, from here on a signal must reach the cleanup */
+ sa.sa_handler = on_signal;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigaction(SIGTERM, &sa, NULL);
+ sigaction(SIGINT, &sa, NULL);
+
+ 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: a random high one, as a container gets */
+ if (getrandom(&veid, sizeof(veid), 0) != sizeof(veid)) {
+ status = errno;
+ destroy_ve(cg, ctid);
+ ksft_exit_skip("cannot draw a veid: %s\n", strerror(status));
+ }
+ veid = (veid >> 2) | (1u << 30);
+
+ /* The ve.veid must match between master / slave */
+ snprintf(path, sizeof(path), "%d/ve.veid", ctid);
+ snprintf(val, sizeof(val), "%u", veid);
+ if (write_file_at(cg, path, val) < 0) {
+ status = errno;
+ destroy_ve(cg, ctid);
+ ksft_exit_skip("cannot set ve.veid: %s\n", strerror(status));
+ }
+
+ /* The log is the only detector of a teardown that does not crash */
+ kmsg = kmsg_open();
+ if (kmsg < 0) {
+ status = errno;
+ destroy_ve(cg, ctid);
+ ksft_exit_skip("cannot read /dev/kmsg: %s\n", strerror(status));
+ }
+
+ status = open_master(ctl, veid);
+ if (status < 0) {
+ status = errno;
+ destroy_ve(cg, ctid);
+ /* Absent or denied is the host's doing, anything else the kernel's */
+ if (status == ENOTTY || status == ENODEV || status == ENOENT ||
+ status == EPERM || status == EACCES)
+ ksft_exit_skip("no vtty master: %s\n", strerror(status));
+ ksft_exit_fail_msg("cannot open the master: %s\n",
+ strerror(status));
+ }
+ 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)) {
+ status = errno;
+ destroy_ve(cg, ctid);
+ ksft_exit_skip("mkdtemp: %s\n", strerror(status));
+ }
+ /* 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_skip("mknod: %s\n", strerror(status));
+ }
+
+ ksft_set_plan(1);
+
+ if (pipe(ready) || pipe(count)) {
+ status = errno;
+ unlink(node);
+ rmdir(dir);
+ destroy_ve(cg, ctid);
+ ksft_exit_fail_msg("pipe: %s\n", strerror(status));
+ }
+
+ /* 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 %u, pair %d, %ld seconds\n", veid, VTTY_IDX, secs);
+
+ parent = getpid();
+ pid = fork();
+ if (pid == 0) {
+ unsigned long n = 0;
+ int err;
+
+ /* The parent stops the run, a signal here just ends this side */
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGINT, SIG_DFL);
+ 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 (getppid() != parent)
+ _exit(0);
+ if (enter_cgroup(cg, ctid) < 0 ||
+ enter_namespaces(&ns, CLONE_NEWVE) < 0)
+ _exit(errno ? errno : EIO);
+
+ 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) {
+ status = errno;
+ close_namespace_fds(&ns);
+ unlink(node);
+ rmdir(dir);
+ destroy_ve(cg, ctid);
+ ksft_exit_fail_msg("fork: %s\n", strerror(status));
+ }
+ close(ready[1]);
+ close(count[1]);
+
+ if (read(ready[0], &c, 1) != 1) {
+ /* A signal lands here too, with the child alive */
+ kill(pid, SIGKILL);
+ wait_child(pid, &status);
+ close_namespace_fds(&ns);
+ unlink(node);
+ rmdir(dir);
+ destroy_ve(cg, ctid);
+ if (stop)
+ ksft_exit_fail_msg("stopped by signal %d\n", (int)stop);
+ ksft_exit_fail_msg("the VE side never came up: %s\n",
+ strerror(WIFEXITED(status) ?
+ WEXITSTATUS(status) : EINTR));
+ }
+
+ while (!stop && (now = time(NULL)) < deadline) {
+ int fd = open_master(ctl, veid);
+
+ if (fd >= 0) {
+ master_opened++;
+ close(fd);
+ } else if (!expected_errno(errno)) {
+ master_err = errno;
+ break;
+ }
+ /* Drain the log as it fills, the ring is small */
+ if (now != last) {
+ complaints += kmsg_check(kmsg);
+ last = now;
+ }
+ }
+
+ if (master_err || stop) {
+ /* The other end runs to its own deadline, stop it */
+ kill(pid, SIGKILL);
+ wait_child(pid, &status);
+ slave_err = 0;
+ } else {
+ if (read(count[0], &slave_opened, sizeof(slave_opened)) !=
+ sizeof(slave_opened))
+ slave_opened = 0;
+ wait_child(pid, &status);
+ slave_err = WIFEXITED(status) ? WEXITSTATUS(status) : EINTR;
+ }
+
+ ksft_print_msg("%lu master opens, %lu slave opens\n", master_opened,
+ slave_opened);
+
+ close_namespace_fds(&ns);
+ unlink(node);
+ rmdir(dir);
+ destroy_ve(cg, ctid);
+ /* The last close frees its tty from a work item, let that run first */
+ usleep(100000);
+ status = complaints + kmsg_check(kmsg);
+
+ /* A complaint outranks how the run ended */
+ if (status)
+ ksft_exit_fail_msg("kernel complained %d time(s)\n", status);
+ if (stop)
+ ksft_exit_fail_msg("stopped by signal %d\n", (int)stop);
+ if (master_err)
+ ksft_exit_fail_msg("the master end stopped: %s\n",
+ strerror(master_err));
+ /* Denied from the first open, by a device policy say */
+ if (!slave_opened && (slave_err == EPERM || slave_err == EACCES ||
+ slave_err == ENOENT || slave_err == ENODEV))
+ ksft_exit_skip("the slave end cannot be opened: %s\n",
+ strerror(slave_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");
+
+ ksft_test_result_pass("vtty pair closed from both ends\n");
+ ksft_finished();
+}
--
2.56.0
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [Devel] [PATCH hci-8.1 v8 1/2] selftests: ve: grant the directories the files sit in
2026-10-08 3:52 ` [Devel] [PATCH hci-8.1 v8 1/2] selftests: ve: grant the directories the files sit in Eva Kurchatova
@ 2026-10-08 3:56 ` no-reply
0 siblings, 0 replies; 8+ messages in thread
From: no-reply @ 2026-10-08 3:56 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:
>
> not ok 14 ve_perms.sysfs.enforce_allows
> not ok 15 ve_perms.sysfs.enforce_denies
>
> Grant x on each directory along the way, taken from the path the
> variant already gives. set_entry() gets a PATH_MAX buffer and fails on
> truncation instead of writing a cut entry, as ve_open_rel() does.
>
> 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/20261008035335.2823931-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] 8+ messages in thread
* Re: [Devel] [PATCH hci-8.1 v8 2/2] selftests: ve: introduce vtty pair teardown stress test
2026-10-08 3:52 ` [Devel] [PATCH hci-8.1 v8 2/2] selftests: ve: introduce vtty pair teardown stress test Eva Kurchatova
@ 2026-10-08 4:03 ` no-reply
2026-10-09 2:43 ` Eva Kurchatova (Virtuozzo)
0 siblings, 1 reply; 8+ messages in thread
From: no-reply @ 2026-10-08 4:03 UTC (permalink / raw)
To: Eva Kurchatova, khorenko; +Cc: devel
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Medium] selftests/ve: kmsg_check() fails on any unrelated host log record or ring overrun
- [Low] selftests/ve: master loop ignores early exit of slave child in main()
- [Low] selftests/ve: deadline uses 1s-granularity time(NULL) in main() and hammer_slave()
- [Low] selftests/ve: wait_child() leaves status 0 when waitpid() fails non-EINTR
- [Low] selftests/ve: stop flag check races with blocking waitpid()/read() in main()
--
commit 8ed5cf9c90a1b3d9d20bbf2774ae5c2adb6076ca
Author: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
selftests: ve: introduce vtty pair teardown stress test
This adds a selftest that opens and closes both ends of a vtty pair
concurrently, to exercise teardown ownership and thread safety. The
slave end runs in a forked child inside a VE, the master end is opened
via the vzctl ioctl, and the kernel log is scanned for splats.
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..f4bdd81133b8
> --- /dev/null
> +++ b/tools/testing/selftests/ve/ve_vtty_stress.c
[ ... ]
> +/* The handler interrupts a wait, the child is still to be reaped */
> +static void wait_child(pid_t pid, int *status)
> +{
> + *status = 0;
> + do {
> + if (stop)
> + kill(pid, SIGKILL);
> + } while (waitpid(pid, status, 0) < 0 && errno == EINTR);
> +}
[Severity: Low]
If waitpid() fails with an error other than EINTR (for example ECHILD),
the loop exits with *status still preset to 0. Callers then see
WIFEXITED(0) true and WEXITSTATUS(0) equal to 0, so slave_err is computed
as 0 and the child is treated as having exited cleanly. Could the
failure be reported to the caller instead of leaving a status that reads
as a clean exit? This path is unlikely, since the child is the parent's
own fork.
[Severity: Low]
Is the check of stop atomic with the blocking waitpid()? If SIGTERM or
SIGINT arrives after the "if (stop)" test but before waitpid() is
entered, the handler (installed with sa_flags = 0) sets stop but nothing
interrupts the wait. The parent then blocks until the child reaches its
own deadline. This is a delay bounded by secs, but if the runner
escalates to SIGKILL in the meantime, the cgroup, the mknod node and the
tmp directory are left behind. Would blocking the signals and using
ppoll()/pselect(), signalfd or a pidfd close this window?
[ ... ]
> +/*
> + * Check for kernel log warnings, one read() per record, hence
> + * the function name is not checked.
> + */
> +static int kmsg_check(int fd)
> +{
> + /* How a warning, a splat or a sanitizer report starts in a record */
> + static const char *const reports[] = {
> + "WARNING: ", "Oops", ";BUG", "kernel BUG at", "UBSAN: ",
> + "soft lockup - ", "general protection fault", "INFO: task ",
> + "rcu: INFO: ",
> + };
[ ... ]
> + for (;;) {
> + r = read(fd, buf, sizeof(buf) - 1);
> + if (r < 0) {
> + /* The ring wrapped since the last read, records are gone */
> + if (errno == EPIPE) {
> + ksft_print_msg("kernel log overrun, records lost\n");
> + found++;
> + continue;
> + }
[ ... ]
> + for (i = 0; i < ARRAY_SIZE(reports); i++) {
> + if (!strstr(buf, reports[i]))
> + continue;
> + ksft_print_msg("kernel log: %s", buf);
> + found++;
> + break;
> + }
> + }
> + return found;
> +}
[Severity: Medium]
Can this give wrong results in both directions? kmsg_open() seeks to the
end of /dev/kmsg, and kmsg_check() then scans every later record on the
host, matching generic substrings such as "WARNING: ", "Oops",
"INFO: task ", "rcu: INFO: " and "soft lockup - " with strstr() anywhere
in the record. There is no filtering by vtty, by tty, or by the pid of
the test.
A warning from an unrelated subsystem or container during the run is
therefore counted as a vtty complaint, and main() lets a complaint
outrank how the run ended. An EPIPE ring overrun is also counted as a
failure (found++), and since the log is drained only about once per
second in main(), a chatty host can overrun it.
In the other direction, without KASAN or a similar detector, a
use-after-free or a double release_tty() that logs nothing passes
silently, because the log scan is the only detector. Could the matching
be narrowed to vtty-related records, and could an overrun be treated
differently from a vtty failure?
[ ... ]
> + deadline = time(NULL) + secs;
[Severity: Low]
Is a 1 second granularity deadline intended here? time(NULL) truncates
to whole seconds, so with VE_VTTY_SECS=1 the stress window can be almost
empty. The parent in main() and the child in hammer_slave() also
evaluate "time(NULL) < deadline" independently and stop at slightly
different instants, and time() is subject to wall-clock steps. Would
clock_gettime(CLOCK_MONOTONIC) be a better fit?
[ ... ]
> + while (!stop && (now = time(NULL)) < deadline) {
> + int fd = open_master(ctl, veid);
> +
> + if (fd >= 0) {
> + master_opened++;
> + close(fd);
> + } else if (!expected_errno(errno)) {
> + master_err = errno;
> + break;
> + }
[Severity: Low]
Does this master loop notice when the slave child has already exited?
hammer_slave() returns on the first unexpected errno (for example EPERM
or EACCES from a device policy), and the child then writes its count and
exits. The loop condition only checks stop and the deadline, so the
master keeps hammering alone until the deadline, and only afterwards does
main() read count[0] and call wait_child().
In a denied-slave environment this means the full 30 second default
passes before the SKIP is reported, and a real slave-side failure is only
reported at the end, with the rest of the run exercising only one end of
the pair. Would polling the child with waitpid(WNOHANG), or poll() on
count[0], inside this loop help?
[ ... ]
> + } else {
> + if (read(count[0], &slave_opened, sizeof(slave_opened)) !=
> + sizeof(slave_opened))
> + slave_opened = 0;
> + wait_child(pid, &status);
> + slave_err = WIFEXITED(status) ? WEXITSTATUS(status) : EINTR;
> + }
[Severity: Low]
Is the same window present here? The blocking read(count[0], ...) has no
check of stop that is atomic with it, so a SIGTERM or SIGINT delivered
after the master loop exits but before read() is entered is not noticed
until the child finishes at its own deadline.
--
Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261008035335.2823931-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] 8+ messages in thread
* Re: [Devel] [PATCH hci-8.1 v8 0/2] selftests: ve: fix ve_perms, introduce vtty_stress
2026-10-08 3:52 [Devel] [PATCH hci-8.1 v8 0/2] selftests: ve: fix ve_perms, introduce vtty_stress Eva Kurchatova
2026-10-08 3:52 ` [Devel] [PATCH hci-8.1 v8 1/2] selftests: ve: grant the directories the files sit in Eva Kurchatova
2026-10-08 3:52 ` [Devel] [PATCH hci-8.1 v8 2/2] selftests: ve: introduce vtty pair teardown stress test Eva Kurchatova
@ 2026-10-08 16:24 ` Konstantin Khorenko
2026-10-08 16:32 ` Konstantin Khorenko
3 siblings, 0 replies; 8+ messages in thread
From: Konstantin Khorenko @ 2026-10-08 16:24 UTC (permalink / raw)
To: Eva Kurchatova; +Cc: devel
On Thu, 08 Oct 2026 06:52:17 +0300, Eva Kurchatova <eva.kurchatova@virtuozzo.com> wrote:
> selftests: ve: fix ve_perms, introduce vtty_stress
>
> 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.
>
> [...]
Reviewed-by: Konstantin Khorenko <khorenko@virtuozzo.com>
--
Konstantin Khorenko <khorenko@virtuozzo.com>
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [Devel] [PATCH hci-8.1 v8 0/2] selftests: ve: fix ve_perms, introduce vtty_stress
2026-10-08 3:52 [Devel] [PATCH hci-8.1 v8 0/2] selftests: ve: fix ve_perms, introduce vtty_stress Eva Kurchatova
` (2 preceding siblings ...)
2026-10-08 16:24 ` [Devel] [PATCH hci-8.1 v8 0/2] selftests: ve: fix ve_perms, introduce vtty_stress Konstantin Khorenko
@ 2026-10-08 16:32 ` Konstantin Khorenko
3 siblings, 0 replies; 8+ messages in thread
From: Konstantin Khorenko @ 2026-10-08 16:32 UTC (permalink / raw)
To: Eva Kurchatova; +Cc: devel
On Thu, 08 Oct 2026 06:52:17 +0300, Eva Kurchatova wrote:
> selftests: ve: fix ve_perms, introduce vtty_stress
>
> 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.
>
> [...]
Applied to branch-rh10-6.12.0-211.52.1.22.x.vz10-ovz, will be released in rh10-6.12.0-211.52.1.22.9.vz10, thanks!
[1/2] selftests: ve: grant the directories the files sit in
https://bitbucket.org/virtuozzocore/vzkernel.vzs/commits/ce7800c78ae9
[2/2] selftests: ve: introduce vtty pair teardown stress test
https://bitbucket.org/virtuozzocore/vzkernel.vzs/commits/defa968cbe8c
--
Konstantin Khorenko <khorenko@virtuozzo.com>
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [Devel] [PATCH hci-8.1 v8 2/2] selftests: ve: introduce vtty pair teardown stress test
2026-10-08 4:03 ` no-reply
@ 2026-10-09 2:43 ` Eva Kurchatova (Virtuozzo)
0 siblings, 0 replies; 8+ messages in thread
From: Eva Kurchatova (Virtuozzo) @ 2026-10-09 2:43 UTC (permalink / raw)
To: no-reply, khorenko; +Cc: devel
For future reference:
On Thu, 8 Oct 2026 04:03:29 +0000, no-reply@sashiko.virtuozzo.com wrote:
> - [Medium] selftests/ve: kmsg_check() fails on any unrelated host log
record or ring overrun
Deliberate, and settled in v7: a kernel that warns during the run is a
kernel to look at, whatever warned, and the test prints the record so
the reader sees what it was. ACKed by Konstantin.
> - [Low] selftests/ve: master loop ignores early exit of slave child
in main()
It changes when the verdict is printed, not the verdict. A slave that
stops on its first open is reported as a skip or a failure at the end
of the run.
> - [Low] selftests/ve: wait_child() leaves status 0 when waitpid()
fails non-EINTR
waitpid() has no way to fail there: the child is this process's own
fork, nothing else reaps it, and SIGCHLD is not ignored, so ECHILD
cannot come back, and the arguments are fixed. The only error it can
return is EINTR, which the loop handles.
> - [Low] selftests/ve: stop flag check races with blocking
waitpid()/read() in main()
A child that is blocked in a syscall is interrupted by the signal,
since SA_RESTART is disabled, and a child that landed on the racy
window will still eventually stop at its own deadline.
I am not aware of an atomic way to read()/waitpid() and check variable,
like a futex, that would be useful here for sure.
> - [Low] selftests/ve: deadline uses 1s-granularity time(NULL) in
main() and hammer_slave()
That one is right: a run asked for one second could end immediately,
and the timeout can jitter in 1-second units, however that is not
a useful run time for this test, and adding monotonic clocksource
would in my opinion be redundant here.
Eva
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-10-09 2:44 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-08 3:52 [Devel] [PATCH hci-8.1 v8 0/2] selftests: ve: fix ve_perms, introduce vtty_stress Eva Kurchatova
2026-10-08 3:52 ` [Devel] [PATCH hci-8.1 v8 1/2] selftests: ve: grant the directories the files sit in Eva Kurchatova
2026-10-08 3:56 ` no-reply
2026-10-08 3:52 ` [Devel] [PATCH hci-8.1 v8 2/2] selftests: ve: introduce vtty pair teardown stress test Eva Kurchatova
2026-10-08 4:03 ` no-reply
2026-10-09 2:43 ` Eva Kurchatova (Virtuozzo)
2026-10-08 16:24 ` [Devel] [PATCH hci-8.1 v8 0/2] selftests: ve: fix ve_perms, introduce vtty_stress Konstantin Khorenko
2026-10-08 16:32 ` Konstantin Khorenko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox