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__ */
 
 #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)
 {
 
 /* 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;
 
                                    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) &&
                                    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,
         * 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;
 
        /*