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 B56F580067 for ; Thu, 24 Sep 2026 15:02:49 +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 68OF1V4N017409; Thu, 24 Sep 2026 18:01:32 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 68OF1V4N017409 Authentication-Results: mail.openvz.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=virtuozzo.com header.i=@virtuozzo.com header.b="g54d7Ay0" Received: from mail-wr1-f69.google.com (mail-wr1-f69.google.com [209.85.221.69]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 68OF1Kg1017396 (version=TLSv1/SSLv3 cipher=AES128-GCM-SHA256 bits=128 verify=FAIL) for ; Thu, 24 Sep 2026 18:01:21 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 68OF1Kg1017396 Received: by mail-wr1-f69.google.com with SMTP id ffacd0b85a97d-48876f48669so233879f8f.2 for ; Thu, 24 Sep 2026 08:01:21 -0700 (PDT) X-Gm-Message-State: AFuF++mIkniDP2DHQRtrb848gAe7GIl52sDewxebd8GuJT/rwll8Hejs QfKVzyl9uBXCYVBKAoCgN5SkIlI6SDrKsKpesPZOZUmKPCbS9fVEjeZ+1n98Zbnz+ffunwJHO6Y /lUlxe05MWhkL0TzBw9t+IWWqu20ICM6b42bf7aASswHrcweqBam8/w== X-Gm-Gg: AYBFou0TZsJ9McN2RocuTGpRx3ouXcMpl1ZIei6iCUTmY5N83601GSEYP8Lj37qqX37 XJTXYIwzmD9y5vOFwndI3/1HNFD/uYB293Y1yObA+svhBuZArbS5IHQoABjQ7nE4pErhz7lbisK bTxXMIBPtXLFt32Co/mDQd2Arxple4TPRAjyeeU3P26cpjPGmtpwiDoo9zuJ2FVxIB4TFQAoZcf 2687iouc/OxqMV1JW+w/B05wp5rUrjVglsO9WKEajnOk6dh+fWjqNCkT+1yaGUmhuurm1RKSHYO Qy21Cvhj7xh/5kMoo9ApVrez73W7kWCHHAFuGrqp+G877mcd35JVhfq75Kio73C+YWJKVgolwGF 9mDo2DILhSZ0goFLhJQM/1u1mnw== X-Received: by 2002:a05:6000:2507:b0:485:9139:2733 with SMTP id ffacd0b85a97d-4887165dcd1mr5375446f8f.6.1790262080340; Thu, 24 Sep 2026 08:01:20 -0700 (PDT) X-Received: by 2002:a05:6000:2507:b0:485:9139:2733 with SMTP id ffacd0b85a97d-4887165dcd1mr5375279f8f.6.1790262079404; Thu, 24 Sep 2026 08:01:19 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1790262079; cv=none; d=google.com; s=arc-20260327; b=fWirHZNORwsy7CwGMQX8j74ia5aNJ1s1gYXh89W72iITdYoqySSkzxcvDWLQ844qGf hcYsVMbEUBBGUMpmjkIKxJdGzGnHVtOpLoXgphEjaTVpRn/qBuE6cgCV1JwINVebFxxj 9tC46RqB88MEUijsLBflMoRWg0FwXWAIMw80e+J6Lw+MpB2Tpm6XdcsZxf1WqdLkjhQO cF4jb2158uk4K97jLh76aSf5HijPCyZ52kDGrfcpVRA47rc/1o5Yv4uM1TOU1P99wiZ+ builHQzpy7GOJ5lbMD4vIwY+jf2NvR1pLhDuGsjI9d5Y2jeGIC2kU0NzT5DDZNHPrujT IDfQ== 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=e/FqeC8ckwBLJcdWLcyEsFkJOiZjQlf8Gu5okMz1K8o=; fh=bRmlDJ6vflC8xapCpe8SIKiBemLWBr+xozKix8GBr/Q=; b=caTxABMz4IsvQYJrDqguU3pq2Er+PGHRqN6kz1mbGfqR/AOuYolHTc0HnqtYuxnYdg g3vTNgKPeboh6+7/myk4pZNkAhRrwq91cyVg7BjAYb8lDWFjA/QYK4N07xoD91nH71LA GITh+ys7mvVP6rusIIONpP1W6tClwR/dPzc6jEXJ2X/+TrHcn2vMwfYOxQXGGb8XpQQt 2LmFw6z6A5bk8GlEqFYqmHMGpRh/7MsZrooC+I3UDJuLGJmkklMrpaSqknQi11aSF6ua EcanwrtawFwYVYpPOsyGX6k74gqlVeCf/bAorhkctQv7dKZQVP64LKjE8fq1Y5b8ejhv 2xTg==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=g54d7Ay0; 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-488688bd7acsi10348604f8f.223.2026.09.24.08.01.19 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 24 Sep 2026 08:01:19 -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=g54d7Ay0; 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=e/FqeC8ckwBLJcdWLcyEsFkJOiZjQlf8Gu5okMz1K8o=; b=g54d7Ay0B/wk aMnfnxK+yKaoeIBG6iBYq8UzMM+YgRwebjlYFjTWtvvPzfaSScIYMtBPQUHE2ubPygedCHoBVTOHP mk3MGg24zt4+1z9Gm3G1cjFSG7DCTayuhfXQfdDvirOigmCnQS6Il+ybq7KkFxOxlIaCv/GQu5Hbi gtsP+HaR2uEK0I6dbAonVWWlvPeFv+U9cuYKi8t6/cufNM0bZCiFFfHwdgw0pSdZT9JB75WFpGEYA NwOTeM/witioAUTSS4g45j+4E9mY7cFri6MiVb4DMnKdBGIkcxkqzLhF8bEn0VrT2WQqkWCpKspt/ DsDlAH42yeuT4yoweF6prg==; 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 1x9kts-00HF72-0g; Thu, 24 Sep 2026 17:01:16 +0200 From: Eva Kurchatova To: vasileios.almpanis@virtuozzo.com Date: Thu, 24 Sep 2026 18:01:13 +0300 Message-ID: <20260924150116.3258992-2-eva.kurchatova@virtuozzo.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260924150116.3258992-1-eva.kurchatova@virtuozzo.com> References: <20260924150116.3258992-1-eva.kurchatova@virtuozzo.com> MIME-Version: 1.0 X-OZ-Fwd: true Cc: devel@openvz.org Subject: [Devel] [PATCH hci-8.1 v3 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. Hammer both ends for ownership correctness & thread safety verification. Sixty seconds by default, enough to bring down a buggy kernel. 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 | 1 + tools/testing/selftests/ve/settings | 1 + tools/testing/selftests/ve/ve_vtty_stress.c | 319 ++++++++++++++++++++ 4 files changed, 322 insertions(+) create mode 100644 tools/testing/selftests/ve/settings 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 afa4c568c2c9..9d8d86093827 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_vtty_stress diff --git a/tools/testing/selftests/ve/Makefile b/tools/testing/selftests/ve/Makefile index ec40cbc7b3a1..6cd5823c4d93 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_vtty_stress include ../lib.mk diff --git a/tools/testing/selftests/ve/settings b/tools/testing/selftests/ve/settings new file mode 100644 index 000000000000..694d70710ff0 --- /dev/null +++ b/tools/testing/selftests/ve/settings @@ -0,0 +1 @@ +timeout=300 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..4de40a45428b --- /dev/null +++ b/tools/testing/selftests/ve/ve_vtty_stress.c @@ -0,0 +1,319 @@ +// 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. + * + * 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. + * + * 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 60, or $VE_VTTY_SECS) + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ve_selftest.h" +#include "../kselftest.h" + +/* The ioctl argument; the trailing data[] the uapi struct has is unused. */ +struct vzctl_ve_configure { + unsigned int veid, key, val, size; +}; + +#define VZCTL_VE_CONFIGURE _IOW('.', 15, struct vzctl_ve_configure) +#define VE_CONFIGURE_OPEN_TTY 5 + +#define VTTY_IDX 0 /* the pair both ends work on */ +#define SLAVE_MINOR (VTTY_IDX + 1) /* vtty_driver(): index = minor - 1 */ +#define DEFAULT_SECS 60 + +/* + * 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); +} + +/* 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 - ")) { + 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]; + 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)); + write_file_at(cg, "cgroup.subtree_control", VE_CONTROLLERS); + + ctid = make_ve(cg, CTID_MIN); + if (ctid < 0) + ksft_exit_skip("cannot make a VE cgroup\n"); + + /* 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)); + } + 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)); + } + + deadline = time(NULL) + secs; + ksft_print_msg("VE %d, pair %d, %ld seconds\n", ctid, VTTY_IDX, secs); + + pid = fork(); + if (pid == 0) { + close(ready[0]); + close(count[0]); + if (enter_cgroup(cg, ctid) < 0) + _exit(errno ? errno : 1); + /* CLONE_NEWVE puts the task in the VE of the cgroup */ + if (unshare(CLONE_NEWVE | CLONE_NEWPID | CLONE_NEWNS) < 0) + _exit(errno ? errno : 1); + if (fork() == 0) { + unsigned long n = 0; + int err; + + /* + * Hammering on after the run has given up would hold + * the cgroup open against destroy_ve(). + */ + prctl(PR_SET_PDEATHSIG, SIGKILL); + + 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); + } + wait(&status); + _exit(WIFEXITED(status) ? WEXITSTATUS(status) : 1); + } else if (pid < 0) { + 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); + unlink(node); + rmdir(dir); + destroy_ve(cg, ctid); + ksft_exit_fail_msg("the VE side never came up\n"); + } + + 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); + 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