]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
um: Track userspace children dying in SECCOMP mode
authorBenjamin Berg <benjamin@sipsolutions.net>
Mon, 2 Jun 2025 13:00:49 +0000 (15:00 +0200)
committerJohannes Berg <johannes.berg@intel.com>
Mon, 2 Jun 2025 13:17:19 +0000 (15:17 +0200)
When in seccomp mode, we would hang forever on the futex if a child has
died unexpectedly. In contrast, ptrace mode will notice it and kill the
corresponding thread when it fails to run it.

Fix this issue using a new IRQ that is fired after a SIGCHLD and keeping
an (internal) list of all MMs. In the IRQ handler, find the affected MM
and set its PID to -1 as well as the futex variable to FUTEX_IN_KERN.

This, together with futex returning -EINTR after the signal is
sufficient to implement a race-free detection of a child dying.

Note that this also enables IRQ handling while starting a userspace
process. This should be safe and SECCOMP requires the IRQ in case the
process does not come up properly.

Signed-off-by: Benjamin Berg <benjamin@sipsolutions.net>
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Link: https://patch.msgid.link/20250602130052.545733-5-benjamin@sipsolutions.net
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
arch/um/include/asm/irq.h
arch/um/include/asm/mmu.h
arch/um/include/shared/irq_user.h
arch/um/include/shared/os.h
arch/um/include/shared/skas/mm_id.h
arch/um/include/shared/skas/skas.h
arch/um/kernel/irq.c
arch/um/kernel/skas/mmu.c
arch/um/os-Linux/process.c
arch/um/os-Linux/signal.c
arch/um/os-Linux/skas/process.c

index 749dfe8512e84195817c2b1601bd2f161206e549..36dbedd1af4804fdeb2c4eab42c9092acf437d9f 100644 (file)
 #define TELNETD_IRQ            8
 #define XTERM_IRQ              9
 #define RANDOM_IRQ             10
+#define SIGCHLD_IRQ            11
 
 #ifdef CONFIG_UML_NET_VECTOR
 
-#define VECTOR_BASE_IRQ                (RANDOM_IRQ + 1)
+#define VECTOR_BASE_IRQ                (SIGCHLD_IRQ + 1)
 #define VECTOR_IRQ_SPACE       8
 
 #define UM_FIRST_DYN_IRQ (VECTOR_IRQ_SPACE + VECTOR_BASE_IRQ)
 
 #else
 
-#define UM_FIRST_DYN_IRQ (RANDOM_IRQ + 1)
+#define UM_FIRST_DYN_IRQ (SIGCHLD_IRQ + 1)
 
 #endif
 
index a3eaca41ff6134143fed6f405dd574498ecb84cb..4d0e4239f3ccc4ed57402643aed64efc470cdbd3 100644 (file)
@@ -6,11 +6,14 @@
 #ifndef __ARCH_UM_MMU_H
 #define __ARCH_UM_MMU_H
 
+#include "linux/types.h"
 #include <mm_id.h>
 
 typedef struct mm_context {
        struct mm_id id;
 
+       struct list_head list;
+
        /* Address range in need of a TLB sync */
        unsigned long sync_tlb_range_from;
        unsigned long sync_tlb_range_to;
index 88835b52ae2b55ed151045432c64fd126951ce3f..746abc24a5d50c61e9971999eb1bccab6a3e6f04 100644 (file)
@@ -17,6 +17,8 @@ enum um_irq_type {
 struct siginfo;
 extern void sigio_handler(int sig, struct siginfo *unused_si,
                          struct uml_pt_regs *regs, void *mc);
+extern void sigchld_handler(int sig, struct siginfo *unused_si,
+                          struct uml_pt_regs *regs, void *mc);
 void sigio_run_timetravel_handlers(void);
 extern void free_irq_by_fd(int fd);
 extern void deactivate_fd(int fd, int irqnum);
index e63a74a5ff19fbdadb9fb39f5f98a91cde80fdc4..3046728ec42ec0c4aec07a34855c986191b6a0a1 100644 (file)
@@ -197,6 +197,7 @@ extern int create_mem_file(unsigned long long len);
 extern void report_enomem(void);
 
 /* process.c */
+pid_t os_reap_child(void);
 extern void os_alarm_process(int pid);
 extern void os_kill_process(int pid, int reap_child);
 extern void os_kill_ptraced_process(int pid, int reap_child);
index 140388c282f6d31b2379419a8e0fe5689f54fab5..0654c57bb28eefe362a15bac48ead55793a9b0dc 100644 (file)
@@ -14,4 +14,6 @@ struct mm_id {
 
 void __switch_mm(struct mm_id *mm_idp);
 
+void notify_mm_kill(int pid);
+
 #endif
index 85c50122ab98132abf2a9a03f3791c9a1b77e237..7d1de4cab551b053183cc1386023c19b8e7d227b 100644 (file)
@@ -8,6 +8,7 @@
 
 #include <sysdep/ptrace.h>
 
+extern int using_seccomp;
 extern int userspace_pid[];
 
 extern void new_thread_handler(void);
index abe8f30a521c0cc130a0a08ef05e228438037215..f1787be3983ce04b6829dd70eacbc07545d821d9 100644 (file)
@@ -690,3 +690,9 @@ void __init init_IRQ(void)
        /* Initialize EPOLL Loop */
        os_setup_epoll();
 }
+
+extern void sigchld_handler(int sig, struct siginfo *unused_si,
+                           struct uml_pt_regs *regs, void *mc)
+{
+       do_IRQ(SIGCHLD_IRQ, regs);
+}
index 0eb5a1d3ba70134f75d9b2af18544fca7248c6d6..1e146a0f9549eb2ea06daf57540833f8642be35b 100644 (file)
@@ -8,6 +8,7 @@
 #include <linux/sched/signal.h>
 #include <linux/slab.h>
 
+#include <shared/irq_kern.h>
 #include <asm/pgalloc.h>
 #include <asm/sections.h>
 #include <asm/mmu_context.h>
@@ -19,6 +20,9 @@
 /* Ensure the stub_data struct covers the allocated area */
 static_assert(sizeof(struct stub_data) == STUB_DATA_PAGES * UM_KERN_PAGE_SIZE);
 
+spinlock_t mm_list_lock;
+struct list_head mm_list;
+
 int init_new_context(struct task_struct *task, struct mm_struct *mm)
 {
        struct mm_id *new_id = &mm->context.id;
@@ -31,10 +35,12 @@ int init_new_context(struct task_struct *task, struct mm_struct *mm)
 
        new_id->stack = stack;
 
-       block_signals_trace();
-       new_id->pid = start_userspace(stack);
-       unblock_signals_trace();
+       scoped_guard(spinlock_irqsave, &mm_list_lock) {
+               /* Insert into list, used for lookups when the child dies */
+               list_add(&mm->context.list, &mm_list);
+       }
 
+       new_id->pid = start_userspace(stack);
        if (new_id->pid < 0) {
                ret = new_id->pid;
                goto out_free;
@@ -60,13 +66,79 @@ void destroy_context(struct mm_struct *mm)
         * zero, resulting in a kill(0), which will result in the
         * whole UML suddenly dying.  Also, cover negative and
         * 1 cases, since they shouldn't happen either.
+        *
+        * Negative cases happen if the child died unexpectedly.
         */
-       if (mmu->id.pid < 2) {
+       if (mmu->id.pid >= 0 && mmu->id.pid < 2) {
                printk(KERN_ERR "corrupt mm_context - pid = %d\n",
                       mmu->id.pid);
                return;
        }
-       os_kill_ptraced_process(mmu->id.pid, 1);
+
+       if (mmu->id.pid > 0) {
+               os_kill_ptraced_process(mmu->id.pid, 1);
+               mmu->id.pid = -1;
+       }
 
        free_pages(mmu->id.stack, ilog2(STUB_DATA_PAGES));
+
+       guard(spinlock_irqsave)(&mm_list_lock);
+
+       list_del(&mm->context.list);
+}
+
+static irqreturn_t mm_sigchld_irq(int irq, void* dev)
+{
+       struct mm_context *mm_context;
+       pid_t pid;
+
+       guard(spinlock)(&mm_list_lock);
+
+       while ((pid = os_reap_child()) > 0) {
+               /*
+               * A child died, check if we have an MM with the PID. This is
+               * only relevant in SECCOMP mode (as ptrace will fail anyway).
+               *
+               * See wait_stub_done_seccomp for more details.
+               */
+               list_for_each_entry(mm_context, &mm_list, list) {
+                       if (mm_context->id.pid == pid) {
+                               struct stub_data *stub_data;
+                               printk("Unexpectedly lost MM child! Affected tasks will segfault.");
+
+                               /* Marks the MM as dead */
+                               mm_context->id.pid = -1;
+
+                               /*
+                                * NOTE: If SMP is implemented, a futex_wake
+                                * needs to be added here.
+                                */
+                               stub_data = (void *)mm_context->id.stack;
+                               stub_data->futex = FUTEX_IN_KERN;
+
+                               /*
+                                * NOTE: Currently executing syscalls by
+                                * affected tasks may finish normally.
+                                */
+                               break;
+                       }
+               }
+       }
+
+       return IRQ_HANDLED;
+}
+
+static int __init init_child_tracking(void)
+{
+       int err;
+
+       spin_lock_init(&mm_list_lock);
+       INIT_LIST_HEAD(&mm_list);
+
+       err = request_irq(SIGCHLD_IRQ, mm_sigchld_irq, 0, "SIGCHLD", NULL);
+       if (err < 0)
+               panic("Failed to register SIGCHLD IRQ: %d", err);
+
+       return 0;
 }
+early_initcall(init_child_tracking)
index 184566edeee99723c834c5293cbae6b77c6690e1..00b49e90d05fee20b4f231aff119f6d64ec7bc2e 100644 (file)
 #include <init.h>
 #include <longjmp.h>
 #include <os.h>
+#include <skas/skas.h>
 
 void os_alarm_process(int pid)
 {
+       if (pid <= 0)
+               return;
+
        kill(pid, SIGALRM);
 }
 
 void os_kill_process(int pid, int reap_child)
 {
+       if (pid <= 0)
+               return;
+
+       /* Block signals until child is reaped */
+       block_signals();
+
        kill(pid, SIGKILL);
        if (reap_child)
                CATCH_EINTR(waitpid(pid, NULL, __WALL));
+
+       unblock_signals();
 }
 
 /* Kill off a ptraced child by all means available.  kill it normally first,
@@ -38,11 +50,27 @@ void os_kill_process(int pid, int reap_child)
 
 void os_kill_ptraced_process(int pid, int reap_child)
 {
+       if (pid <= 0)
+               return;
+
+       /* Block signals until child is reaped */
+       block_signals();
+
        kill(pid, SIGKILL);
        ptrace(PTRACE_KILL, pid);
        ptrace(PTRACE_CONT, pid);
        if (reap_child)
                CATCH_EINTR(waitpid(pid, NULL, __WALL));
+
+       unblock_signals();
+}
+
+pid_t os_reap_child(void)
+{
+       int status;
+
+       /* Try to reap a child */
+       return waitpid(-1, &status, WNOHANG);
 }
 
 /* Don't use the glibc version, which caches the result in TLS. It misses some
@@ -151,6 +179,9 @@ void init_new_thread_signals(void)
        set_handler(SIGBUS);
        signal(SIGHUP, SIG_IGN);
        set_handler(SIGIO);
+       /* We (currently) only use the child reaper IRQ in seccomp mode */
+       if (using_seccomp)
+               set_handler(SIGCHLD);
        signal(SIGWINCH, SIG_IGN);
 }
 
index e71e5b4878d13c5801c6896f344a1d51ecdcf5c7..11f07f49827005b00e32d5153d8b54b1a077b6f2 100644 (file)
@@ -29,6 +29,7 @@ void (*sig_info[NSIG])(int, struct siginfo *, struct uml_pt_regs *, void *mc) =
        [SIGBUS]        = relay_signal,
        [SIGSEGV]       = segv_handler,
        [SIGIO]         = sigio_handler,
+       [SIGCHLD]       = sigchld_handler,
 };
 
 static void sig_handler_common(int sig, struct siginfo *si, mcontext_t *mc)
@@ -44,7 +45,7 @@ static void sig_handler_common(int sig, struct siginfo *si, mcontext_t *mc)
        }
 
        /* enable signals if sig isn't IRQ signal */
-       if ((sig != SIGIO) && (sig != SIGWINCH))
+       if ((sig != SIGIO) && (sig != SIGWINCH) && (sig != SIGCHLD))
                unblock_signals_trace();
 
        (*sig_info[sig])(sig, si, &r, mc);
@@ -64,6 +65,9 @@ static void sig_handler_common(int sig, struct siginfo *si, mcontext_t *mc)
 #define SIGALRM_BIT 1
 #define SIGALRM_MASK (1 << SIGALRM_BIT)
 
+#define SIGCHLD_BIT 2
+#define SIGCHLD_MASK (1 << SIGCHLD_BIT)
+
 int signals_enabled;
 #if IS_ENABLED(CONFIG_UML_TIME_TRAVEL_SUPPORT)
 static int signals_blocked, signals_blocked_pending;
@@ -102,6 +106,11 @@ static void sig_handler(int sig, struct siginfo *si, mcontext_t *mc)
                return;
        }
 
+       if (!enabled && (sig == SIGCHLD)) {
+               signals_pending |= SIGCHLD_MASK;
+               return;
+       }
+
        block_signals_trace();
 
        sig_handler_common(sig, si, mc);
@@ -181,6 +190,8 @@ static void (*handlers[_NSIG])(int sig, struct siginfo *si, mcontext_t *mc) = {
 
        [SIGIO] = sig_handler,
        [SIGWINCH] = sig_handler,
+       /* SIGCHLD is only actually registered in seccomp mode. */
+       [SIGCHLD] = sig_handler,
        [SIGALRM] = timer_alarm_handler,
 
        [SIGUSR1] = sigusr1_handler,
@@ -309,6 +320,12 @@ void unblock_signals(void)
                if (save_pending & SIGIO_MASK)
                        sig_handler_common(SIGIO, NULL, NULL);
 
+               if (save_pending & SIGCHLD_MASK) {
+                       struct uml_pt_regs regs = {};
+
+                       sigchld_handler(SIGCHLD, NULL, &regs, NULL);
+               }
+
                /* Do not reenter the handler */
 
                if ((save_pending & SIGALRM_MASK) && (!(signals_active & SIGALRM_MASK)))
index 97c2f964f5fc8ce4142e8019c1ede45be8f3414b..150e4f6ba6332624a899de2df8a0fb248aa831ac 100644 (file)
@@ -309,6 +309,7 @@ static int __init init_stub_exe_fd(void)
 }
 __initcall(init_stub_exe_fd);
 
+int using_seccomp;
 int userspace_pid[NR_CPUS];
 
 /**