Skip to content

Commit

Permalink
powerpc/mm: Detect bad KUAP faults
Browse files Browse the repository at this point in the history
When KUAP is enabled we have logic to detect page faults that occur
outside of a valid user access region and are blocked by the AMR.

What we don't have at the moment is logic to detect a fault *within* a
valid user access region, that has been incorrectly blocked by AMR.
This is not meant to ever happen, but it can if we incorrectly
save/restore the AMR, or if the AMR was overwritten for some other
reason.

Currently if that happens we assume it's just a regular fault that
will be corrected by handling the fault normally, so we just return.
But there is nothing the fault handling code can do to fix it, so the
fault just happens again and we spin forever, leading to soft lockups.

So add some logic to detect that case and WARN() if we ever see it.
Arguably it should be a BUG(), but it's more polite to fail the access
and let the kernel continue, rather than taking down the box. There
should be no data integrity issue with failing the fault rather than
BUG'ing, as we're just going to disallow an access that should have
been allowed.

To make the code a little easier to follow, unroll the condition at
the end of bad_kernel_fault() and comment each case, before adding the
call to bad_kuap_fault().

Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
  • Loading branch information
Michael Ellerman committed Apr 21, 2019
1 parent 890274c commit 5e5be3a
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 3 deletions.
6 changes: 6 additions & 0 deletions arch/powerpc/include/asm/book3s/64/kup-radix.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ static inline void prevent_user_access(void __user *to, const void __user *from,
set_kuap(AMR_KUAP_BLOCKED);
}

static inline bool bad_kuap_fault(struct pt_regs *regs, bool is_write)
{
return WARN(mmu_has_feature(MMU_FTR_RADIX_KUAP) &&
(regs->kuap & (is_write ? AMR_KUAP_BLOCK_WRITE : AMR_KUAP_BLOCK_READ)),
"Bug: %s fault blocked by AMR!", is_write ? "Write" : "Read");
}
#endif /* CONFIG_PPC_KUAP */

#endif /* __ASSEMBLY__ */
Expand Down
1 change: 1 addition & 0 deletions arch/powerpc/include/asm/kup.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ static inline void allow_user_access(void __user *to, const void __user *from,
unsigned long size) { }
static inline void prevent_user_access(void __user *to, const void __user *from,
unsigned long size) { }
static inline bool bad_kuap_fault(struct pt_regs *regs, bool is_write) { return false; }
#endif /* CONFIG_PPC_KUAP */

static inline void allow_read_from_user(const void __user *from, unsigned long size)
Expand Down
25 changes: 22 additions & 3 deletions arch/powerpc/mm/fault.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include <asm/mmu_context.h>
#include <asm/siginfo.h>
#include <asm/debug.h>
#include <asm/kup.h>

static inline bool notify_page_fault(struct pt_regs *regs)
{
Expand Down Expand Up @@ -224,7 +225,7 @@ static int mm_fault_error(struct pt_regs *regs, unsigned long addr,

/* Is this a bad kernel fault ? */
static bool bad_kernel_fault(struct pt_regs *regs, unsigned long error_code,
unsigned long address)
unsigned long address, bool is_write)
{
int is_exec = TRAP(regs) == 0x400;

Expand All @@ -235,6 +236,9 @@ static bool bad_kernel_fault(struct pt_regs *regs, unsigned long error_code,
address >= TASK_SIZE ? "exec-protected" : "user",
address,
from_kuid(&init_user_ns, current_uid()));

// Kernel exec fault is always bad
return true;
}

if (!is_exec && address < TASK_SIZE && (error_code & DSISR_PROTFAULT) &&
Expand All @@ -244,7 +248,22 @@ static bool bad_kernel_fault(struct pt_regs *regs, unsigned long error_code,
from_kuid(&init_user_ns, current_uid()));
}

return is_exec || (address >= TASK_SIZE) || !search_exception_tables(regs->nip);
// Kernel fault on kernel address is bad
if (address >= TASK_SIZE)
return true;

// Fault on user outside of certain regions (eg. copy_tofrom_user()) is bad
if (!search_exception_tables(regs->nip))
return true;

// Read/write fault in a valid region (the exception table search passed
// above), but blocked by KUAP is bad, it can never succeed.
if (bad_kuap_fault(regs, is_write))
return true;

// What's left? Kernel fault on user in well defined regions (extable
// matched), and allowed by KUAP in the faulting context.
return false;
}

static bool bad_stack_expansion(struct pt_regs *regs, unsigned long address,
Expand Down Expand Up @@ -467,7 +486,7 @@ static int __do_page_fault(struct pt_regs *regs, unsigned long address,
* take a page fault to a kernel address or a page fault to a user
* address outside of dedicated places
*/
if (unlikely(!is_user && bad_kernel_fault(regs, error_code, address)))
if (unlikely(!is_user && bad_kernel_fault(regs, error_code, address, is_write)))
return SIGSEGV;

/*
Expand Down

0 comments on commit 5e5be3a

Please sign in to comment.