mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-09-04 20:19:47 +08: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
@ -12,6 +12,7 @@ config UML
|
|||||||
select ARCH_HAS_KCOV
|
select ARCH_HAS_KCOV
|
||||||
select ARCH_HAS_STRNCPY_FROM_USER
|
select ARCH_HAS_STRNCPY_FROM_USER
|
||||||
select ARCH_HAS_STRNLEN_USER
|
select ARCH_HAS_STRNLEN_USER
|
||||||
|
select ARCH_HAS_STRICT_KERNEL_RWX
|
||||||
select HAVE_ARCH_AUDITSYSCALL
|
select HAVE_ARCH_AUDITSYSCALL
|
||||||
select HAVE_ARCH_KASAN if X86_64
|
select HAVE_ARCH_KASAN if X86_64
|
||||||
select HAVE_ARCH_KASAN_VMALLOC if HAVE_ARCH_KASAN
|
select HAVE_ARCH_KASAN_VMALLOC if HAVE_ARCH_KASAN
|
||||||
|
@ -31,6 +31,8 @@ struct thread_struct {
|
|||||||
} thread;
|
} thread;
|
||||||
} request;
|
} request;
|
||||||
|
|
||||||
|
void *segv_continue;
|
||||||
|
|
||||||
/* Contains variable sized FP registers */
|
/* Contains variable sized FP registers */
|
||||||
struct pt_regs regs;
|
struct pt_regs regs;
|
||||||
};
|
};
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include <asm/elf.h>
|
#include <asm/elf.h>
|
||||||
#include <linux/unaligned.h>
|
#include <linux/unaligned.h>
|
||||||
|
#include <sysdep/faultinfo.h>
|
||||||
|
|
||||||
#define __under_task_size(addr, size) \
|
#define __under_task_size(addr, size) \
|
||||||
(((unsigned long) (addr) < TASK_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));
|
__access_ok_vsyscall(addr, size));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* no pagefaults for kernel addresses in um */
|
|
||||||
#define __get_kernel_nofault(dst, src, type, err_label) \
|
#define __get_kernel_nofault(dst, src, type, err_label) \
|
||||||
do { \
|
do { \
|
||||||
*((type *)dst) = get_unaligned((type *)(src)); \
|
int __faulted; \
|
||||||
if (0) /* make sure the label looks used to the compiler */ \
|
\
|
||||||
|
___backtrack_faulted(__faulted); \
|
||||||
|
if (__faulted) { \
|
||||||
|
*((type *)dst) = (type) 0; \
|
||||||
goto err_label; \
|
goto err_label; \
|
||||||
|
} \
|
||||||
|
*((type *)dst) = get_unaligned((type *)(src)); \
|
||||||
|
current->thread.segv_continue = NULL; \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
#define __put_kernel_nofault(dst, src, type, err_label) \
|
#define __put_kernel_nofault(dst, src, type, err_label) \
|
||||||
do { \
|
do { \
|
||||||
put_unaligned(*((type *)src), (type *)(dst)); \
|
int __faulted; \
|
||||||
if (0) /* make sure the label looks used to the compiler */ \
|
\
|
||||||
|
___backtrack_faulted(__faulted); \
|
||||||
|
if (__faulted) \
|
||||||
goto err_label; \
|
goto err_label; \
|
||||||
|
put_unaligned(*((type *)src), (type *)(dst)); \
|
||||||
|
current->thread.segv_continue = NULL; \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -12,4 +12,6 @@ extern void arch_check_bugs(void);
|
|||||||
extern int arch_fixup(unsigned long address, struct uml_pt_regs *regs);
|
extern int arch_fixup(unsigned long address, struct uml_pt_regs *regs);
|
||||||
extern void arch_examine_signal(int sig, 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
|
#endif
|
||||||
|
@ -50,7 +50,7 @@ extern int linux_main(int argc, char **argv, char **envp);
|
|||||||
extern void uml_finishsetup(void);
|
extern void uml_finishsetup(void);
|
||||||
|
|
||||||
struct siginfo;
|
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
|
#endif
|
||||||
|
|
||||||
|
@ -15,7 +15,8 @@ enum um_irq_type {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct siginfo;
|
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);
|
void sigio_run_timetravel_handlers(void);
|
||||||
extern void free_irq_by_fd(int fd);
|
extern void free_irq_by_fd(int fd);
|
||||||
extern void deactivate_fd(int fd, int irqnum);
|
extern void deactivate_fd(int fd, int irqnum);
|
||||||
|
@ -24,10 +24,12 @@ extern void free_stack(unsigned long stack, int order);
|
|||||||
struct pt_regs;
|
struct pt_regs;
|
||||||
extern void do_signal(struct pt_regs *regs);
|
extern void do_signal(struct pt_regs *regs);
|
||||||
extern void interrupt_end(void);
|
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,
|
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,
|
extern int handle_page_fault(unsigned long address, unsigned long ip,
|
||||||
int is_write, int is_user, int *code_out);
|
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 int singlestepping(void);
|
||||||
|
|
||||||
extern void segv_handler(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,
|
||||||
extern void winch(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));
|
extern void fatal_sigsegv(void) __attribute__ ((noreturn));
|
||||||
|
|
||||||
void um_idle_sleep(void);
|
void um_idle_sleep(void);
|
||||||
|
@ -236,7 +236,8 @@ static void _sigio_handler(struct uml_pt_regs *regs,
|
|||||||
free_irqs();
|
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();
|
preempt_disable();
|
||||||
_sigio_handler(regs, irqs_suspended);
|
_sigio_handler(regs, irqs_suspended);
|
||||||
|
@ -9,6 +9,8 @@
|
|||||||
#include <linux/mm.h>
|
#include <linux/mm.h>
|
||||||
#include <linux/swap.h>
|
#include <linux/swap.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <asm/sections.h>
|
||||||
#include <asm/page.h>
|
#include <asm/page.h>
|
||||||
#include <asm/pgalloc.h>
|
#include <asm/pgalloc.h>
|
||||||
#include <as-layout.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
|
[VM_SHARED | VM_EXEC | VM_WRITE | VM_READ] = PAGE_SHARED
|
||||||
};
|
};
|
||||||
DECLARE_VM_GET_PAGE_PROT
|
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 <kern_util.h>
|
||||||
#include <os.h>
|
#include <os.h>
|
||||||
#include <skas.h>
|
#include <skas.h>
|
||||||
|
#include <arch.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Note this is constrained to return 0, -EFAULT, -EACCES, -ENOMEM by
|
* Note this is constrained to return 0, -EFAULT, -EACCES, -ENOMEM by
|
||||||
@ -175,12 +176,14 @@ void fatal_sigsegv(void)
|
|||||||
* @sig: the signal number
|
* @sig: the signal number
|
||||||
* @unused_si: the signal info struct; unused in this handler
|
* @unused_si: the signal info struct; unused in this handler
|
||||||
* @regs: the ptrace register information
|
* @regs: the ptrace register information
|
||||||
|
* @mc: the mcontext of the signal
|
||||||
*
|
*
|
||||||
* The handler first extracts the faultinfo from the UML ptrace regs struct.
|
* 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.
|
* 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.
|
* 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);
|
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));
|
bad_segv(*fi, UPT_IP(regs));
|
||||||
return;
|
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!
|
* give us bad data!
|
||||||
*/
|
*/
|
||||||
unsigned long segv(struct faultinfo fi, unsigned long ip, int is_user,
|
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 si_code;
|
||||||
int err;
|
int err;
|
||||||
@ -223,6 +226,19 @@ unsigned long segv(struct faultinfo fi, unsigned long ip, int is_user,
|
|||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
else if (current->mm == NULL) {
|
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));
|
show_regs(container_of(regs, struct pt_regs, regs));
|
||||||
panic("Segfault with no mm");
|
panic("Segfault with no mm");
|
||||||
}
|
}
|
||||||
@ -274,7 +290,8 @@ out:
|
|||||||
return 0;
|
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;
|
int code, err;
|
||||||
if (!UPT_IS_USER(regs)) {
|
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);
|
do_IRQ(WINCH_IRQ, regs);
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
#include <sys/ucontext.h>
|
#include <sys/ucontext.h>
|
||||||
#include <timetravel.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,
|
[SIGTRAP] = relay_signal,
|
||||||
[SIGFPE] = relay_signal,
|
[SIGFPE] = relay_signal,
|
||||||
[SIGILL] = 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))
|
if ((sig != SIGIO) && (sig != SIGWINCH))
|
||||||
unblock_signals_trace();
|
unblock_signals_trace();
|
||||||
|
|
||||||
(*sig_info[sig])(sig, si, &r);
|
(*sig_info[sig])(sig, si, &r, mc);
|
||||||
|
|
||||||
errno = save_errno;
|
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)
|
static void handle_segv(int pid, struct uml_pt_regs *regs)
|
||||||
{
|
{
|
||||||
get_skas_faultinfo(pid, ®s->faultinfo);
|
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)
|
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,
|
get_skas_faultinfo(pid,
|
||||||
®s->faultinfo);
|
®s->faultinfo);
|
||||||
(*sig_info[SIGSEGV])(SIGSEGV, (struct siginfo *)&si,
|
(*sig_info[SIGSEGV])(SIGSEGV, (struct siginfo *)&si,
|
||||||
regs);
|
regs, NULL);
|
||||||
}
|
}
|
||||||
else handle_segv(pid, regs);
|
else handle_segv(pid, regs);
|
||||||
break;
|
break;
|
||||||
@ -533,7 +533,7 @@ void userspace(struct uml_pt_regs *regs)
|
|||||||
handle_trap(pid, regs);
|
handle_trap(pid, regs);
|
||||||
break;
|
break;
|
||||||
case SIGTRAP:
|
case SIGTRAP:
|
||||||
relay_signal(SIGTRAP, (struct siginfo *)&si, regs);
|
relay_signal(SIGTRAP, (struct siginfo *)&si, regs, NULL);
|
||||||
break;
|
break;
|
||||||
case SIGALRM:
|
case SIGALRM:
|
||||||
break;
|
break;
|
||||||
@ -543,7 +543,7 @@ void userspace(struct uml_pt_regs *regs)
|
|||||||
case SIGFPE:
|
case SIGFPE:
|
||||||
case SIGWINCH:
|
case SIGWINCH:
|
||||||
block_signals_trace();
|
block_signals_trace();
|
||||||
(*sig_info[sig])(sig, (struct siginfo *)&si, regs);
|
(*sig_info[sig])(sig, (struct siginfo *)&si, regs, NULL);
|
||||||
unblock_signals_trace();
|
unblock_signals_trace();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include <asm/ptrace.h>
|
#include <asm/ptrace.h>
|
||||||
#include <sysdep/ptrace.h>
|
#include <sysdep/ptrace.h>
|
||||||
#include <sysdep/mcontext.h>
|
#include <sysdep/mcontext.h>
|
||||||
|
#include <arch.h>
|
||||||
|
|
||||||
void get_regs_from_mc(struct uml_pt_regs *regs, mcontext_t *mc)
|
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;
|
regs->gp[CS / sizeof(unsigned long)] |= 3;
|
||||||
#endif
|
#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 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
|
#endif
|
||||||
|
@ -29,4 +29,16 @@ struct faultinfo {
|
|||||||
|
|
||||||
#define PTRACE_FULL_FAULTINFO 1
|
#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
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user