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 8193A80726 for ; Tue, 6 Oct 2026 22:14:18 +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 696MD4Mu028347; Wed, 7 Oct 2026 01:13:05 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 696MD4Mu028347 Authentication-Results: mail.openvz.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=virtuozzo.com header.i=@virtuozzo.com header.b="Yg2fE95B" Received: from mail-wm1-f72.google.com (mail-wm1-f72.google.com [209.85.128.72]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 696MCxni028318 (version=TLSv1/SSLv3 cipher=AES128-GCM-SHA256 bits=128 verify=FAIL) for ; Wed, 7 Oct 2026 01:12:59 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 696MCxni028318 Received: by mail-wm1-f72.google.com with SMTP id 5b1f17b1804b1-4a178f50137so13797255e9.2 for ; Tue, 06 Oct 2026 15:12:59 -0700 (PDT) X-Forwarded-Encrypted: i=2; AKwUvBz9jUwbd8KQnDkg/K6XE60QoI8z0Zr6ERbEGNNSqK4jJ04T47cuI8QPbI0yiK0Mz+2zeYtjOQ==@openvz.org X-Gm-Message-State: AFuF++lPivfEpE1VZL0UW+QmOQHQFPhRBfTQl8L3g5tiDkxMKbDEpiZn R6rqtws98xgba+S+Cd/P+1TGZpysqjH9Jt+TNOFAiFbA5P5AJrdITnAr0K1hZhMzu+sCxOObrGs 7oksgyFpPyPhxv5QDgVoVWrLXPLzSDbRCXvda34W4vEPb96FNdgouoA== X-Gm-Gg: AYBFou20VUxVae3WXpTD/5urMIZfVoOi/JHZREP7VbERgPgA+6xoUFiLO9USlKr5guI HXj3jPrJSvtN4gT5NJcwWAa6IUKcTjb17YHWCJSO+UzcVoRtTVrxvTCjH8TsOAFu2LFa5p7cG7O RHsu8fvkIjX2pggqH6JOo/drAW8H+RbAL0V5kFv9PZbgaRIElWW2g71k/MqK8ojWRg2Yc1rNtQL 0K17+4EULavWwv+FNASIsvjxzl/4Drm0KrOXjoaZQ3ZX+jSKW8kDu8e0GyZR+sMNemSaUGapTLR iIsISQMNPlaCnjnZ5+xqsiZOiYXK3PXM1gHxaCPEg4EJ/TgRrHdz19GWvatScfw0twbmkRDSdNS 4kF2QIRb+l1wNa50p X-Received: by 2002:a05:600c:4fcb:b0:49f:bd3c:bc1b with SMTP id 5b1f17b1804b1-4a1804557f1mr2750025e9.22.1791324779148; Tue, 06 Oct 2026 15:12:59 -0700 (PDT) X-Received: by 2002:a05:600c:4fcb:b0:49f:bd3c:bc1b with SMTP id 5b1f17b1804b1-4a1804557f1mr2749795e9.22.1791324778619; Tue, 06 Oct 2026 15:12:58 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1791324778; cv=none; d=google.com; s=arc-20260327; b=MbK4anjwG+kCXsx+CrS2TP/tg0OIWSIJRl7pcSGEqT/8Uq7WGtGQBIXQKusuJVWImv QgUEjVavypE7XlJxBuiXQSzvKfdZcyVVz666LDK1rpVyzFuGZYi4V0/4Wh2asFEAXm7+ zrD1y8ldD+K/nMZP6WAIQfEeADXQIMJGaeJMru6qBoh357SjtqhtR9LXeQSy25GyNYJb bMN8RBvM5EtK+viF9h1pFBYQ2VkQL9zsRXHPtio1RouJbKt03vR+QOsDv0NPFfFzHxmU uWBrdhIJgqFLMEbciYJYPMFv6MnLjzWbNPirR7Jv95ygqZPOwmJEOaD4jXdQe8buUpG4 rh9g== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:dkim-signature; bh=YN8s6l2kkrgh5lNIciklcuIPyswSdK5LzCavDbVsHNE=; fh=bRkZfFa6377EgOVW8zBDri2gGIQqe/ZeiH+ijEM/ROI=; b=GSgH9DhGolSpfDWQrWBHSwCFWdTbGkshV/U/P2lkgsetV+huWPU/jqe13ejn6bfJ/i CvqeWhDNFEXaxHEQ/3rWUfpcaFOMksvhbxvYElXC3c7fyPl5jZgaf93Tm3jaPW/zK9Zx wbtBMbpA0PT3RHHj53R1nYxy8khUOZ7BHIV9q/JHUNSek1RNbg3PhOUCHOeY+THkC5Gg pRXU6XKnnyG9TpGPnyGfQnChjZ+nlClGemN92UG4JA3zkv+2fT8DZQglJkPXHTMUphYJ 3FUvPzfJsGWo/V0dSroTct9xna1r5mI5Akh+xLFhwf2xQPya+FDuhp6cDx4L6xFj9Hf0 6BeQ==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=Yg2fE95B; spf=pass (google.com: domain of khorenko@virtuozzo.com designates 130.117.225.111 as permitted sender) smtp.mailfrom=khorenko@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-48c72471e59si1400517f8f.157.2026.10.06.15.12.58 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 06 Oct 2026 15:12:58 -0700 (PDT) Received-SPF: pass (google.com: domain of khorenko@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=Yg2fE95B; spf=pass (google.com: domain of khorenko@virtuozzo.com designates 130.117.225.111 as permitted sender) smtp.mailfrom=khorenko@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=Message-Id:Content-Type:MIME-Version:Subject:Date :From; bh=YN8s6l2kkrgh5lNIciklcuIPyswSdK5LzCavDbVsHNE=; b=Yg2fE95BeoHZzOLgc1w ztiaxAq1ETwak/hihWRwNC7PFPygx6SSQetdD/kBM2mE8cxB37K1es+Ju/kNnFx9lrb91aGMUJ07A VJKHyIjgN4M1XRKx3VHwn5NmSKBvWJOanC/Cbrr0SQOqEhW2TvL2tlAmWSQ7MA4+smjSsKIrqi/dI bt7A/DduovjPOpKrrc3VpseTZ/UhdYpwgXKTZ6aB/FdAq2MLjPiM6k3CcOjtDpE0FrrnGwwfOMO/L FQRoPiN3VewwsesZLHM4VnWWf+/GWlImTMn3YmPABRCcfVP6ZnmI2ZhZMkQ48UycBlUB+w3boZijE H+K/MeH5gAc4+zA==; Received: from ch-demo-asa.virtuozzo.com ([130.117.225.8] helo=f0.sw.ru) by relay.virtuozzo.com with esmtp (Exim 4.96) (envelope-from ) id 1xEDLs-004aGG-3C; Wed, 07 Oct 2026 00:12:47 +0200 From: Konstantin Khorenko Date: Wed, 07 Oct 2026 00:12:51 +0200 MIME-Version: 1.0 Message-Id: <20261007-b4-prep-khorenko-vstor-148656-cpuid-v4-1-bf16c29bea24@virtuozzo.com> References: <20261007-b4-prep-khorenko-vstor-148656-cpuid-v4-0-bf16c29bea24@virtuozzo.com> In-Reply-To: <20261007-b4-prep-khorenko-vstor-148656-cpuid-v4-0-bf16c29bea24@virtuozzo.com> To: Pavel Tikhomirov , Vasileios Almpanis X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=10559; i=khorenko@virtuozzo.com; h=from:subject:message-id; bh=A+2XXpqeXpgMP9NV0ZbmLgzm2DjvkblZjXZPWvbPavQ=; b=owEBbQKS/ZANAwAKAVGWCkf5YVwqAcsmYgBqxXJn2sq6rlmGBudjQgCsbYLbegXS3vcPSPPko R2sr1oizh6JAjMEAAEKAB0WIQRWD23IPcXT0GO9CCxRlgpH+WFcKgUCasVyZwAKCRBRlgpH+WFc KnzVD/9wI282IaLb76KrU1pm3rGV276Plfh0og/ddJ9KDbv0FutdJrJZKg9BcJuL28lLWgIDc+0 W9AggXcNl8AkEEec/cyfxPiL9kGNSIqOm3l+raGmZv1HT4nF5JOULqda33hUVui2hd3jv+mYpCy XghfmvB8ISO6Ujg5VB4oHEpihzTZqDf63DHVlZFwlrNkX3FLTLxCQBsUJqTx/nyUnaX/xWE8qiW Hfd4e7Aw2HrHlJnXuf4uV7Y0fHdu5kyDhz+tLQgS9LfgA3C+PI9plApvxe23imrP1dIZ6YRXI5D IYmCVr+ITUavgeCY3RL8ynCwpFeM05Ae4JUnaHAmzwb1C/uufpJjqWm50mvtojy8g/y/HCxGaZJ 1Hbt6hIm349Wod52GLAn4lEKTiLCake5d4aT48azDiu2JU2Lg7vVeA2tJlODLTpnD5n33F8e/ZF rYeO8fzfDsjb8cJ9v0Y6+BsBuR6sj4OOjNImVZu81qhDeiFy+M96MNvw7k9RukqGjuYZPGBzort b78WbYO+OjBDCGetskzpjX6CW9DAE8ppGOWwdlb7JDMalpdcz1iRtXZfacWBO/mfZxtRjngzeql 18tBmwhJNoDNlCwzG69ERIo/QAzS8KRSqc7nLN4j9h1kJ/rn6HXlticZlKcm7kyORop2HwebtH3 rT8ztTSYvO2MidQ== X-Developer-Key: i=khorenko@virtuozzo.com; a=openpgp; fpr=560F6DC83DC5D3D063BD082C51960A47F9615C2A X-OZ-Fwd: true Cc: devel@openvz.org Subject: [Devel] [PATCH hci-8.1 v4 1/5] selftests/ve: check CPUID override emulation for VE tasks 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 Write a one-entry /proc/vz/cpuid_override table that clears one leaf 1 feature bit and compare what a task in a fresh VE and the host see: the VE task must get the table values, the host must keep the hardware bit. A second test flushes the table after the task was armed and expects the hardware bit back, since faulting stays on for the task and the emulation has to fall back to the real CPUID. On its own that test would also pass on a kernel that never arms faulting; the first one proves the arming. The child stops itself with SIGSTOP after entering the VE and the parent resumes it before the CPUID: faulting is switched on for a task at its next context switch, not when the flag is set, and a stopped task is off the CPU for sure. For the second test the table is flushed while the child stands still. Without commit 07338fbae5a3 ("x86/traps: emulate CPUID faults for user tasks in exc_general_protection()") both children die with SIGSEGV. The tests skip on CPUs without CPUID faulting and restore the previous table on exit; a table the kernel cannot take back in one write fails the setup instead. The harness skips FIXTURE_TEARDOWN once an ASSERT has fired, so the cleanup is a FIXTURE_TEARDOWN_PARENT, which runs whether or not one did; it also kills a child that such an ASSERT left stopped in the VE cgroup. The test is built on x86 only. https://virtuozzo.atlassian.net/browse/VSTOR-148843 Feature: x86: cpu pools (cpuid override) Signed-off-by: Konstantin Khorenko --- tools/testing/selftests/ve/.gitignore | 1 + tools/testing/selftests/ve/Makefile | 10 + .../testing/selftests/ve/ve_cpuid_override_test.c | 247 +++++++++++++++++++++ 3 files changed, 258 insertions(+) diff --git a/tools/testing/selftests/ve/.gitignore b/tools/testing/selftests/ve/.gitignore index 3df4d05888dce..bdc322c645d95 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_cpuid_override_test diff --git a/tools/testing/selftests/ve/Makefile b/tools/testing/selftests/ve/Makefile index c6efe7c4b4fba..d919c1c289c8b 100644 --- a/tools/testing/selftests/ve/Makefile +++ b/tools/testing/selftests/ve/Makefile @@ -6,4 +6,14 @@ TEST_GEN_PROGS += ve_ns_owner_test TEST_GEN_PROGS += ve_perms_test TEST_GEN_PROGS += ve_mount_accounting_test +# ARCH stays untouched: lib.mk picks the clang target by its emptiness. +ifeq ($(ARCH),) +ve_arch := $(shell uname -m 2>/dev/null || echo not) +else +ve_arch := $(ARCH) +endif +ifeq ($(ve_arch),$(filter $(ve_arch),x86 x86_64)) +TEST_GEN_PROGS += ve_cpuid_override_test +endif + include ../lib.mk diff --git a/tools/testing/selftests/ve/ve_cpuid_override_test.c b/tools/testing/selftests/ve/ve_cpuid_override_test.c new file mode 100644 index 0000000000000..f2883539bac3f --- /dev/null +++ b/tools/testing/selftests/ve/ve_cpuid_override_test.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * /proc/vz/cpuid_override selftests + * + * A non-empty override table turns CPUID faulting on for every task that + * enters a VE, and the #GP handler has to emulate the instruction for + * them. The tests check that a task in a VE sees the table values, that + * the host keeps seeing the hardware values, and that a task which was + * armed while the table was set falls back to the hardware once the + * table is flushed. A kernel that does not emulate the fault kills the + * child with SIGSEGV instead. + * + * The table is global: run this on a node without live Containers, a task + * entering a VE while a case runs is armed for life. A harness timeout + * kills the test without its teardown and leaves the test table in place + * and the VE cgroup behind. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../kselftest_harness.h" +#include "ve_selftest.h" + +#define CPUID_OVERRIDE_PATH "/proc/vz/cpuid_override" +#define CPUID_LEAF 1 +#define CPUID_LINE_LEN 128 +/* The kernel takes a table back only in one write below a page. */ +#define CPUID_WRITE_MAX 4096 +#define CPUID_SAVED_MAX (CPUID_WRITE_MAX + 1) + +struct cpuid_regs { + unsigned int eax, ebx, ecx, edx; +}; + +/* vz builds these tests for x86-64 only; there ebx is not the PIC base. */ +static void do_cpuid(unsigned int leaf, struct cpuid_regs *r) +{ + asm volatile("cpuid" + : "=a" (r->eax), "=b" (r->ebx), "=c" (r->ecx), "=d" (r->edx) + : "a" (leaf), "c" (0)); +} + +struct child_ctx { + int result_pipe[2]; +}; + +/* + * The child enters a fresh VE, so it is armed with the table state of that + * moment, then reports its view of the leaf to the parent over a pipe. It + * stops itself in between: a stopped task is off the CPU, so the resume is + * a context switch, and the parent may change the table while the child + * stands still. + */ +static int cpuid_child(struct child_ctx *ctx) +{ + struct cpuid_regs r; + + close(ctx->result_pipe[0]); + + if (unshare(CLONE_NEWVE) < 0) + return 21; + if (raise(SIGSTOP)) + return 22; + + do_cpuid(CPUID_LEAF, &r); + + if (write(ctx->result_pipe[1], &r, sizeof(r)) != sizeof(r)) + return 23; + return 0; +} + +FIXTURE(ve_cpuid_override) +{ + int cgv2_fd; + int ctid; + struct cpuid_regs host; + unsigned int bit; + pid_t child; + char table[CPUID_LINE_LEN]; + char saved[CPUID_SAVED_MAX]; +}; + +FIXTURE_SETUP(ve_cpuid_override) +{ + int fd, len; + + fd = open(CPUID_OVERRIDE_PATH, O_RDONLY); + if (fd < 0) + SKIP(return, "%s: %s", CPUID_OVERRIDE_PATH, strerror(errno)); + close(fd); + + len = read_file_at(AT_FDCWD, CPUID_OVERRIDE_PATH, self->saved, + sizeof(self->saved)); + ASSERT_GE(len, 0) TH_LOG("%s: %s", CPUID_OVERRIDE_PATH, strerror(errno)); + ASSERT_LT(len, CPUID_WRITE_MAX) + TH_LOG("a table of at least %d bytes cannot be restored", len); + + /* Mask one feature bit the host has; SSE2 when present. */ + do_cpuid(CPUID_LEAF, &self->host); + self->bit = 1u << 26; + if (!(self->host.edx & self->bit)) + self->bit = self->host.edx & -self->host.edx; + ASSERT_NE(self->bit, 0u); + + snprintf(self->table, sizeof(self->table), + "0x%08x: 0x%08x 0x%08x 0x%08x 0x%08x\n", CPUID_LEAF, + self->host.eax, self->host.ebx, self->host.ecx, + self->host.edx & ~self->bit); + + /* Last, so that a failed ASSERT above leaves no VE cgroup behind. */ + self->cgv2_fd = mount_cg2_fd(); + ASSERT_GE(self->cgv2_fd, 0) TH_LOG("cgroup2 mount: %s", strerror(errno)); + ASSERT_EQ(write_file_at(self->cgv2_fd, "cgroup.subtree_control", + VE_CONTROLLERS), 0); + self->ctid = make_ve(self->cgv2_fd, CTID_MIN); + ASSERT_GE(self->ctid, 0); +} + +/* + * Runs after the test body in whichever process the harness picks, the test + * process itself after a failed ASSERT, so the table is restored whether or + * not one fired. The setup fd may be gone by then, hence the fresh mount. + */ +FIXTURE_TEARDOWN_PARENT(ve_cpuid_override) +{ + int cgv2_fd; + + if (write_file_at(AT_FDCWD, CPUID_OVERRIDE_PATH, + self->saved[0] ? self->saved : "\n")) + TH_LOG("could not restore %s: %s", CPUID_OVERRIDE_PATH, + strerror(errno)); + + /* A child left stopped by a failed ASSERT keeps the VE cgroup busy. */ + if (self->child > 0) + kill(self->child, SIGKILL); + + cgv2_fd = mount_cg2_fd(); + if (cgv2_fd < 0) + return; + destroy_ve(cgv2_fd, self->ctid); + close(cgv2_fd); +} + +/* Fork the child into the VE and return once it stands still there. */ +static pid_t spawn_cpuid_child(struct __test_metadata *_metadata, + FIXTURE_DATA(ve_cpuid_override) * self, + struct child_ctx *ctx) +{ + int status; + pid_t pid; + + ASSERT_EQ(pipe(ctx->result_pipe), 0); + ASSERT_GE(enter_cgroup(self->cgv2_fd, self->ctid), 0); + + pid = fork(); + if (pid == 0) + _exit(cpuid_child(ctx)); + if (pid > 0) + self->child = pid; + + /* Leave the VE cgroup before any ASSERT, or destroy_ve() gets EBUSY. */ + ASSERT_GE(enter_cgroup(self->cgv2_fd, 0), 0); + ASSERT_GE(pid, 0); + close(ctx->result_pipe[1]); + + ASSERT_GE(waitpid(pid, &status, WUNTRACED), 0); + if (!WIFSTOPPED(status)) { + /* Reaped: the pid may be reused, keep it from teardown. */ + self->child = 0; + if (WIFEXITED(status)) + TH_LOG("child exited with %d", WEXITSTATUS(status)); + if (WIFSIGNALED(status)) + TH_LOG("child killed by signal %d", WTERMSIG(status)); + } + ASSERT_TRUE(WIFSTOPPED(status)); + return pid; +} + +static void resume_cpuid_child(struct __test_metadata *_metadata, pid_t pid) +{ + ASSERT_EQ(kill(pid, SIGCONT), 0); +} + +static void collect_cpuid_child(struct __test_metadata *_metadata, + FIXTURE_DATA(ve_cpuid_override) * self, + struct child_ctx *ctx, struct cpuid_regs *r) +{ + int status, len; + + len = read(ctx->result_pipe[0], r, sizeof(*r)); + ASSERT_GE(waitpid(self->child, &status, 0), 0); + self->child = 0; + if (WIFSIGNALED(status)) + TH_LOG("child killed by signal %d", WTERMSIG(status)); + ASSERT_TRUE(WIFEXITED(status)); + ASSERT_EQ(WEXITSTATUS(status), 0); + ASSERT_EQ(len, (int)sizeof(*r)); + + close(ctx->result_pipe[0]); +} + +TEST_F(ve_cpuid_override, ve_sees_table_host_sees_hardware) +{ + struct child_ctx ctx; + struct cpuid_regs r; + pid_t pid; + + ASSERT_EQ(write_file_at(AT_FDCWD, CPUID_OVERRIDE_PATH, self->table), 0); + + pid = spawn_cpuid_child(_metadata, self, &ctx); + resume_cpuid_child(_metadata, pid); + collect_cpuid_child(_metadata, self, &ctx, &r); + + EXPECT_EQ(r.eax, self->host.eax); + EXPECT_EQ(r.ebx, self->host.ebx); + EXPECT_EQ(r.ecx, self->host.ecx); + EXPECT_EQ(r.edx, self->host.edx & ~self->bit); + + do_cpuid(CPUID_LEAF, &r); + EXPECT_NE(r.edx & self->bit, 0u); +} + +TEST_F(ve_cpuid_override, flushed_table_falls_back_to_hardware) +{ + struct child_ctx ctx; + struct cpuid_regs r; + pid_t pid; + + ASSERT_EQ(write_file_at(AT_FDCWD, CPUID_OVERRIDE_PATH, self->table), 0); + + pid = spawn_cpuid_child(_metadata, self, &ctx); + + ASSERT_EQ(write_file_at(AT_FDCWD, CPUID_OVERRIDE_PATH, "\n"), 0); + resume_cpuid_child(_metadata, pid); + collect_cpuid_child(_metadata, self, &ctx, &r); + + EXPECT_NE(r.edx & self->bit, 0u); +} + +TEST_HARNESS_MAIN -- 2.43.0 _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel