Christophe Leroy <christophe.le...@csgroup.eu> writes: > Le 08/12/2020 à 14:00, Aneesh Kumar K.V a écrit : >> On 12/8/20 2:07 PM, Christophe Leroy wrote: >>> search_exception_tables() is an heavy operation, we have to avoid it. >>> When KUAP is selected, we'll know the fault has been blocked by KUAP. >>> Otherwise, it behaves just as if the address was already in the TLBs >>> and no fault was generated. >>> >>> Signed-off-by: Christophe Leroy <christophe.le...@csgroup.eu> >>> Reviewed-by: Nicholas Piggin <npig...@gmail.com> >>> --- >>> v3: rebased >>> v2: Squashed with the preceeding patch which was re-ordering tests that get >>> removed in this patch. >>> --- >>> arch/powerpc/mm/fault.c | 23 +++++++---------------- >>> 1 file changed, 7 insertions(+), 16 deletions(-) >>> >>> diff --git a/arch/powerpc/mm/fault.c b/arch/powerpc/mm/fault.c >>> index 3fcd34c28e10..1770b41e4730 100644 >>> --- a/arch/powerpc/mm/fault.c >>> +++ b/arch/powerpc/mm/fault.c >>> @@ -210,28 +210,19 @@ static bool bad_kernel_fault(struct pt_regs *regs, >>> unsigned long error_code, >>> return true; >>> } >>> - if (!is_exec && address < TASK_SIZE && (error_code & (DSISR_PROTFAULT >>> | DSISR_KEYFAULT)) && >>> - !search_exception_tables(regs->nip)) { >>> - pr_crit_ratelimited("Kernel attempted to access user page (%lx) - >>> exploit attempt? (uid: >>> %d)\n", >>> - address, >>> - from_kuid(&init_user_ns, current_uid())); >>> - } >>> - >>> // 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, address, is_write)) >>> + // Read/write fault blocked by KUAP is bad, it can never succeed. >>> + if (bad_kuap_fault(regs, address, is_write)) { >>> + pr_crit_ratelimited("Kernel attempted to %s user page (%lx) - >>> exploit attempt? (uid: %d)\n", >>> + is_write ? "write" : "read", address, >>> + from_kuid(&init_user_ns, current_uid())); >>> return true; >>> + } >> >> >> Should we update bad_kuap_fault to check for !is_kernel_addr() and >> error_code & (DSISIR_PROT_FAULT | >> DSISIR_KEYFAULT). I am wondering whether we can take another fault w.r.t >> kernel address/user address >> and end up reporting that as KUAP fault? > > Just before this we do: > > if (address >= TASK_SIZE) > return true; > > About the error code, I don't know. Can we take a fault that is not a > DSISR_PROT_FAULT | > DSISR_KEYFAULT and that is not a KUAP fault ? > > Previously (before this patch), the error code was taken into account for the > call to > search_exception_tables(), but has never been for the bad_kuap_fault(). >
a KUAP fault on radix will result in PROTFAULT and on hash it will generate KEYFAULT. ie, something like below diff --git a/arch/powerpc/include/asm/book3s/32/kup.h b/arch/powerpc/include/asm/book3s/32/kup.h index 32fd4452e960..b18cd931e325 100644 --- a/arch/powerpc/include/asm/book3s/32/kup.h +++ b/arch/powerpc/include/asm/book3s/32/kup.h @@ -177,8 +177,8 @@ static inline void restore_user_access(unsigned long flags) allow_user_access(to, to, end - addr, KUAP_READ_WRITE); } -static inline bool -bad_kuap_fault(struct pt_regs *regs, unsigned long address, bool is_write) +static inline bool bad_kuap_fault(struct pt_regs *regs, unsigned long address, + bool is_write, unsigned long error_code) { unsigned long begin = regs->kuap & 0xf0000000; unsigned long end = regs->kuap << 28; diff --git a/arch/powerpc/include/asm/book3s/64/kup.h b/arch/powerpc/include/asm/book3s/64/kup.h index 91af50e9b09a..16bb6aee9fcd 100644 --- a/arch/powerpc/include/asm/book3s/64/kup.h +++ b/arch/powerpc/include/asm/book3s/64/kup.h @@ -392,14 +392,24 @@ static inline void set_kuap(unsigned long value) } static inline bool bad_kuap_fault(struct pt_regs *regs, unsigned long address, - bool is_write) + bool is_write, unsigned long error_code) { + unsigned long fault; + if (!mmu_has_feature(MMU_FTR_BOOK3S_KUAP)) return false; + + if (radix_enabled()) + fault = DSISR_PROTFAULT; + else + fault = DSISR_KEYFAULT; + /* * For radix this will be a storage protection fault (DSISR_PROTFAULT). * For hash this will be a key fault (DSISR_KEYFAULT) */ + if (!(error_code & fault)) + return false; /* * We do have exception table entry, but accessing the * userspace results in fault. This could be because we diff --git a/arch/powerpc/include/asm/kup.h b/arch/powerpc/include/asm/kup.h index 49fe4b4a9434..bdffa2664bf0 100644 --- a/arch/powerpc/include/asm/kup.h +++ b/arch/powerpc/include/asm/kup.h @@ -62,8 +62,8 @@ void setup_kuap(bool disabled); #else static inline void setup_kuap(bool disabled) { } -static inline bool -bad_kuap_fault(struct pt_regs *regs, unsigned long address, bool is_write) +static inline bool bad_kuap_fault(struct pt_regs *regs, unsigned long address, + bool is_write, unsigned long error_code) { return false; } diff --git a/arch/powerpc/include/asm/nohash/32/kup-8xx.h b/arch/powerpc/include/asm/nohash/32/kup-8xx.h index 567cdc557402..7bdd9e5b63ed 100644 --- a/arch/powerpc/include/asm/nohash/32/kup-8xx.h +++ b/arch/powerpc/include/asm/nohash/32/kup-8xx.h @@ -60,8 +60,8 @@ static inline void restore_user_access(unsigned long flags) mtspr(SPRN_MD_AP, flags); } -static inline bool -bad_kuap_fault(struct pt_regs *regs, unsigned long address, bool is_write) +static inline bool bad_kuap_fault(struct pt_regs *regs, unsigned long address, + bool is_write, unsigned long error_code) { return WARN(!((regs->kuap ^ MD_APG_KUAP) & 0xff000000), "Bug: fault blocked by AP register !"); diff --git a/arch/powerpc/mm/fault.c b/arch/powerpc/mm/fault.c index b12595102525..03c3414bdc79 100644 --- a/arch/powerpc/mm/fault.c +++ b/arch/powerpc/mm/fault.c @@ -218,7 +218,7 @@ static bool bad_kernel_fault(struct pt_regs *regs, unsigned long error_code, } // Kernel fault on kernel address is bad - if (address >= TASK_SIZE) + if (is_kernel_addr(address)) return true; // Fault on user outside of certain regions (eg. copy_tofrom_user()) is bad @@ -227,7 +227,7 @@ static bool bad_kernel_fault(struct pt_regs *regs, unsigned long error_code, // 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, address, is_write)) + if (bad_kuap_fault(regs, address, is_write, error_code)) return true; // What's left? Kernel fault on user in well defined regions (extable