]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
um: mark rodata read-only and implement _nofault accesses
authorJohannes Berg <johannes.berg@intel.com>
Mon, 10 Feb 2025 16:09:25 +0000 (17:09 +0100)
committerJohannes Berg <johannes.berg@intel.com>
Tue, 18 Mar 2025 10:03:14 +0000 (11:03 +0100)
Mark read-only data actually read-only (simple mprotect), and
to be able to test it also implement _nofault accesses. This
works by setting up a new "segv_continue" pointer in current,
and then when we hit a segfault we change the signal return
context so that we continue at that address. The code using
this sets it up so that it jumps to a label and then aborts
the access that way, returning -EFAULT.

It's possible to optimize the ___backtrack_faulted() thing by
using asm goto (compiler version dependent) and/or gcc's (not
sure if clang has it) &&label extension, but at least in one
attempt I made the && caused the compiler to not load -EFAULT
into the register in case of jumping to the &&label from the
fault handler. So leave it like this for now.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Co-developed-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Link: https://patch.msgid.link/20250210160926.420133-2-benjamin@sipsolutions.net
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
15 files changed:
arch/um/Kconfig
arch/um/include/asm/processor-generic.h
arch/um/include/asm/uaccess.h
arch/um/include/shared/arch.h
arch/um/include/shared/as-layout.h
arch/um/include/shared/irq_user.h
arch/um/include/shared/kern_util.h
arch/um/kernel/irq.c
arch/um/kernel/mem.c
arch/um/kernel/trap.c
arch/um/os-Linux/signal.c
arch/um/os-Linux/skas/process.c
arch/x86/um/os-Linux/mcontext.c
arch/x86/um/shared/sysdep/faultinfo_32.h
arch/x86/um/shared/sysdep/faultinfo_64.h

index 18051b1cfce0a264f93c86f4054fadeff010de4c..79509c7f39def3d3e13c4787dbe2491b7f97e638 100644 (file)
@@ -12,6 +12,7 @@ config UML
        select ARCH_HAS_KCOV
        select ARCH_HAS_STRNCPY_FROM_USER
        select ARCH_HAS_STRNLEN_USER
+       select ARCH_HAS_STRICT_KERNEL_RWX
        select HAVE_ARCH_AUDITSYSCALL
        select HAVE_ARCH_KASAN if X86_64
        select HAVE_ARCH_KASAN_VMALLOC if HAVE_ARCH_KASAN
index 5d6356eafffee0c2e16e88d890c637b71088480f..8a789c17acd833e9ba22a714bbf39ec7dedabafd 100644 (file)
@@ -31,6 +31,8 @@ struct thread_struct {
                } thread;
        } request;
 
+       void *segv_continue;
+
        /* Contains variable sized FP registers */
        struct pt_regs regs;
 };
index 1d4b6bbc1b65ceb2a32154cdac20e5efc42fe42f..3a08f9029a3f9a22bbab4b720250d9a34050049c 100644 (file)
@@ -9,6 +9,7 @@
 
 #include <asm/elf.h>
 #include <linux/unaligned.h>
+#include <sysdep/faultinfo.h>
 
 #define __under_task_size(addr, size) \
        (((unsigned long) (addr) < TASK_SIZE) && \
@@ -44,19 +45,28 @@ static inline int __access_ok(const void __user *ptr, unsigned long size)
                 __access_ok_vsyscall(addr, size));
 }
 
-/* no pagefaults for kernel addresses in um */
 #define __get_kernel_nofault(dst, src, type, err_label)                        \
 do {                                                                   \
-       *((type *)dst) = get_unaligned((type *)(src));                  \
-       if (0) /* make sure the label looks used to the compiler */     \
+       int __faulted;                                                  \
+                                                                       \
+       ___backtrack_faulted(__faulted);                                \
+       if (__faulted) {                                                \
+               *((type *)dst) = (type) 0;                              \
                goto err_label;                                         \
+       }                                                               \
+       *((type *)dst) = get_unaligned((type *)(src));                  \
+       current->thread.segv_continue = NULL;                           \
 } while (0)
 
 #define __put_kernel_nofault(dst, src, type, err_label)                        \
 do {                                                                   \
-       put_unaligned(*((type *)src), (type *)(dst));                   \
-       if (0) /* make sure the label looks used to the compiler */     \
+       int __faulted;                                                  \
+                                                                       \
+       ___backtrack_faulted(__faulted);                                \
+       if (__faulted)                                                  \
                goto err_label;                                         \
+       put_unaligned(*((type *)src), (type *)(dst));                   \
+       current->thread.segv_continue = NULL;                           \
 } while (0)
 
 #endif
index 880ee42a3329e3355beff5103077a5b9fc31d471..cc398a21ad9608b9ee5032a99a1abd9652c420f8 100644 (file)
@@ -12,4 +12,6 @@ extern void arch_check_bugs(void);
 extern int arch_fixup(unsigned long address, struct uml_pt_regs *regs);
 extern void arch_examine_signal(int sig, struct uml_pt_regs *regs);
 
+void mc_set_rip(void *_mc, void *target);
+
 #endif
index ea65f151bf48473aa609eb455f3db970e3a9702a..4f44dcce8a7ca1863cead8c238ea1f4a10551f2e 100644 (file)
@@ -50,7 +50,7 @@ extern int linux_main(int argc, char **argv, char **envp);
 extern void uml_finishsetup(void);
 
 struct siginfo;
-extern void (*sig_info[])(int, struct siginfo *si, struct uml_pt_regs *);
+extern void (*sig_info[])(int, struct siginfo *si, struct uml_pt_regs *, void *);
 
 #endif
 
index da0f6eea30d01d07fea2384a88f65b8b03894623..88835b52ae2b55ed151045432c64fd126951ce3f 100644 (file)
@@ -15,7 +15,8 @@ enum um_irq_type {
 };
 
 struct siginfo;
-extern void sigio_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs);
+extern void sigio_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 f21dc851753812018e41616a88ae44f885881cc6..00ca3e12fd9aeef4f9579fca22f9f16b579aeb15 100644 (file)
@@ -24,10 +24,12 @@ extern void free_stack(unsigned long stack, int order);
 struct pt_regs;
 extern void do_signal(struct pt_regs *regs);
 extern void interrupt_end(void);
-extern void relay_signal(int sig, struct siginfo *si, struct uml_pt_regs *regs);
+extern void relay_signal(int sig, struct siginfo *si, struct uml_pt_regs *regs,
+                        void *mc);
 
 extern unsigned long segv(struct faultinfo fi, unsigned long ip,
-                         int is_user, struct uml_pt_regs *regs);
+                         int is_user, struct uml_pt_regs *regs,
+                         void *mc);
 extern int handle_page_fault(unsigned long address, unsigned long ip,
                             int is_write, int is_user, int *code_out);
 
@@ -59,8 +61,10 @@ extern unsigned long from_irq_stack(int nested);
 
 extern int singlestepping(void);
 
-extern void segv_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs);
-extern void winch(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs);
+extern void segv_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs,
+                        void *mc);
+extern void winch(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs,
+                 void *mc);
 extern void fatal_sigsegv(void) __attribute__ ((noreturn));
 
 void um_idle_sleep(void);
index a4991746f5eac649e2fa2fccf74395845791d93c..abe8f30a521c0cc130a0a08ef05e228438037215 100644 (file)
@@ -236,7 +236,8 @@ static void _sigio_handler(struct uml_pt_regs *regs,
                free_irqs();
 }
 
-void sigio_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs)
+void sigio_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs,
+                  void *mc)
 {
        preempt_disable();
        _sigio_handler(regs, irqs_suspended);
index befed230aac28e96fe54b3b41cd68f46fa2cfac3..05ffceb555b4e3e6a9676d08ce8e60d820c73115 100644 (file)
@@ -9,6 +9,8 @@
 #include <linux/mm.h>
 #include <linux/swap.h>
 #include <linux/slab.h>
+#include <linux/init.h>
+#include <asm/sections.h>
 #include <asm/page.h>
 #include <asm/pgalloc.h>
 #include <as-layout.h>
@@ -241,3 +243,11 @@ static const pgprot_t protection_map[16] = {
        [VM_SHARED | VM_EXEC | VM_WRITE | VM_READ]      = PAGE_SHARED
 };
 DECLARE_VM_GET_PAGE_PROT
+
+void mark_rodata_ro(void)
+{
+       unsigned long rodata_start = PFN_ALIGN(__start_rodata);
+       unsigned long rodata_end = PFN_ALIGN(__end_rodata);
+
+       os_protect_memory((void *)rodata_start, rodata_end - rodata_start, 1, 0, 0);
+}
index cdaee3e94273418c51856b26da0e5c977929b8f4..ce073150dc20a8a72ca9f2e4e7d22595fa4a7fcd 100644 (file)
@@ -16,6 +16,7 @@
 #include <kern_util.h>
 #include <os.h>
 #include <skas.h>
+#include <arch.h>
 
 /*
  * Note this is constrained to return 0, -EFAULT, -EACCES, -ENOMEM by
@@ -175,12 +176,14 @@ void fatal_sigsegv(void)
  * @sig:       the signal number
  * @unused_si: the signal info struct; unused in this handler
  * @regs:      the ptrace register information
+ * @mc:                the mcontext of the signal
  *
  * The handler first extracts the faultinfo from the UML ptrace regs struct.
  * If the userfault did not happen in an UML userspace process, bad_segv is called.
  * Otherwise the signal did happen in a cloned userspace process, handle it.
  */
-void segv_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs)
+void segv_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs,
+                 void *mc)
 {
        struct faultinfo * fi = UPT_FAULTINFO(regs);
 
@@ -189,7 +192,7 @@ void segv_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs)
                bad_segv(*fi, UPT_IP(regs));
                return;
        }
-       segv(*fi, UPT_IP(regs), UPT_IS_USER(regs), regs);
+       segv(*fi, UPT_IP(regs), UPT_IS_USER(regs), regs, mc);
 }
 
 /*
@@ -199,7 +202,7 @@ void segv_handler(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs)
  * give us bad data!
  */
 unsigned long segv(struct faultinfo fi, unsigned long ip, int is_user,
-                  struct uml_pt_regs *regs)
+                  struct uml_pt_regs *regs, void *mc)
 {
        int si_code;
        int err;
@@ -223,6 +226,19 @@ unsigned long segv(struct faultinfo fi, unsigned long ip, int is_user,
                goto out;
        }
        else if (current->mm == NULL) {
+               if (current->pagefault_disabled) {
+                       if (!mc) {
+                               show_regs(container_of(regs, struct pt_regs, regs));
+                               panic("Segfault with pagefaults disabled but no mcontext");
+                       }
+                       if (!current->thread.segv_continue) {
+                               show_regs(container_of(regs, struct pt_regs, regs));
+                               panic("Segfault without recovery target");
+                       }
+                       mc_set_rip(mc, current->thread.segv_continue);
+                       current->thread.segv_continue = NULL;
+                       goto out;
+               }
                show_regs(container_of(regs, struct pt_regs, regs));
                panic("Segfault with no mm");
        }
@@ -274,7 +290,8 @@ out:
        return 0;
 }
 
-void relay_signal(int sig, struct siginfo *si, struct uml_pt_regs *regs)
+void relay_signal(int sig, struct siginfo *si, struct uml_pt_regs *regs,
+                 void *mc)
 {
        int code, err;
        if (!UPT_IS_USER(regs)) {
@@ -302,7 +319,8 @@ void relay_signal(int sig, struct siginfo *si, struct uml_pt_regs *regs)
        }
 }
 
-void winch(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs)
+void winch(int sig, struct siginfo *unused_si, struct uml_pt_regs *regs,
+          void *mc)
 {
        do_IRQ(WINCH_IRQ, regs);
 }
index 9ea7269ffb778289086fa365b77d4eba124fa49a..e71e5b4878d13c5801c6896f344a1d51ecdcf5c7 100644 (file)
@@ -21,7 +21,7 @@
 #include <sys/ucontext.h>
 #include <timetravel.h>
 
-void (*sig_info[NSIG])(int, struct siginfo *, struct uml_pt_regs *) = {
+void (*sig_info[NSIG])(int, struct siginfo *, struct uml_pt_regs *, void *mc) = {
        [SIGTRAP]       = relay_signal,
        [SIGFPE]        = relay_signal,
        [SIGILL]        = relay_signal,
@@ -47,7 +47,7 @@ static void sig_handler_common(int sig, struct siginfo *si, mcontext_t *mc)
        if ((sig != SIGIO) && (sig != SIGWINCH))
                unblock_signals_trace();
 
-       (*sig_info[sig])(sig, si, &r);
+       (*sig_info[sig])(sig, si, &r, mc);
 
        errno = save_errno;
 }
index e2f8f156402f50331558272e6a5bb772bcf6d180..ae2aea062f06a1508806bb07dc366045df737d92 100644 (file)
@@ -166,7 +166,7 @@ static void get_skas_faultinfo(int pid, struct faultinfo *fi)
 static void handle_segv(int pid, struct uml_pt_regs *regs)
 {
        get_skas_faultinfo(pid, &regs->faultinfo);
-       segv(regs->faultinfo, 0, 1, NULL);
+       segv(regs->faultinfo, 0, 1, NULL, NULL);
 }
 
 static void handle_trap(int pid, struct uml_pt_regs *regs)
@@ -525,7 +525,7 @@ void userspace(struct uml_pt_regs *regs)
                                        get_skas_faultinfo(pid,
                                                           &regs->faultinfo);
                                        (*sig_info[SIGSEGV])(SIGSEGV, (struct siginfo *)&si,
-                                                            regs);
+                                                            regs, NULL);
                                }
                                else handle_segv(pid, regs);
                                break;
@@ -533,7 +533,7 @@ void userspace(struct uml_pt_regs *regs)
                                handle_trap(pid, regs);
                                break;
                        case SIGTRAP:
-                               relay_signal(SIGTRAP, (struct siginfo *)&si, regs);
+                               relay_signal(SIGTRAP, (struct siginfo *)&si, regs, NULL);
                                break;
                        case SIGALRM:
                                break;
@@ -543,7 +543,7 @@ void userspace(struct uml_pt_regs *regs)
                        case SIGFPE:
                        case SIGWINCH:
                                block_signals_trace();
-                               (*sig_info[sig])(sig, (struct siginfo *)&si, regs);
+                               (*sig_info[sig])(sig, (struct siginfo *)&si, regs, NULL);
                                unblock_signals_trace();
                                break;
                        default:
index e80ab7d281177b887adddd30d4f8312adca569ed..d2f3a595b4ef163e024c9c6ed5a57c8110581f44 100644 (file)
@@ -4,6 +4,7 @@
 #include <asm/ptrace.h>
 #include <sysdep/ptrace.h>
 #include <sysdep/mcontext.h>
+#include <arch.h>
 
 void get_regs_from_mc(struct uml_pt_regs *regs, mcontext_t *mc)
 {
@@ -31,3 +32,14 @@ void get_regs_from_mc(struct uml_pt_regs *regs, mcontext_t *mc)
        regs->gp[CS / sizeof(unsigned long)] |= 3;
 #endif
 }
+
+void mc_set_rip(void *_mc, void *target)
+{
+       mcontext_t *mc = _mc;
+
+#ifdef __i386__
+       mc->gregs[REG_EIP] = (unsigned long)target;
+#else
+       mc->gregs[REG_RIP] = (unsigned long)target;
+#endif
+}
index b6f2437ec29c7a882ca42bdb8bdc723cd445ef8c..ab5c8e47049c31ced1986e029dbb99e55d500b17 100644 (file)
@@ -29,4 +29,16 @@ struct faultinfo {
 
 #define PTRACE_FULL_FAULTINFO 0
 
+#define ___backtrack_faulted(_faulted)                                 \
+       asm volatile (                                                  \
+               "mov $0, %0\n"                                          \
+               "movl $__get_kernel_nofault_faulted_%=,%1\n"            \
+               "jmp _end_%=\n"                                         \
+               "__get_kernel_nofault_faulted_%=:\n"                    \
+               "mov $1, %0;"                                           \
+               "_end_%=:"                                              \
+               : "=r" (_faulted),                                      \
+                 "=m" (current->thread.segv_continue) ::               \
+       )
+
 #endif
index ee88f88974ea0fffe13d1a4a0ab42c391e788a20..26fb4835d3e9a32b1f93fe4b390aa9b0f28765fe 100644 (file)
@@ -29,4 +29,16 @@ struct faultinfo {
 
 #define PTRACE_FULL_FAULTINFO 1
 
+#define ___backtrack_faulted(_faulted)                                 \
+       asm volatile (                                                  \
+               "mov $0, %0\n"                                          \
+               "movq $__get_kernel_nofault_faulted_%=,%1\n"            \
+               "jmp _end_%=\n"                                         \
+               "__get_kernel_nofault_faulted_%=:\n"                    \
+               "mov $1, %0;"                                           \
+               "_end_%=:"                                              \
+               : "=r" (_faulted),                                      \
+                 "=m" (current->thread.segv_continue) ::               \
+       )
+
 #endif