arm64: expose FAR_EL1 tag bits in siginfo
The kernel currently clears the tag bits (i.e. bits 56-63) in the fault address exposed via siginfo.si_addr and sigcontext.fault_address. However, the tag bits may be needed by tools in order to accurately diagnose memory errors, such as HWASan [1] or future tools based on the Memory Tagging Extension (MTE). Expose these bits via the arch_untagged_si_addr mechanism, so that they are only exposed to signal handlers with the SA_EXPOSE_TAGBITS flag set. [1] http://clang.llvm.org/docs/HardwareAssistedAddressSanitizerDesign.html Signed-off-by: Peter Collingbourne <pcc@google.com> Reviewed-by: Catalin Marinas <catalin.marinas@arm.com> Link: https://linux-review.googlesource.com/id/Ia8876bad8c798e0a32df7c2ce1256c4771c81446 Link: https://lore.kernel.org/r/0010296597784267472fa13b39f8238d87a72cf8.1605904350.git.pcc@google.com Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
This commit is contained in:
parent
6ac05e832a
commit
dceec3ff78
@ -53,12 +53,25 @@ visibility.
|
||||
Preserving tags
|
||||
---------------
|
||||
|
||||
Non-zero tags are not preserved when delivering signals. This means that
|
||||
signal handlers in applications making use of tags cannot rely on the
|
||||
tag information for user virtual addresses being maintained for fields
|
||||
inside siginfo_t. One exception to this rule is for signals raised in
|
||||
response to watchpoint debug exceptions, where the tag information will
|
||||
be preserved.
|
||||
When delivering signals, non-zero tags are not preserved in
|
||||
siginfo.si_addr unless the flag SA_EXPOSE_TAGBITS was set in
|
||||
sigaction.sa_flags when the signal handler was installed. This means
|
||||
that signal handlers in applications making use of tags cannot rely
|
||||
on the tag information for user virtual addresses being maintained
|
||||
in these fields unless the flag was set.
|
||||
|
||||
Due to architecture limitations, bits 63:60 of the fault address
|
||||
are not preserved in response to synchronous tag check faults
|
||||
(SEGV_MTESERR) even if SA_EXPOSE_TAGBITS was set. Applications should
|
||||
treat the values of these bits as undefined in order to accommodate
|
||||
future architecture revisions which may preserve the bits.
|
||||
|
||||
For signals raised in response to watchpoint debug exceptions, the
|
||||
tag information will be preserved regardless of the SA_EXPOSE_TAGBITS
|
||||
flag setting.
|
||||
|
||||
Non-zero tags are never preserved in sigcontext.fault_address
|
||||
regardless of the SA_EXPOSE_TAGBITS flag setting.
|
||||
|
||||
The architecture prevents the use of a tagged PC, so the upper byte will
|
||||
be set to a sign-extension of bit 55 on exception return.
|
||||
|
@ -32,7 +32,7 @@ static inline u32 disr_to_esr(u64 disr)
|
||||
}
|
||||
|
||||
asmlinkage void enter_from_user_mode(void);
|
||||
void do_mem_abort(unsigned long addr, unsigned int esr, struct pt_regs *regs);
|
||||
void do_mem_abort(unsigned long far, unsigned int esr, struct pt_regs *regs);
|
||||
void do_undefinstr(struct pt_regs *regs);
|
||||
void do_bti(struct pt_regs *regs);
|
||||
asmlinkage void bad_mode(struct pt_regs *regs, int reason, unsigned int esr);
|
||||
|
25
arch/arm64/include/asm/signal.h
Normal file
25
arch/arm64/include/asm/signal.h
Normal file
@ -0,0 +1,25 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef __ARM64_ASM_SIGNAL_H
|
||||
#define __ARM64_ASM_SIGNAL_H
|
||||
|
||||
#include <asm/memory.h>
|
||||
#include <uapi/asm/signal.h>
|
||||
#include <uapi/asm/siginfo.h>
|
||||
|
||||
static inline void __user *arch_untagged_si_addr(void __user *addr,
|
||||
unsigned long sig,
|
||||
unsigned long si_code)
|
||||
{
|
||||
/*
|
||||
* For historical reasons, all bits of the fault address are exposed as
|
||||
* address bits for watchpoint exceptions. New architectures should
|
||||
* handle the tag bits consistently.
|
||||
*/
|
||||
if (sig == SIGTRAP && si_code == TRAP_BRKPT)
|
||||
return addr;
|
||||
|
||||
return untagged_addr(addr);
|
||||
}
|
||||
#define arch_untagged_si_addr arch_untagged_si_addr
|
||||
|
||||
#endif
|
@ -22,7 +22,7 @@ void die(const char *msg, struct pt_regs *regs, int err);
|
||||
|
||||
struct siginfo;
|
||||
void arm64_notify_die(const char *str, struct pt_regs *regs,
|
||||
int signo, int sicode, void __user *addr,
|
||||
int signo, int sicode, unsigned long far,
|
||||
int err);
|
||||
|
||||
void hook_debug_fault_code(int nr, int (*fn)(unsigned long, unsigned int,
|
||||
|
@ -26,9 +26,9 @@ void register_undef_hook(struct undef_hook *hook);
|
||||
void unregister_undef_hook(struct undef_hook *hook);
|
||||
void force_signal_inject(int signal, int code, unsigned long address, unsigned int err);
|
||||
void arm64_notify_segfault(unsigned long addr);
|
||||
void arm64_force_sig_fault(int signo, int code, void __user *addr, const char *str);
|
||||
void arm64_force_sig_mceerr(int code, void __user *addr, short lsb, const char *str);
|
||||
void arm64_force_sig_ptrace_errno_trap(int errno, void __user *addr, const char *str);
|
||||
void arm64_force_sig_fault(int signo, int code, unsigned long far, const char *str);
|
||||
void arm64_force_sig_mceerr(int code, unsigned long far, short lsb, const char *str);
|
||||
void arm64_force_sig_ptrace_errno_trap(int errno, unsigned long far, const char *str);
|
||||
|
||||
/*
|
||||
* Move regs->pc to next instruction and do necessary setup before it
|
||||
|
@ -234,8 +234,7 @@ static void send_user_sigtrap(int si_code)
|
||||
if (interrupts_enabled(regs))
|
||||
local_irq_enable();
|
||||
|
||||
arm64_force_sig_fault(SIGTRAP, si_code,
|
||||
(void __user *)instruction_pointer(regs),
|
||||
arm64_force_sig_fault(SIGTRAP, si_code, instruction_pointer(regs),
|
||||
"User debug trap");
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,6 @@ static void notrace el1_abort(struct pt_regs *regs, unsigned long esr)
|
||||
unsigned long far = read_sysreg(far_el1);
|
||||
|
||||
local_daif_inherit(regs);
|
||||
far = untagged_addr(far);
|
||||
do_mem_abort(far, esr, regs);
|
||||
}
|
||||
NOKPROBE_SYMBOL(el1_abort);
|
||||
@ -114,7 +113,6 @@ static void notrace el0_da(struct pt_regs *regs, unsigned long esr)
|
||||
|
||||
user_exit_irqoff();
|
||||
local_daif_restore(DAIF_PROCCTX);
|
||||
far = untagged_addr(far);
|
||||
do_mem_abort(far, esr, regs);
|
||||
}
|
||||
NOKPROBE_SYMBOL(el0_da);
|
||||
|
@ -192,14 +192,11 @@ static void ptrace_hbptriggered(struct perf_event *bp,
|
||||
break;
|
||||
}
|
||||
}
|
||||
arm64_force_sig_ptrace_errno_trap(si_errno,
|
||||
(void __user *)bkpt->trigger,
|
||||
arm64_force_sig_ptrace_errno_trap(si_errno, bkpt->trigger,
|
||||
desc);
|
||||
}
|
||||
#endif
|
||||
arm64_force_sig_fault(SIGTRAP, TRAP_HWBKPT,
|
||||
(void __user *)(bkpt->trigger),
|
||||
desc);
|
||||
arm64_force_sig_fault(SIGTRAP, TRAP_HWBKPT, bkpt->trigger, desc);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -68,7 +68,7 @@ do_compat_cache_op(unsigned long start, unsigned long end, int flags)
|
||||
*/
|
||||
long compat_arm_syscall(struct pt_regs *regs, int scno)
|
||||
{
|
||||
void __user *addr;
|
||||
unsigned long addr;
|
||||
|
||||
switch (scno) {
|
||||
/*
|
||||
@ -111,8 +111,7 @@ long compat_arm_syscall(struct pt_regs *regs, int scno)
|
||||
break;
|
||||
}
|
||||
|
||||
addr = (void __user *)instruction_pointer(regs) -
|
||||
(compat_thumb_mode(regs) ? 2 : 4);
|
||||
addr = instruction_pointer(regs) - (compat_thumb_mode(regs) ? 2 : 4);
|
||||
|
||||
arm64_notify_die("Oops - bad compat syscall(2)", regs,
|
||||
SIGILL, ILL_ILLTRP, addr, scno);
|
||||
|
@ -170,32 +170,32 @@ static void arm64_show_signal(int signo, const char *str)
|
||||
__show_regs(regs);
|
||||
}
|
||||
|
||||
void arm64_force_sig_fault(int signo, int code, void __user *addr,
|
||||
void arm64_force_sig_fault(int signo, int code, unsigned long far,
|
||||
const char *str)
|
||||
{
|
||||
arm64_show_signal(signo, str);
|
||||
if (signo == SIGKILL)
|
||||
force_sig(SIGKILL);
|
||||
else
|
||||
force_sig_fault(signo, code, addr);
|
||||
force_sig_fault(signo, code, (void __user *)far);
|
||||
}
|
||||
|
||||
void arm64_force_sig_mceerr(int code, void __user *addr, short lsb,
|
||||
void arm64_force_sig_mceerr(int code, unsigned long far, short lsb,
|
||||
const char *str)
|
||||
{
|
||||
arm64_show_signal(SIGBUS, str);
|
||||
force_sig_mceerr(code, addr, lsb);
|
||||
force_sig_mceerr(code, (void __user *)far, lsb);
|
||||
}
|
||||
|
||||
void arm64_force_sig_ptrace_errno_trap(int errno, void __user *addr,
|
||||
void arm64_force_sig_ptrace_errno_trap(int errno, unsigned long far,
|
||||
const char *str)
|
||||
{
|
||||
arm64_show_signal(SIGTRAP, str);
|
||||
force_sig_ptrace_errno_trap(errno, addr);
|
||||
force_sig_ptrace_errno_trap(errno, (void __user *)far);
|
||||
}
|
||||
|
||||
void arm64_notify_die(const char *str, struct pt_regs *regs,
|
||||
int signo, int sicode, void __user *addr,
|
||||
int signo, int sicode, unsigned long far,
|
||||
int err)
|
||||
{
|
||||
if (user_mode(regs)) {
|
||||
@ -203,7 +203,7 @@ void arm64_notify_die(const char *str, struct pt_regs *regs,
|
||||
current->thread.fault_address = 0;
|
||||
current->thread.fault_code = err;
|
||||
|
||||
arm64_force_sig_fault(signo, sicode, addr, str);
|
||||
arm64_force_sig_fault(signo, sicode, far, str);
|
||||
} else {
|
||||
die(str, regs, err);
|
||||
}
|
||||
@ -374,7 +374,7 @@ void force_signal_inject(int signal, int code, unsigned long address, unsigned i
|
||||
signal = SIGKILL;
|
||||
}
|
||||
|
||||
arm64_notify_die(desc, regs, signal, code, (void __user *)address, err);
|
||||
arm64_notify_die(desc, regs, signal, code, address, err);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -385,7 +385,7 @@ void arm64_notify_segfault(unsigned long addr)
|
||||
int code;
|
||||
|
||||
mmap_read_lock(current->mm);
|
||||
if (find_vma(current->mm, addr) == NULL)
|
||||
if (find_vma(current->mm, untagged_addr(addr)) == NULL)
|
||||
code = SEGV_MAPERR;
|
||||
else
|
||||
code = SEGV_ACCERR;
|
||||
@ -448,12 +448,13 @@ NOKPROBE_SYMBOL(do_ptrauth_fault);
|
||||
|
||||
static void user_cache_maint_handler(unsigned int esr, struct pt_regs *regs)
|
||||
{
|
||||
unsigned long address;
|
||||
unsigned long tagged_address, address;
|
||||
int rt = ESR_ELx_SYS64_ISS_RT(esr);
|
||||
int crm = (esr & ESR_ELx_SYS64_ISS_CRM_MASK) >> ESR_ELx_SYS64_ISS_CRM_SHIFT;
|
||||
int ret = 0;
|
||||
|
||||
address = untagged_addr(pt_regs_read_reg(regs, rt));
|
||||
tagged_address = pt_regs_read_reg(regs, rt);
|
||||
address = untagged_addr(tagged_address);
|
||||
|
||||
switch (crm) {
|
||||
case ESR_ELx_SYS64_ISS_CRM_DC_CVAU: /* DC CVAU, gets promoted */
|
||||
@ -480,7 +481,7 @@ static void user_cache_maint_handler(unsigned int esr, struct pt_regs *regs)
|
||||
}
|
||||
|
||||
if (ret)
|
||||
arm64_notify_segfault(address);
|
||||
arm64_notify_segfault(tagged_address);
|
||||
else
|
||||
arm64_skip_faulting_instruction(regs, AARCH64_INSN_SIZE);
|
||||
}
|
||||
@ -772,7 +773,7 @@ asmlinkage void bad_mode(struct pt_regs *regs, int reason, unsigned int esr)
|
||||
*/
|
||||
void bad_el0_sync(struct pt_regs *regs, int reason, unsigned int esr)
|
||||
{
|
||||
void __user *pc = (void __user *)instruction_pointer(regs);
|
||||
unsigned long pc = instruction_pointer(regs);
|
||||
|
||||
current->thread.fault_address = 0;
|
||||
current->thread.fault_code = esr;
|
||||
|
@ -40,7 +40,7 @@
|
||||
#include <asm/traps.h>
|
||||
|
||||
struct fault_info {
|
||||
int (*fn)(unsigned long addr, unsigned int esr,
|
||||
int (*fn)(unsigned long far, unsigned int esr,
|
||||
struct pt_regs *regs);
|
||||
int sig;
|
||||
int code;
|
||||
@ -385,8 +385,11 @@ static void set_thread_esr(unsigned long address, unsigned int esr)
|
||||
current->thread.fault_code = esr;
|
||||
}
|
||||
|
||||
static void do_bad_area(unsigned long addr, unsigned int esr, struct pt_regs *regs)
|
||||
static void do_bad_area(unsigned long far, unsigned int esr,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
unsigned long addr = untagged_addr(far);
|
||||
|
||||
/*
|
||||
* If we are in kernel mode at this point, we have no context to
|
||||
* handle this fault with.
|
||||
@ -395,8 +398,7 @@ static void do_bad_area(unsigned long addr, unsigned int esr, struct pt_regs *re
|
||||
const struct fault_info *inf = esr_to_fault_info(esr);
|
||||
|
||||
set_thread_esr(addr, esr);
|
||||
arm64_force_sig_fault(inf->sig, inf->code, (void __user *)addr,
|
||||
inf->name);
|
||||
arm64_force_sig_fault(inf->sig, inf->code, far, inf->name);
|
||||
} else {
|
||||
__do_kernel_fault(addr, esr, regs);
|
||||
}
|
||||
@ -448,7 +450,7 @@ static bool is_write_abort(unsigned int esr)
|
||||
return (esr & ESR_ELx_WNR) && !(esr & ESR_ELx_CM);
|
||||
}
|
||||
|
||||
static int __kprobes do_page_fault(unsigned long addr, unsigned int esr,
|
||||
static int __kprobes do_page_fault(unsigned long far, unsigned int esr,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
const struct fault_info *inf;
|
||||
@ -456,6 +458,7 @@ static int __kprobes do_page_fault(unsigned long addr, unsigned int esr,
|
||||
vm_fault_t fault;
|
||||
unsigned long vm_flags = VM_ACCESS_FLAGS;
|
||||
unsigned int mm_flags = FAULT_FLAG_DEFAULT;
|
||||
unsigned long addr = untagged_addr(far);
|
||||
|
||||
if (kprobe_page_fault(regs, esr))
|
||||
return 0;
|
||||
@ -567,8 +570,7 @@ retry:
|
||||
* We had some memory, but were unable to successfully fix up
|
||||
* this page fault.
|
||||
*/
|
||||
arm64_force_sig_fault(SIGBUS, BUS_ADRERR, (void __user *)addr,
|
||||
inf->name);
|
||||
arm64_force_sig_fault(SIGBUS, BUS_ADRERR, far, inf->name);
|
||||
} else if (fault & (VM_FAULT_HWPOISON_LARGE | VM_FAULT_HWPOISON)) {
|
||||
unsigned int lsb;
|
||||
|
||||
@ -576,8 +578,7 @@ retry:
|
||||
if (fault & VM_FAULT_HWPOISON_LARGE)
|
||||
lsb = hstate_index_to_shift(VM_FAULT_GET_HINDEX(fault));
|
||||
|
||||
arm64_force_sig_mceerr(BUS_MCEERR_AR, (void __user *)addr, lsb,
|
||||
inf->name);
|
||||
arm64_force_sig_mceerr(BUS_MCEERR_AR, far, lsb, inf->name);
|
||||
} else {
|
||||
/*
|
||||
* Something tried to access memory that isn't in our memory
|
||||
@ -585,8 +586,7 @@ retry:
|
||||
*/
|
||||
arm64_force_sig_fault(SIGSEGV,
|
||||
fault == VM_FAULT_BADACCESS ? SEGV_ACCERR : SEGV_MAPERR,
|
||||
(void __user *)addr,
|
||||
inf->name);
|
||||
far, inf->name);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -596,33 +596,35 @@ no_context:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __kprobes do_translation_fault(unsigned long addr,
|
||||
static int __kprobes do_translation_fault(unsigned long far,
|
||||
unsigned int esr,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
if (is_ttbr0_addr(addr))
|
||||
return do_page_fault(addr, esr, regs);
|
||||
unsigned long addr = untagged_addr(far);
|
||||
|
||||
do_bad_area(addr, esr, regs);
|
||||
if (is_ttbr0_addr(addr))
|
||||
return do_page_fault(far, esr, regs);
|
||||
|
||||
do_bad_area(far, esr, regs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_alignment_fault(unsigned long addr, unsigned int esr,
|
||||
static int do_alignment_fault(unsigned long far, unsigned int esr,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
do_bad_area(addr, esr, regs);
|
||||
do_bad_area(far, esr, regs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_bad(unsigned long addr, unsigned int esr, struct pt_regs *regs)
|
||||
static int do_bad(unsigned long far, unsigned int esr, struct pt_regs *regs)
|
||||
{
|
||||
return 1; /* "fault" */
|
||||
}
|
||||
|
||||
static int do_sea(unsigned long addr, unsigned int esr, struct pt_regs *regs)
|
||||
static int do_sea(unsigned long far, unsigned int esr, struct pt_regs *regs)
|
||||
{
|
||||
const struct fault_info *inf;
|
||||
void __user *siaddr;
|
||||
unsigned long siaddr;
|
||||
|
||||
inf = esr_to_fault_info(esr);
|
||||
|
||||
@ -634,19 +636,30 @@ static int do_sea(unsigned long addr, unsigned int esr, struct pt_regs *regs)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (esr & ESR_ELx_FnV)
|
||||
siaddr = NULL;
|
||||
else
|
||||
siaddr = (void __user *)addr;
|
||||
if (esr & ESR_ELx_FnV) {
|
||||
siaddr = 0;
|
||||
} else {
|
||||
/*
|
||||
* The architecture specifies that the tag bits of FAR_EL1 are
|
||||
* UNKNOWN for synchronous external aborts. Mask them out now
|
||||
* so that userspace doesn't see them.
|
||||
*/
|
||||
siaddr = untagged_addr(far);
|
||||
}
|
||||
arm64_notify_die(inf->name, regs, inf->sig, inf->code, siaddr, esr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_tag_check_fault(unsigned long addr, unsigned int esr,
|
||||
static int do_tag_check_fault(unsigned long far, unsigned int esr,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
do_bad_area(addr, esr, regs);
|
||||
/*
|
||||
* The architecture specifies that bits 63:60 of FAR_EL1 are UNKNOWN for tag
|
||||
* check faults. Mask them out now so that userspace doesn't see them.
|
||||
*/
|
||||
far &= (1UL << 60) - 1;
|
||||
do_bad_area(far, esr, regs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -717,11 +730,12 @@ static const struct fault_info fault_info[] = {
|
||||
{ do_bad, SIGKILL, SI_KERNEL, "unknown 63" },
|
||||
};
|
||||
|
||||
void do_mem_abort(unsigned long addr, unsigned int esr, struct pt_regs *regs)
|
||||
void do_mem_abort(unsigned long far, unsigned int esr, struct pt_regs *regs)
|
||||
{
|
||||
const struct fault_info *inf = esr_to_fault_info(esr);
|
||||
unsigned long addr = untagged_addr(far);
|
||||
|
||||
if (!inf->fn(addr, esr, regs))
|
||||
if (!inf->fn(far, esr, regs))
|
||||
return;
|
||||
|
||||
if (!user_mode(regs)) {
|
||||
@ -730,8 +744,12 @@ void do_mem_abort(unsigned long addr, unsigned int esr, struct pt_regs *regs)
|
||||
show_pte(addr);
|
||||
}
|
||||
|
||||
arm64_notify_die(inf->name, regs,
|
||||
inf->sig, inf->code, (void __user *)addr, esr);
|
||||
/*
|
||||
* At this point we have an unrecognized fault type whose tag bits may
|
||||
* have been defined as UNKNOWN. Therefore we only expose the untagged
|
||||
* address to the signal handler.
|
||||
*/
|
||||
arm64_notify_die(inf->name, regs, inf->sig, inf->code, addr, esr);
|
||||
}
|
||||
NOKPROBE_SYMBOL(do_mem_abort);
|
||||
|
||||
@ -744,8 +762,8 @@ NOKPROBE_SYMBOL(do_el0_irq_bp_hardening);
|
||||
|
||||
void do_sp_pc_abort(unsigned long addr, unsigned int esr, struct pt_regs *regs)
|
||||
{
|
||||
arm64_notify_die("SP/PC alignment exception", regs,
|
||||
SIGBUS, BUS_ADRALN, (void __user *)addr, esr);
|
||||
arm64_notify_die("SP/PC alignment exception", regs, SIGBUS, BUS_ADRALN,
|
||||
addr, esr);
|
||||
}
|
||||
NOKPROBE_SYMBOL(do_sp_pc_abort);
|
||||
|
||||
@ -871,8 +889,7 @@ void do_debug_exception(unsigned long addr_if_watchpoint, unsigned int esr,
|
||||
arm64_apply_bp_hardening();
|
||||
|
||||
if (inf->fn(addr_if_watchpoint, esr, regs)) {
|
||||
arm64_notify_die(inf->name, regs,
|
||||
inf->sig, inf->code, (void __user *)pc, esr);
|
||||
arm64_notify_die(inf->name, regs, inf->sig, inf->code, pc, esr);
|
||||
}
|
||||
|
||||
debug_exception_exit(regs);
|
||||
|
Loading…
Reference in New Issue
Block a user