[PATCH v5 10/10] powerpc/mm: Detect bad KUAP faults

Christophe Leroy christophe.leroy at c-s.fr
Fri Mar 8 19:53:31 AEDT 2019



Le 08/03/2019 à 02:16, Michael Ellerman a écrit :
> 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 at ellerman.id.au>
> ---
> 
> v5: New.
> 
>   .../powerpc/include/asm/book3s/64/kup-radix.h | 12 +++++++++
>   arch/powerpc/include/asm/kup.h                |  1 +
>   arch/powerpc/mm/fault.c                       | 25 ++++++++++++++++---
>   3 files changed, 35 insertions(+), 3 deletions(-)
> 
> diff --git a/arch/powerpc/include/asm/book3s/64/kup-radix.h b/arch/powerpc/include/asm/book3s/64/kup-radix.h
> index 3d60b04fc3f6..8d2ddc61e92e 100644
> --- a/arch/powerpc/include/asm/book3s/64/kup-radix.h
> +++ b/arch/powerpc/include/asm/book3s/64/kup-radix.h
> @@ -100,6 +100,18 @@ 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)
> +{
> +	if (mmu_has_feature(MMU_FTR_RADIX_KUAP) &&
> +	    ((is_write && (regs->kuap & AMR_KUAP_BLOCK_WRITE)) ||
> +	     (!is_write && (regs->kuap & AMR_KUAP_BLOCK_READ))))
> +	{

Should this { go on the previous line ?

> +		WARN(true, "Bug: %s fault blocked by AMR!", is_write ? "Write" : "Read");
> +		return true;

Could just be
	return WARN(true, ....)

Or even
	return WARN(mmu_has_feature(MMU_FTR_RADIX_KUAP) &&
	    ((is_write && (regs->kuap & AMR_KUAP_BLOCK_WRITE)) ||
	     (!is_write && (regs->kuap & AMR_KUAP_BLOCK_READ))), ...);

> +	}
> +
> +	return false;
> +}
>   #endif /* CONFIG_PPC_KUAP */
>   
>   #endif /* __ASSEMBLY__ */
> diff --git a/arch/powerpc/include/asm/kup.h b/arch/powerpc/include/asm/kup.h
> index f79d4d970852..ccbd2a249575 100644
> --- a/arch/powerpc/include/asm/kup.h
> +++ b/arch/powerpc/include/asm/kup.h
> @@ -28,6 +28,7 @@ static inline void prevent_user_access(void __user *to, const void __user *from,
>   				       unsigned long size) { }
>   static inline void allow_read_from_user(const void __user *from, unsigned long size) {}
>   static inline void allow_write_to_user(void __user *to, 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 prevent_read_from_user(const void __user *from, unsigned long size)
> diff --git a/arch/powerpc/mm/fault.c b/arch/powerpc/mm/fault.c
> index 463d1e9d026e..b5d3578d9f65 100644
> --- a/arch/powerpc/mm/fault.c
> +++ b/arch/powerpc/mm/fault.c
> @@ -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)
>   {
> @@ -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)

We have regs, do we need is_write in addition ?

Christophe

>   {
>   	int is_exec = TRAP(regs) == 0x400;
>   
> @@ -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) &&
> @@ -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,
> @@ -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;
>   
>   	/*
> 


More information about the Linuxppc-dev mailing list