The verification and message introduced by commit 374f3f5979f9
("powerpc/mm/hash: Handle user access of kernel address gracefully")
applies to all platforms, it should not be limited to BOOK3S.
Make the BOOK3S version of sanity_check_fault() the one for all,
and bail out earlier if not BOOK3S.
Fixes: 374f3f5979f9 ("powerpc/mm/hash: Handle user access of kernel address gracefully")
Signed-off-by: Christophe Leroy <redacted>
---
arch/powerpc/mm/fault.c | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
@@ -210,17 +210,17 @@ static bool bad_kernel_fault(struct pt_regs *regs, unsigned long error_code,returntrue;}-if(!is_exec&&address<TASK_SIZE&&(error_code&DSISR_PROTFAULT)&&+// Kernel fault on kernel address is bad+if(address>=TASK_SIZE)+returntrue;++if(!is_exec&&(error_code&DSISR_PROTFAULT)&&!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)-returntrue;-// Fault on user outside of certain regions (eg. copy_tofrom_user()) is badif(!search_exception_tables(regs->nip))returntrue;
To make it more readable, separate page_fault_is_write() and page_fault_is_bad()
to avoir several levels of #ifdefs
Signed-off-by: Christophe Leroy <redacted>
---
arch/powerpc/mm/fault.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
Exception fixup doesn't require the heady full regs saving,
do it from do_page_fault() directly.
For that, split bad_page_fault() in two parts.
As bad_page_fault() can also be called from other places than
handle_page_fault(), it will still perform exception fixup and
fallback on __bad_page_fault().
handle_page_fault() directly calls __bad_page_fault() as the
exception fixup will now be done by do_page_fault()
Signed-off-by: Christophe Leroy <redacted>
---
arch/powerpc/kernel/entry_32.S | 2 +-
arch/powerpc/kernel/exceptions-64e.S | 2 +-
arch/powerpc/kernel/exceptions-64s.S | 2 +-
arch/powerpc/mm/fault.c | 33 ++++++++++++++++++++--------
4 files changed, 27 insertions(+), 12 deletions(-)
@@ -554,17 +564,10 @@ NOKPROBE_SYMBOL(do_page_fault);*ItiscalledfromtheDSIandISIhandlersinhead.Sandfromsome*oftheproceduresintraps.c.*/-voidbad_page_fault(structpt_regs*regs,unsignedlongaddress,intsig)+void__bad_page_fault(structpt_regs*regs,unsignedlongaddress,intsig){-conststructexception_table_entry*entry;intis_write=page_fault_is_write(regs->dsisr);-/* Are we prepared to handle this fault? */-if((entry=search_exception_tables(regs->nip))!=NULL){-regs->nip=extable_fixup(entry);-return;-}-/* kernel has accessed a bad area */switch(TRAP(regs)){
@@ -598,3 +601,15 @@ void bad_page_fault(struct pt_regs *regs, unsigned long address, int sig)die("Kernel access of bad area",regs,sig);}++voidbad_page_fault(structpt_regs*regs,unsignedlongaddress,intsig)+{+conststructexception_table_entry*entry;++/* Are we prepared to handle this fault? */+entry=search_exception_tables(instruction_pointer(regs));+if(entry)+instruction_pointer_set(regs,extable_fixup(entry));+else+__bad_page_fault(regs,address,sig);+}
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 <redacted>
---
arch/powerpc/mm/fault.c | 20 +++++---------------
1 file changed, 5 insertions(+), 15 deletions(-)
@@ -214,24 +214,14 @@ static bool bad_kernel_fault(struct pt_regs *regs, unsigned long error_code,if(address>=TASK_SIZE)returntrue;-if(!is_exec&&(error_code&DSISR_PROTFAULT)&&-!search_exception_tables(regs->nip)){+// 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 access user page (%lx) - exploit attempt? (uid: %d)\n",-address,-from_kuid(&init_user_ns,current_uid()));-}--// Fault on user outside of certain regions (eg. copy_tofrom_user()) is bad-if(!search_exception_tables(regs->nip))-returntrue;--// 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))+address,from_kuid(&init_user_ns,current_uid()));returntrue;+}-// What's left? Kernel fault on user in well defined regions (extable-// matched), and allowed by KUAP in the faulting context.+// What's left? Kernel fault on user and allowed by KUAP in the faulting context.returnfalse;}
arch/powerpc/mm/fault.c:567:6: warning: no previous prototype for '__bad_page_fault' [-Wmissing-prototypes]
567 | void __bad_page_fault(struct pt_regs *regs, unsigned long address, int sig)
| ^~~~~~~~~~~~~~~~
vim +/__bad_page_fault +567 arch/powerpc/mm/fault.c
561
562 /*
563 * bad_page_fault is called when we have a bad access from the kernel.
564 * It is called from the DSI and ISI handlers in head.S and from some
565 * of the procedures in traps.c.
566 */
> 567 void __bad_page_fault(struct pt_regs *regs, unsigned long address, int sig)
568 {
569 int is_write = page_fault_is_write(regs->dsisr);
570
571 /* kernel has accessed a bad area */
572
573 switch (TRAP(regs)) {
574 case 0x300:
575 case 0x380:
576 case 0xe00:
577 pr_alert("BUG: %s on %s at 0x%08lx\n",
578 regs->dar < PAGE_SIZE ? "Kernel NULL pointer dereference" :
579 "Unable to handle kernel data access",
580 is_write ? "write" : "read", regs->dar);
581 break;
582 case 0x400:
583 case 0x480:
584 pr_alert("BUG: Unable to handle kernel instruction fetch%s",
585 regs->nip < PAGE_SIZE ? " (NULL pointer?)\n" : "\n");
586 break;
587 case 0x600:
588 pr_alert("BUG: Unable to handle kernel unaligned access at 0x%08lx\n",
589 regs->dar);
590 break;
591 default:
592 pr_alert("BUG: Unable to handle unknown paging fault at 0x%08lx\n",
593 regs->dar);
594 break;
595 }
596 printk(KERN_ALERT "Faulting instruction address: 0x%08lx\n",
597 regs->nip);
598
599 if (task_stack_end_corrupted(current))
600 printk(KERN_ALERT "Thread overran stack, or stack corrupted\n");
601
602 die("Kernel access of bad area", regs, sig);
603 }
604
---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
From: Nicholas Piggin <npiggin@gmail.com> Date: 2020-09-08 08:43:59
Excerpts from Christophe Leroy's message of August 7, 2020 3:15 am:
quoted hunk
The verification and message introduced by commit 374f3f5979f9
("powerpc/mm/hash: Handle user access of kernel address gracefully")
applies to all platforms, it should not be limited to BOOK3S.
Make the BOOK3S version of sanity_check_fault() the one for all,
and bail out earlier if not BOOK3S.
Fixes: 374f3f5979f9 ("powerpc/mm/hash: Handle user access of kernel address gracefully")
Signed-off-by: Christophe Leroy <redacted>
---
arch/powerpc/mm/fault.c | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
Seems okay. Why is address == -1 special though? I guess it's because
it may not be an exploit kernel reference but a buggy pointer underflow?
In that case -1 doesn't seem like it would catch very much. Would it be
better to test for high bit set for example ((long)address < 0) ?
Anyway for your patch
Reviewed-by: Nicholas Piggin <npiggin@gmail.com>
quoted hunk
+
/*
* For hash translation mode, we should never get a
* PROTFAULT. Any update to pte to reduce access will result in us
@@ -354,10 +356,6 @@ static void sanity_check_fault(bool is_write, bool is_user, WARN_ON_ONCE(error_code & DSISR_PROTFAULT); }-#else-static void sanity_check_fault(bool is_write, bool is_user,- unsigned long error_code, unsigned long address) { }-#endif /* CONFIG_PPC_BOOK3S */ /* * Define the correct "is_write" bit in error_code based
@@ -210,17 +210,17 @@ static bool bad_kernel_fault(struct pt_regs *regs, unsigned long error_code,returntrue;}-if(!is_exec&&address<TASK_SIZE&&(error_code&DSISR_PROTFAULT)&&+// Kernel fault on kernel address is bad+if(address>=TASK_SIZE)+returntrue;++if(!is_exec&&(error_code&DSISR_PROTFAULT)&&!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)-returntrue;-// Fault on user outside of certain regions (eg. copy_tofrom_user()) is badif(!search_exception_tables(regs->nip))returntrue;
From: Nicholas Piggin <npiggin@gmail.com> Date: 2020-09-08 08:54:51
Excerpts from Christophe Leroy's message of August 7, 2020 3:15 am:
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 <redacted>
Sorry I missed reviewing this. Yes, we discussed this and decided
that it's not effective I think (and KUAP solves it properly).
Reviewed-by: Nicholas Piggin <npiggin@gmail.com>
@@ -214,24 +214,14 @@ static bool bad_kernel_fault(struct pt_regs *regs, unsigned long error_code,if(address>=TASK_SIZE)returntrue;-if(!is_exec&&(error_code&DSISR_PROTFAULT)&&-!search_exception_tables(regs->nip)){+// 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 access user page (%lx) - exploit attempt? (uid: %d)\n",-address,-from_kuid(&init_user_ns,current_uid()));-}--// Fault on user outside of certain regions (eg. copy_tofrom_user()) is bad-if(!search_exception_tables(regs->nip))-returntrue;--// 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))+address,from_kuid(&init_user_ns,current_uid()));returntrue;+}-// What's left? Kernel fault on user in well defined regions (extable-// matched), and allowed by KUAP in the faulting context.+// What's left? Kernel fault on user and allowed by KUAP in the faulting context.returnfalse;}
Excerpts from Christophe Leroy's message of August 7, 2020 3:15 am:
quoted
The verification and message introduced by commit 374f3f5979f9
("powerpc/mm/hash: Handle user access of kernel address gracefully")
applies to all platforms, it should not be limited to BOOK3S.
Make the BOOK3S version of sanity_check_fault() the one for all,
and bail out earlier if not BOOK3S.
Fixes: 374f3f5979f9 ("powerpc/mm/hash: Handle user access of kernel address gracefully")
Signed-off-by: Christophe Leroy <redacted>
---
arch/powerpc/mm/fault.c | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
Seems okay. Why is address == -1 special though? I guess it's because
it may not be an exploit kernel reference but a buggy pointer underflow?
In that case -1 doesn't seem like it would catch very much. Would it be
better to test for high bit set for example ((long)address < 0) ?
From: Nicholas Piggin <npiggin@gmail.com> Date: 2020-09-08 09:13:50
Excerpts from Christophe Leroy's message of August 7, 2020 3:15 am:
Exception fixup doesn't require the heady full regs saving,
heavy
do it from do_page_fault() directly.
For that, split bad_page_fault() in two parts.
As bad_page_fault() can also be called from other places than
handle_page_fault(), it will still perform exception fixup and
fallback on __bad_page_fault().
handle_page_fault() directly calls __bad_page_fault() as the
exception fixup will now be done by do_page_fault()
Looks good. We can probably get rid of bad_page_fault completely after
this too.
Hmm, the alignment exception might(?) hit user copies if the user points
it to CI memory. Then you could race and the memory gets unmapped. In
that case the exception table check might be better to be explicit there
with comments.
The first call in do_hash_fault is not required (copy user will never
be in nmi context). The second one and the one in slb_fault could be
made explicit too. Anyway for now this is fine.
Thanks,
Nick
Reviewed-by: Nicholas Piggin <npiggin@gmail.com>
@@ -554,17 +564,10 @@ NOKPROBE_SYMBOL(do_page_fault);*ItiscalledfromtheDSIandISIhandlersinhead.Sandfromsome*oftheproceduresintraps.c.*/-voidbad_page_fault(structpt_regs*regs,unsignedlongaddress,intsig)+void__bad_page_fault(structpt_regs*regs,unsignedlongaddress,intsig){-conststructexception_table_entry*entry;intis_write=page_fault_is_write(regs->dsisr);-/* Are we prepared to handle this fault? */-if((entry=search_exception_tables(regs->nip))!=NULL){-regs->nip=extable_fixup(entry);-return;-}-/* kernel has accessed a bad area */switch(TRAP(regs)){
@@ -598,3 +601,15 @@ void bad_page_fault(struct pt_regs *regs, unsigned long address, int sig)die("Kernel access of bad area",regs,sig);}++voidbad_page_fault(structpt_regs*regs,unsignedlongaddress,intsig)+{+conststructexception_table_entry*entry;++/* Are we prepared to handle this fault? */+entry=search_exception_tables(instruction_pointer(regs));+if(entry)+instruction_pointer_set(regs,extable_fixup(entry));+else+__bad_page_fault(regs,address,sig);+}
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 <redacted>
---
arch/powerpc/mm/fault.c | 20 +++++---------------
1 file changed, 5 insertions(+), 15 deletions(-)
@@ -214,24 +214,14 @@ static bool bad_kernel_fault(struct pt_regs *regs, unsigned long error_code,if(address>=TASK_SIZE)returntrue;-if(!is_exec&&(error_code&DSISR_PROTFAULT)&&-!search_exception_tables(regs->nip)){+// 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 access user page (%lx) - exploit attempt? (uid: %d)\n",-address,-from_kuid(&init_user_ns,current_uid()));-}--// Fault on user outside of certain regions (eg. copy_tofrom_user()) is bad-if(!search_exception_tables(regs->nip))-returntrue;
We still need to keep this ? Without that we detect the lack of
exception tables pretty late.
-
- // 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))
+ address, from_kuid(&init_user_ns, current_uid()));
return true;
+ }
- // What's left? Kernel fault on user in well defined regions (extable
- // matched), and allowed by KUAP in the faulting context.
+ // What's left? Kernel fault on user and allowed by KUAP in the faulting context.
return false;
}
--
2.25.0
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 <redacted>
---
arch/powerpc/mm/fault.c | 20 +++++---------------
1 file changed, 5 insertions(+), 15 deletions(-)
@@ -214,24 +214,14 @@ static bool bad_kernel_fault(struct pt_regs *regs, unsigned long error_code,if(address>=TASK_SIZE)returntrue;-if(!is_exec&&(error_code&DSISR_PROTFAULT)&&-!search_exception_tables(regs->nip)){+// 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 access user page (%lx) - exploit attempt? (uid: %d)\n",-address,-from_kuid(&init_user_ns,current_uid()));-}--// Fault on user outside of certain regions (eg. copy_tofrom_user()) is bad-if(!search_exception_tables(regs->nip))-returntrue;
We still need to keep this ? Without that we detect the lack of
exception tables pretty late.
Is that a problem at all to detect the lack of exception tables late ?
That case is very unlikely and will lead to failure anyway. So, is it
worth impacting performance of the likely case which will always have an
exception table and where we expect the exception to run as fast as
possible ?
The other architectures I have looked at (arm64 and x86) only have the
exception table search together with the down_read_trylock(&mm->mmap_sem).
Christophe
From: Nicholas Piggin <npiggin@gmail.com> Date: 2020-09-14 02:23:33
Excerpts from Christophe Leroy's message of September 9, 2020 4:20 pm:
Le 09/09/2020 à 08:04, Aneesh Kumar K.V a écrit :
quoted
Christophe Leroy [off-list ref] writes:
quoted
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 <redacted>
---
arch/powerpc/mm/fault.c | 20 +++++---------------
1 file changed, 5 insertions(+), 15 deletions(-)
@@ -214,24 +214,14 @@ static bool bad_kernel_fault(struct pt_regs *regs, unsigned long error_code,if(address>=TASK_SIZE)returntrue;-if(!is_exec&&(error_code&DSISR_PROTFAULT)&&-!search_exception_tables(regs->nip)){+// 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 access user page (%lx) - exploit attempt? (uid: %d)\n",-address,-from_kuid(&init_user_ns,current_uid()));-}--// Fault on user outside of certain regions (eg. copy_tofrom_user()) is bad-if(!search_exception_tables(regs->nip))-returntrue;
We still need to keep this ? Without that we detect the lack of
exception tables pretty late.
Is that a problem at all to detect the lack of exception tables late ?
That case is very unlikely and will lead to failure anyway. So, is it
worth impacting performance of the likely case which will always have an
exception table and where we expect the exception to run as fast as
possible ?
The other architectures I have looked at (arm64 and x86) only have the
exception table search together with the down_read_trylock(&mm->mmap_sem).
Yeah I don't see how it'd be a problem. User could arrange for page
table to already be at this address and avoid the fault so it's not the
right way to stop an attacker, KUAP is.
Thanks,
Nick