x86/fault: #PF improvements, mostly related to USER bit
Subsystems (3)
Andy Lutomirski
maintainercover x86/fault: #PF improvements, mostly related to USER bit
This series is a whole bunch of page fault cleanups, plus a couple of OOPS diagnostic improvements. The overall goals are to clean up handling of the faulting CPL, the USER bit in the error_code, and the log messages generated by #PF OOPSes.
This series can also be seen as CET preparation. CET introduces the WRUSS instruction, which is the very first way for CPL 0 code to cause a #PF fault with the USER bit set. Let's get the page fault code into shape before we start using WRUSS :)
Andy Lutomirski (13):
x86/fault: Check user_mode(regs) when avoiding an mmap_sem deadlock x86/fault: Check user_mode(regs) when validating a stack extension x86/cpufeatures, x86/fault: Mark SMAP as disabled when configured out x86/fault: Fold smap_violation() into do_user_addr_fault() x86/fault: Fix SMAP #PF handling buglet for implicit supervisor
accesses
x86/fault: Improve the condition for signalling vs OOPSing x86/fault: Make error_code sanitization more robust x86/fault: Don't set thread.cr2, etc before OOPSing x86/fault: Remove sw_error_code
x86/fault: Don't try to recover from an implicit supervisor access x86/oops: Show the correct CS value in show_regs() x86/fault: Decode page fault OOPSes better x86/vsyscall/64: Use X86_PF constants in the simulated #PF error code
arch/x86/entry/vsyscall/vsyscall_64.c | 2 +- arch/x86/include/asm/disabled-features.h | 8 +- arch/x86/kernel/process_64.c | 5 +- arch/x86/mm/fault.c | 224 ++++++++++++++--------- 4 files changed, 150 insertions(+), 89 deletions(-)
Signature · 1 line
2.17.2
Andy Lutomirski
maintainerPATCH 1/13 x86/fault: Check user_mode(regs) when avoiding an mmap_sem deadlock
The fault-handling code that takes mmap_sem needs to avoid a deadlock that could occur if the kernel took a bad (OOPS-worthy) page fault on a user address while holding mmap_sem. This can only happen if the faulting instruction was in the kernel (i.e. user_mode(regs)). Rather than checking the sw_error_code (which will have the USER bit set if the fault was a USER-permission access *or* if user_mode(regs)), just check user_mode(regs) directly.
The old code would have malfunctioned if the kernel executed a bogus WRUSS instruction while holding mmap_sem. Fortunately, that is extremely unlikely in current kernels, which don't use WRUSS.
--- arch/x86/mm/fault.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-)
arch/x86/mm/fault.c +2 -5
@@ -1344,13 +1344,10 @@ void do_user_addr_fault(struct pt_regs *regs,* Only do the expensive exception table search when we might be at * risk of a deadlock. This happens if we * 1. Failed to acquire mmap_sem, and -* 2. The access did not originate in userspace. Note: either the -* hardware or earlier page fault code may set X86_PF_USER -* in sw_error_code. +* 2. The access did not originate in userspace. */ if (unlikely (!down_read_trylock (&mm->mmap_sem))) { -if (!(sw_error_code &X86_PF_USER ) && - !search_exception_tables (regs->ip)) { +if (!user_mode (regs) && !search_exception_tables (regs->ip)) {/* * Fault from code in kernel from * which we do not expect faults.
Signature · 1 line
2.17.2
Peter Zijlstra
maintainerRe: [PATCH 01/13] x86/fault: Check user_mode(regs) when avoiding an mmap_sem deadlock
Quoted 5 lines · Andy Lutomirski
The fault-handling code that takes
mmap_semneeds to avoid a deadlock that could occur if the kernel took a bad (OOPS-worthy) page fault on a user address while holdingmmap_sem. This can only happen if the faulting instruction was in the kernel (i.e.user_mode(regs)). Rather than checking thesw_error_code
!user_mode(regs), surely, as the patch actually does.
Quoted 36 lines
(which will have the USER bit set if the fault was a USER-permission access *or* if
user_mode(regs)), just checkuser_mode(regs)directly.The old code would have malfunctioned if the kernel executed a bogus WRUSS instruction while holding
mmap_sem. Fortunately, that is extremely unlikely in current kernels, which don't use WRUSS.Signed-off-by Andy Lutomirski <[email protected]>--- arch/x86/mm/fault.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-)arch/x86/mm/fault.c +2
-5@@ -1344,13 +1344,10 @@ void do_user_addr_fault(struct pt_regs *regs,* Only do the expensive exception table search when we might be at * risk of a deadlock. This happens if we * 1. Failed to acquire mmap_sem, and -* 2. The access did not originate in userspace. Note: either the -* hardware or earlier page fault code may set X86_PF_USER -* in sw_error_code. +* 2. The access did not originate in userspace. */ if (unlikely (!down_read_trylock (&mm->mmap_sem))) { -if (!(sw_error_code &X86_PF_USER ) && - !search_exception_tables (regs->ip)) { +if (!user_mode (regs) && !search_exception_tables (regs->ip)) {/* * Fault from code in kernel from * which we do not expect faults. Signature · 1 line
2.17.2
tip-bot for Andy Lutomirski
merge bot[tip:x86/mm] x86/fault: Check user_mode(regs) when avoiding an mmap_sem deadlock
In reply to Andy Lutomirskix86/fault: Check user_mode(regs) when avoiding an mmap_sem deadlock
Commit-ID: 6344be608c039f3a787f1144c46fcb04c0f76561 Gitweb: https://git.kernel.org/tip/6344be608c039f3a787f1144c46fcb04c0f76561 Author: Andy Lutomirski <[email protected]> AuthorDate: Mon, 19 Nov 2018 14:45:25 -0800 Committer: Ingo Molnar <[email protected]> CommitDate: Tue, 20 Nov 2018 08:44:27 +0100
x86/fault: Check user_mode(regs) when avoiding an mmap_sem deadlock
The fault-handling code that takes mmap_sem needs to avoid a deadlock that could occur if the kernel took a bad (OOPS-worthy) page fault on a user address while holding mmap_sem. This can only happen if the faulting instruction was in the kernel (i.e. user_mode(regs)). Rather than checking the sw_error_code (which will have the USER bit set if the fault was a USER-permission access *or* if user_mode(regs)), just check user_mode(regs) directly.
The old code would have malfunctioned if the kernel executed a bogus WRUSS instruction while holding mmap_sem. Fortunately, that is extremely unlikely in current kernels, which don't use WRUSS.
--- arch/x86/mm/fault.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-)
arch/x86/mm/fault.c +2 -5
@@ -1344,13 +1344,10 @@ void do_user_addr_fault(struct pt_regs *regs,* Only do the expensive exception table search when we might be at * risk of a deadlock. This happens if we * 1. Failed to acquire mmap_sem, and -* 2. The access did not originate in userspace. Note: either the -* hardware or earlier page fault code may set X86_PF_USER -* in sw_error_code. +* 2. The access did not originate in userspace. */ if (unlikely (!down_read_trylock (&mm->mmap_sem))) { -if (!(sw_error_code &X86_PF_USER ) && - !search_exception_tables (regs->ip)) { +if (!user_mode (regs) && !search_exception_tables (regs->ip)) {/* * Fault from code in kernel from * which we do not expect faults.
Andy Lutomirski
maintainerPATCH 2/13 x86/fault: Check user_mode(regs) when validating a stack extension
In reply to Andy LutomirskiThe fault handling code tries to validate that a page fault from user mode that would extend the stack is within a certain range of the user SP. regs->sp is only equal to the user SP if user_mode(regs). In the extremely unlikely event that that sw_error_code had the USER bit set but the faulting instruction was in the kernel (i.e. the faulting instruction was WRUSS), then the *kernel* stack pointer would have been checked, which would be an info leak.
Note to -stable maintainers: don't backport this unless you backport CET. The bug it fixes is unobservable in current kernels.
--- arch/x86/mm/fault.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
arch/x86/mm/fault.c +1 -1
@@ -1377,7 +1377,7 @@ void do_user_addr_fault(struct pt_regs *regs,bad_area (regs, sw_error_code, address);return ; } -if (sw_error_code &X86_PF_USER ) { +if (user_mode (regs)) {/* * Accessing the stack below %sp is always a bug. * The large cushion allows instructions like enter
Signature · 1 line
2.17.2
Re: [PATCH 02/13] x86/fault: Check user_mode(regs) when validating a stack extension
Quoted 22 lines · Andy Lutomirski
The fault handling code tries to validate that a page fault from user mode that would extend the stack is within a certain range of the user SP.
regs->spis only equal to the user SP ifuser_mode(regs). In the extremely unlikely event that thatsw_error_codehad the USER bit set but the faulting instruction was in the kernel (i.e. the faulting instruction was WRUSS), then the *kernel* stack pointer would have been checked, which would be an info leak.Note to -stable maintainers: don't backport this unless you backport CET. The bug it fixes is unobservable in current kernels.
Signed-off-by Andy Lutomirski <[email protected]>--- arch/x86/mm/fault.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)arch/x86/mm/fault.c +0
-0
arch/x86/mm/fault.c void do_user_addr_fault(struct pt_regs *regs, · line 1383
Quoted 8 lines
arch/x86/mm/fault.c +1
-1@@ -1377,7 +1377,7 @@ void do_user_addr_fault(struct pt_regs *regs,bad_area (regs, sw_error_code, address);return ; } -if (sw_error_code &X86_PF_USER ) { +if (user_mode (regs)) {/* * Accessing the stack below %sp is always a bug. * The large cushion allows instructions like enter
Note that this check is gone now due to:
1d8ca3be86eb: x86/mm/fault: Allow stack access below %rsp
Thanks,
Ingo
Re: [PATCH 02/13] x86/fault: Check user_mode(regs) when validating a stack extension
Quoted 36 lines · Ingo Molnar
Quoted 22 lines · Andy Lutomirski
The fault handling code tries to validate that a page fault from user mode that would extend the stack is within a certain range of the user SP.
regs->spis only equal to the user SP ifuser_mode(regs). In the extremely unlikely event that thatsw_error_codehad the USER bit set but the faulting instruction was in the kernel (i.e. the faulting instruction was WRUSS), then the *kernel* stack pointer would have been checked, which would be an info leak.Note to -stable maintainers: don't backport this unless you backport CET. The bug it fixes is unobservable in current kernels.
Signed-off-by Andy Lutomirski <[email protected]>--- arch/x86/mm/fault.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)arch/x86/mm/fault.c +0
-0arch/x86/mm/fault.c void do_user_addr_fault(struct pt_regs *regs, · line 1383
Quoted 8 lines
arch/x86/mm/fault.c +1
-1@@ -1377,7 +1377,7 @@ void do_user_addr_fault(struct pt_regs *regs,bad_area (regs, sw_error_code, address);return ; } -if (sw_error_code &X86_PF_USER ) { +if (user_mode (regs)) {/* * Accessing the stack below %sp is always a bug. * The large cushion allows instructions like enter Note that this check is gone now due to:
Quoted 3 lines
1d8ca3be86eb: x86/mm/fault: Allow stack access below %rspThanks,
Ok, I like your series - I have applied the first ~7 patches of it to tip:x86/mm, the rest is interacting with 1d8ca3be86eb - will apply the rest as well once you send a v2.
Thanks,
Ingo
Andy Lutomirski
maintainerPATCH 3/13 x86/cpufeatures, x86/fault: Mark SMAP as disabled when configured out
In reply to Andy LutomirskiAdd X86_FEATURE_SMAP to the disabled features mask as appropriate and use cpu_feature_enabled() in the fault code. This lets us get rid of a redundant IS_ENABLED(CONFIG_X86_SMAP).
--- arch/x86/include/asm/disabled-features.h | 8 +++++++- arch/x86/mm/fault.c | 5 +---- 2 files changed, 8 insertions(+), 5 deletions(-)
arch/x86/include/asm/disabled-features.h +7 -1
@@ -16,6 +16,12 @@# define DISABLE_MPX (1<<(X86_FEATURE_MPX & 31))#endif +#ifdef CONFIG_X86_SMAP +# define DISABLE_SMAP 0 +#else +# define DISABLE_SMAP (1<<(X86_FEATURE_SMAP & 31)) +#endif +#ifdef CONFIG_X86_INTEL_UMIP # define DISABLE_UMIP 0#else
@@ -68,7 +74,7 @@#define DISABLED_MASK6 0#define DISABLED_MASK7 (DISABLE_PTI)#define DISABLED_MASK8 0 -#define DISABLED_MASK9 (DISABLE_MPX) +#define DISABLED_MASK9 (DISABLE_MPX|DISABLE_SMAP)#define DISABLED_MASK10 0#define DISABLED_MASK11 0#define DISABLED_MASK12 0
arch/x86/mm/fault.c +1 -4
@@ -1150,10 +1150,7 @@ static int fault_in_kernel_space(unsigned long address)static inline bool smap_violation (int error_code,struct pt_regs *regs) { -if (!IS_ENABLED (CONFIG_X86_SMAP )) -return false; - -if (!static_cpu_has (X86_FEATURE_SMAP )) +if (!cpu_feature_enabled (X86_FEATURE_SMAP ))return false;if (error_code &X86_PF_USER )
Signature · 1 line
2.17.2
tip-bot for Andy Lutomirski
merge bot[tip:x86/mm] x86/cpufeatures, x86/fault: Mark SMAP as disabled when configured out
x86/cpufeatures, x86/fault: Mark SMAP as disabled when configured out
Commit-ID: dae0a10593007d049ea71601357ac41d4f247ee9 Gitweb: https://git.kernel.org/tip/dae0a10593007d049ea71601357ac41d4f247ee9 Author: Andy Lutomirski <[email protected]> AuthorDate: Mon, 19 Nov 2018 14:45:27 -0800 Committer: Ingo Molnar <[email protected]> CommitDate: Tue, 20 Nov 2018 08:44:28 +0100
x86/cpufeatures, x86/fault: Mark SMAP as disabled when configured out
Add X86_FEATURE_SMAP to the disabled features mask as appropriate and use cpu_feature_enabled() in the fault code. This lets us get rid of a redundant IS_ENABLED(CONFIG_X86_SMAP).
--- arch/x86/include/asm/disabled-features.h | 8 +++++++- arch/x86/mm/fault.c | 5 +---- 2 files changed, 8 insertions(+), 5 deletions(-)
arch/x86/include/asm/disabled-features.h +7 -1
@@ -16,6 +16,12 @@# define DISABLE_MPX (1<<(X86_FEATURE_MPX & 31))#endif +#ifdef CONFIG_X86_SMAP +# define DISABLE_SMAP 0 +#else +# define DISABLE_SMAP (1<<(X86_FEATURE_SMAP & 31)) +#endif +#ifdef CONFIG_X86_INTEL_UMIP # define DISABLE_UMIP 0#else
@@ -68,7 +74,7 @@#define DISABLED_MASK6 0#define DISABLED_MASK7 (DISABLE_PTI)#define DISABLED_MASK8 0 -#define DISABLED_MASK9 (DISABLE_MPX) +#define DISABLED_MASK9 (DISABLE_MPX|DISABLE_SMAP)#define DISABLED_MASK10 0#define DISABLED_MASK11 0#define DISABLED_MASK12 0
arch/x86/mm/fault.c +1 -4
@@ -1150,10 +1150,7 @@ static int fault_in_kernel_space(unsigned long address)static inline bool smap_violation (int error_code,struct pt_regs *regs) { -if (!IS_ENABLED (CONFIG_X86_SMAP )) -return false; - -if (!static_cpu_has (X86_FEATURE_SMAP )) +if (!cpu_feature_enabled (X86_FEATURE_SMAP ))return false;if (error_code &X86_PF_USER )
Andy Lutomirski
maintainerPATCH 4/13 x86/fault: Fold smap_violation() into do_user_addr_fault()
In reply to Andy Lutomirskismap_violation() has a single caller, and the contents are a bit nonsensical. I'm going to fix it, but first let's fold it into its caller for ease of comprehension.
In this particular case, the user_mode(regs) check is incorrect -- it will cause false positives in the case of a user-initiated kernel-privileged access.
--- arch/x86/mm/fault.c | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-)
arch/x86/mm/fault.c +6 -17
@@ -1148,20 +1148,6 @@ static int fault_in_kernel_space(unsigned long address)return address >=TASK_SIZE_MAX ; } -static inline bool smap_violation (int error_code,struct pt_regs *regs) -{ -if (!cpu_feature_enabled (X86_FEATURE_SMAP )) -return false; - -if (error_code &X86_PF_USER ) -return false; - -if (!user_mode (regs) && (regs->flags &X86_EFLAGS_AC )) -return false; - -return true; -} -/* * Called for all faults where 'address' is part of the kernel address * space. Might get called for faults that originate from *code* that
@@ -1249,10 +1235,13 @@ void do_user_addr_fault(struct pt_regs *regs,pgtable_bad (regs, hw_error_code, address);/* -* Check for invalid kernel (supervisor) access to user -* pages in the user address space. +* If SMAP is on, check for invalid kernel (supervisor) +* access to user pages in the user address space. */ -if (unlikely (smap_violation (hw_error_code, regs))) { +if (unlikely (cpu_feature_enabled (X86_FEATURE_SMAP ) && + !(hw_error_code &X86_PF_USER ) && + (user_mode (regs) || !(regs->flags &X86_EFLAGS_AC )))) + {bad_area_nosemaphore (regs, hw_error_code, address);return ; }
Signature · 1 line
2.17.2
tip-bot for Andy Lutomirski
merge bot[tip:x86/mm] x86/fault: Fold smap_violation() into do_user_addr_fault()
x86/fault: Fold smap_violation() into do_user_addr_fault()
Commit-ID: a15781b536293edc32bf374233f3b8ad77c3f72b Gitweb: https://git.kernel.org/tip/a15781b536293edc32bf374233f3b8ad77c3f72b Author: Andy Lutomirski <[email protected]> AuthorDate: Mon, 19 Nov 2018 14:45:28 -0800 Committer: Ingo Molnar <[email protected]> CommitDate: Tue, 20 Nov 2018 08:44:28 +0100
x86/fault: Fold smap_violation() into do_user_addr_fault()
smap_violation() has a single caller, and the contents are a bit nonsensical. I'm going to fix it, but first let's fold it into its caller for ease of comprehension.
In this particular case, the user_mode(regs) check is incorrect -- it will cause false positives in the case of a user-initiated kernel-privileged access.
--- arch/x86/mm/fault.c | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-)
arch/x86/mm/fault.c +6 -17
@@ -1148,20 +1148,6 @@ static int fault_in_kernel_space(unsigned long address)return address >=TASK_SIZE_MAX ; } -static inline bool smap_violation (int error_code,struct pt_regs *regs) -{ -if (!cpu_feature_enabled (X86_FEATURE_SMAP )) -return false; - -if (error_code &X86_PF_USER ) -return false; - -if (!user_mode (regs) && (regs->flags &X86_EFLAGS_AC )) -return false; - -return true; -} -/* * Called for all faults where 'address' is part of the kernel address * space. Might get called for faults that originate from *code* that
@@ -1249,10 +1235,13 @@ void do_user_addr_fault(struct pt_regs *regs,pgtable_bad (regs, hw_error_code, address);/* -* Check for invalid kernel (supervisor) access to user -* pages in the user address space. +* If SMAP is on, check for invalid kernel (supervisor) +* access to user pages in the user address space. */ -if (unlikely (smap_violation (hw_error_code, regs))) { +if (unlikely (cpu_feature_enabled (X86_FEATURE_SMAP ) && + !(hw_error_code &X86_PF_USER ) && + (user_mode (regs) || !(regs->flags &X86_EFLAGS_AC )))) + {bad_area_nosemaphore (regs, hw_error_code, address);return ; }
Andy Lutomirski
maintainerPATCH 5/13 x86/fault: Fix SMAP #PF handling buglet for implicit supervisor accesses
In reply to Andy LutomirskiCurrently, if a user program somehow triggers an implicit supervisor access to a user address (e.g. if the kernel somehow sets LDTR to a user address), it will be incorrectly detected as a SMAP violation if AC is clear and SMAP is enabled. This is incorrect -- the error has nothing to do with SMAP. Fix the condition so that only accesses with the hardware USER bit set are diagnosed as SMAP violations.
With the logic fixed, an implicit supervisor access to a user address will hit the code lower in the function that is intended to handle it even if SMAP is enabled. That logic is still a bit buggy, and later patches will clean it up.
I *think* this code is still correct for WRUSS, and I've added a comment to that effect.
--- arch/x86/mm/fault.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-)
arch/x86/mm/fault.c +6 -3
@@ -1235,12 +1235,15 @@ void do_user_addr_fault(struct pt_regs *regs,pgtable_bad (regs, hw_error_code, address);/* -* If SMAP is on, check for invalid kernel (supervisor) -* access to user pages in the user address space. +* If SMAP is on, check for invalid kernel (supervisor) access to user +* pages in the user address space. The odd case here is WRUSS, +* which, according to the preliminary documentation, does not respect +* SMAP and will have the USER bit set so, in all cases, SMAP +* enforcement appears to be consistent with the USER bit. */ if (unlikely (cpu_feature_enabled (X86_FEATURE_SMAP ) && !(hw_error_code &X86_PF_USER ) && - (user_mode (regs) || !(regs->flags &X86_EFLAGS_AC )))) + !(regs->flags &X86_EFLAGS_AC ))) {bad_area_nosemaphore (regs, hw_error_code, address);return ;
Signature · 1 line
2.17.2
tip-bot for Andy Lutomirski
merge bot[tip:x86/mm] x86/fault: Fix SMAP #PF handling buglet for implicit supervisor accesses
x86/fault: Fix SMAP #PF handling buglet for implicit supervisor accesses
Commit-ID: e50928d7213e72ee95507221a89ed07d2bb6517b Gitweb: https://git.kernel.org/tip/e50928d7213e72ee95507221a89ed07d2bb6517b Author: Andy Lutomirski <[email protected]> AuthorDate: Mon, 19 Nov 2018 14:45:29 -0800 Committer: Ingo Molnar <[email protected]> CommitDate: Tue, 20 Nov 2018 08:44:29 +0100
x86/fault: Fix SMAP #PF handling buglet for implicit supervisor accesses
Currently, if a user program somehow triggers an implicit supervisor access to a user address (e.g. if the kernel somehow sets LDTR to a user address), it will be incorrectly detected as a SMAP violation if AC is clear and SMAP is enabled. This is incorrect -- the error has nothing to do with SMAP. Fix the condition so that only accesses with the hardware USER bit set are diagnosed as SMAP violations.
With the logic fixed, an implicit supervisor access to a user address will hit the code lower in the function that is intended to handle it even if SMAP is enabled. That logic is still a bit buggy, and later patches will clean it up.
I *think* this code is still correct for WRUSS, and I've added a comment to that effect.
--- arch/x86/mm/fault.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-)
arch/x86/mm/fault.c +6 -3
@@ -1235,12 +1235,15 @@ void do_user_addr_fault(struct pt_regs *regs,pgtable_bad (regs, hw_error_code, address);/* -* If SMAP is on, check for invalid kernel (supervisor) -* access to user pages in the user address space. +* If SMAP is on, check for invalid kernel (supervisor) access to user +* pages in the user address space. The odd case here is WRUSS, +* which, according to the preliminary documentation, does not respect +* SMAP and will have the USER bit set so, in all cases, SMAP +* enforcement appears to be consistent with the USER bit. */ if (unlikely (cpu_feature_enabled (X86_FEATURE_SMAP ) && !(hw_error_code &X86_PF_USER ) && - (user_mode (regs) || !(regs->flags &X86_EFLAGS_AC )))) + !(regs->flags &X86_EFLAGS_AC ))) {bad_area_nosemaphore (regs, hw_error_code, address);return ;
Andy Lutomirski
maintainerPATCH 6/13 x86/fault: Improve the condition for signalling vs OOPSing
In reply to Andy Lutomirski__bad_area_nosemaphore() currently checks the X86_PF_USER bit in the error code to decide whether to send a signal or to treat the fault as a kernel error. This can cause somewhat erratic behavior. The straightforward cases where the CPL agrees with the hardware USER bit are all correct, but the other cases are confusing.
- A user instruction accessing a kernel address with supervisor privilege (e.g. a descriptor table access failed). The USER bit will be clear, and we OOPS. This is correct, because it indicates a kernel bug, not a user error.
- A user instruction accessing a user address with supervisor privilege (e.g. a descriptor table was incorrectly pointing at user memory). __bad_area_nosemaphore() will be passed a modified error code with the user bit set, and we will send a signal. Sending the signal will work (because the regs and the entry frame genuinely come from user mode), but we really ought to OOPS, as this event indicates a severe kernel bug.
- A kernel instruction with user privilege (i.e. WRUSS). This should OOPS or get fixed up. The current code would instead try send a signal and malfunction.
Change the logic: a signal should be sent if the faulting context is user mode *and* the access has user privilege. Otherwise it's either a kernel mode fault or a failed implicit access, either of which should end up in no_context().
Note to -stable maintainers: don't backport this unless you backport CET. The bug it fixes is unobservable in current kernels unless something is extremely wrong.
--- arch/x86/mm/fault.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
arch/x86/mm/fault.c +1 -1
@@ -794,7 +794,7 @@ __bad_area_nosemaphore(struct pt_regs *regs, unsigned long error_code,struct task_struct *tsk = current;/* User mode accesses just cause a SIGSEGV */ -if (error_code &X86_PF_USER ) { +if (user_mode (regs) && (error_code &X86_PF_USER )) {/* * It's possible to have interrupts off here: */
Signature · 1 line
2.17.2
tip-bot for Andy Lutomirski
merge bot[tip:x86/mm] x86/fault: Improve the condition for signalling vs OOPSing
x86/fault: Improve the condition for signalling vs OOPSing
Commit-ID: 6ea59b074f15e7ef4b042a108950861b383e7b02 Gitweb: https://git.kernel.org/tip/6ea59b074f15e7ef4b042a108950861b383e7b02 Author: Andy Lutomirski <[email protected]> AuthorDate: Mon, 19 Nov 2018 14:45:30 -0800 Committer: Ingo Molnar <[email protected]> CommitDate: Tue, 20 Nov 2018 08:44:29 +0100
x86/fault: Improve the condition for signalling vs OOPSing
__bad_area_nosemaphore() currently checks the X86_PF_USER bit in the error code to decide whether to send a signal or to treat the fault as a kernel error. This can cause somewhat erratic behavior. The straightforward cases where the CPL agrees with the hardware USER bit are all correct, but the other cases are confusing.
- A user instruction accessing a kernel address with supervisor privilege (e.g. a descriptor table access failed). The USER bit will be clear, and we OOPS. This is correct, because it indicates a kernel bug, not a user error.
- A user instruction accessing a user address with supervisor privilege (e.g. a descriptor table was incorrectly pointing at user memory). __bad_area_nosemaphore() will be passed a modified error code with the user bit set, and we will send a signal. Sending the signal will work (because the regs and the entry frame genuinely come from user mode), but we really ought to OOPS, as this event indicates a severe kernel bug.
- A kernel instruction with user privilege (i.e. WRUSS). This should OOPS or get fixed up. The current code would instead try send a signal and malfunction.
Change the logic: a signal should be sent if the faulting context is user mode *and* the access has user privilege. Otherwise it's either a kernel mode fault or a failed implicit access, either of which should end up in no_context().
Note to -stable maintainers: don't backport this unless you backport CET. The bug it fixes is unobservable in current kernels unless something is extremely wrong.
--- arch/x86/mm/fault.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
arch/x86/mm/fault.c +1 -1
@@ -794,7 +794,7 @@ __bad_area_nosemaphore(struct pt_regs *regs, unsigned long error_code,struct task_struct *tsk = current;/* User mode accesses just cause a SIGSEGV */ -if (error_code &X86_PF_USER ) { +if (user_mode (regs) && (error_code &X86_PF_USER )) {/* * It's possible to have interrupts off here: */
Andy Lutomirski
maintainerPATCH 7/13 x86/fault: Make error_code sanitization more robust
In reply to Andy LutomirskiThe error code in a page fault on a kernel address indicates whether that address is mapped, which should not be revealed in a signal. The normal code path for a page fault on a kernel address sanitizes the bit, but the paths for vsyscall emulation and SIGBUS do not. Both are harmless, but for subtle reasons. SIGBUS is never sent for a kernel address, and vsyscall emulation will never fault on a kernel address per se because it will fail an access_ok() check instead.
Make the code more robust by adding a helper that sets the relevant fields and sanitizing the error code in the helper. This also cleans up the code -- we had three copies of roughly the same thing.
--- arch/x86/mm/fault.c | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-)
arch/x86/mm/fault.c +21 -9
@@ -631,6 +631,24 @@ pgtable_bad(struct pt_regs *regs, unsigned long error_code,oops_end (flags, regs, sig); } +static void set_signal_archinfo (unsigned long address, +unsigned long error_code) +{ +struct task_struct *tsk = current; + +/* +* To avoid leaking information about the kernel page +* table layout, pretend that user-mode accesses to +* kernel addresses are always protection faults. +*/ +if (address >=TASK_SIZE_MAX ) + error_code |=X86_PF_PROT ; + + tsk->thread.trap_nr =X86_TRAP_PF ; + tsk->thread.error_code = error_code |X86_PF_USER ; + tsk->thread.cr2 = address; +} + static noinline voidno_context (struct pt_regs *regs,unsigned long error_code,unsigned long address,int signal,int si_code)
@@ -656,9 +674,7 @@ no_context(struct pt_regs *regs, unsigned long error_code,* faulting through the emulate_vsyscall() logic. */ if (current->thread.sig_on_uaccess_err && signal) { - tsk->thread.trap_nr =X86_TRAP_PF ; - tsk->thread.error_code = error_code |X86_PF_USER ; - tsk->thread.cr2 = address; +set_signal_archinfo (address, error_code);/* XXX: hwpoison faults will set the wrong code. */ force_sig_fault(signal, si_code, (void __user *)address,
@@ -821,9 +837,7 @@ __bad_area_nosemaphore(struct pt_regs *regs, unsigned long error_code,if (likely (show_unhandled_signals))show_signal_msg (regs, error_code, address, tsk); - tsk->thread.cr2 = address; - tsk->thread.error_code = error_code; - tsk->thread.trap_nr =X86_TRAP_PF ; +set_signal_archinfo (address, error_code);if (si_code ==SEGV_PKUERR )force_sig_pkuerr ((void __user *)address, pkey);
@@ -937,9 +951,7 @@ do_sigbus(struct pt_regs *regs, unsigned long error_code, unsigned long address,if (is_prefetch (regs, error_code, address))return ; - tsk->thread.cr2 = address; - tsk->thread.error_code = error_code; - tsk->thread.trap_nr =X86_TRAP_PF ; +set_signal_archinfo (address, error_code);#ifdef CONFIG_MEMORY_FAILURE if (fault & (VM_FAULT_HWPOISON |VM_FAULT_HWPOISON_LARGE )) {
Signature · 1 line
2.17.2
tip-bot for Andy Lutomirski
merge bot[tip:x86/mm] x86/fault: Make error_code sanitization more robust
x86/fault: Make error_code sanitization more robust
Commit-ID: e49d3cbef0176c182b86206185f137a87f16ab91 Gitweb: https://git.kernel.org/tip/e49d3cbef0176c182b86206185f137a87f16ab91 Author: Andy Lutomirski <[email protected]> AuthorDate: Mon, 19 Nov 2018 14:45:31 -0800 Committer: Ingo Molnar <[email protected]> CommitDate: Tue, 20 Nov 2018 08:44:29 +0100
x86/fault: Make error_code sanitization more robust
The error code in a page fault on a kernel address indicates whether that address is mapped, which should not be revealed in a signal.
The normal code path for a page fault on a kernel address sanitizes the bit, but the paths for vsyscall emulation and SIGBUS do not. Both are harmless, but for subtle reasons. SIGBUS is never sent for a kernel address, and vsyscall emulation will never fault on a kernel address per se because it will fail an access_ok() check instead.
Make the code more robust by adding a helper that sets the relevant fields and sanitizing the error code in the helper. This also cleans up the code -- we had three copies of roughly the same thing.
--- arch/x86/mm/fault.c | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-)
arch/x86/mm/fault.c +21 -9
@@ -631,6 +631,24 @@ pgtable_bad(struct pt_regs *regs, unsigned long error_code,oops_end (flags, regs, sig); } +static void set_signal_archinfo (unsigned long address, +unsigned long error_code) +{ +struct task_struct *tsk = current; + +/* +* To avoid leaking information about the kernel page +* table layout, pretend that user-mode accesses to +* kernel addresses are always protection faults. +*/ +if (address >=TASK_SIZE_MAX ) + error_code |=X86_PF_PROT ; + + tsk->thread.trap_nr =X86_TRAP_PF ; + tsk->thread.error_code = error_code |X86_PF_USER ; + tsk->thread.cr2 = address; +} + static noinline voidno_context (struct pt_regs *regs,unsigned long error_code,unsigned long address,int signal,int si_code)
@@ -656,9 +674,7 @@ no_context(struct pt_regs *regs, unsigned long error_code,* faulting through the emulate_vsyscall() logic. */ if (current->thread.sig_on_uaccess_err && signal) { - tsk->thread.trap_nr =X86_TRAP_PF ; - tsk->thread.error_code = error_code |X86_PF_USER ; - tsk->thread.cr2 = address; +set_signal_archinfo (address, error_code);/* XXX: hwpoison faults will set the wrong code. */ force_sig_fault(signal, si_code, (void __user *)address,
@@ -821,9 +837,7 @@ __bad_area_nosemaphore(struct pt_regs *regs, unsigned long error_code,if (likely (show_unhandled_signals))show_signal_msg (regs, error_code, address, tsk); - tsk->thread.cr2 = address; - tsk->thread.error_code = error_code; - tsk->thread.trap_nr =X86_TRAP_PF ; +set_signal_archinfo (address, error_code);if (si_code ==SEGV_PKUERR )force_sig_pkuerr ((void __user *)address, pkey);
@@ -937,9 +951,7 @@ do_sigbus(struct pt_regs *regs, unsigned long error_code, unsigned long address,if (is_prefetch (regs, error_code, address))return ; - tsk->thread.cr2 = address; - tsk->thread.error_code = error_code; - tsk->thread.trap_nr =X86_TRAP_PF ; +set_signal_archinfo (address, error_code);#ifdef CONFIG_MEMORY_FAILURE if (fault & (VM_FAULT_HWPOISON |VM_FAULT_HWPOISON_LARGE )) {
Andy Lutomirski
maintainerPATCH 8/13 x86/fault: Don't set thread.cr2, etc before OOPSing
In reply to Andy LutomirskiThe fault handling code sets the cr2, trap_nr, and error_code fields in thread_struct before OOPSing. No one reads those fields during an OOPS, so remove the code to set them.
--- arch/x86/mm/fault.c | 8 -------- 1 file changed, 8 deletions(-)
arch/x86/mm/fault.c +0 -8
@@ -621,10 +621,6 @@ pgtable_bad(struct pt_regs *regs, unsigned long error_code, tsk->comm, address);dump_pagetable (address); - tsk->thread.cr2 = address; - tsk->thread.trap_nr =X86_TRAP_PF ; - tsk->thread.error_code = error_code; -if (__die ("Bad pagetable" , regs, error_code)) sig =0 ;
@@ -753,10 +749,6 @@ no_context(struct pt_regs *regs, unsigned long error_code,if (task_stack_end_corrupted (tsk))printk (KERN_EMERG "Thread overran stack, or stack corrupted\n" ); - tsk->thread.cr2 = address; - tsk->thread.trap_nr =X86_TRAP_PF ; - tsk->thread.error_code = error_code; - sig =SIGKILL ;if (__die ("Oops" , regs, error_code)) sig =0 ;
Signature · 1 line
2.17.2
tip-bot for Andy Lutomirski
merge bot[tip:x86/mm] x86/fault: Don't set thread.cr2, etc before OOPSing
x86/fault: Don't set thread.cr2, etc before OOPSing
Commit-ID: 1ad33f5aec20f53785dbad44c6fb3b204aefd921 Gitweb: https://git.kernel.org/tip/1ad33f5aec20f53785dbad44c6fb3b204aefd921 Author: Andy Lutomirski <[email protected]> AuthorDate: Mon, 19 Nov 2018 14:45:32 -0800 Committer: Ingo Molnar <[email protected]> CommitDate: Tue, 20 Nov 2018 08:44:30 +0100
x86/fault: Don't set thread.cr2, etc before OOPSing
The fault handling code sets the cr2, trap_nr, and error_code fields in thread_struct before OOPSing. No one reads those fields during an OOPS, so remove the code to set them.
--- arch/x86/mm/fault.c | 8 -------- 1 file changed, 8 deletions(-)
arch/x86/mm/fault.c +0 -8
@@ -621,10 +621,6 @@ pgtable_bad(struct pt_regs *regs, unsigned long error_code, tsk->comm, address);dump_pagetable (address); - tsk->thread.cr2 = address; - tsk->thread.trap_nr =X86_TRAP_PF ; - tsk->thread.error_code = error_code; -if (__die ("Bad pagetable" , regs, error_code)) sig =0 ;
@@ -753,10 +749,6 @@ no_context(struct pt_regs *regs, unsigned long error_code,if (task_stack_end_corrupted (tsk))printk (KERN_EMERG "Thread overran stack, or stack corrupted\n" ); - tsk->thread.cr2 = address; - tsk->thread.trap_nr =X86_TRAP_PF ; - tsk->thread.error_code = error_code; - sig =SIGKILL ;if (__die ("Oops" , regs, error_code)) sig =0 ;
Andy Lutomirski
maintainerPATCH 9/13 x86/fault: Remove sw_error_code
In reply to Andy LutomirskiAll of the fault handling code now corrently checks user_mode(regs) as needed, and nothing depends on the X86_PF_USER bit being munged. Get rid of the sw_error code and use hw_error_code everywhere.
--- arch/x86/mm/fault.c | 52 +++++++++++---------------------------------- 1 file changed, 12 insertions(+), 40 deletions(-)
arch/x86/mm/fault.c +12 -40
@@ -1217,7 +1217,6 @@ void do_user_addr_fault(struct pt_regs *regs, unsigned long hw_error_code, unsigned long address) { -unsigned long sw_error_code;struct vm_area_struct *vma;struct task_struct *tsk;struct mm_struct *mm;
@@ -1262,13 +1261,6 @@ void do_user_addr_fault(struct pt_regs *regs,return ; } -/* -* hw_error_code is literally the "page fault error code" passed to -* the kernel directly from the hardware. But, we will shortly be -* modifying it in software, so give it a new name. -*/ - sw_error_code = hw_error_code; -/* * It's safe to allow irq's after cr2 has been saved and the * vmalloc fault has been handled.
@@ -1278,26 +1270,6 @@ void do_user_addr_fault(struct pt_regs *regs,*/ if (user_mode (regs)) {local_irq_enable (); -/* -* Up to this point, X86_PF_USER set in hw_error_code -* indicated a user-mode access. But, after this, -* X86_PF_USER in sw_error_code will indicate either -* that, *or* an implicit kernel(supervisor)-mode access -* which originated from user mode. -*/ -if (!(hw_error_code &X86_PF_USER )) { -/* -* The CPU was in user mode, but the CPU says -* the fault was not a user-mode access. -* Must be an implicit kernel-mode access, -* which we do not expect to happen in the -* user address space. -*/ -pr_warn_once ("kernel-mode error from user-mode: %lx\n" , - hw_error_code); - - sw_error_code |=X86_PF_USER ; - } flags |=FAULT_FLAG_USER ; }else {if (regs->flags &X86_EFLAGS_IF )
@@ -1306,9 +1278,9 @@ void do_user_addr_fault(struct pt_regs *regs,perf_sw_event (PERF_COUNT_SW_PAGE_FAULTS ,1 , regs, address); -if (sw_error_code &X86_PF_WRITE ) +if (hw_error_code &X86_PF_WRITE ) flags |=FAULT_FLAG_WRITE ; -if (sw_error_code &X86_PF_INSTR ) +if (hw_error_code &X86_PF_INSTR ) flags |=FAULT_FLAG_INSTRUCTION ;#ifdef CONFIG_X86_64
@@ -1321,7 +1293,7 @@ void do_user_addr_fault(struct pt_regs *regs,* The vsyscall page does not have a "real" VMA, so do this * emulation before we go searching for VMAs. */ -if ((sw_error_code &X86_PF_INSTR ) &&is_vsyscall_vaddr (address)) { +if ((hw_error_code &X86_PF_INSTR ) &&is_vsyscall_vaddr (address)) {if (emulate_vsyscall (regs, address))return ; }
@@ -1345,7 +1317,7 @@ void do_user_addr_fault(struct pt_regs *regs,* Fault from code in kernel from * which we do not expect faults. */ -bad_area_nosemaphore (regs, sw_error_code, address); +bad_area_nosemaphore (regs, hw_error_code, address);return ; } retry:
@@ -1361,13 +1333,13 @@ void do_user_addr_fault(struct pt_regs *regs, vma =find_vma (mm, address);if (unlikely (!vma)) { -bad_area (regs, sw_error_code, address); +bad_area (regs, hw_error_code, address);return ; }if (likely (vma->vm_start <= address)) goto good_area;if (unlikely (!(vma->vm_flags &VM_GROWSDOWN ))) { -bad_area (regs, sw_error_code, address); +bad_area (regs, hw_error_code, address);return ; }if (user_mode (regs)) {
@@ -1378,12 +1350,12 @@ void do_user_addr_fault(struct pt_regs *regs,* 32 pointers and then decrements %sp by 65535.) */ if (unlikely (address +65536 +32 *sizeof (unsigned long ) < regs->sp)) { -bad_area (regs, sw_error_code, address); +bad_area (regs, hw_error_code, address);return ; } }if (unlikely (expand_stack (vma, address))) { -bad_area (regs, sw_error_code, address); +bad_area (regs, hw_error_code, address);return ; }
@@ -1392,8 +1364,8 @@ void do_user_addr_fault(struct pt_regs *regs,* we can handle it.. */ good_area: -if (unlikely (access_error (sw_error_code, vma))) { -bad_area_access_error (regs, sw_error_code, address, vma); +if (unlikely (access_error (hw_error_code, vma))) { +bad_area_access_error (regs, hw_error_code, address, vma);return ; }
@@ -1432,13 +1404,13 @@ void do_user_addr_fault(struct pt_regs *regs,return ;/* Not returning to user mode? Handle exceptions or die: */ -no_context (regs, sw_error_code, address,SIGBUS ,BUS_ADRERR ); +no_context (regs, hw_error_code, address,SIGBUS ,BUS_ADRERR );return ; }up_read (&mm->mmap_sem);if (unlikely (fault &VM_FAULT_ERROR )) { -mm_fault_error (regs, sw_error_code, address, fault); +mm_fault_error (regs, hw_error_code, address, fault);return ; }
Signature · 1 line
2.17.2
Andy Lutomirski
maintainerPATCH 10/13 x86/fault: Don't try to recover from an implicit supervisor access
In reply to Andy LutomirskiThis avoids a situation in which we attempt to apply various fixups that are not intended to handle implicit supervisor accesses from user mode if we screw up in away that causes this type of fault.
--- arch/x86/mm/fault.c | 10 ++++++++++ 1 file changed, 10 insertions(+)
arch/x86/mm/fault.c +10 -0
@@ -653,6 +653,15 @@ no_context(struct pt_regs *regs, unsigned long error_code,unsigned long flags;int sig; +if (user_mode (regs)) { +/* +* This is an implicit supervisor-mode access from user +* mode. Bypass all the kernel-mode recovery code and just +* OOPS. +*/ + goto oops; + } +/* Are we prepared to handle this kernel fault? */ if (fixup_exception (regs,X86_TRAP_PF , error_code, address)) {/*
@@ -738,6 +747,7 @@ no_context(struct pt_regs *regs, unsigned long error_code,if (IS_ENABLED (CONFIG_EFI ))efi_recover_from_page_fault (address); +oops:/* * Oops. The kernel tried to access some bad page. We'll have to * terminate things with extreme prejudice:
Signature · 1 line
2.17.2
Andy Lutomirski
maintainerPATCH 11/13 x86/oops: Show the correct CS value in show_regs()
In reply to Andy Lutomirskishow_regs() shows the CS in the CPU register instead of the value in regs. This means that we'll probably print "CS: 0010" almost all the time regardless of what was actually in CS when the kernel malfunctioned. This gives a particularly confusing result if we OOPSed due to an implicit supervisor access from user mode.
--- arch/x86/kernel/process_64.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-)
arch/x86/kernel/process_64.c +2 -3
@@ -66,7 +66,7 @@ void __show_regs(struct pt_regs *regs, enum show_regs_mode mode)unsigned long cr0 =0L , cr2 =0L , cr3 =0L , cr4 =0L , fs, gs, shadowgs;unsigned long d0, d1, d2, d3, d6, d7;unsigned int fsindex, gsindex; -unsigned int ds, cs, es; +unsigned int ds, es;show_iret_regs (regs);
@@ -98,7 +98,6 @@ void __show_regs(struct pt_regs *regs, enum show_regs_mode mode) } asm("movl %%ds,%0" :"=r" (ds)); - asm("movl %%cs,%0" :"=r" (cs)); asm("movl %%es,%0" :"=r" (es)); asm("movl %%fs,%0" :"=r" (fsindex)); asm("movl %%gs,%0" :"=r" (gsindex));
@@ -114,7 +113,7 @@ void __show_regs(struct pt_regs *regs, enum show_regs_mode mode)printk (KERN_DEFAULT "FS: %016lx(%04x) GS:%016lx(%04x) knlGS:%016lx\n" , fs, fsindex, gs, gsindex, shadowgs); -printk (KERN_DEFAULT "CS: %04x DS: %04x ES: %04x CR0: %016lx\n" , cs, ds, +printk (KERN_DEFAULT "CS: %04lx DS: %04x ES: %04x CR0: %016lx\n" , regs->cs, ds, es, cr0);printk (KERN_DEFAULT "CR2: %016lx CR3: %016lx CR4: %016lx\n" , cr2, cr3, cr4);
Signature · 1 line
2.17.2
Andy Lutomirski
maintainerPATCH 12/13 x86/fault: Decode page fault OOPSes better
In reply to Andy LutomirskiOne of Linus' favorite hobbies seems to be looking at OOPSes and decoding the error code in his head. This is not one of my favorite hobbies :)
Teach the page fault OOPS hander to decode the error code. If it's a !USER fault from user mode, print an explicit note to that effect and print out the addresses of various tables that might cause such an error.
With this patch applied, if I intentionally point the LDT at 0x0 and run the x86 selftests, I get:
BUG: unable to handle kernel NULL pointer dereference at 0000000000000000
HW error: normal kernel read fault
This was a system access from user code
IDT: 0xfffffe0000000000 (limit=0xfff) GDT: 0xfffffe0000001000 (limit=0x7f)
LDTR: 0x50 -- base=0x0 limit=0xfff7
TR: 0x40 -- base=0xfffffe0000003000 limit=0x206f
PGD 800000000456e067 P4D 800000000456e067 PUD 4623067 PMD 0 SMP PTI
CPU: 0 PID: 153 Comm: ldt_gdt_64 Not tainted 4.19.0+ #1317 Hardware name: ... RIP: 0033:0x401454
--- arch/x86/mm/fault.c | 84 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+)
arch/x86/mm/fault.c +84 -0
@@ -27,6 +27,7 @@#include <asm/vm86.h> /* struct vm86 */ #include <asm/mmu_context.h> /* vma_pkey() */ #include <asm/efi.h> /* efi_recover_from_page_fault()*/ +#include <asm/desc.h> /* store_idt(), ... */ #define CREATE_TRACE_POINTS #include <asm/trace/exceptions.h>
@@ -571,10 +572,53 @@ static int is_f00f_bug(struct pt_regs *regs, unsigned long address)return 0 ; } +static void show_ldttss (const struct desc_ptr *gdt,const char *name,u16 index) +{ +u32 offset = (index >>3 ) *sizeof (struct desc_struct ); +unsigned long addr; +struct ldttss_desc desc; + +if (index ==0 ) { +pr_alert ("%s: NULL\n" , name); +return ; + } + +if (offset +sizeof (struct ldttss_desc ) >= gdt->size) { +pr_alert ("%s: 0x%hx -- out of bounds\n" , name, index); +return ; + } + +if (probe_kernel_read (&desc, (void *)(gdt->address + offset), +sizeof (struct ldttss_desc ))) { +pr_alert ("%s: 0x%hx -- GDT entry is not readable\n" , + name, index); +return ; + } + + addr = desc.base0 | (desc.base1 <<16 ) | (desc.base2 <<24 ); +#ifdef CONFIG_X86_64 + addr |= ((u64 )desc.base3 <<32 ); +#endif +pr_alert ("%s: 0x%hx -- base=0x%lx limit=0x%x\n" , + name, index, addr, (desc.limit0 | (desc.limit1 <<16 ))); +} + +static void errstr (unsigned long ec,char *buf,unsigned long mask, +const char *txt) +{ +if (ec & mask) { +if (buf[0 ]) +strcat (buf," " ); +strcat (buf, txt); + } +} +static void show_fault_oops (struct pt_regs *regs,unsigned long error_code,unsigned long address) { +char errtxt[64 ]; +if (!oops_may_print ())return ;
@@ -602,6 +646,46 @@ show_fault_oops(struct pt_regs *regs, unsigned long error_code, address <PAGE_SIZE ?"NULL pointer dereference" :"paging request" , (void *)address); + errtxt[0 ] =0 ; +errstr (error_code, errtxt,X86_PF_PROT ,"PROT" ); +errstr (error_code, errtxt,X86_PF_WRITE ,"WRITE" ); +errstr (error_code, errtxt,X86_PF_USER ,"USER" ); +errstr (error_code, errtxt,X86_PF_RSVD ,"RSVD" ); +errstr (error_code, errtxt,X86_PF_INSTR ,"INSTR" ); +errstr (error_code, errtxt,X86_PF_PK ,"PK" ); +pr_alert ("HW error: %s\n" , error_code ? errtxt : +"normal kernel read fault" ); +if (!(error_code &X86_PF_USER ) &&user_mode (regs)) { +struct desc_ptr idt, gdt; +u16 ldtr, tr; + +pr_alert ("This was a system access from user code\n" ); + +/* +* This can happen for quite a few reasons. The more obvious +* ones are faults accessing the GDT, or LDT. Perhaps +* surprisingly, if the CPU tries to deliver a benign or +* contributory exception from user code and gets a page fault +* during delivery, the page fault can be delivered as though +* it originated directly from user code. This could happen +* due to wrong permissions on the IDT, GDT, LDT, TSS, or +* kernel or IST stack. +*/ +store_idt (&idt); + +/* Usable even on Xen PV -- it's just slow. */ +native_store_gdt (&gdt); + +pr_alert ("IDT: 0x%lx (limit=0x%hx) GDT: 0x%lx (limit=0x%hx)\n" , + idt.address, idt.size, gdt.address, gdt.size); + +store_ldt (ldtr); +show_ldttss (&gdt,"LDTR" , ldtr); + +store_tr (tr); +show_ldttss (&gdt,"TR" , tr); + } +dump_pagetable (address); }
Signature · 1 line
2.17.2
Re: [PATCH 12/13] x86/fault: Decode page fault OOPSes better
Quoted 98 lines · Andy Lutomirski
One of Linus' favorite hobbies seems to be looking at OOPSes and decoding the error code in his head. This is not one of my favorite hobbies :)
Teach the page fault OOPS hander to decode the error code. If it's a !USER fault from user mode, print an explicit note to that effect and print out the addresses of various tables that might cause such an error.
With this patch applied, if I intentionally point the LDT at
0x0and run the x86 selftests, I get:BUG: unable to handle kernel NULL pointer dereference at 0000000000000000HW error: normal kernel read fault
This was a system access from user code
IDT:0xfffffe0000000000(limit=0xfff) GDT:0xfffffe0000001000(limit=0x7f)
LDTR:0x50-- base=0x0 limit=0xfff7
TR:0x40-- base=0xfffffe0000003000 limit=0x206f
PGD800000000456e067P4D800000000456e067PUD 4623067 PMD 0 SMP PTICPU: 0 PID: 153 Comm: ldt_gdt_64 Not tainted 4.19.0+ #1317 Hardware name: ... RIP: 0033:0x401454Signed-off-by Andy Lutomirski <[email protected]>--- arch/x86/mm/fault.c | 84 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+)arch/x86/mm/fault.c +44
-0@@ -27,6 +27,7 @@#include <asm/vm86.h> /* struct vm86 */ #include <asm/mmu_context.h> /* vma_pkey() */ #include <asm/efi.h> /* efi_recover_from_page_fault()*/ +#include <asm/desc.h> /* store_idt(), ... */ #define CREATE_TRACE_POINTS #include <asm/trace/exceptions.h> @@ -571,10 +572,53 @@ static int is_f00f_bug(struct pt_regs *regs, unsigned long address)return 0 ; } +static void show_ldttss (const struct desc_ptr *gdt,const char *name,u16 index) +{ +u32 offset = (index >>3 ) *sizeof (struct desc_struct ); +unsigned long addr; +struct ldttss_desc desc; + +if (index ==0 ) { +pr_alert ("%s: NULL\n" , name); +return ; + } + +if (offset +sizeof (struct ldttss_desc ) >= gdt->size) { +pr_alert ("%s: 0x%hx -- out of bounds\n" , name, index); +return ; + } + +if (probe_kernel_read (&desc, (void *)(gdt->address + offset), +sizeof (struct ldttss_desc ))) { +pr_alert ("%s: 0x%hx -- GDT entry is not readable\n" , + name, index); +return ; + } + + addr = desc.base0 | (desc.base1 <<16 ) | (desc.base2 <<24 ); +#ifdef CONFIG_X86_64 + addr |= ((u64 )desc.base3 <<32 ); +#endif +pr_alert ("%s: 0x%hx -- base=0x%lx limit=0x%x\n" , + name, index, addr, (desc.limit0 | (desc.limit1 <<16 ))); +} + +static void errstr (unsigned long ec,char *buf,unsigned long mask, +const char *txt) +{ +if (ec & mask) { +if (buf[0 ]) +strcat (buf," " ); +strcat (buf, txt); + } +} +static void show_fault_oops (struct pt_regs *regs,unsigned long error_code,unsigned long address) { +char errtxt[64 ]; +if (!oops_may_print ())return ;@@ -602,6 +646,46 @@ show_fault_oops(struct pt_regs *regs, unsigned long error_code, address <PAGE_SIZE ?"NULL pointer dereference" :"paging request" , (void *)address);
arch/x86/mm/fault.c show_fault_oops(struct pt_regs *regs, unsigned long error_code, · line 657
Quoted 10 lines
arch/x86/mm/fault.c +9
-0+ errtxt[0 ] =0 ; +errstr (error_code, errtxt,X86_PF_PROT ,"PROT" ); +errstr (error_code, errtxt,X86_PF_WRITE ,"WRITE" ); +errstr (error_code, errtxt,X86_PF_USER ,"USER" ); +errstr (error_code, errtxt,X86_PF_RSVD ,"RSVD" ); +errstr (error_code, errtxt,X86_PF_INSTR ,"INSTR" ); +errstr (error_code, errtxt,X86_PF_PK ,"PK" ); +pr_alert ("HW error: %s\n" , error_code ? errtxt : +"normal kernel read fault" );
What about something like this instead of manually handling the case where error_code==0 so that we get e.g. "!PROT KERNEL READ" instead of "normal kernel read fault"? Not sure !PROT and/or KERNEL are needed, but getting at least "PROT READ" seems useful.
errstr(!error_code, errtxt, X86_PF_PROT, "!PROT"); errstr(!error_code, errtxt, X86_PF_USER, "KERNEL"); errstr(!error_code, errtxt, X86_PF_WRITE | X86_PF_INSTR, "READ");
And change the pr_alert to "HW error code:"?
The original is confusing (to me) because "HW error: normal kernel read fault" obfuscates the fact that we're printing the #PF error code, i.e. it looks like an arbitrary kernel message.
This:
HW error code: !PROT KERNEL READ
This was a system access from user code
or:
HW error code: !PROT READ
This was a system access from user code
or:
HW error code: KERNEL READ
This was a system access from user code
or:
HW error code: READ
This was a system access from user code
are all less confusing IMO.
Quoted 37 lines
arch/x86/mm/fault.c +31
-0+if (!(error_code &X86_PF_USER ) &&user_mode (regs)) { +struct desc_ptr idt, gdt; +u16 ldtr, tr; + +pr_alert ("This was a system access from user code\n" ); + +/* +* This can happen for quite a few reasons. The more obvious +* ones are faults accessing the GDT, or LDT. Perhaps +* surprisingly, if the CPU tries to deliver a benign or +* contributory exception from user code and gets a page fault +* during delivery, the page fault can be delivered as though +* it originated directly from user code. This could happen +* due to wrong permissions on the IDT, GDT, LDT, TSS, or +* kernel or IST stack. +*/ +store_idt (&idt); + +/* Usable even on Xen PV -- it's just slow. */ +native_store_gdt (&gdt); + +pr_alert ("IDT: 0x%lx (limit=0x%hx) GDT: 0x%lx (limit=0x%hx)\n" , + idt.address, idt.size, gdt.address, gdt.size); + +store_ldt (ldtr); +show_ldttss (&gdt,"LDTR" , ldtr); + +store_tr (tr); +show_ldttss (&gdt,"TR" , tr); + } +dump_pagetable (address); }Signature · 1 line
2.17.2
Andy Lutomirski
maintainerPATCH 13/13 x86/vsyscall/64: Use X86_PF constants in the simulated #PF error code
In reply to Andy LutomirskiRather than hardcoding 6 with a comment, use the defined constants.
--- arch/x86/entry/vsyscall/vsyscall_64.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
arch/x86/entry/vsyscall/vsyscall_64.c +1 -1
@@ -102,7 +102,7 @@ static bool write_ok_or_segv(unsigned long ptr, size_t size)if (!access_ok (VERIFY_WRITE , (void __user *)ptr, size)) {struct thread_struct *thread = ¤t->thread; - thread->error_code =6 ;/* user fault, no page, write */ + thread->error_code =X86_PF_USER |X86_PF_WRITE ; thread->cr2 = ptr; thread->trap_nr =X86_TRAP_PF ;
Signature · 1 line
2.17.2