From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail.openvz.org (unknown [69.168.225.77]) by lore.virtuozzo.com (Postfix) with ESMTPS id 88C33802A7 for ; Fri, 2 Oct 2026 04:45:59 +0000 (UTC) Received: from mail.openvz.org (localhost [127.0.0.1]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 6924ijwX014399; Fri, 2 Oct 2026 07:44:46 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 6924ijwX014399 Authentication-Results: mail.openvz.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=virtuozzo.com header.i=@virtuozzo.com header.b="czRbSvVN" Received: from mail-wr1-f72.google.com (mail-wr1-f72.google.com [209.85.221.72]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 6924ibX5014376 (version=TLSv1/SSLv3 cipher=AES128-GCM-SHA256 bits=128 verify=FAIL) for ; Fri, 2 Oct 2026 07:44:37 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 6924ibX5014376 Received: by mail-wr1-f72.google.com with SMTP id ffacd0b85a97d-486f1ecf9bbso2355205f8f.0 for ; Thu, 01 Oct 2026 21:44:37 -0700 (PDT) X-Gm-Message-State: AFq9FYIg1pPoJXQzWTyOWzGryAIdWpP9J6PtkarGt9TQlegoRdxxna7N DkEM9BFsGMF95zhieA3dhs7o3xNKlmpX5wQlubT2U3WDFxfGTd2K9N0fN5j0yooVo5iP+kFG+B8 eBpIuq6dLs7EBRD3dZdU0IVJy0j3nGulTezw7Rjt7B9WafI05Mr5U9w== X-Gm-Gg: AYBFou2eI6N8T06yE49wejQBMhiBifiR2fkFgF+j15kGoK80pxAQ3U7xBopIoZim2MC 20vwRepdBJ2fu1oMHc8U5s1M9FdTjUz8iHnBAae0BwGN0DxlJMgu4Imwx1qHrHrbb/b39kJCf9d 8ehPHUwfwTFO2TTNKFPeDfcyFAL6PW737whD6g+l3fBavpCe7UT2Bvw2NoZ4mGTQpweag+Hlt+r 5IxgA95ymo8n1oeUmVjNfPhSrn/s0ndz5McCtTxwau2LjxaEAvKlsK1cPq4RPJ/VdWENB4hmty7 H3VxFH932l73P4wGcQpk8ZqQ4XOpXRK6hec11hPrvWRmS9iMHJjvdMdJ6Q8Nk5g+HXDGHka0/+w PK2m9TOtCBVjWKiaJWFqm6KsYmw== X-Received: by 2002:a05:6000:25ea:b0:48c:4272:86b2 with SMTP id ffacd0b85a97d-48c42728a67mr164241f8f.11.1790916276776; Thu, 01 Oct 2026 21:44:36 -0700 (PDT) X-Received: by 2002:a05:6000:25ea:b0:48c:4272:86b2 with SMTP id ffacd0b85a97d-48c42728a67mr164228f8f.11.1790916276291; Thu, 01 Oct 2026 21:44:36 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1790916276; cv=none; d=google.com; s=arc-20260327; b=DLvRLzE7UwC90GXhewHyvF+P8F93Q2ZC534PHPbPHXbwP7OVsP8jExWUge4bST5rhc ny+ifFqor4P2GojAl8wkTC3ZGHGR1d3umsDSP9bs8cnWI9BDPelGdTNUQUglpoeKk+tC T1uMUZe+WI2wlS4VJEzV5C1x9iMY0slhRmbW8QJQj7ssvYG48XdyT8NWgYbi7N5NLhRF GMzJr1QUj5HeutungxoRIXsQpf1pIjsx9Kjl235MhJVZBsZPDtdra70aiWUsGAIEI49t mJpB9NrOIKFTIHT0nUI4vJQTUINPypNJzXzuNz1R1j7tHwi26A7q10q4J3A3zbykd9M8 8Asw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:dkim-signature; bh=VenQoCbDp2hkhaYp+7X9gU98YAPY+OuoWaJHd/Fj/m8=; fh=WpT6aB7RBAGHZeWZzqAr71yzRFbPTYkPWKsRagLn48Q=; b=VIolCsoZjx+5mfp5XRhGRAX2qPW3AM5P4HGEjXREBHW4mTXg2cPf7ivAheVr+f/lQr iVvF9erBYOUwvYoNA99/McHT2g7I7gVhzhpPS/x9QW49G2HG4SbvfjsVYfX+Dw/3GBEz dZEFFhPa4ekRhY1sSJQ58YWvOq/zo0f1qQaxXuGXwySrv4J2yTpZ3KiXRpJ6hiUDmQqt fk7IXmHfW5dR16tlp8FGxN4uGS02Uxudz57GW6fEsQiBmdOZM80BKUyiw8fLTqCZhGWG A6V1SeAf2TUS9eW5J8ZlZYHwgKHisFFUY60qyX4ec7SFoiljfd9MJb27nIBVc9hT8uSt 0XdQ==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=czRbSvVN; spf=pass (google.com: domain of eva.kurchatova@virtuozzo.com designates 130.117.225.111 as permitted sender) smtp.mailfrom=eva.kurchatova@virtuozzo.com; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=virtuozzo.com Received: from relay.virtuozzo.com (relay.virtuozzo.com. [130.117.225.111]) by mx.google.com with ESMTPS id ffacd0b85a97d-48b5b448e90si2076972f8f.468.2026.10.01.21.44.36 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 01 Oct 2026 21:44:36 -0700 (PDT) Received-SPF: pass (google.com: domain of eva.kurchatova@virtuozzo.com designates 130.117.225.111 as permitted sender) client-ip=130.117.225.111; Authentication-Results: mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=czRbSvVN; spf=pass (google.com: domain of eva.kurchatova@virtuozzo.com designates 130.117.225.111 as permitted sender) smtp.mailfrom=eva.kurchatova@virtuozzo.com; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=virtuozzo.com DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=virtuozzo.com; s=relay; h=MIME-Version:Message-ID:Date:Subject:From: Content-Type; bh=VenQoCbDp2hkhaYp+7X9gU98YAPY+OuoWaJHd/Fj/m8=; b=czRbSvVNkK2c GzSYWyMCxczs32Ju37XiCKdyoiEj0NFghGGEewC0iMM66qmW3qib2TULHu/lKfVr1Q//UnaGN0ZIC lbAhBjZ7fKt+1dXJgCOxxw3Kae30z/sKPtWcclZDh5kPfEixwAUrzWtN/iDjgu42Pl7I2uIqVtACO Zg3pnL8hhswN9tAa2/AWSNuNpl7C1+UZ4L0rQ2cuzwGgK+u9B7FD4BSV/VMBnywQlffoQprr4BnGn nmw9A/av8E+bl7p367119+LnhckCuCzZl1+vpa/cDfsL/b3aMEhRugnw81wj+HxIaQUBX5TU1eDlo gYnHgJAsWUs8LsCVK7BDCg==; Received: from ch-vpn.virtuozzo.com ([130.117.225.6] helo=LekKit-T14) by relay.virtuozzo.com with esmtp (Exim 4.96) (envelope-from ) id 1xCV5F-00F2fs-2c; Fri, 02 Oct 2026 06:44:32 +0200 From: Eva Kurchatova To: khorenko@virtuozzo.com Date: Fri, 2 Oct 2026 07:43:14 +0300 Message-ID: <20261002044431.1632888-3-eva.kurchatova@virtuozzo.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261002044431.1632888-1-eva.kurchatova@virtuozzo.com> References: <20261002044431.1632888-1-eva.kurchatova@virtuozzo.com> MIME-Version: 1.0 X-OZ-Fwd: true Cc: devel@openvz.org Subject: [Devel] [PATCH hci-8.1 v6 2/2] selftests: ve: introduce vtty pair teardown stress test X-BeenThere: devel@openvz.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: OpenVZ development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Sender: devel-bounces@openvz.org Errors-To: devel-bounces@openvz.org 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 --- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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