Skip to content

Commit

Permalink
um: mark rodata read-only and implement _nofault accesses
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Johannes Berg committed Mar 18, 2025
1 parent 5550187 commit d1d7f01
Showing 15 changed files with 108 additions and 23 deletions.
1 change: 1 addition & 0 deletions arch/um/Kconfig
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions arch/um/include/asm/processor-generic.h
Original file line number Diff line number Diff line change
@@ -31,6 +31,8 @@ struct thread_struct {
} thread;
} request;

void *segv_continue;

/* Contains variable sized FP registers */
struct pt_regs regs;
};
20 changes: 15 additions & 5 deletions arch/um/include/asm/uaccess.h
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions arch/um/include/shared/arch.h
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion arch/um/include/shared/as-layout.h
Original file line number Diff line number Diff line change
@@ -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

3 changes: 2 additions & 1 deletion arch/um/include/shared/irq_user.h
Original file line number Diff line number Diff line change
@@ -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);
12 changes: 8 additions & 4 deletions arch/um/include/shared/kern_util.h
Original file line number Diff line number Diff line change
@@ -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);
3 changes: 2 additions & 1 deletion arch/um/kernel/irq.c
Original file line number Diff line number Diff line change
@@ -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);
10 changes: 10 additions & 0 deletions arch/um/kernel/mem.c
Original file line number Diff line number Diff line change
@@ -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);
}
28 changes: 23 additions & 5 deletions arch/um/kernel/trap.c
Original file line number Diff line number Diff line change
@@ -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 @@ unsigned long segv(struct faultinfo fi, unsigned long ip, int is_user,
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);
}
4 changes: 2 additions & 2 deletions arch/um/os-Linux/signal.c
Original file line number Diff line number Diff line change
@@ -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;
}
8 changes: 4 additions & 4 deletions arch/um/os-Linux/skas/process.c
Original file line number Diff line number Diff line change
@@ -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,15 +525,15 @@ 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;
case SIGTRAP + 0x80:
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:
12 changes: 12 additions & 0 deletions arch/x86/um/os-Linux/mcontext.c
Original file line number Diff line number Diff line change
@@ -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
}
12 changes: 12 additions & 0 deletions arch/x86/um/shared/sysdep/faultinfo_32.h
Original file line number Diff line number Diff line change
@@ -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
12 changes: 12 additions & 0 deletions arch/x86/um/shared/sysdep/faultinfo_64.h
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit d1d7f01

Please sign in to comment.