aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Ellerman <mpe@ellerman.id.au>2019-04-18 02:51:25 -0400
committerMichael Ellerman <mpe@ellerman.id.au>2019-04-21 09:06:04 -0400
commit5e5be3aed23032d40d5ab7407f344f1a74f2765b (patch)
treea0e2d362e04f1503da7fcd64987ddb9dc3622c05
parent890274c2dc4c0a57ae5a12d6a76fa6d05b599d98 (diff)
powerpc/mm: Detect bad KUAP faults
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>
-rw-r--r--arch/powerpc/include/asm/book3s/64/kup-radix.h6
-rw-r--r--arch/powerpc/include/asm/kup.h1
-rw-r--r--arch/powerpc/mm/fault.c25
3 files changed, 29 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 6d6628424134..7679bd0c5af0 100644
--- a/arch/powerpc/include/asm/book3s/64/kup-radix.h
+++ b/arch/powerpc/include/asm/book3s/64/kup-radix.h
@@ -95,6 +95,12 @@ static inline void prevent_user_access(void __user *to, const void __user *from,
95 set_kuap(AMR_KUAP_BLOCKED); 95 set_kuap(AMR_KUAP_BLOCKED);
96} 96}
97 97
98static inline bool bad_kuap_fault(struct pt_regs *regs, bool is_write)
99{
100 return WARN(mmu_has_feature(MMU_FTR_RADIX_KUAP) &&
101 (regs->kuap & (is_write ? AMR_KUAP_BLOCK_WRITE : AMR_KUAP_BLOCK_READ)),
102 "Bug: %s fault blocked by AMR!", is_write ? "Write" : "Read");
103}
98#endif /* CONFIG_PPC_KUAP */ 104#endif /* CONFIG_PPC_KUAP */
99 105
100#endif /* __ASSEMBLY__ */ 106#endif /* __ASSEMBLY__ */
diff --git a/arch/powerpc/include/asm/kup.h b/arch/powerpc/include/asm/kup.h
index d7312defbe1c..28ad4654eed2 100644
--- a/arch/powerpc/include/asm/kup.h
+++ b/arch/powerpc/include/asm/kup.h
@@ -26,6 +26,7 @@ static inline void allow_user_access(void __user *to, const void __user *from,
26 unsigned long size) { } 26 unsigned long size) { }
27static inline void prevent_user_access(void __user *to, const void __user *from, 27static inline void prevent_user_access(void __user *to, const void __user *from,
28 unsigned long size) { } 28 unsigned long size) { }
29static inline bool bad_kuap_fault(struct pt_regs *regs, bool is_write) { return false; }
29#endif /* CONFIG_PPC_KUAP */ 30#endif /* CONFIG_PPC_KUAP */
30 31
31static inline void allow_read_from_user(const void __user *from, unsigned long size) 32static inline void allow_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 @@
44#include <asm/mmu_context.h> 44#include <asm/mmu_context.h>
45#include <asm/siginfo.h> 45#include <asm/siginfo.h>
46#include <asm/debug.h> 46#include <asm/debug.h>
47#include <asm/kup.h>
47 48
48static inline bool notify_page_fault(struct pt_regs *regs) 49static inline bool notify_page_fault(struct pt_regs *regs)
49{ 50{
@@ -224,7 +225,7 @@ static int mm_fault_error(struct pt_regs *regs, unsigned long addr,
224 225
225/* Is this a bad kernel fault ? */ 226/* Is this a bad kernel fault ? */
226static bool bad_kernel_fault(struct pt_regs *regs, unsigned long error_code, 227static bool bad_kernel_fault(struct pt_regs *regs, unsigned long error_code,
227 unsigned long address) 228 unsigned long address, bool is_write)
228{ 229{
229 int is_exec = TRAP(regs) == 0x400; 230 int is_exec = TRAP(regs) == 0x400;
230 231
@@ -235,6 +236,9 @@ static bool bad_kernel_fault(struct pt_regs *regs, unsigned long error_code,
235 address >= TASK_SIZE ? "exec-protected" : "user", 236 address >= TASK_SIZE ? "exec-protected" : "user",
236 address, 237 address,
237 from_kuid(&init_user_ns, current_uid())); 238 from_kuid(&init_user_ns, current_uid()));
239
240 // Kernel exec fault is always bad
241 return true;
238 } 242 }
239 243
240 if (!is_exec && address < TASK_SIZE && (error_code & DSISR_PROTFAULT) && 244 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,
244 from_kuid(&init_user_ns, current_uid())); 248 from_kuid(&init_user_ns, current_uid()));
245 } 249 }
246 250
247 return is_exec || (address >= TASK_SIZE) || !search_exception_tables(regs->nip); 251 // Kernel fault on kernel address is bad
252 if (address >= TASK_SIZE)
253 return true;
254
255 // Fault on user outside of certain regions (eg. copy_tofrom_user()) is bad
256 if (!search_exception_tables(regs->nip))
257 return true;
258
259 // Read/write fault in a valid region (the exception table search passed
260 // above), but blocked by KUAP is bad, it can never succeed.
261 if (bad_kuap_fault(regs, is_write))
262 return true;
263
264 // What's left? Kernel fault on user in well defined regions (extable
265 // matched), and allowed by KUAP in the faulting context.
266 return false;
248} 267}
249 268
250static bool bad_stack_expansion(struct pt_regs *regs, unsigned long address, 269static 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,
467 * take a page fault to a kernel address or a page fault to a user 486 * take a page fault to a kernel address or a page fault to a user
468 * address outside of dedicated places 487 * address outside of dedicated places
469 */ 488 */
470 if (unlikely(!is_user && bad_kernel_fault(regs, error_code, address))) 489 if (unlikely(!is_user && bad_kernel_fault(regs, error_code, address, is_write)))
471 return SIGSEGV; 490 return SIGSEGV;
472 491
473 /* 492 /*