diff mbox series

[v2,2/2] Add mseal01 test

Message ID 20240723-mseal-v2-2-5aa872d611bc@suse.com
State Accepted
Headers show
Series Add mseal() testing suite | expand

Commit Message

Andrea Cervesato July 23, 2024, 8:41 a.m. UTC
From: Andrea Cervesato <andrea.cervesato@suse.com>

This is a smoke test that verifies if mseal() protects specific VMA
portions of a process. According to documentation, the syscall should
protect memory from the following actions:

- unmapping, moving to another location, and shrinking the size, via
  munmap() and mremap()
- moving or expanding a different VMA into the current location, via
  mremap()
- modifying a VMA via mmap(MAP_FIXED)
- mprotect() and pkey_mprotect()
- destructive madvice() behaviors (e.g. MADV_DONTNEED) for anonymous
  memory, when users don’t have write permission to the memory

Any of the described actions is recognized via EPERM errno.

Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
 testcases/kernel/syscalls/mseal/.gitignore |   1 +
 testcases/kernel/syscalls/mseal/Makefile   |   7 ++
 testcases/kernel/syscalls/mseal/mseal01.c  | 173 +++++++++++++++++++++++++++++
 3 files changed, 181 insertions(+)

Comments

Cyril Hrubis July 25, 2024, 3:55 p.m. UTC | #1
Hi!
> +static void child(unsigned int n)
> +{
> +	struct tcase *tc = &tcases[n];
> +
> +	mem_addr = SAFE_MMAP(NULL, mem_size,
> +		tc->prot,
> +		MAP_ANONYMOUS | MAP_PRIVATE,
> +		-1, 0);
> +
> +	tst_res(TINFO, "Testing %s", tc->message);
> +
> +	TST_EXP_PASS(sys_mseal(mem_addr + mem_offset, mem_alignment));
> +
> +	tc->func_test();
> +}
> +
> +static void run(unsigned int n)
> +{
> +	/* the reason why we spawn a child is that mseal() will
> +	 * protect VMA until process will call _exit()
> +	 */
> +	if (!SAFE_FORK()) {
> +		child(n);
> +		_exit(0);
> +	}
> +
> +	tst_reap_children();
> +
> +	if (mem_addr != MAP_FAILED)
> +		SAFE_MUNMAP(mem_addr, mem_size);

The memory is mapped in the child, it's hence not propagated to the
parent. With fork() only changes done in parent before the fork happens
are visible in child as well, since the memory is CoW.

> +}
> +
> +static void setup(void)
> +{
> +	mem_alignment = getpagesize();
> +	mem_size = mem_alignment * MEMPAGES;
> +	mem_offset = mem_alignment * MEMSEAL;
> +}
> +
> +static void cleanup(void)
> +{
> +	if (mem_addr != MAP_FAILED)
> +		SAFE_MUNMAP(mem_addr, mem_size);

Here as well, this is useless, since the memory was allocated in child,
and freed automatically when the child exits.


The rest look fine.
diff mbox series

Patch

diff --git a/testcases/kernel/syscalls/mseal/.gitignore b/testcases/kernel/syscalls/mseal/.gitignore
new file mode 100644
index 000000000..e13090994
--- /dev/null
+++ b/testcases/kernel/syscalls/mseal/.gitignore
@@ -0,0 +1 @@ 
+mseal01
diff --git a/testcases/kernel/syscalls/mseal/Makefile b/testcases/kernel/syscalls/mseal/Makefile
new file mode 100644
index 000000000..35317f446
--- /dev/null
+++ b/testcases/kernel/syscalls/mseal/Makefile
@@ -0,0 +1,7 @@ 
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2023 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
+
+top_srcdir		?= ../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/syscalls/mseal/mseal01.c b/testcases/kernel/syscalls/mseal/mseal01.c
new file mode 100644
index 000000000..13f1a0b92
--- /dev/null
+++ b/testcases/kernel/syscalls/mseal/mseal01.c
@@ -0,0 +1,173 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
+ */
+
+/*\
+ * [Description]
+ *
+ * This is a smoke test that verifies if mseal() protects specific VMA portions
+ * of a process. According to documentation, the syscall should protect memory
+ * from the following actions:
+ *
+ * - unmapping, moving to another location, and shrinking the size, via munmap()
+ *   and mremap()
+ * - moving or expanding a different VMA into the current location, via mremap()
+ * - modifying a VMA via mmap(MAP_FIXED)
+ * - mprotect() and pkey_mprotect()
+ * - destructive madvice() behaviors (e.g. MADV_DONTNEED) for anonymous memory,
+ *   when users don’t have write permission to the memory
+ *
+ * Any of the described actions is recognized via EPERM errno.
+ */
+
+#define _GNU_SOURCE
+
+#include "tst_test.h"
+#include "lapi/syscalls.h"
+
+#define MEMPAGES 8
+#define MEMSEAL 2
+
+static void *mem_addr;
+static int mem_size;
+static int mem_offset;
+static int mem_alignment;
+
+static inline int sys_mseal(void *start, size_t len)
+{
+	return tst_syscall(__NR_mseal, start, len, 0);
+}
+
+static void test_mprotect(void)
+{
+	TST_EXP_FAIL(mprotect(mem_addr, mem_size, PROT_NONE), EPERM);
+}
+
+static void test_pkey_mprotect(void)
+{
+	int ret;
+	int pkey;
+
+	pkey = tst_syscall(__NR_pkey_alloc, 0, 0);
+	if (pkey == -1) {
+		if (errno == EINVAL)
+			tst_brk(TCONF, "pku is not supported on this CPU");
+
+		tst_brk(TBROK | TERRNO, "pkey_alloc() error");
+	}
+
+	TST_EXP_FAIL(tst_syscall(__NR_pkey_mprotect,
+		mem_addr, mem_size,
+		PROT_NONE,
+		pkey),
+		EPERM);
+
+	ret = tst_syscall(__NR_pkey_free, pkey);
+	if (ret == -1)
+		tst_brk(TBROK | TERRNO, "pkey_free() error");
+}
+
+static void test_madvise(void)
+{
+	TST_EXP_FAIL(madvise(mem_addr, mem_size, MADV_DONTNEED), EPERM);
+}
+
+static void test_munmap(void)
+{
+	TST_EXP_FAIL(munmap(mem_addr, mem_size), EPERM);
+}
+
+static void test_mremap_resize(void)
+{
+	void *new_addr;
+	size_t new_size = 2 * mem_alignment;
+
+	new_addr = SAFE_MMAP(NULL, mem_size,
+		PROT_READ,
+		MAP_ANONYMOUS | MAP_PRIVATE,
+		-1, 0);
+
+	TST_EXP_FAIL_PTR_VOID(mremap(mem_addr, mem_size, new_size,
+		MREMAP_MAYMOVE | MREMAP_FIXED,
+		new_addr),
+		EPERM);
+
+	SAFE_MUNMAP(new_addr, new_size);
+}
+
+static void test_mmap_change_prot(void)
+{
+	TST_EXP_FAIL_PTR_VOID(mmap(mem_addr, mem_size,
+		PROT_READ,
+		MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED,
+		-1, 0), EPERM);
+}
+
+static struct tcase {
+	void (*func_test)(void);
+	int prot;
+	char *message;
+} tcases[] = {
+	{test_mprotect, PROT_READ | PROT_WRITE, "mprotect() availability"},
+	{test_pkey_mprotect, PROT_READ | PROT_WRITE, "pkey_mprotect() availability"},
+	{test_madvise, PROT_READ, "madvise() availability"},
+	{test_munmap, PROT_READ | PROT_WRITE, "munmap() availability from child"},
+	{test_mremap_resize, PROT_READ | PROT_WRITE, "mremap() address move/resize"},
+	{test_mmap_change_prot, PROT_READ | PROT_WRITE, "mmap() protection change"},
+};
+
+static void child(unsigned int n)
+{
+	struct tcase *tc = &tcases[n];
+
+	mem_addr = SAFE_MMAP(NULL, mem_size,
+		tc->prot,
+		MAP_ANONYMOUS | MAP_PRIVATE,
+		-1, 0);
+
+	tst_res(TINFO, "Testing %s", tc->message);
+
+	TST_EXP_PASS(sys_mseal(mem_addr + mem_offset, mem_alignment));
+
+	tc->func_test();
+}
+
+static void run(unsigned int n)
+{
+	/* the reason why we spawn a child is that mseal() will
+	 * protect VMA until process will call _exit()
+	 */
+	if (!SAFE_FORK()) {
+		child(n);
+		_exit(0);
+	}
+
+	tst_reap_children();
+
+	if (mem_addr != MAP_FAILED)
+		SAFE_MUNMAP(mem_addr, mem_size);
+}
+
+static void setup(void)
+{
+	mem_alignment = getpagesize();
+	mem_size = mem_alignment * MEMPAGES;
+	mem_offset = mem_alignment * MEMSEAL;
+}
+
+static void cleanup(void)
+{
+	if (mem_addr != MAP_FAILED)
+		SAFE_MUNMAP(mem_addr, mem_size);
+}
+
+static struct tst_test test = {
+	.test = run,
+	.tcnt = ARRAY_SIZE(tcases),
+	.setup = setup,
+	.cleanup = cleanup,
+	.min_kver = "6.10",
+	.forks_child = 1,
+};
+