diff options
Diffstat (limited to 'arch/powerpc/mm/fault.c')
-rw-r--r-- | arch/powerpc/mm/fault.c | 49 |
1 files changed, 39 insertions, 10 deletions
diff --git a/arch/powerpc/mm/fault.c b/arch/powerpc/mm/fault.c index 887f11bcf330..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 | ||
48 | static inline bool notify_page_fault(struct pt_regs *regs) | 49 | static inline bool notify_page_fault(struct pt_regs *regs) |
49 | { | 50 | { |
@@ -223,19 +224,46 @@ static int mm_fault_error(struct pt_regs *regs, unsigned long addr, | |||
223 | } | 224 | } |
224 | 225 | ||
225 | /* Is this a bad kernel fault ? */ | 226 | /* Is this a bad kernel fault ? */ |
226 | static bool bad_kernel_fault(bool is_exec, unsigned long error_code, | 227 | static 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 | { |
230 | int is_exec = TRAP(regs) == 0x400; | ||
231 | |||
229 | /* NX faults set DSISR_PROTFAULT on the 8xx, DSISR_NOEXEC_OR_G on others */ | 232 | /* NX faults set DSISR_PROTFAULT on the 8xx, DSISR_NOEXEC_OR_G on others */ |
230 | if (is_exec && (error_code & (DSISR_NOEXEC_OR_G | DSISR_KEYFAULT | | 233 | if (is_exec && (error_code & (DSISR_NOEXEC_OR_G | DSISR_KEYFAULT | |
231 | DSISR_PROTFAULT))) { | 234 | DSISR_PROTFAULT))) { |
232 | printk_ratelimited(KERN_CRIT "kernel tried to execute" | 235 | pr_crit_ratelimited("kernel tried to execute %s page (%lx) - exploit attempt? (uid: %d)\n", |
233 | " exec-protected page (%lx) -" | 236 | address >= TASK_SIZE ? "exec-protected" : "user", |
234 | "exploit attempt? (uid: %d)\n", | 237 | address, |
235 | address, from_kuid(&init_user_ns, | 238 | from_kuid(&init_user_ns, current_uid())); |
236 | current_uid())); | 239 | |
240 | // Kernel exec fault is always bad | ||
241 | return true; | ||
237 | } | 242 | } |
238 | return is_exec || (address >= TASK_SIZE); | 243 | |
244 | if (!is_exec && address < TASK_SIZE && (error_code & DSISR_PROTFAULT) && | ||
245 | !search_exception_tables(regs->nip)) { | ||
246 | pr_crit_ratelimited("Kernel attempted to access user page (%lx) - exploit attempt? (uid: %d)\n", | ||
247 | address, | ||
248 | from_kuid(&init_user_ns, current_uid())); | ||
249 | } | ||
250 | |||
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; | ||
239 | } | 267 | } |
240 | 268 | ||
241 | static bool bad_stack_expansion(struct pt_regs *regs, unsigned long address, | 269 | static bool bad_stack_expansion(struct pt_regs *regs, unsigned long address, |
@@ -455,9 +483,10 @@ static int __do_page_fault(struct pt_regs *regs, unsigned long address, | |||
455 | 483 | ||
456 | /* | 484 | /* |
457 | * The kernel should never take an execute fault nor should it | 485 | * The kernel should never take an execute fault nor should it |
458 | * take a page fault to a kernel address. | 486 | * take a page fault to a kernel address or a page fault to a user |
487 | * address outside of dedicated places | ||
459 | */ | 488 | */ |
460 | if (unlikely(!is_user && bad_kernel_fault(is_exec, error_code, address))) | 489 | if (unlikely(!is_user && bad_kernel_fault(regs, error_code, address, is_write))) |
461 | return SIGSEGV; | 490 | return SIGSEGV; |
462 | 491 | ||
463 | /* | 492 | /* |