* [Devel] [PATCH hci-8.1 1/5] selftests/ve: check CPUID override emulation for VE tasks
2026-10-06 14:56 [Devel] [PATCH hci-8.1 0/5] x86/ve: CPUID override follow-ups: arm at once, prefixes, selftests Konstantin Khorenko
@ 2026-10-06 14:56 ` Konstantin Khorenko
2026-10-06 15:07 ` no-reply
2026-10-06 14:56 ` [Devel] [PATCH hci-8.1 2/5] x86/ve: enable CPUID faulting at once when the current task enters a VE Konstantin Khorenko
` (3 subsequent siblings)
4 siblings, 1 reply; 11+ messages in thread
From: Konstantin Khorenko @ 2026-10-06 14:56 UTC (permalink / raw)
To: Pavel Tikhomirov, Vasileios Almpanis; +Cc: devel
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 the hardware ones.
A second test flushes the table after the task was armed and expects the
hardware values again, since faulting stays on for the task and the
emulation has to fall back to the real CPUID.
The child blocks before executing CPUID: faulting is switched on for a
task at its next context switch, not when the flag is set.
Without the fix both children die with SIGSEGV. The tests skip on CPUs
without CPUID faulting and restore the previous table on exit. The
teardown runs in the parent because the harness skips a child-side
teardown once an ASSERT has fired.
https://virtuozzo.atlassian.net/browse/VSTOR-148843
Feature: x86: cpu pools (cpuid override)
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
tools/testing/selftests/ve/Makefile | 1 +
.../testing/selftests/ve/ve_cpuid_override_test.c | 227 +++++++++++++++++++++
2 files changed, 228 insertions(+)
diff --git a/tools/testing/selftests/ve/Makefile b/tools/testing/selftests/ve/Makefile
index c6efe7c4b4fba..e7932408b1305 100644
--- a/tools/testing/selftests/ve/Makefile
+++ b/tools/testing/selftests/ve/Makefile
@@ -5,5 +5,6 @@ CFLAGS += -g -Wall -O2
TEST_GEN_PROGS += ve_ns_owner_test
TEST_GEN_PROGS += ve_perms_test
TEST_GEN_PROGS += ve_mount_accounting_test
+TEST_GEN_PROGS += ve_cpuid_override_test
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..c066d67d72a82
--- /dev/null
+++ b/tools/testing/selftests/ve/ve_cpuid_override_test.c
@@ -0,0 +1,227 @@
+// 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.
+ */
+#define _GNU_SOURCE
+#include <sched.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/wait.h>
+
+#include "../kselftest_harness.h"
+#include "ve_selftest.h"
+
+#define CPUID_OVERRIDE_PATH "/proc/vz/cpuid_override"
+#define CPUID_LEAF 1
+#define CPUID_TABLE_LEN 128
+#define CPUID_TABLE_MAX 4096
+
+struct cpuid_regs {
+ unsigned int eax, ebx, ecx, edx;
+};
+
+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));
+}
+
+static int write_cpuid_override(const char *table)
+{
+ int fd, ret;
+
+ fd = open(CPUID_OVERRIDE_PATH, O_WRONLY);
+ if (fd < 0)
+ return -1;
+ ret = write(fd, table, strlen(table));
+ close(fd);
+ return ret == (int)strlen(table) ? 0 : -1;
+}
+
+/*
+ * 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
+ * blocks between the two steps: faulting is switched on for the task at its
+ * next context switch, and the parent may change the table meanwhile.
+ */
+struct child_ctx {
+ int result_pipe[2];
+ int go_pipe[2];
+};
+
+static int cpuid_child(struct child_ctx *ctx)
+{
+ struct cpuid_regs r;
+ char go;
+
+ close(ctx->result_pipe[0]);
+ close(ctx->go_pipe[1]);
+
+ if (unshare(CLONE_NEWVE) < 0)
+ return 21;
+ if (write(ctx->result_pipe[1], "R", 1) != 1)
+ return 22;
+ if (read(ctx->go_pipe[0], &go, 1) != 1)
+ return 23;
+
+ do_cpuid(CPUID_LEAF, &r);
+ if (write(ctx->result_pipe[1], &r, sizeof(r)) != sizeof(r))
+ return 24;
+ return 0;
+}
+
+FIXTURE(ve_cpuid_override)
+{
+ int cgv2_fd;
+ int ctid;
+ struct cpuid_regs host;
+ unsigned int bit;
+ char table[CPUID_TABLE_LEN];
+ char saved[CPUID_TABLE_MAX];
+};
+
+FIXTURE_SETUP(ve_cpuid_override)
+{
+ int fd;
+
+ fd = open(CPUID_OVERRIDE_PATH, O_RDONLY);
+ if (fd < 0)
+ SKIP(return, "%s: %s", CPUID_OVERRIDE_PATH, strerror(errno));
+ close(fd);
+
+ ASSERT_GE(read_file_at(AT_FDCWD, CPUID_OVERRIDE_PATH, self->saved,
+ sizeof(self->saved)), 0);
+
+ self->cgv2_fd = mount_cg2_fd();
+ ASSERT_GE(self->cgv2_fd, 0);
+ 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);
+
+ /* 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, 0);
+
+ 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);
+}
+
+/*
+ * Runs in the parent: a child-side teardown is skipped when an ASSERT fires,
+ * and the table must be restored even then. The fd from setup belongs to the
+ * child, so the cgroup2 tree is mounted again here.
+ */
+FIXTURE_TEARDOWN_PARENT(ve_cpuid_override)
+{
+ int cgv2_fd;
+
+ write_cpuid_override(self->saved[0] ? self->saved : "\n");
+
+ cgv2_fd = mount_cg2_fd();
+ if (cgv2_fd < 0)
+ return;
+ destroy_ve(cgv2_fd, self->ctid);
+ close(cgv2_fd);
+}
+
+static pid_t spawn_cpuid_child(struct __test_metadata *_metadata,
+ int cgv2_fd, int ctid, struct child_ctx *ctx)
+{
+ char ready;
+ pid_t pid;
+
+ ASSERT_EQ(pipe(ctx->result_pipe), 0);
+ ASSERT_EQ(pipe(ctx->go_pipe), 0);
+ ASSERT_GE(enter_cgroup(cgv2_fd, ctid), 0);
+
+ pid = fork();
+ ASSERT_GE(pid, 0);
+ if (pid == 0)
+ _exit(cpuid_child(ctx));
+
+ ASSERT_GE(enter_cgroup(cgv2_fd, 0), 0);
+ close(ctx->result_pipe[1]);
+ close(ctx->go_pipe[0]);
+
+ ASSERT_EQ(read(ctx->result_pipe[0], &ready, 1), 1);
+ ASSERT_EQ(ready, 'R');
+ return pid;
+}
+
+static void collect_cpuid_child(struct __test_metadata *_metadata, pid_t pid,
+ struct child_ctx *ctx, struct cpuid_regs *r)
+{
+ int status, len;
+
+ len = read(ctx->result_pipe[0], r, sizeof(*r));
+ ASSERT_GE(waitpid(pid, &status, 0), 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, sizeof(*r));
+
+ close(ctx->result_pipe[0]);
+ close(ctx->go_pipe[1]);
+}
+
+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_cpuid_override(self->table), 0);
+
+ pid = spawn_cpuid_child(_metadata, self->cgv2_fd, self->ctid, &ctx);
+ ASSERT_EQ(write(ctx.go_pipe[1], "G", 1), 1);
+ collect_cpuid_child(_metadata, pid, &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, 0);
+}
+
+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_cpuid_override(self->table), 0);
+
+ pid = spawn_cpuid_child(_metadata, self->cgv2_fd, self->ctid, &ctx);
+
+ ASSERT_EQ(write_cpuid_override("\n"), 0);
+ ASSERT_EQ(write(ctx.go_pipe[1], "G", 1), 1);
+ collect_cpuid_child(_metadata, pid, &ctx, &r);
+
+ EXPECT_EQ(r.eax, self->host.eax);
+ EXPECT_NE(r.edx & self->bit, 0);
+}
+
+TEST_HARNESS_MAIN
--
2.43.0
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 11+ messages in thread* Re: [Devel] [PATCH hci-8.1 1/5] selftests/ve: check CPUID override emulation for VE tasks
2026-10-06 14:56 ` [Devel] [PATCH hci-8.1 1/5] selftests/ve: check CPUID override emulation for VE tasks Konstantin Khorenko
@ 2026-10-06 15:07 ` no-reply
0 siblings, 0 replies; 11+ messages in thread
From: no-reply @ 2026-10-06 15:07 UTC (permalink / raw)
To: Pavel Tikhomirov, Vasileios Almpanis, Konstantin Khorenko; +Cc: devel
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [Medium] selftests/ve: child may run CPUID before faulting is armed, so tests are racy
- [Low] selftests/ve: ve_cpuid_override_test binary missing from .gitignore
- [Low] selftests/ve: x86-only cpuid asm built unconditionally for all ARCH
- [Low] selftests/ve: 4096-byte snapshot of cpuid_override can truncate the table, so restore fails
- [Low] selftests/ve: VE cgroup leaks when an ASSERT fails in FIXTURE_SETUP after make_ve()
- [Low] selftests/ve: leaf 1 EAX compared against single-CPU sample while child is unpinned
--
commit 931878a5cb863ada037d978eae5d17e57f54cba3
Author: Konstantin Khorenko <khorenko@virtuozzo.com>
selftests/ve: check CPUID override emulation for VE tasks
This commit adds a selftest that writes a one-entry /proc/vz/cpuid_override
table and checks that a task in a fresh VE sees the table values while the
host sees the hardware values. A second test flushes the table after the
task was armed and expects the hardware values again.
Link: https://virtuozzo.atlassian.net/browse/VSTOR-148843
> diff --git a/tools/testing/selftests/ve/Makefile b/tools/testing/selftests/ve/Makefile
> --- a/tools/testing/selftests/ve/Makefile
> +++ b/tools/testing/selftests/ve/Makefile
> @@ -5,5 +5,6 @@ CFLAGS += -g -Wall -O2
> TEST_GEN_PROGS += ve_ns_owner_test
> TEST_GEN_PROGS += ve_perms_test
> TEST_GEN_PROGS += ve_mount_accounting_test
> +TEST_GEN_PROGS += ve_cpuid_override_test
[Severity: Low]
This isn't a bug in functionality, but the new binary ve_cpuid_override_test
is not added to tools/testing/selftests/ve/.gitignore, which lists only
ve_ns_owner_test, ve_perms_test and ve_mount_accounting_test.
Running make in tools/testing/selftests/ve will leave this binary untracked
in git status. Should it get an entry there?
[Severity: Low]
The test is added unconditionally for every architecture, and do_cpuid()
below uses x86-only inline asm.
Since tools/testing/selftests/Makefile has an unconditional "TARGETS += ve",
can this break the build of the whole ve selftest directory on non-x86 ARCH,
and on 32-bit PIC x86 because of the "=b" constraint? Would an arch check in
this Makefile be appropriate?
[ ... ]
> 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
> --- /dev/null
> +++ b/tools/testing/selftests/ve/ve_cpuid_override_test.c
[ ... ]
> +static int cpuid_child(struct child_ctx *ctx)
> +{
> + struct cpuid_regs r;
> + char go;
> +
> + close(ctx->result_pipe[0]);
> + close(ctx->go_pipe[1]);
> +
> + if (unshare(CLONE_NEWVE) < 0)
> + return 21;
> + if (write(ctx->result_pipe[1], "R", 1) != 1)
> + return 22;
> + if (read(ctx->go_pipe[0], &go, 1) != 1)
> + return 23;
> +
> + do_cpuid(CPUID_LEAF, &r);
[Severity: Medium]
Is the child guaranteed to be armed before it executes CPUID here?
CPUID faulting is only switched on in __switch_to_xtra() at the next
context switch, when prev_cpuid != next_cpuid:
arch/x86/kernel/process.c:__switch_to_xtra() {
...
prev_cpuid = (tifp & _TIF_NOCPUID) || (tifp & _TIF_CPUID_OVERRIDE);
next_cpuid = (tifn & _TIF_NOCPUID) || (tifn & _TIF_CPUID_OVERRIDE);
if (prev_cpuid != next_cpuid)
set_cpuid_faulting(next_cpuid);
...
}
The only blocking point of the child is read(ctx->go_pipe[0]). In
ve_sees_table_host_sees_hardware() the parent writes "G" right after
spawn_cpuid_child() has read "R".
On SMP the byte can already be in the pipe when the child calls read(). The
read then returns without sleeping, no context switch occurs, and the child
executes CPUID natively. It would then report the hardware EDX and fail
EXPECT_EQ(r.edx, self->host.edx & ~self->bit).
In flushed_table_falls_back_to_hardware() the parent calls
write_cpuid_override("\n") right after reading "R", and nothing waits for
the child to sleep. Can the child end up never being armed, so that the test
passes without exercising the armed-then-flushed path?
Later patches in the series (including "selftests/ve: check CPUID override
right after entering a VE") keep this handshake, and the final series state
still has the "R" then immediate "G" pattern with no wait for the child to
sleep, so the race is not fixed there either.
> + if (write(ctx->result_pipe[1], &r, sizeof(r)) != sizeof(r))
> + return 24;
> + return 0;
> +}
[ ... ]
> +FIXTURE_SETUP(ve_cpuid_override)
> +{
> + int fd;
> +
> + fd = open(CPUID_OVERRIDE_PATH, O_RDONLY);
> + if (fd < 0)
> + SKIP(return, "%s: %s", CPUID_OVERRIDE_PATH, strerror(errno));
> + close(fd);
> +
> + ASSERT_GE(read_file_at(AT_FDCWD, CPUID_OVERRIDE_PATH, self->saved,
> + sizeof(self->saved)), 0);
[Severity: Low]
Can this snapshot be truncated? self->saved is CPUID_TABLE_MAX (4096) bytes,
and read_file_at() fills at most buflen-1 bytes without reporting an error.
With up to 128 entries of roughly 45 to 64 bytes each, a large pre-existing
table can exceed 4095 bytes. If the cut falls mid-entry, the restore write in
FIXTURE_TEARDOWN_PARENT() fails with -EINVAL. If it falls on an entry
boundary, a shorter table is installed.
> +
> + self->cgv2_fd = mount_cg2_fd();
> + ASSERT_GE(self->cgv2_fd, 0);
> + 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);
> +
> + /* 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, 0);
[Severity: Low]
If an ASSERT fails in FIXTURE_SETUP after make_ve() (for example
ASSERT_NE(self->bit, 0)), will the VE cgroup be leaked?
The harness runs teardown only when setup has completed, so the statement in
the commit message that "The teardown runs in the parent because the harness
skips a child-side teardown once an ASSERT has fired" only covers the test
body, not a failure in setup. The ASSERT_NE path is very unlikely on x86, so
the impact looks small.
[ ... ]
> +FIXTURE_TEARDOWN_PARENT(ve_cpuid_override)
> +{
> + int cgv2_fd;
> +
> + write_cpuid_override(self->saved[0] ? self->saved : "\n");
[Severity: Low]
Should the return value of write_cpuid_override() be checked here? This is
the only restore of host state, and if it fails, the test's one-entry table
stays installed with no diagnostic.
[ ... ]
> +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_cpuid_override(self->table), 0);
> +
> + pid = spawn_cpuid_child(_metadata, self->cgv2_fd, self->ctid, &ctx);
> +
> + ASSERT_EQ(write_cpuid_override("\n"), 0);
> + ASSERT_EQ(write(ctx.go_pipe[1], "G", 1), 1);
> + collect_cpuid_child(_metadata, pid, &ctx, &r);
> +
> + EXPECT_EQ(r.eax, self->host.eax);
> + EXPECT_NE(r.edx & self->bit, 0);
[Severity: Low]
Can EXPECT_EQ(r.eax, self->host.eax) fail spuriously on hybrid CPUs?
In this test the child's CPUID falls through to the real hardware, but
self->host was sampled by the parent in FIXTURE_SETUP(), and neither process
is pinned with sched_setaffinity(). If leaf 1 EAX (family/model/stepping)
differs between core types and the child runs on a different core type, the
comparison can fail without any kernel bug.
> +}
> +
> +TEST_HARNESS_MAIN
--
Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261006-b4-prep-khorenko-vstor-148656-cpuid-v1-0-741609f4f03a@virtuozzo.com?part=1
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 11+ messages in thread
* [Devel] [PATCH hci-8.1 2/5] x86/ve: enable CPUID faulting at once when the current task enters a VE
2026-10-06 14:56 [Devel] [PATCH hci-8.1 0/5] x86/ve: CPUID override follow-ups: arm at once, prefixes, selftests Konstantin Khorenko
2026-10-06 14:56 ` [Devel] [PATCH hci-8.1 1/5] selftests/ve: check CPUID override emulation for VE tasks Konstantin Khorenko
@ 2026-10-06 14:56 ` Konstantin Khorenko
2026-10-06 15:01 ` no-reply
2026-10-06 14:56 ` [Devel] [PATCH hci-8.1 3/5] selftests/ve: check CPUID override right after entering " Konstantin Khorenko
` (2 subsequent siblings)
4 siblings, 1 reply; 11+ messages in thread
From: Konstantin Khorenko @ 2026-10-06 14:56 UTC (permalink / raw)
To: Pavel Tikhomirov, Vasileios Almpanis; +Cc: devel
ve_setup_task() only sets TIF_CPUID_OVERRIDE. The MSR follows the flag
in __switch_to_xtra(), so a task that enters a VE through unshare() or
setns() keeps executing CPUID natively until it is scheduled out for the
first time and sees the host values instead of the override table.
Add set_cpuid_override(), which sets the flag and, like disable_cpuid(),
flips the CPU state synchronously when the task is the current one.
Tasks set up through clone() are not running yet and are still handled
at their first switch-in.
https://virtuozzo.atlassian.net/browse/VSTOR-148843
Feature: x86: cpu pools (cpuid override)
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
arch/x86/kernel/process.c | 12 ++++++++++++
include/linux/cpuid_override.h | 3 +++
kernel/ve/ve.c | 2 +-
3 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kernel/process.c b/arch/x86/kernel/process.c
index 28fc4168b6c9f..2673f2e51a833 100644
--- a/arch/x86/kernel/process.c
+++ b/arch/x86/kernel/process.c
@@ -23,6 +23,7 @@
#include <linux/utsname.h>
#include <linux/stackprotector.h>
#include <linux/cpuidle.h>
+#include <linux/cpuid_override.h>
#include <linux/acpi.h>
#include <linux/elf-randomize.h>
#include <linux/static_call.h>
@@ -381,6 +382,17 @@ static void enable_cpuid(void)
preempt_enable();
}
+#ifdef CONFIG_VE
+void set_cpuid_override(struct task_struct *tsk)
+{
+ preempt_disable();
+ if (!test_and_set_tsk_thread_flag(tsk, TIF_CPUID_OVERRIDE) &&
+ tsk == current)
+ set_cpuid_faulting(true);
+ preempt_enable();
+}
+#endif
+
static int get_cpuid_mode(void)
{
return !test_thread_flag(TIF_NOCPUID);
diff --git a/include/linux/cpuid_override.h b/include/linux/cpuid_override.h
index 1f5223728c9ba..83a955b628cdf 100644
--- a/include/linux/cpuid_override.h
+++ b/include/linux/cpuid_override.h
@@ -35,4 +35,7 @@ static inline bool cpuid_override_on(void)
{
return rcu_access_pointer(cpuid_override);
}
+
+struct task_struct;
+void set_cpuid_override(struct task_struct *tsk);
#endif
diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c
index 579628a8a310a..8df881039c3c6 100644
--- a/kernel/ve/ve.c
+++ b/kernel/ve/ve.c
@@ -1857,7 +1857,7 @@ void ve_setup_task(struct task_struct *task, struct ve_struct *ve)
#ifdef CONFIG_X86
if (cpuid_override_on())
- set_tsk_thread_flag(task, TIF_CPUID_OVERRIDE);
+ set_cpuid_override(task);
#endif
}
EXPORT_SYMBOL(ve_setup_task);
--
2.43.0
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 11+ messages in thread* [Devel] [PATCH hci-8.1 3/5] selftests/ve: check CPUID override right after entering a VE
2026-10-06 14:56 [Devel] [PATCH hci-8.1 0/5] x86/ve: CPUID override follow-ups: arm at once, prefixes, selftests Konstantin Khorenko
2026-10-06 14:56 ` [Devel] [PATCH hci-8.1 1/5] selftests/ve: check CPUID override emulation for VE tasks Konstantin Khorenko
2026-10-06 14:56 ` [Devel] [PATCH hci-8.1 2/5] x86/ve: enable CPUID faulting at once when the current task enters a VE Konstantin Khorenko
@ 2026-10-06 14:56 ` Konstantin Khorenko
2026-10-06 15:03 ` no-reply
2026-10-06 14:56 ` [Devel] [PATCH hci-8.1 4/5] x86/traps: decode the faulting CPUID with the instruction decoder Konstantin Khorenko
2026-10-06 14:56 ` [Devel] [PATCH hci-8.1 5/5] selftests/ve: check CPUID override emulation for a prefixed CPUID Konstantin Khorenko
4 siblings, 1 reply; 11+ messages in thread
From: Konstantin Khorenko @ 2026-10-06 14:56 UTC (permalink / raw)
To: Pavel Tikhomirov, Vasileios Almpanis; +Cc: devel
Add a case where the child executes CPUID straight after unshare(),
without blocking or waking anyone in between, and expects the table
values. Before the kernel change the override took effect only after
the first context switch, so the child saw the hardware values.
https://virtuozzo.atlassian.net/browse/VSTOR-148843
Feature: x86: cpu pools (cpuid override)
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
.../testing/selftests/ve/ve_cpuid_override_test.c | 42 ++++++++++++++++------
1 file changed, 32 insertions(+), 10 deletions(-)
diff --git a/tools/testing/selftests/ve/ve_cpuid_override_test.c b/tools/testing/selftests/ve/ve_cpuid_override_test.c
index c066d67d72a82..be2b808f1c77e 100644
--- a/tools/testing/selftests/ve/ve_cpuid_override_test.c
+++ b/tools/testing/selftests/ve/ve_cpuid_override_test.c
@@ -54,13 +54,14 @@ static int write_cpuid_override(const char *table)
/*
* 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
- * blocks between the two steps: faulting is switched on for the task at its
- * next context switch, and the parent may change the table meanwhile.
+ * moment, then reports its view of the leaf to the parent over a pipe. With
+ * @wait_go it blocks between the two steps, which lets the parent change
+ * the table meanwhile and forces a context switch before the CPUID.
*/
struct child_ctx {
int result_pipe[2];
int go_pipe[2];
+ int wait_go;
};
static int cpuid_child(struct child_ctx *ctx)
@@ -73,12 +74,18 @@ static int cpuid_child(struct child_ctx *ctx)
if (unshare(CLONE_NEWVE) < 0)
return 21;
- if (write(ctx->result_pipe[1], "R", 1) != 1)
- return 22;
- if (read(ctx->go_pipe[0], &go, 1) != 1)
- return 23;
-
+ if (ctx->wait_go) {
+ if (write(ctx->result_pipe[1], "R", 1) != 1)
+ return 22;
+ if (read(ctx->go_pipe[0], &go, 1) != 1)
+ return 23;
+ }
+
+ /* Without wait_go nothing wakes the parent before the CPUID. */
do_cpuid(CPUID_LEAF, &r);
+
+ if (!ctx->wait_go && write(ctx->result_pipe[1], "R", 1) != 1)
+ return 22;
if (write(ctx->result_pipe[1], &r, sizeof(r)) != sizeof(r))
return 24;
return 0;
@@ -187,7 +194,7 @@ static void collect_cpuid_child(struct __test_metadata *_metadata, pid_t pid,
TEST_F(ve_cpuid_override, ve_sees_table_host_sees_hardware)
{
- struct child_ctx ctx;
+ struct child_ctx ctx = { .wait_go = 1 };
struct cpuid_regs r;
pid_t pid;
@@ -208,7 +215,7 @@ TEST_F(ve_cpuid_override, ve_sees_table_host_sees_hardware)
TEST_F(ve_cpuid_override, flushed_table_falls_back_to_hardware)
{
- struct child_ctx ctx;
+ struct child_ctx ctx = { .wait_go = 1 };
struct cpuid_regs r;
pid_t pid;
@@ -224,4 +231,19 @@ TEST_F(ve_cpuid_override, flushed_table_falls_back_to_hardware)
EXPECT_NE(r.edx & self->bit, 0);
}
+/* No context switch between entering the VE and the CPUID. */
+TEST_F(ve_cpuid_override, armed_before_first_reschedule)
+{
+ struct child_ctx ctx = { .wait_go = 0 };
+ struct cpuid_regs r;
+ pid_t pid;
+
+ ASSERT_EQ(write_cpuid_override(self->table), 0);
+
+ pid = spawn_cpuid_child(_metadata, self->cgv2_fd, self->ctid, &ctx);
+ collect_cpuid_child(_metadata, pid, &ctx, &r);
+
+ EXPECT_EQ(r.edx, self->host.edx & ~self->bit);
+}
+
TEST_HARNESS_MAIN
--
2.43.0
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 11+ messages in thread* Re: [Devel] [PATCH hci-8.1 3/5] selftests/ve: check CPUID override right after entering a VE
2026-10-06 14:56 ` [Devel] [PATCH hci-8.1 3/5] selftests/ve: check CPUID override right after entering " Konstantin Khorenko
@ 2026-10-06 15:03 ` no-reply
0 siblings, 0 replies; 11+ messages in thread
From: no-reply @ 2026-10-06 15:03 UTC (permalink / raw)
To: Pavel Tikhomirov, Vasileios Almpanis, Konstantin Khorenko; +Cc: devel
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] selftests/ve: faulting child in wait_go=0 case fails at ready-read, not diagnosed
- [Low] selftests/ve: armed_before_first_reschedule cannot guarantee no reschedule, may falsely pass
--
commit 56789e953f13472fc78d80df081028a09179dc56
Author: Konstantin Khorenko <khorenko@virtuozzo.com>
selftests/ve: check CPUID override right after entering a VE
This commit adds a test case in which the child executes CPUID straight
after unshare(CLONE_NEWVE), with no blocking in between. It expects the
table values. A new wait_go field in struct child_ctx selects between the
old blocking flow and the new flow.
Link: https://virtuozzo.atlassian.net/browse/VSTOR-148843
> diff --git a/tools/testing/selftests/ve/ve_cpuid_override_test.c b/tools/testing/selftests/ve/ve_cpuid_override_test.c
> --- a/tools/testing/selftests/ve/ve_cpuid_override_test.c
> +++ b/tools/testing/selftests/ve/ve_cpuid_override_test.c
[ ... ]
> @@ -73,12 +74,18 @@ static int cpuid_child(struct child_ctx *ctx)
>
> if (unshare(CLONE_NEWVE) < 0)
> return 21;
> - if (write(ctx->result_pipe[1], "R", 1) != 1)
> - return 22;
> - if (read(ctx->go_pipe[0], &go, 1) != 1)
> - return 23;
> -
> + if (ctx->wait_go) {
> + if (write(ctx->result_pipe[1], "R", 1) != 1)
> + return 22;
> + if (read(ctx->go_pipe[0], &go, 1) != 1)
> + return 23;
> + }
> +
> + /* Without wait_go nothing wakes the parent before the CPUID. */
> do_cpuid(CPUID_LEAF, &r);
> +
> + if (!ctx->wait_go && write(ctx->result_pipe[1], "R", 1) != 1)
> + return 22;
[Severity: Low]
If the kernel does not emulate the CPUID fault, can the child die with
SIGSEGV inside do_cpuid() in the wait_go=0 case, before it writes the "R"
byte?
In that case the parent's read() of the ready byte in spawn_cpuid_child()
would see EOF, and ASSERT_EQ(read(...), 1) would abort the test there.
collect_cpuid_child() would then not be reached, so the "child killed by
signal" TH_LOG is not printed and waitpid() is never called. The child is
left as a zombie until the harness exits.
The test still fails, but with a misleading assertion. The later patch
"selftests/ve: check CPUID override emulation for a prefixed CPUID" keeps
the same ordering of "R" after CPUID for wait_go=0, so this does not appear
to be fixed later in the series.
> if (write(ctx->result_pipe[1], &r, sizeof(r)) != sizeof(r))
> return 24;
> return 0;
[ ... ]
> +/* No context switch between entering the VE and the CPUID. */
> +TEST_F(ve_cpuid_override, armed_before_first_reschedule)
> +{
> + struct child_ctx ctx = { .wait_go = 0 };
> + struct cpuid_regs r;
> + pid_t pid;
> +
> + ASSERT_EQ(write_cpuid_override(self->table), 0);
> +
> + pid = spawn_cpuid_child(_metadata, self->cgv2_fd, self->ctid, &ctx);
> + collect_cpuid_child(_metadata, pid, &ctx, &r);
> +
> + EXPECT_EQ(r.edx, self->host.edx & ~self->bit);
> +}
[Severity: Low]
Can this test pass on a kernel without the immediate-arming fix?
The comment says "No context switch between entering the VE and the
CPUID", but cpuid_child() only avoids voluntary blocking between
unshare(CLONE_NEWVE) and do_cpuid(). Nothing pins the child to a CPU or
prevents preemption, a timer tick, or a migration in that window.
Any such context switch would apply the CPUID-faulting state through
__switch_to_xtra(). The child would then see the table values even without
the fix.
The test is a single run with no retries, so it can miss the bug on a
loaded host. The later patches "x86/traps: decode the faulting CPUID with
the instruction decoder" and "selftests/ve: check CPUID override emulation
for a prefixed CPUID" add neither iterations nor pinning to this case.
Should the comment be softened, or should the test repeat the sequence?
--
Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261006-b4-prep-khorenko-vstor-148656-cpuid-v1-0-741609f4f03a@virtuozzo.com?part=3
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 11+ messages in thread
* [Devel] [PATCH hci-8.1 4/5] x86/traps: decode the faulting CPUID with the instruction decoder
2026-10-06 14:56 [Devel] [PATCH hci-8.1 0/5] x86/ve: CPUID override follow-ups: arm at once, prefixes, selftests Konstantin Khorenko
` (2 preceding siblings ...)
2026-10-06 14:56 ` [Devel] [PATCH hci-8.1 3/5] selftests/ve: check CPUID override right after entering " Konstantin Khorenko
@ 2026-10-06 14:56 ` Konstantin Khorenko
2026-10-06 15:04 ` no-reply
2026-10-06 14:56 ` [Devel] [PATCH hci-8.1 5/5] selftests/ve: check CPUID override emulation for a prefixed CPUID Konstantin Khorenko
4 siblings, 1 reply; 11+ messages in thread
From: Konstantin Khorenko @ 2026-10-06 14:56 UTC (permalink / raw)
To: Pavel Tikhomirov, Vasileios Almpanis; +Cc: devel
check_cpuid_fault() reads two bytes at the faulting ip and emulates
only when they are 0f a2, the bare CPUID encoding.
CPUID has no operands, so the CPU accepts and ignores every legacy
and REX prefix in front of it. All of these are valid CPUID and raise
the same #GP(0) when CPUID faulting is on:
0f a2 cpuid
66 0f a2 data16 cpuid (operand-size override)
f3 0f a2 repz cpuid
2e 0f a2 cs cpuid (segment override)
48 0f a2 rex.W cpuid
Only the first one is recognised. For the others the handler reads
the prefix byte instead of the opcode, declines, and the task is
killed with SIGSEGV although the instruction is legal and the host
would have executed it.
Such encodings could come from hand-written and generated assembly, from
binary translators and JITs that emit prefixes for alignment or as
padding, and from any binary that disassembles as "data16 cpuid" rather
than "cpuid".
Fetch and decode the instruction with insn_fetch_from_user() and
insn_decode_from_regs(), as fixup_umip_exception() does, match the
opcode bytes and advance ip by the decoded length. The decoder derives
the linear ip from the code segment itself, so convert_ip_to_linear()
is not needed any more.
Fixes: 1033fe455434 ("arch/x86: introduce cpuid override")
https://virtuozzo.atlassian.net/browse/VSTOR-148843
Feature: x86: cpu pools (cpuid override)
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
arch/x86/kernel/traps.c | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)
diff --git a/arch/x86/kernel/traps.c b/arch/x86/kernel/traps.c
index 8074387bf4f43..fa12da67adbf0 100644
--- a/arch/x86/kernel/traps.c
+++ b/arch/x86/kernel/traps.c
@@ -633,8 +633,9 @@ static enum kernel_gp_hint get_kernel_gp_address(struct pt_regs *regs,
static int check_cpuid_fault(struct pt_regs *regs, long error_code)
{
- unsigned long addr;
- unsigned short opcode;
+ unsigned char buf[MAX_INSN_SIZE];
+ struct insn insn;
+ int nr_copied;
if (error_code != 0)
return 0;
@@ -642,16 +643,19 @@ static int check_cpuid_fault(struct pt_regs *regs, long error_code)
if (test_thread_flag(TIF_NOCPUID))
return 0;
- addr = convert_ip_to_linear(current, regs);
- if (get_user(opcode, (unsigned short __user *)addr))
+ nr_copied = insn_fetch_from_user(regs, buf);
+ if (nr_copied <= 0)
+ return 0;
+
+ if (!insn_decode_from_regs(&insn, regs, buf, nr_copied))
return 0;
- if (opcode != 0xa20f)
+ if (insn.opcode.bytes[0] != 0x0f || insn.opcode.bytes[1] != 0xa2)
return 0;
do_cpuid_fault(regs);
- regs->ip += 2;
+ regs->ip += insn.length;
return 1;
}
--
2.43.0
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 11+ messages in thread* [Devel] [PATCH hci-8.1 5/5] selftests/ve: check CPUID override emulation for a prefixed CPUID
2026-10-06 14:56 [Devel] [PATCH hci-8.1 0/5] x86/ve: CPUID override follow-ups: arm at once, prefixes, selftests Konstantin Khorenko
` (3 preceding siblings ...)
2026-10-06 14:56 ` [Devel] [PATCH hci-8.1 4/5] x86/traps: decode the faulting CPUID with the instruction decoder Konstantin Khorenko
@ 2026-10-06 14:56 ` Konstantin Khorenko
2026-10-06 15:04 ` no-reply
4 siblings, 1 reply; 11+ messages in thread
From: Konstantin Khorenko @ 2026-10-06 14:56 UTC (permalink / raw)
To: Pavel Tikhomirov, Vasileios Almpanis; +Cc: devel
Execute CPUID with an operand-size prefix, which the CPU ignores, and
expect the table values. Before the kernel change the #GP handler
matched only the bare opcode and the child died with SIGSEGV.
https://virtuozzo.atlassian.net/browse/VSTOR-148843
Feature: x86: cpu pools (cpuid override)
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
.../testing/selftests/ve/ve_cpuid_override_test.c | 29 +++++++++++++++++++++-
1 file changed, 28 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/ve/ve_cpuid_override_test.c b/tools/testing/selftests/ve/ve_cpuid_override_test.c
index be2b808f1c77e..83cbea42974b4 100644
--- a/tools/testing/selftests/ve/ve_cpuid_override_test.c
+++ b/tools/testing/selftests/ve/ve_cpuid_override_test.c
@@ -40,6 +40,14 @@ static void do_cpuid(unsigned int leaf, struct cpuid_regs *r)
: "a" (leaf), "c" (0));
}
+/* CPUID with an operand-size prefix, which the CPU accepts and ignores. */
+static void do_cpuid_prefixed(unsigned int leaf, struct cpuid_regs *r)
+{
+ asm volatile(".byte 0x66, 0x0f, 0xa2"
+ : "=a" (r->eax), "=b" (r->ebx), "=c" (r->ecx), "=d" (r->edx)
+ : "a" (leaf), "c" (0));
+}
+
static int write_cpuid_override(const char *table)
{
int fd, ret;
@@ -62,6 +70,7 @@ struct child_ctx {
int result_pipe[2];
int go_pipe[2];
int wait_go;
+ int prefixed;
};
static int cpuid_child(struct child_ctx *ctx)
@@ -82,7 +91,10 @@ static int cpuid_child(struct child_ctx *ctx)
}
/* Without wait_go nothing wakes the parent before the CPUID. */
- do_cpuid(CPUID_LEAF, &r);
+ if (ctx->prefixed)
+ do_cpuid_prefixed(CPUID_LEAF, &r);
+ else
+ do_cpuid(CPUID_LEAF, &r);
if (!ctx->wait_go && write(ctx->result_pipe[1], "R", 1) != 1)
return 22;
@@ -246,4 +258,19 @@ TEST_F(ve_cpuid_override, armed_before_first_reschedule)
EXPECT_EQ(r.edx, self->host.edx & ~self->bit);
}
+TEST_F(ve_cpuid_override, prefixed_cpuid_is_emulated)
+{
+ struct child_ctx ctx = { .wait_go = 1, .prefixed = 1 };
+ struct cpuid_regs r;
+ pid_t pid;
+
+ ASSERT_EQ(write_cpuid_override(self->table), 0);
+
+ pid = spawn_cpuid_child(_metadata, self->cgv2_fd, self->ctid, &ctx);
+ ASSERT_EQ(write(ctx.go_pipe[1], "G", 1), 1);
+ collect_cpuid_child(_metadata, pid, &ctx, &r);
+
+ EXPECT_EQ(r.edx, self->host.edx & ~self->bit);
+}
+
TEST_HARNESS_MAIN
--
2.43.0
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 11+ messages in thread