diff mbox series

[RFC,3/3] drivers/misc: add test driver and selftest for proclocal allocator

Message ID 20240621201501.1059948-4-rkagan@amazon.de
State New
Headers show
Series add support for mm-local memory allocations | expand

Commit Message

Roman Kagan June 21, 2024, 8:15 p.m. UTC
Introduce a simple driver for functional and stress testing of proclocal
kernel allocator.  The driver exposes a device node /dev/proclocal-test,
which allows userland programs to request creation of proclocal areas
and to obtain their addresses as seen by the kernel, and in addition to
read and write kernel memory at arbitrary address content (simplified
/dev/kmem good enough to access proclocal allocations under selftest
responsibility).

The driver is not meant for use with production kernels, as it exposes
internal kernel pointers and data.

Also add a basic selftest that uses this driver.

Signed-off-by: Roman Kagan <rkagan@amazon.de>
---
 drivers/misc/Makefile                         |   1 +
 tools/testing/selftests/proclocal/Makefile    |   6 +
 drivers/misc/proclocal-test.c                 | 200 ++++++++++++++++++
 .../selftests/proclocal/proclocal-test.c      | 150 +++++++++++++
 drivers/misc/Kconfig                          |  15 ++
 tools/testing/selftests/proclocal/.gitignore  |   1 +
 6 files changed, 373 insertions(+)
 create mode 100644 tools/testing/selftests/proclocal/Makefile
 create mode 100644 drivers/misc/proclocal-test.c
 create mode 100644 tools/testing/selftests/proclocal/proclocal-test.c
 create mode 100644 tools/testing/selftests/proclocal/.gitignore
diff mbox series

Patch

diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 153a3f4837e8..33c244cee92d 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -69,3 +69,4 @@  obj-$(CONFIG_TMR_INJECT)	+= xilinx_tmr_inject.o
 obj-$(CONFIG_TPS6594_ESM)	+= tps6594-esm.o
 obj-$(CONFIG_TPS6594_PFSM)	+= tps6594-pfsm.o
 obj-$(CONFIG_NSM)		+= nsm.o
+obj-$(CONFIG_PROCLOCAL_TEST)	+= proclocal-test.o
diff --git a/tools/testing/selftests/proclocal/Makefile b/tools/testing/selftests/proclocal/Makefile
new file mode 100644
index 000000000000..b93baecee762
--- /dev/null
+++ b/tools/testing/selftests/proclocal/Makefile
@@ -0,0 +1,6 @@ 
+# SPDX-License-Identifier: GPL-2.0
+
+TEST_GEN_PROGS := proclocal-test
+CFLAGS += -O2 -g -Wall $(KHDR_INCLUDES)
+
+include ../lib.mk
diff --git a/drivers/misc/proclocal-test.c b/drivers/misc/proclocal-test.c
new file mode 100644
index 000000000000..9b3d0ed9b2f9
--- /dev/null
+++ b/drivers/misc/proclocal-test.c
@@ -0,0 +1,200 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2024 Amazon.com, Inc. or its affiliates. All rights reserved.
+ * Author: Roman Kagan <rkagan@amazon.de>
+ *
+ * test driver for proclocal memory allocator
+ */
+
+#include <linux/compat.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/file.h>
+#include <linux/secretmem.h>
+
+struct proclocal_test_alloc {
+	u64 size;
+	u64 ptr;
+};
+
+#define PROCLOCAL_TEST_ALLOC _IOWR('A', 0x10, struct proclocal_test_alloc)
+
+#define BOUNCE_BUF_SIZE PAGE_SIZE
+
+struct proclocal_test {
+	struct secretmem_area *area;
+	size_t size;
+	void *bounce;
+};
+
+static int proclocal_test_open(struct inode *inode, struct file *f)
+{
+	struct proclocal_test *plt;
+
+	plt = kzalloc(sizeof(*plt), GFP_KERNEL);
+	if (!plt)
+		return -ENOMEM;
+
+	plt->bounce = kmalloc(BOUNCE_BUF_SIZE, GFP_KERNEL);
+	if (!plt->bounce) {
+		kfree(plt);
+		return -ENOMEM;
+	}
+
+	f->f_mode |= FMODE_UNSIGNED_OFFSET;
+	f->private_data = plt;
+	return 0;
+}
+
+static int proclocal_test_release(struct inode *inode, struct file *f)
+{
+	struct proclocal_test *plt = f->private_data;
+	if (plt->area)
+		secretmem_release_pages(plt->area);
+	kfree(plt->bounce);
+	kfree(plt);
+	return 0;
+}
+
+static ssize_t proclocal_test_read(struct file *f, char __user *buf,
+				   size_t count, loff_t *ppos)
+{
+	struct proclocal_test *plt = f->private_data;
+	const void *p = (const void *)*ppos;
+	ssize_t ret = -EFAULT;
+
+	if (p + count < p)
+		return -EINVAL;
+
+	while (count) {
+		size_t chunk = min_t(size_t, count, BOUNCE_BUF_SIZE);
+		size_t left;
+
+		/*
+		 * copy_to_user() disables superuser checks, so need to copy to
+		 * bounce buffer first to test the access
+		 */
+		memcpy(plt->bounce, p, chunk);
+
+		left = copy_to_user(buf, plt->bounce, chunk);
+		if (left == chunk)
+			goto out;
+		chunk -= left;
+
+		buf += chunk;
+		p += chunk;
+		count -= chunk;
+	}
+
+	ret = p - (const void *)*ppos;
+	*ppos = (loff_t)p;
+out:
+	return ret;
+}
+
+static ssize_t proclocal_test_write(struct file *f, const char __user *buf,
+				    size_t count, loff_t *ppos)
+{
+	struct proclocal_test *plt = f->private_data;
+	void *p = (void *)*ppos;
+	ssize_t ret = -EFAULT;
+
+	if (p + count < p)
+		return -EINVAL;
+
+	while (count) {
+		size_t chunk = min_t(size_t, count, BOUNCE_BUF_SIZE);
+		size_t left;
+
+		/*
+		 * copy_from_user() disables superuser checks, so need to copy
+		 * to bounce buffer first to test the access
+		 */
+		left = copy_from_user(plt->bounce, buf, chunk);
+		if (left == chunk)
+			goto out;
+		chunk -= left;
+
+		memcpy(p, plt->bounce, chunk);
+
+		buf += chunk;
+		p += chunk;
+		count -= chunk;
+	}
+
+	ret = p - (void *)*ppos;
+	*ppos = (loff_t)p;
+out:
+	return ret;
+}
+
+static long proclocal_test_alloc(struct proclocal_test *plt,
+				 void __user *argp)
+{
+	struct proclocal_test_alloc pta;
+	unsigned long pages_needed;
+
+	if (plt->size)
+		return -EEXIST;
+
+	if (copy_from_user(&pta, argp, sizeof(pta)))
+		return -EFAULT;
+
+	if (!pta.size)
+		return -EINVAL;
+
+	pages_needed = (pta.size + PAGE_SIZE - 1) / PAGE_SIZE;
+	plt->area = secretmem_allocate_pages(fls(pages_needed - 1));
+	if (!plt->area)
+		return -ENOMEM;
+
+	plt->size = pta.size;
+
+	pta.ptr = (u64)plt->area->ptr;
+	if (copy_to_user(argp, &pta, sizeof(pta)))
+		goto err;
+
+	return 0;
+err:
+	secretmem_release_pages(plt->area);
+	plt->area = NULL;
+	plt->size = 0;
+	return -EFAULT;
+}
+
+static long proclocal_test_ioctl(struct file *f, unsigned int ioctl,
+				 unsigned long arg)
+{
+	struct proclocal_test *plt = f->private_data;
+	void __user *argp = (void __user *)arg;
+
+	switch (ioctl) {
+	case PROCLOCAL_TEST_ALLOC:
+		return proclocal_test_alloc(plt, argp);
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct file_operations proclocal_test_fops = {
+	.owner          = THIS_MODULE,
+	.release        = proclocal_test_release,
+	.unlocked_ioctl = proclocal_test_ioctl,
+	.compat_ioctl   = compat_ptr_ioctl,
+	.open           = proclocal_test_open,
+	.read           = proclocal_test_read,
+	.write          = proclocal_test_write,
+	.llseek		= no_seek_end_llseek,
+};
+
+static struct miscdevice proclocal_test_misc = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name  = "proclocal-test",
+	.fops  = &proclocal_test_fops,
+};
+module_misc_device(proclocal_test_misc);
+
+MODULE_VERSION("0.0.1");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Roman Kagan");
+MODULE_DESCRIPTION("Test driver for proclocal allocator");
diff --git a/tools/testing/selftests/proclocal/proclocal-test.c b/tools/testing/selftests/proclocal/proclocal-test.c
new file mode 100644
index 000000000000..386cc5d9e51a
--- /dev/null
+++ b/tools/testing/selftests/proclocal/proclocal-test.c
@@ -0,0 +1,150 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2024 Amazon.com, Inc. or its affiliates. All rights reserved.
+ * Author: Roman Kagan <rkagan@amazon.de>
+ *
+ * test for proclocal memory allocator using the corresponding test device
+ */
+
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include "../kselftest_harness.h"
+
+struct proclocal_test_alloc {
+	uint64_t size;
+	uint64_t ptr;
+};
+
+#define PROCLOCAL_TEST_ALLOC _IOWR('A', 0x10, struct proclocal_test_alloc)
+
+const char proclocal_content[] = "this is test";
+char buf[256];
+
+FIXTURE(proclocal) {
+	int fd;
+	void *ptr;
+};
+
+FIXTURE_SETUP(proclocal)
+{
+	struct proclocal_test_alloc pta = {
+		.size = sizeof(buf),
+	};
+
+	self->fd = open("/dev/proclocal-test", O_RDWR);
+	ASSERT_LE(0, self->fd);
+
+	ASSERT_LE(0, ioctl(self->fd, PROCLOCAL_TEST_ALLOC, &pta));
+
+	self->ptr = (void *) pta.ptr;
+	TH_LOG("self->ptr = %p\n", self->ptr);
+}
+
+FIXTURE_TEARDOWN(proclocal)
+{
+}
+
+TEST_F(proclocal, kernel_access)
+{
+	ASSERT_EQ((off_t)self->ptr,
+		  lseek(self->fd, (off_t)self->ptr, SEEK_SET));
+	EXPECT_EQ(sizeof(proclocal_content),
+		  write(self->fd,
+			proclocal_content, sizeof(proclocal_content)));
+	ASSERT_EQ((off_t)self->ptr,
+		  lseek(self->fd, (off_t)self->ptr, SEEK_SET));
+	EXPECT_EQ(sizeof(proclocal_content),
+		  read(self->fd, buf, sizeof(proclocal_content)));
+	EXPECT_STREQ(proclocal_content, buf);
+}
+
+sigjmp_buf jmpbuf;
+void segv_handler(int signum, siginfo_t *si, void *uc)
+{
+	if (signum == SIGSEGV)
+		siglongjmp(jmpbuf, 1);
+}
+
+TEST_F(proclocal, direct_access)
+{
+	bool access_succeeded;
+	struct sigaction sa;
+
+	if (sigsetjmp(jmpbuf, 1) == 0) {
+		sa.sa_sigaction = segv_handler;
+		sa.sa_flags = SA_SIGINFO | SA_RESETHAND;
+		sigemptyset(&sa.sa_mask);
+
+		sigaction(SIGSEGV, &sa, NULL);
+
+		(void)((volatile char *)self->ptr)[0];
+
+		access_succeeded = true;
+	} else
+		access_succeeded = false;
+
+	EXPECT_FALSE(access_succeeded);
+}
+
+#define PAGE_SIZE 0x1000
+
+TEST_F(proclocal, map_over)
+{
+	void *ptr_page = (void *)((uintptr_t)self->ptr & ~(PAGE_SIZE - 1));
+	void *map;
+	int errno_save;
+
+	errno = 0;
+	map = mmap(ptr_page, PAGE_SIZE, PROT_NONE,
+		   MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
+	errno_save = errno;
+
+	EXPECT_EQ(MAP_FAILED, map);
+	TH_LOG("errno = %d", errno_save);
+
+	if (map != MAP_FAILED)
+		munmap(map, PAGE_SIZE);
+}
+
+TEST_F(proclocal, release)
+{
+	EXPECT_EQ(0, close(self->fd));
+}
+
+TEST_F(proclocal, map_over_closed)
+{
+	void *ptr_page = (void *)((uintptr_t)self->ptr & ~(PAGE_SIZE - 1));
+	void *map;
+	int errno_save;
+
+	ASSERT_EQ(0, close(self->fd));
+
+	errno = 0;
+	map = mmap(ptr_page, PAGE_SIZE, PROT_NONE,
+		   MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
+	errno_save = errno;
+
+	EXPECT_EQ(ptr_page, map);
+	TH_LOG("errno = %d", errno_save);
+
+	if (map != MAP_FAILED)
+		munmap(map, PAGE_SIZE);
+}
+
+TEST_F(proclocal, kernel_access_closed)
+{
+	ASSERT_EQ(0, close(self->fd));
+	self->fd = open("/dev/proclocal-test", O_RDWR);
+	ASSERT_LE(0, self->fd);
+
+	ASSERT_EQ((off_t)self->ptr,
+		  lseek(self->fd, (off_t)self->ptr, SEEK_SET));
+	EXPECT_EQ(-1, read(self->fd, buf, sizeof(proclocal_content)));
+}
+
+TEST_HARNESS_MAIN
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index faf983680040..29a334de0ca8 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -585,6 +585,21 @@  config NSM
 	  To compile this driver as a module, choose M here.
 	  The module will be called nsm.
 
+config PROCLOCAL_TEST
+	tristate "Proclocal allocator test driver"
+	depends on SECRETMEM
+	help
+	  This driver allows to perform functional and stress tests for
+	  proclocal memory allocator.  It exposes /dev/proclocal-test that
+	  userland test programs can use to create and manipulate proclocal
+	  kernel allocations.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called proclocal-test.
+
+	  If unsure, say N.
+	  This driver is not meant to be used on production systems.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/tools/testing/selftests/proclocal/.gitignore b/tools/testing/selftests/proclocal/.gitignore
new file mode 100644
index 000000000000..47e0fdcd6e3a
--- /dev/null
+++ b/tools/testing/selftests/proclocal/.gitignore
@@ -0,0 +1 @@ 
+/proclocal-test