[Devel] [PATCH hci-8.1 v2 2/4] selftests/ve: check that the NUMA API syscalls return ENOSYS in a VE

Konstantin Khorenko khorenko at virtuozzo.com
Fri Nov 6 17:01:33 MSK 2026


A CT must see a kernel without NUMA, so all six NUMA API syscalls
return -ENOSYS in a non-super VE.

The test makes each call with arguments that succeed on the host and
checks that the host gets 0, while a task in a VE gets -ENOSYS. The
host result shows that the ENOSYS can only come from the VE check, not
from a bad argument.

https://virtuozzo.atlassian.net/browse/VSTOR-153679

Feature: mm: prohibit NUMA API inside CT
Signed-off-by: Konstantin Khorenko <khorenko at virtuozzo.com>
---
 tools/testing/selftests/ve/.gitignore              |   1 +
 tools/testing/selftests/ve/Makefile                |   1 +
 .../ve/ve_numa_syscalls_prohibited_test.c          | 196 +++++++++++++++++++++
 3 files changed, 198 insertions(+)

diff --git a/tools/testing/selftests/ve/.gitignore b/tools/testing/selftests/ve/.gitignore
index 3d1179bd2c5f4..5e07985c8fed3 100644
--- a/tools/testing/selftests/ve/.gitignore
+++ b/tools/testing/selftests/ve/.gitignore
@@ -4,3 +4,4 @@ ve_mount_accounting_test
 ve_cpuid_override_test
 ve_vtty_stress
 ve_numa_migrate_test
+ve_numa_syscalls_prohibited_test
diff --git a/tools/testing/selftests/ve/Makefile b/tools/testing/selftests/ve/Makefile
index e96320a183844..451a7e1cc17d1 100644
--- a/tools/testing/selftests/ve/Makefile
+++ b/tools/testing/selftests/ve/Makefile
@@ -7,6 +7,7 @@ TEST_GEN_PROGS += ve_perms_test
 TEST_GEN_PROGS += ve_mount_accounting_test
 TEST_GEN_PROGS += ve_vtty_stress
 TEST_GEN_PROGS += ve_numa_migrate_test
+TEST_GEN_PROGS += ve_numa_syscalls_prohibited_test
 
 # CPUID faulting is x86-64 only. ARCH says "x86" for 32-bit too and lib.mk
 # picks the clang target by its emptiness, so decide by the compiler.
diff --git a/tools/testing/selftests/ve/ve_numa_syscalls_prohibited_test.c b/tools/testing/selftests/ve/ve_numa_syscalls_prohibited_test.c
new file mode 100644
index 0000000000000..c76fcb1c79ff7
--- /dev/null
+++ b/tools/testing/selftests/ve/ve_numa_syscalls_prohibited_test.c
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Every NUMA API syscall works on the host and returns ENOSYS in a VE. */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <sched.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <linux/mempolicy.h>
+
+#include "../kselftest_harness.h"
+#include "ve_selftest.h"
+
+#define NODE_BITS	1024
+#define LONG_BITS	(8 * sizeof(unsigned long))
+
+static unsigned long node_mask[NODE_BITS / LONG_BITS];
+static void *page;
+static size_t page_size;
+
+static long call_get_mempolicy(void)
+{
+	int mode;
+
+	return syscall(__NR_get_mempolicy, &mode, NULL, 0UL, 0UL, 0UL);
+}
+
+static long call_set_mempolicy(void)
+{
+	return syscall(__NR_set_mempolicy, MPOL_DEFAULT, NULL, 0UL);
+}
+
+static long call_mbind(void)
+{
+	return syscall(__NR_mbind, page, page_size, MPOL_BIND, node_mask,
+		       NODE_BITS + 1UL, 0U);
+}
+
+static long call_set_mempolicy_home_node(void)
+{
+	unsigned long node;
+
+	for (node = 0; node < NODE_BITS; node++)
+		if (node_mask[node / LONG_BITS] & (1UL << (node % LONG_BITS)))
+			break;
+	return syscall(__NR_set_mempolicy_home_node, page, page_size, node, 0UL);
+}
+
+static long call_migrate_pages(void)
+{
+	unsigned long none[NODE_BITS / LONG_BITS] = { 0 };
+
+	return syscall(__NR_migrate_pages, 0, NODE_BITS + 1UL, none, node_mask);
+}
+
+static long call_move_pages(void)
+{
+	return syscall(__NR_move_pages, 0, 0UL, NULL, NULL, NULL, 0);
+}
+
+/* set_mempolicy_home_node() needs the MPOL_BIND policy mbind() installs. */
+static const struct numa_call {
+	const char *name;
+	long (*call)(void);
+} numa_calls[] = {
+	{ "get_mempolicy",		call_get_mempolicy },
+	{ "set_mempolicy",		call_set_mempolicy },
+	{ "mbind",			call_mbind },
+	{ "set_mempolicy_home_node",	call_set_mempolicy_home_node },
+	{ "migrate_pages",		call_migrate_pages },
+	{ "move_pages",			call_move_pages },
+};
+
+#define NR_CALLS	ARRAY_SIZE(numa_calls)
+
+struct call_result {
+	long ret;
+	int err;
+};
+
+static void run_calls(struct call_result *res)
+{
+	unsigned int i;
+
+	for (i = 0; i < NR_CALLS; i++) {
+		errno = 0;
+		res[i].ret = numa_calls[i].call();
+		res[i].err = errno;
+	}
+}
+
+FIXTURE(numa_syscalls)
+{
+	int cgv2_fd;
+	int ctid;
+};
+
+FIXTURE_SETUP(numa_syscalls)
+{
+	unsigned long allowed[NODE_BITS / LONG_BITS] = { 0 };
+	unsigned int i;
+	long ret;
+
+	self->cgv2_fd = -1;
+
+	if (geteuid() != 0)
+		SKIP(return, "creating a ve cgroup needs root");
+
+	page_size = getpagesize();
+	page = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
+		    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+	ASSERT_NE(page, MAP_FAILED);
+
+	ret = syscall(__NR_get_mempolicy, NULL, allowed, NODE_BITS + 1UL, 0UL,
+		      MPOL_F_MEMS_ALLOWED);
+	if (ret == -1 && errno == ENOSYS)
+		SKIP(return, "no NUMA API on the host");
+	ASSERT_EQ(ret, 0);
+	for (i = 0; i < NODE_BITS; i++) {
+		if (allowed[i / LONG_BITS] & (1UL << (i % LONG_BITS))) {
+			node_mask[i / LONG_BITS] = 1UL << (i % LONG_BITS);
+			break;
+		}
+	}
+	ASSERT_LT(i, NODE_BITS);
+
+	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);
+}
+
+FIXTURE_TEARDOWN(numa_syscalls)
+{
+	if (self->cgv2_fd >= 0) {
+		destroy_ve(self->cgv2_fd, self->ctid);
+		close(self->cgv2_fd);
+	}
+}
+
+TEST_F(numa_syscalls, host_calls_succeed)
+{
+	struct call_result res[NR_CALLS];
+	unsigned int i;
+
+	run_calls(res);
+	for (i = 0; i < NR_CALLS; i++) {
+		EXPECT_EQ(res[i].ret, 0)
+			TH_LOG("%s on the host: %s", numa_calls[i].name,
+			       strerror(res[i].err));
+	}
+}
+
+TEST_F(numa_syscalls, ve_calls_return_enosys)
+{
+	struct call_result res[NR_CALLS];
+	int pipefd[2], status;
+	unsigned int i;
+	pid_t pid;
+
+	ASSERT_EQ(pipe(pipefd), 0);
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0) {
+		close(pipefd[0]);
+		if (enter_cgroup(self->cgv2_fd, self->ctid) < 0)
+			_exit(1);
+		if (unshare(CLONE_NEWVE) < 0)
+			_exit(2);
+		run_calls(res);
+		if (write(pipefd[1], res, sizeof(res)) != sizeof(res))
+			_exit(3);
+		_exit(0);
+	}
+	close(pipefd[1]);
+	ASSERT_EQ(waitpid(pid, &status, 0), pid);
+	ASSERT_TRUE(WIFEXITED(status));
+	ASSERT_EQ(WEXITSTATUS(status), 0)
+		TH_LOG("VE child setup failed at step %d", WEXITSTATUS(status));
+	ASSERT_EQ(read(pipefd[0], res, sizeof(res)), sizeof(res));
+	close(pipefd[0]);
+
+	for (i = 0; i < NR_CALLS; i++) {
+		EXPECT_TRUE(res[i].ret == -1 && res[i].err == ENOSYS)
+			TH_LOG("%s in a VE: returned %ld (%s), expected ENOSYS",
+			       numa_calls[i].name, res[i].ret,
+			       strerror(res[i].err));
+	}
+}
+
+TEST_HARNESS_MAIN

-- 
2.43.0



More information about the Devel mailing list