[PATCH v5 3/7] um: use execveat on memfd to create userspace MMs

Benjamin Berg benjamin at sipsolutions.net
Wed Jun 19 08:34:07 PDT 2024


From: Benjamin Berg <benjamin.berg at intel.com>

Using clone will not undo features that have been enabled by libc. An
example of this already happening is rseq, which could cause the kernel
to read/write memory of the userspace process. In the future the
standard library might also use mseal by default to protect itself,
which would also thwart our attempts at unmapping everything.

Solve all this by taking a step back and doing an execve into a tiny
static binary that sets up the minimal environment required for the
stub without using any standard library. That way we have a clean
execution environment that is fully under the control of UML.

Note that this changes things a bit as the FDs are not anymore shared
with the kernel. Instead, we explicitly share the FDs for the physical
memory and all existing iomem regions. Doing this is fine, as iomem
regions cannot be added at runtime.

Signed-off-by: Benjamin Berg <benjamin.berg at intel.com>
---
 arch/um/include/shared/skas/stub-data.h |  11 ++
 arch/um/os-Linux/skas/process.c         | 145 +++++++++++++++---------
 arch/x86/um/.gitignore                  |   2 +
 arch/x86/um/Makefile                    |  32 +++++-
 arch/x86/um/stub_elf.c                  |  84 ++++++++++++++
 arch/x86/um/stub_elf_embed.S            |  11 ++
 6 files changed, 227 insertions(+), 58 deletions(-)
 create mode 100644 arch/x86/um/.gitignore
 create mode 100644 arch/x86/um/stub_elf.c
 create mode 100644 arch/x86/um/stub_elf_embed.S

diff --git a/arch/um/include/shared/skas/stub-data.h b/arch/um/include/shared/skas/stub-data.h
index 5e3ade3fb38b..83d210f59956 100644
--- a/arch/um/include/shared/skas/stub-data.h
+++ b/arch/um/include/shared/skas/stub-data.h
@@ -8,6 +8,17 @@
 #ifndef __STUB_DATA_H
 #define __STUB_DATA_H
 
+struct stub_init_data {
+	unsigned long stub_start;
+
+	int stub_code_fd;
+	unsigned long stub_code_offset;
+	int stub_data_fd;
+	unsigned long stub_data_offset;
+
+	unsigned long segv_handler;
+};
+
 struct stub_data {
 	unsigned long offset;
 	int fd;
diff --git a/arch/um/os-Linux/skas/process.c b/arch/um/os-Linux/skas/process.c
index 41a288dcfc34..6cd82ca7a43d 100644
--- a/arch/um/os-Linux/skas/process.c
+++ b/arch/um/os-Linux/skas/process.c
@@ -23,6 +23,8 @@
 #include <skas.h>
 #include <sysdep/stub.h>
 #include <linux/threads.h>
+#include <fcntl.h>
+#include <mem_user.h>
 #include "../internal.h"
 
 int is_skas_winch(int pid, int fd, void *data)
@@ -188,69 +190,100 @@ static void handle_trap(int pid, struct uml_pt_regs *regs)
 
 extern char __syscall_stub_start[];
 
-/**
- * userspace_tramp() - userspace trampoline
- * @stack:	pointer to the new userspace stack page
- *
- * The userspace trampoline is used to setup a new userspace process in start_userspace() after it was clone()'ed.
- * This function will run on a temporary stack page.
- * It ptrace()'es itself, then
- * Two pages are mapped into the userspace address space:
- * - STUB_CODE (with EXEC), which contains the skas stub code
- * - STUB_DATA (with R/W), which contains a data page that is used to transfer certain data between the UML userspace process and the UML kernel.
- * Also for the userspace process a SIGSEGV handler is installed to catch pagefaults in the userspace process.
- * And last the process stops itself to give control to the UML kernel for this userspace process.
- *
- * Return: Always zero, otherwise the current userspace process is ended with non null exit() call
- */
+int userspace_pid[NR_CPUS];
+
+extern char stub_elf_start[];
+extern char stub_elf_end[];
+
+int stub_exec_fd;
+
 static int userspace_tramp(void *stack)
 {
-	struct sigaction sa;
-	void *addr;
-	int fd;
+	char *const argv[] = { "uml-userspace", NULL };
+	int pipe_fds[2];
 	unsigned long long offset;
-	unsigned long segv_handler = STUB_CODE +
-				     (unsigned long) stub_segv_handler -
-				     (unsigned long) __syscall_stub_start;
-
-	ptrace(PTRACE_TRACEME, 0, 0, 0);
-
-	signal(SIGTERM, SIG_DFL);
-	signal(SIGWINCH, SIG_IGN);
-
-	fd = phys_mapping(uml_to_phys(__syscall_stub_start), &offset);
-	addr = mmap64((void *) STUB_CODE, UM_KERN_PAGE_SIZE,
-		      PROT_EXEC, MAP_FIXED | MAP_PRIVATE, fd, offset);
-	if (addr == MAP_FAILED) {
-		os_info("mapping mmap stub at 0x%lx failed, errno = %d\n",
-			STUB_CODE, errno);
-		exit(1);
+	struct stub_init_data init_data = {
+		.stub_start = STUB_START,
+		.segv_handler = STUB_CODE +
+				(unsigned long) stub_segv_handler -
+				(unsigned long) __syscall_stub_start,
+	};
+	struct iomem_region *iomem = iomem_regions;
+	int ret;
+
+	init_data.stub_code_fd = phys_mapping(uml_to_phys(__syscall_stub_start),
+					      &offset);
+	init_data.stub_code_offset = MMAP_OFFSET(offset);
+
+	init_data.stub_data_fd = phys_mapping(uml_to_phys(stack), &offset);
+	init_data.stub_data_offset = MMAP_OFFSET(offset);
+
+	/* Set CLOEXEC on all FDs and then unset on all memory related FDs */
+	close_range(0, ~0U, CLOSE_RANGE_CLOEXEC);
+
+	fcntl(init_data.stub_data_fd, F_SETFD, 0);
+	while (iomem) {
+		fcntl(init_data.stub_data_fd, F_SETFD, 0);
+		iomem = iomem->next;
 	}
 
-	fd = phys_mapping(uml_to_phys(stack), &offset);
-	addr = mmap((void *) STUB_DATA,
-		    STUB_DATA_PAGES * UM_KERN_PAGE_SIZE, PROT_READ | PROT_WRITE,
-		    MAP_FIXED | MAP_SHARED, fd, offset);
-	if (addr == MAP_FAILED) {
-		os_info("mapping segfault stack at 0x%lx failed, errno = %d\n",
-			STUB_DATA, errno);
-		exit(1);
+	/* Create a pipe for init_data (no CLOEXEC) and dup2 to STDIN */
+	if (pipe2(pipe_fds, 0))
+		exit(2);
+
+	close(0);
+	if (dup2(pipe_fds[0], 0) < 0) {
+		close(pipe_fds[0]);
+		close(pipe_fds[1]);
+		exit(3);
 	}
+	close(pipe_fds[0]);
+
+	/* Write init_data and close write side */
+	ret = write(pipe_fds[1], &init_data, sizeof(init_data));
+	close(pipe_fds[1]);
+
+	if (ret != sizeof(init_data))
+		exit(4);
+
+	execveat(stub_exec_fd, "", argv, NULL, AT_EMPTY_PATH);
 
-	set_sigstack((void *) STUB_DATA, STUB_DATA_PAGES * UM_KERN_PAGE_SIZE);
-	sigemptyset(&sa.sa_mask);
-	sa.sa_flags = SA_ONSTACK | SA_NODEFER | SA_SIGINFO;
-	sa.sa_sigaction = (void *) segv_handler;
-	sa.sa_restorer = NULL;
-	if (sigaction(SIGSEGV, &sa, NULL) < 0) {
-		os_info("%s - setting SIGSEGV handler failed - errno = %d\n",
-			__func__, errno);
-		exit(1);
+	close(0);
+
+	exit(4);
+}
+
+static int __init init_stub_exec_fd(void)
+{
+	size_t len = 0;
+	int res;
+
+	stub_exec_fd = memfd_create("uml-userspace",
+				    MFD_EXEC | MFD_CLOEXEC | MFD_ALLOW_SEALING);
+
+	if (stub_exec_fd < 0)
+		panic("%s: Failed to create memfd, check vm.memfd_noexec",
+		      __func__);
+
+	while (len < stub_elf_end - stub_elf_start) {
+		res = write(stub_exec_fd, stub_elf_start + len,
+			    stub_elf_end - stub_elf_start - len);
+		if (res < 0) {
+			if (errno == EINTR)
+				continue;
+
+			panic("%s: Failed write to memfd: %d", __func__, errno);
+		}
+
+		len += res;
 	}
 
-	kill(os_getpid(), SIGSTOP);
+	fcntl(stub_exec_fd, F_ADD_SEALS,
+	      F_SEAL_WRITE | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_SEAL);
+
 	return 0;
 }
+__initcall(init_stub_exec_fd);
 
 int userspace_pid[NR_CPUS];
 int kill_userspace_mm[NR_CPUS];
@@ -270,7 +303,7 @@ int start_userspace(unsigned long stub_stack)
 {
 	void *stack;
 	unsigned long sp;
-	int pid, status, n, flags, err;
+	int pid, status, n, err;
 
 	/* setup a temporary stack page */
 	stack = mmap(NULL, UM_KERN_PAGE_SIZE,
@@ -286,10 +319,10 @@ int start_userspace(unsigned long stub_stack)
 	/* set stack pointer to the end of the stack page, so it can grow downwards */
 	sp = (unsigned long)stack + UM_KERN_PAGE_SIZE;
 
-	flags = CLONE_FILES | SIGCHLD;
-
 	/* clone into new userspace process */
-	pid = clone(userspace_tramp, (void *) sp, flags, (void *) stub_stack);
+	pid = clone(userspace_tramp, (void *) sp,
+		    CLONE_VFORK | CLONE_VM | SIGCHLD,
+		    (void *)stub_stack);
 	if (pid < 0) {
 		err = -errno;
 		printk(UM_KERN_ERR "%s : clone failed, errno = %d\n",
diff --git a/arch/x86/um/.gitignore b/arch/x86/um/.gitignore
new file mode 100644
index 000000000000..91f9df29d1c3
--- /dev/null
+++ b/arch/x86/um/.gitignore
@@ -0,0 +1,2 @@
+stub_elf
+stub_elf.dbg
diff --git a/arch/x86/um/Makefile b/arch/x86/um/Makefile
index 8bc72a51b257..6e8b59498b64 100644
--- a/arch/x86/um/Makefile
+++ b/arch/x86/um/Makefile
@@ -11,10 +11,37 @@ endif
 
 obj-y = bugs_$(BITS).o delay.o fault.o ldt.o \
 	ptrace_$(BITS).o ptrace_user.o setjmp_$(BITS).o signal.o \
-	stub_$(BITS).o stub_segv.o \
+	stub_$(BITS).o stub_segv.o stub_elf_embed.o \
 	sys_call_table_$(BITS).o sysrq_$(BITS).o tls_$(BITS).o \
 	mem_$(BITS).o subarch.o os-Linux/
 
+# Stub executable
+
+stub_elf_objs-y := stub_elf.o
+
+stub_elf_objs := $(foreach F,$(stub_elf_objs-y),$(obj)/$F)
+
+# Object file containing the ELF executable
+$(obj)/stub_elf_embed.o: $(src)/stub_elf_embed.S $(obj)/stub_elf
+
+$(obj)/stub_elf.dbg: $(stub_elf_objs) FORCE
+	$(call if_changed,stub_elf)
+
+$(obj)/stub_elf: OBJCOPYFLAGS := -S
+$(obj)/stub_elf: $(obj)/stub_elf.dbg FORCE
+	$(call if_changed,objcopy)
+
+quiet_cmd_stub_elf = STUB_ELF $@
+      cmd_stub_elf = $(CC) -nostdlib -o $@ \
+			    $(CC_FLAGS_LTO) $(STUB_ELF_LDFLAGS) \
+			    $(filter %.o,$^)
+
+STUB_ELF_LDFLAGS = -n -static
+
+targets += stub_elf.dbg stub_elf $(stub_elf_objs-y)
+
+# end
+
 ifeq ($(CONFIG_X86_32),y)
 
 obj-y += syscalls_32.o
@@ -46,7 +73,8 @@ targets += user-offsets.s
 include/generated/user_constants.h: $(obj)/user-offsets.s FORCE
 	$(call filechk,offsets,__USER_CONSTANT_H__)
 
-UNPROFILE_OBJS := stub_segv.o
+UNPROFILE_OBJS := stub_segv.o stub_elf.o
 CFLAGS_stub_segv.o := $(CFLAGS_NO_HARDENING)
+CFLAGS_stub_elf.o := $(CFLAGS_NO_HARDENING)
 
 include $(srctree)/arch/um/scripts/Makefile.rules
diff --git a/arch/x86/um/stub_elf.c b/arch/x86/um/stub_elf.c
new file mode 100644
index 000000000000..b634740a6a15
--- /dev/null
+++ b/arch/x86/um/stub_elf.c
@@ -0,0 +1,84 @@
+#include <sys/ptrace.h>
+#include <sys/prctl.h>
+#include <asm/unistd.h>
+#include <sysdep/stub.h>
+#include <stub-data.h>
+
+void _start(void);
+
+static void real_init(void)
+{
+	struct stub_init_data init_data;
+	unsigned long res;
+	struct {
+		void  *ss_sp;
+		int    ss_flags;
+		size_t ss_size;
+	} stack;
+	struct {
+		void *sa_handler_;
+		unsigned long sa_flags;
+		void *sa_restorer;
+		unsigned long sa_mask;
+	} sa = {};
+
+	/* set a nice name */
+	stub_syscall2(__NR_prctl, PR_SET_NAME, (unsigned long)"uml-userspace");
+
+	/* read information from STDIN and close it */
+	stub_syscall3(__NR_read, 0,
+		      (unsigned long)&init_data, sizeof(init_data));
+
+	stub_syscall1(__NR_close, 0);
+
+	/* map stub code + data */
+	res = stub_syscall6(STUB_MMAP_NR,
+			    init_data.stub_start, UM_KERN_PAGE_SIZE,
+			    PROT_READ | PROT_EXEC, MAP_FIXED | MAP_SHARED,
+			    init_data.stub_code_fd, init_data.stub_code_offset);
+	if (res != init_data.stub_start)
+		stub_syscall1(__NR_exit, 10);
+
+	res = stub_syscall6(STUB_MMAP_NR,
+			    init_data.stub_start + UM_KERN_PAGE_SIZE,
+			    STUB_DATA_PAGES * UM_KERN_PAGE_SIZE,
+			    PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED,
+			    init_data.stub_data_fd, init_data.stub_data_offset);
+	if (res != init_data.stub_start + UM_KERN_PAGE_SIZE)
+		stub_syscall1(__NR_exit, 11);
+
+	/* setup signal stack inside stub data */
+	stack.ss_flags = 0;
+	stack.ss_size = STUB_DATA_PAGES * UM_KERN_PAGE_SIZE;
+	stack.ss_sp = (void *)init_data.stub_start + UM_KERN_PAGE_SIZE;
+	stub_syscall2(__NR_sigaltstack, (unsigned long)&stack, 0);
+
+	/* register SIGSEGV handler (SA_RESTORER, the handler never returns) */
+	sa.sa_flags = SA_ONSTACK | SA_NODEFER | SA_SIGINFO | 0x04000000;
+	sa.sa_handler_ = (void *) init_data.segv_handler;
+	sa.sa_restorer = NULL;
+	sa.sa_mask = 0L; /* No need to mask anything */
+	res = stub_syscall4(__NR_rt_sigaction, SIGSEGV, (unsigned long)&sa, 0,
+			    sizeof(sa.sa_mask));
+	if (res < 0)
+		stub_syscall1(__NR_exit, 13);
+
+	stub_syscall4(__NR_ptrace, PTRACE_TRACEME, 0, 0, 0);
+
+	stub_syscall2(__NR_kill, stub_syscall0(__NR_getpid), SIGSTOP);
+
+	stub_syscall1(__NR_exit, 14);
+
+	__builtin_unreachable();
+}
+
+void _start(void)
+{
+	char *alloc;
+
+	/* bump the stack pointer as the stub is mapped into our stack */
+	alloc = __builtin_alloca((1 + STUB_DATA_PAGES) * UM_KERN_PAGE_SIZE);
+	asm volatile("" : "+r,m"(alloc) : : "memory");
+
+	real_init();
+}
diff --git a/arch/x86/um/stub_elf_embed.S b/arch/x86/um/stub_elf_embed.S
new file mode 100644
index 000000000000..e39321b4c313
--- /dev/null
+++ b/arch/x86/um/stub_elf_embed.S
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <linux/init.h>
+#include <linux/linkage.h>
+
+__INITDATA
+
+SYM_DATA_START(stub_elf_start)
+	.incbin "arch/x86/um/stub_elf"
+SYM_DATA_END_LABEL(stub_elf_start, SYM_L_GLOBAL, stub_elf_end)
+
+__FINIT
-- 
2.45.1




More information about the linux-um mailing list