diff options
Diffstat (limited to 'arch/powerpc/mm/fault.c')
-rw-r--r-- | arch/powerpc/mm/fault.c | 25 |
1 files changed, 22 insertions, 3 deletions
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 | ||
48 | static inline bool notify_page_fault(struct pt_regs *regs) | 49 | static 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 ? */ |
226 | static bool bad_kernel_fault(struct pt_regs *regs, 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 | { |
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 | ||
250 | 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, |
@@ -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 | /* |