mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00
um: mark rodata read-only and implement _nofault accesses
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>
This commit is contained in:
parent
5550187c4c
commit
d1d7f01f7c
15 changed files with 108 additions and 23 deletions
|
@ -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
|
||||
|
|
|
@ -31,6 +31,8 @@ struct thread_struct {
|
|||
} thread;
|
||||
} request;
|
||||
|
||||
void *segv_continue;
|
||||
|
||||
/* Contains variable sized FP registers */
|
||||
struct pt_regs regs;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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, ®s->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,
|
||||
®s->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:
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue