@@ -31,6 +31,7 @@ map_fixed_noreplace
write_to_hugetlbfs
hmm-tests
memfd_restricted
+memfd_restricted_bind
memfd_secret
soft-dirty
split_huge_page_test
@@ -46,6 +46,8 @@ TEST_GEN_FILES += map_fixed_noreplace
TEST_GEN_FILES += map_hugetlb
TEST_GEN_FILES += map_populate
TEST_GEN_FILES += memfd_restricted
+TEST_GEN_FILES += memfd_restricted_bind
+TEST_GEN_FILES += restrictedmem_testmod.ko
TEST_GEN_FILES += memfd_secret
TEST_GEN_FILES += migration
TEST_GEN_FILES += mlock-random-test
@@ -171,6 +173,12 @@ $(OUTPUT)/ksm_tests: LDLIBS += -lnuma
$(OUTPUT)/migration: LDLIBS += -lnuma
+$(OUTPUT)/memfd_restricted_bind: LDLIBS += -lnuma
+$(OUTPUT)/restrictedmem_testmod.ko: $(wildcard restrictedmem_testmod/Makefile restrictedmem_testmod/*.[ch])
+ $(call msg,MOD,,$@)
+ $(Q)$(MAKE) -C restrictedmem_testmod
+ $(Q)cp restrictedmem_testmod/restrictedmem_testmod.ko $@
+
local_config.mk local_config.h: check_config.sh
/bin/sh ./check_config.sh $(CC)
new file mode 100644
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <fcntl.h>
+#include <linux/mempolicy.h>
+#include <numa.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include "../kselftest_harness.h"
+
+int memfd_restricted(int flags, int fd)
+{
+ return syscall(__NR_memfd_restricted, flags, fd);
+}
+
+int memfd_restricted_bind(
+ int fd, loff_t offset, unsigned long len, unsigned long mode,
+ const unsigned long *nmask, unsigned long maxnode, unsigned int flags)
+{
+ struct file_range range = {
+ .offset = offset,
+ .len = len,
+ };
+
+ return syscall(__NR_memfd_restricted_bind, fd, &range, mode, nmask, maxnode, flags);
+}
+
+int memfd_restricted_bind_node(
+ int fd, loff_t offset, unsigned long len,
+ unsigned long mode, int node, unsigned int flags)
+{
+ int ret;
+ struct bitmask *mask = numa_allocate_nodemask();
+
+ numa_bitmask_setbit(mask, node);
+
+ ret = memfd_restricted_bind(fd, offset, len, mode, mask->maskp, mask->size, flags);
+
+ numa_free_nodemask(mask);
+
+ return ret;
+}
+
+/**
+ * Allocates a page in restrictedmem_fd, reads the node that the page was
+ * allocated it and returns it. Returns -1 on error.
+ */
+int read_node(int restrictedmem_fd, unsigned long offset)
+{
+ int ret;
+ int fd;
+
+ fd = open("/proc/restrictedmem", O_RDWR);
+ if (!fd)
+ return -ENOTSUP;
+
+ ret = ioctl(fd, restrictedmem_fd, offset);
+
+ close(fd);
+
+ return ret;
+}
+
+bool restrictedmem_testmod_loaded(void)
+{
+ struct stat buf;
+
+ return stat("/proc/restrictedmem", &buf) == 0;
+}
+
+FIXTURE(restrictedmem_file)
+{
+ int fd;
+ size_t page_size;
+};
+
+FIXTURE_SETUP(restrictedmem_file)
+{
+ int fd;
+ int ret;
+ struct stat stat;
+
+ fd = memfd_restricted(0, -1);
+ ASSERT_GT(fd, 0);
+
+#define RESTRICTEDMEM_TEST_NPAGES 16
+ ret = ftruncate(fd, getpagesize() * RESTRICTEDMEM_TEST_NPAGES);
+ ASSERT_EQ(ret, 0);
+
+ ret = fstat(fd, &stat);
+ ASSERT_EQ(ret, 0);
+
+ self->fd = fd;
+ self->page_size = stat.st_blksize;
+};
+
+FIXTURE_TEARDOWN(restrictedmem_file)
+{
+ int ret;
+
+ ret = close(self->fd);
+ EXPECT_EQ(ret, 0);
+}
+
+#define ASSERT_REQUIREMENTS() \
+ do { \
+ struct bitmask *mask = numa_get_membind(); \
+ ASSERT_GT(numa_num_configured_nodes(), 1); \
+ ASSERT_TRUE(numa_bitmask_isbitset(mask, 0)); \
+ ASSERT_TRUE(numa_bitmask_isbitset(mask, 1)); \
+ numa_bitmask_free(mask); \
+ ASSERT_TRUE(restrictedmem_testmod_loaded()); \
+ } while (0)
+
+TEST_F(restrictedmem_file, memfd_restricted_bind_works_as_expected)
+{
+ int ret;
+ int node;
+ int i;
+ int node_bindings[] = { 1, 0, 1, 0, 1, 1, 0, 1 };
+
+ ASSERT_REQUIREMENTS();
+
+ for (i = 0; i < ARRAY_SIZE(node_bindings); i++) {
+ ret = memfd_restricted_bind_node(
+ self->fd, i * self->page_size, self->page_size,
+ MPOL_BIND, node_bindings[i], 0);
+ ASSERT_EQ(ret, 0);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(node_bindings); i++) {
+ node = read_node(self->fd, i * self->page_size);
+ ASSERT_EQ(node, node_bindings[i]);
+ }
+}
+
+TEST_HARNESS_MAIN
new file mode 100644
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+RESTRICTEDMEM_TESTMOD_DIR := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
+KDIR ?= $(abspath $(RESTRICTEDMEM_TESTMOD_DIR)/../../../../..)
+
+ifeq ($(V),1)
+Q =
+else
+Q = @
+endif
+
+MODULES = restrictedmem_testmod.ko
+
+obj-m += restrictedmem_testmod.o
+CFLAGS_restrictedmem_testmod.o = -I$(src)
+
+all:
+ +$(Q)make -C $(KDIR) M=$(RESTRICTEDMEM_TESTMOD_DIR) modules
+
+clean:
+ +$(Q)make -C $(KDIR) M=$(RESTRICTEDMEM_TESTMOD_DIR) clean
new file mode 100644
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "linux/printk.h"
+#include "linux/types.h"
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/restrictedmem.h>
+
+MODULE_DESCRIPTION("A kernel module to support restrictedmem testing");
+MODULE_AUTHOR("ackerleytng@google.com");
+MODULE_LICENSE("GPL");
+
+void dummy_op(struct restrictedmem_notifier *notifier, pgoff_t start, pgoff_t end)
+{
+}
+
+static const struct restrictedmem_notifier_ops dummy_notifier_ops = {
+ .invalidate_start = dummy_op,
+ .invalidate_end = dummy_op,
+ .error = dummy_op,
+};
+
+static struct restrictedmem_notifier dummy_notifier = {
+ .ops = &dummy_notifier_ops,
+};
+
+static long restrictedmem_testmod_ioctl(
+ struct file *file, unsigned int cmd, unsigned long offset)
+{
+ long ret;
+ struct fd f;
+ struct page *page;
+ pgoff_t start = offset >> PAGE_SHIFT;
+
+ f = fdget(cmd);
+ if (!f.file)
+ return -EBADF;
+
+ ret = -EINVAL;
+ if (!file_is_restrictedmem(f.file))
+ goto out;
+
+
+ ret = restrictedmem_bind(f.file, start, start + 1, &dummy_notifier, true);
+ if (ret)
+ goto out;
+
+ ret = restrictedmem_get_page(f.file, (unsigned long)start, &page, NULL);
+ if (ret)
+ goto out;
+
+ ret = page_to_nid(page);
+
+ folio_put(page_folio(page));
+
+ restrictedmem_unbind(f.file, start, start + 1, &dummy_notifier);
+
+out:
+ fdput(f);
+
+ return ret;
+}
+
+static const struct proc_ops restrictedmem_testmod_ops = {
+ .proc_ioctl = restrictedmem_testmod_ioctl,
+};
+
+static struct proc_dir_entry *restrictedmem_testmod_entry;
+
+static int restrictedmem_testmod_init(void)
+{
+ restrictedmem_testmod_entry = proc_create(
+ "restrictedmem", 0660, NULL, &restrictedmem_testmod_ops);
+
+ return 0;
+}
+
+static void restrictedmem_testmod_exit(void)
+{
+ proc_remove(restrictedmem_testmod_entry);
+}
+
+module_init(restrictedmem_testmod_init);
+module_exit(restrictedmem_testmod_exit);
@@ -40,6 +40,8 @@ separated by spaces:
test memadvise(2) MADV_POPULATE_{READ,WRITE} options
- memfd_restricted_
test memfd_restricted(2)
+- memfd_restricted_bind
+ test memfd_restricted_bind(2)
- memfd_secret
test memfd_secret(2)
- process_mrelease
@@ -240,6 +242,10 @@ CATEGORY="madv_populate" run_test ./madv_populate
CATEGORY="memfd_restricted" run_test ./memfd_restricted
+test_selected "memfd_restricted_bind" && insmod ./restrictedmem_testmod.ko && \
+ CATEGORY="memfd_restricted_bind" run_test ./memfd_restricted_bind && \
+ rmmod restrictedmem_testmod > /dev/null
+
CATEGORY="memfd_secret" run_test ./memfd_secret
# KSM MADV_MERGEABLE test with 10 identical pages