x86, mpx updates for 4.2 (take 8)
Subsystems (4)
cover x86, mpx updates for 4.2 (take 8)
Changes from take 7 / v22:
* Add Thomas's reviewed-by
* merge with tip/x86/fpu changes
* Fix tiny spelling nit
Changes from take 6 / v21:
* Address a bunch of Thomas's review comments.
Changes from take 5 / v20:
* Fix get_xsave_addr() to consult xstate_bv in anticipation of fixes to xsave code.
* Bug fix for when an VMA being unmapped has neighbors which are bounds tables.
* Rewrite unmapping code. I didn't do this lightly. It was not originally my own code, and I resisted changing it because it worked. But, I started bug chasing and decided it was unmaintainable. The rewrite ended up removing about 20% of the unmapping code and made it much simpler.
Changes from take 4 / v19:
* Do not pass a task_struct around when we are
really just going to operate on current
Changes from take 3 / v18 (all minor):
* use DECLARE_EVENT_CLASS()/DEFINE_EVENT() for the ranged tracepoints to save 10 lines of code.
Changes from take 2 / v17 (all minor):
* fix a couple of whitespace borkages caught by checkpatch, and a spelling error or two.
* replace printk with pr_info() for boot disable
* change trace print format for address intervals
* fix up variable name in tsk_get_xsave_addr() comment
* remove tsk_get_xsave_field() GPL export
* fix up Qiaowei's From:
--
Hi x86 maintainers,
There are a few basic things going on here:
1. Make FPU/xsave code preempt safe and work properly
2. Add trace points to make kernel and app debugging easier
3. Add a boot-time disable for mpx
4. Rewrite the unmapping code.
5. Support 32-bit binaries to run on 64-bit kernels
This set is also available against tip/x86/fpu (8c05f05edb) in git:
git://git.kernel.org/pub/scm/linux/kernel/git/daveh/x86-mpx.git mpx-v23
Dave Hansen (19):
x86, mpx, xsave: Fix up bad get_xsave_addr() assumptions x86, fpu: Wrap get_xsave_addr() to make it safer x86, mpx: Use new get_xsave_field_ptr()
x86, mpx: Cleanup: Do not pass task around when unnecessary x86, mpx: remove redundant MPX_BNDCFG_ADDR_MASK x86, mpx: Restrict mmap size check to bounds tables x86, mpx: boot-time disable
x86, mpx: trace #BR exceptions
x86, mpx: trace entry to bounds exception paths x86, mpx: Trace the attempts to find bounds tables x86, mpx: trace allocation of new bounds tables
x86: make is_64bit_mm() widely available
x86, mpx: Add temporary variable to reduce masking x86, mpx: new directory entry to addr helper
x86, mpx: do 32-bit-only cmpxchg for 32-bit apps x86, mpx: support 32-bit binaries on 64-bit kernel x86, mpx: rewrite unmap code
x86, mpx: do not count MPX VMAs as neighbors when unmapping x86, mpx: allow mixed binaries again
Documentation/kernel-parameters.txt | 4 + arch/x86/include/asm/fpu/xstate.h | 1 + arch/x86/include/asm/mmu_context.h | 13 + arch/x86/include/asm/mpx.h | 74 +++--- arch/x86/include/asm/processor.h | 12 +- arch/x86/include/asm/trace/mpx.h | 131 ++++++++++ arch/x86/kernel/cpu/common.c | 16 ++ arch/x86/kernel/fpu/xstate.c | 78 +++++- arch/x86/kernel/traps.c | 18 +- arch/x86/kernel/uprobes.c | 10 +- arch/x86/mm/mpx.c | 508 ++++++++++++++++++++++-------------- kernel/sys.c | 8 +- 12 files changed, 606 insertions(+), 267 deletions(-) create mode 100644 arch/x86/include/asm/trace/mpx.h
PATCH 1/19 x86, mpx, xsave: Fix up bad get_xsave_addr() assumptions
From: Dave Hansen <[email protected]>
get_xsave_addr() assumes that if an xsave bit is present in the hardware (pcntxt_mask) that it is present in a given xsave buffer. Due to an bug in the xsave code on all of the systems that have MPX (and thus all the users of this code), that has been a true assumption.
But, the bug is getting fixed, so our assumption is not going to hold any more.
It's quite possible (and normal) for an enabled state to be present on 'pcntxt_mask', but *not* in 'xstate_bv'. We need to consult 'xstate_bv'.
---
b/arch/x86/kernel/fpu/xstate.c | 45 +++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-)
arch/x86/kernel/fpu/xstate.c renamed from arch/x86/kernel/fpu/xstate.c~consullt-xstate_bv +37 -8
@@ -382,19 +382,48 @@ void fpu__resume_cpu(void)* This is the API that is called to get xstate address in either * standard format or compacted format of xsave area. * +* Note that if there is no data for the field in the xsave buffer +* this will return NULL. +* * Inputs: -* xsave: base address of the xsave area; -* xstate: state which is defined in xsave.h (e.g. XSTATE_FP, XSTATE_SSE, -* etc.) +* xstate: the thread's storage area for all FPU data +* xstate_feature: state which is defined in xsave.h (e.g. +* XSTATE_FP, XSTATE_SSE, etc...) * Output: -* address of the state in the xsave area. +* address of the state in the xsave area, or NULL if the +* field is not present in the xsave buffer. */ -void *get_xsave_addr (struct xregs_state *xsave,int xstate) +void *get_xsave_addr (struct xregs_state *xsave,int xstate_feature) { -int feature =fls64 (xstate) -1 ; -if (!test_bit (feature, (unsigned long *)&xfeatures_mask)) +int feature_nr =fls64 (xstate_feature) -1 ; +/* +* Do we even *have* xsave state? +*/ +if (!boot_cpu_has (X86_FEATURE_XSAVE )) +return NULL ; + + xsave = ¤t->thread.fpu.state.xsave; +/* +* We should not ever be requesting features that we +* have not enabled. Remember that pcntxt_mask is +* what we write to the XCR0 register. +*/ +WARN_ONCE (!(xfeatures_mask & xstate_feature), +"get of unsupported state" ); +/* +* This assumes the last 'xsave*' instruction to +* have requested that 'xstate_feature' be saved. +* If it did not, we might be seeing and old value +* of the field in the buffer. +* +* This can happen because the last 'xsave' did not +* request that this feature be saved (unlikely) +* or because the "init optimization" caused it +* to not be saved. +*/ +if (!(xsave->header.xfeatures & xstate_feature))return NULL ; -return (void *)xsave + xstate_comp_offsets[feature]; +return (void *)xsave + xstate_comp_offsets[feature_nr]; }EXPORT_SYMBOL_GPL (get_xsave_addr);
_
PATCH 2/19 x86, fpu: Wrap get_xsave_addr() to make it safer
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
The MPX code appears to be saving off the FPU in an unsafe way. It does not disable preemption or ensure that the FPU state has been allocated. All of the preemption safety comes from the unfortunatley-named 'unlazy_fpu()'.
This patch introduces a new helper which will do both of those things internally.
Note that this requires a patch from Oleg in order to work properly. It is currently in tip/x86/fpu.
Quoted 5 lines
commit
f893959b0898bd876673adbeb6798bdf25c034d7Author: Oleg Nesterov <[email protected]> Date: Fri Mar 13 18:30:30 2015 +0100x86/fpu: Don't abuse
drop_init_fpu()inflush_thread()
---
Changes from v21:
* add comments about preemption
* rename helper to get_xsave_field_ptr()
Changes from "v19":
* remove 'tsk' argument to get_xsave_addr() since the code can only realistically work on 'current', and fix up the comment a bit to match.
Changes from "v17":
* fix s/xstate/xsave_field/ in the function comment
* remove EXPORT_SYMBOL_GPL()
---
b/arch/x86/include/asm/fpu/xstate.h | 1 + b/arch/x86/kernel/fpu/xstate.c | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+)
arch/x86/include/asm/fpu/xstate.h renamed from arch/x86/include/asm/fpu/xstate.h~tsk_get_xsave_addr +1 -0
@@ -41,5 +41,6 @@ extern u64 xstate_fx_sw_bytes[USER_XSTATextern void update_regset_xstate_info (unsigned int size,u64 xstate_mask);void *get_xsave_addr (struct xregs_state *xsave,int xstate); +void *get_xsave_field_ptr (int xstate_field);#endif
arch/x86/kernel/fpu/xstate.c renamed from arch/x86/kernel/fpu/xstate.c~tsk_get_xsave_addr +33 -0
@@ -427,3 +427,36 @@ void *get_xsave_addr(struct xregs_statereturn (void *)xsave + xstate_comp_offsets[feature_nr]; }EXPORT_SYMBOL_GPL (get_xsave_addr); + +/* +* This wraps up the common operations that need to occur when retrieving +* data from xsave state. It first ensures that the current task was +* using the FPU and retrieves the data in to a buffer. It then calculates +* the offset of the requested field in the buffer. +* +* This function is safe to call whether the FPU is in use or not. +* +* Note that this only works on the current task. +* +* Inputs: +* @xsave_state: state which is defined in xsave.h (e.g. XSTATE_FP, +* XSTATE_SSE, etc...) +* Output: +* address of the state in the xsave area or NULL if the state +* is not present or is in its 'init state'. +*/ +void *get_xsave_field_ptr (int xsave_state) +{ +union fpregs_state *xstate; + +if (!current->thread.fpu.fpstate_active) +return NULL ; +/* +* fpu__save() takes the CPU's xstate registers +* and saves them off to the 'fpu memory buffer. +*/ +fpu__save (¤t->thread.fpu); + xstate = ¤t->thread.fpu.state; + +return get_xsave_addr (&xstate->xsave, xsave_state); +}
_
Re: [PATCH 02/19] x86, fpu: Wrap get_xsave_addr() to make it safer
Quoted 7 lines · Dave Hansen
From: Dave Hansen <[email protected]>
The MPX code appears to be saving off the FPU in an unsafe way. It does not disable preemption or ensure that the FPU state has been allocated. All of the preemption safety comes from the unfortunatley-named '
unlazy_fpu()'.
Btw., with the new FPU code these functions are named differently, and the bug in the MPX code became a lot more obvious:
copy_fpregs_to_fpstate(&tsk->thread.fpu);
xsave_buf = &(tsk->thread.fpu.state.xsave);
bndcsr = get_xsave_addr(xsave_buf, XSTATE_BNDCSR);it's indeed generally unsafe to access/copy FPU registers with preemption enabled, for two reasons:
- on older systems that use FSAVE the instruction destroys FPU register contents, which has to be handled carefully
- even on newer systems if we copy to FPU registers (which this code doesn't) then we don't want a context switch to occur in the middle of it, because a context switch will write to the fpstate, potentially overwriting our new data with old FPU state.
But it's safe to access FPU registers with preemption enabled in a couple of special cases:
- potentially destructively saving FPU registers: the signal handling code does this in copy_fpstate_to_sigframe(), because it can rely on the signal restore side to restore the original FPU state.
- reading FPU registers on modern systems: we don't do this anywhere at the moment, mostly to keep symmetry with older systems where FSAVE is destructive.
- initializing FPU registers on modern systems: fpu__clear() does this. Here it's safe because we don't copy from the fpstate.
- directly writing FPU registers from user-space memory (!). We do this in fpu__restore_sig(), and it's safe because neither context switches nor irq-handler FPU use can corrupt the source context of the copy (which is user-space memory).
Note that the MPX code's current use of copy_fpregs_to_fpstate() was safe I think, because:
- MPX is predicated on eagerfpu, so the destructive F[N]SAVE instruction won't be used.
- the code was only reading FPU registers, and was doing it only in places that guaranteed that an FPU state was already active (i.e. didn't do it in kthreads)
But ... I agree that a more robust API should be used to access FPU registers:
Quoted 13 lines
arch/x86/kernel/fpu/xstate.c renamed from arch/x86/kernel/fpu/xstate.c~tsk_get_xsave_addr +8
-0@@ -427,3 +427,36 @@ void *get_xsave_addr(struct xregs_statereturn (void *)xsave + xstate_comp_offsets[feature_nr]; }EXPORT_SYMBOL_GPL (get_xsave_addr); +/* +* This wraps up the common operations that need to occur when retrieving +* data from xsave state. It first ensures that the current task was +* using the FPU and retrieves the data in to a buffer. It then calculates +* the offset of the requested field in the buffer. +* +* This function is safe to call whether the FPU is in use or not. +*
arch/x86/kernel/fpu/xstate.c void *get_xsave_addr(struct xregs_state · line 448
Quoted 10 lines
arch/x86/kernel/fpu/xstate.c renamed from arch/x86/kernel/fpu/xstate.c~tsk_get_xsave_addr +10
-0+* Note that this only works on the current task. +* +* Inputs: +* @xsave_state: state which is defined in xsave.h (e.g. XSTATE_FP, +* XSTATE_SSE, etc...) +* Output: +* address of the state in the xsave area or NULL if the state +* is not present or is in its 'init state'. +*/ +void *get_xsave_field_ptr (int xsave_state)
So this is retrieving (reading) data from FPU registers, but returns a writable 'void *'. So the return pointer from this interface should be constified, to make sure no modifications may occur over them (which modificiations would be unsafe).
arch/x86/kernel/fpu/xstate.c void *get_xsave_addr(struct xregs_state · line 461
Quoted 12 lines
arch/x86/kernel/fpu/xstate.c renamed from arch/x86/kernel/fpu/xstate.c~tsk_get_xsave_addr +12
-0+union fpregs_state *xstate; + +if (!current->thread.fpu.fpstate_active) +return NULL ; +/* +* fpu__save() takes the CPU's xstate registers +* and saves them off to the 'fpu memory buffer. +*/ +fpu__save (¤t->thread.fpu); + xstate = ¤t->thread.fpu.state; + +return get_xsave_addr (&xstate->xsave, xsave_state);
Small nit, this would become a lot shorter if you introduced a helper local variable:
struct fpu *fpu = ¤t->thread.fpu;
But more importantly, for a generic get_xsave_field_ptr() API, fpu__save() is not
enough: fpu__save() will only save FPU registers into memory if necessary (i.e. if the FPU is already in use), and if you call it on a task with no FPU state then it will still have an !fpu->fpstate_active FPU state after the call, with random, invalid data in the xsave area.
What you want here is to make the (in-memory) FPU state valid and current, before reading it, and the function to use for that is fpu__activate_fpstate_read() (available in the latest tip:x86/fpu tree).
Thanks,
Ingo
Re: [PATCH 02/19] x86, fpu: Wrap get_xsave_addr() to make it safer
Quoted 3 lines · Ingo Molnar
What you want here is to make the (in-memory) FPU state valid and current, before reading it, and the function to use for that is
fpu__activate_fpstate_read()(available in the latest tip:x86/fpu tree).
Do we really want to unconditionally activate the FPU?
Let's say supporting MPX didn't require eager mode and someone called get_xsave_addr(). We would ideally want to keep the FPU inactive and just return NULL. Right?
Re: [PATCH 02/19] x86, fpu: Wrap get_xsave_addr() to make it safer
Quoted 11 lines · Dave Hansen
Quoted 3 lines · Ingo Molnar
What you want here is to make the (in-memory) FPU state valid and current, before reading it, and the function to use for that is
fpu__activate_fpstate_read()(available in the latest tip:x86/fpu tree).Do we really want to unconditionally activate the FPU?
Let's say supporting MPX didn't require eager mode and someone called
get_xsave_addr(). We would ideally want to keep the FPU inactive and just return NULL. Right?
So there's two distinct types of 'active' here:
- active fpstate (in-kernel memory context buffer)
- active fpregs (in-FPU hardware registers)
fpu__activate_fpstate_read() will only activate the fpstate for reads (as the name suggests it).
In your hypothetical case, if it's called with lazy FPU state then the fpstate is active already, and the fpstate represents the 'real' FPU state of the current task - while the FPU's contents are still some previous task's FPU state. So we can return the contents of this task's fpstate just fine even if the registers themselves are not (yet) loaded with them.
But the real question is: can we support in-use MPX with asynchronous lazy restore, while it's still semantically correct? I don't think so, unless you add MPX specific synchronous restore to the context switch path, which isn't such a good idea IMHO.
Furthermore, I don't think we want to extend lazy FPU use, in fact I'm considering getting rid of it altogether, even on old CPUs: the CR0 fault costs are horrible all across the CPU spectrum (even for legacy CPUs), and modern user-space makes use of the FPU all the time.
Yes, on older CPUs, if user-space does not use the FPU but context switches frequently, then the cost of always doing FPU save/restore is measurable, but the worst-case I've measured was something like a 10% increase in context switching cost.
Thanks,
Ingo
Re: [PATCH 02/19] x86, fpu: Wrap get_xsave_addr() to make it safer
Quoted 2 lines · Ingo Molnar
fpu__activate_fpstate_read()will only activate the fpstate for reads (as the name suggests it).
I've got no problem doing it this way. But are you planning to push this function in to 4.2? Is there a tree you want me to merge this on top of?
Re: [PATCH 02/19] x86, fpu: Wrap get_xsave_addr() to make it safer
Quoted 6 lines · Dave Hansen
Quoted 2 lines · Ingo Molnar
fpu__activate_fpstate_read()will only activate the fpstate for reads (as the name suggests it).I've got no problem doing it this way. But are you planning to push this function in to 4.2? Is there a tree you want me to merge this on top of?
Yes and yes, please use tip:x86/fpu (or tip:master) for all FPU related work.
Thanks,
Ingo
Re: [PATCH 02/19] x86, fpu: Wrap get_xsave_addr() to make it safer
In reply to Ingo MolnarQuoted 4 lines · Ingo Molnar
But the real question is: can we support in-use MPX with asynchronous lazy restore, while it's still semantically correct? I don't think so, unless you add MPX specific synchronous restore to the context switch path, which isn't such a good idea IMHO.
Right now, we assume that the first use of the FPU gets an #ND exception to tell us that someone is using the FPU. MPX doesn't generate #ND, thus the need to do it eagerly.
On CPUs that support it we could, instead, do an xgetbv during the context switch to ensure that all things having an xstate/xfeature but that do not generate #ND exceptions are in their init state. If they are not in their init state, we exit lazy mode.
We could theoretically use the same kind of thing with the compacted xsave format to ensure that we only allocate enough space for what we *need* in the xsave buffer and not allocate for the worst-case. AVX512 has 32x512-bit registers (2kbytes) and it would be a bit of a shame to need to allocate ~3k of space.
Re: [PATCH 02/19] x86, fpu: Wrap get_xsave_addr() to make it safer
Quoted 15 lines · Dave Hansen
Quoted 4 lines · Ingo Molnar
But the real question is: can we support in-use MPX with asynchronous lazy restore, while it's still semantically correct? I don't think so, unless you add MPX specific synchronous restore to the context switch path, which isn't such a good idea IMHO.
Right now, we assume that the first use of the FPU gets an #ND exception to tell us that someone is using the FPU. MPX doesn't generate #ND, thus the need to do it eagerly.
On CPUs that support it we could, instead, do an xgetbv during the context switch to ensure that all things having an xstate/xfeature but that do not generate #ND exceptions are in their init state. If they are not in their init state, we exit lazy mode.
Quoted 5 lines
We could theoretically use the same kind of thing with the compacted xsave format to ensure that we only allocate enough space for what we *need* in the xsave buffer and not allocate for the worst-case. AVX512 has 32x512-bit registers (2kbytes) and it would be a bit of a shame to need to allocate ~3k of space.
I understand the point of this type of optimization (except that I really don't like the idea of sending SIGBUS or whatever if we fail an allocation at context switch time), but why are we even considering trying to support MPX and lazy fpu at the same time? Judging from all the bug reports, it seems like it's a giant mess, and the code to support lazy restore is not exactly pretty.
I would propose that we take the opposite approach and just ban eagerfpu=off when MPX is enabled. We could then take the next step and default eagerfpu=on for everyone and, if nothing breaks, then just delete lazy mode entirely.
I suspect we'd have to go back to Pentium 3 or earlier to find a CPU on which lazy mode is actually a good idea. Fiddling with CR0 and handling exceptions is really slow, and I think we should trust CPUs with XSAVEOPT support to do their job and let the older CPUs take the small performance hit, if it even is a performance hit.
--Andy
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Re: [PATCH 02/19] x86, fpu: Wrap get_xsave_addr() to make it safer
Quoted 4 lines · Andy Lutomirski
I would propose that we take the opposite approach and just ban eagerfpu=off when MPX is enabled. We could then take the next step and default eagerfpu=on for everyone and, if nothing breaks, then just delete lazy mode entirely.
No objections from me on this.
It's definitely the simplest thing to do, and it's one less potential delta that enabling MPX could impose on someone, so it makes me happy on that front.
Borislav Petkov
maintainerRe: [PATCH 02/19] x86, fpu: Wrap get_xsave_addr() to make it safer
In reply to Andy LutomirskiQuoted 7 lines · Andy Lutomirski
I would propose that we take the opposite approach and just ban eagerfpu=off when MPX is enabled. We could then take the next step and default eagerfpu=on for everyone and, if nothing breaks, then just delete lazy mode entirely.
I suspect we'd have to go back to Pentium 3 or earlier to find a CPU on which lazy mode is actually a good idea.
Last time I checked (and ran some benchmarks) it was only a minute slowdown so I say we kill lazy mode if it means significant code complexity drop.
Can I also emulate Greg here and suggest that Pentium 3 people should buy newer hw? They should think about the environment, if nothing else.
:-P
Signature · 5 lines
Regards/Gruss,
Boris.
ECO tip #101: Trim your mails when you reply.
--Re: [PATCH 02/19] x86, fpu: Wrap get_xsave_addr() to make it safer
Quoted 9 lines · Borislav Petkov
Quoted 7 lines · Andy Lutomirski
I would propose that we take the opposite approach and just ban eagerfpu=off when MPX is enabled. We could then take the next step and default eagerfpu=on for everyone and, if nothing breaks, then just delete lazy mode entirely.
I suspect we'd have to go back to Pentium 3 or earlier to find a CPU on which lazy mode is actually a good idea.
Quoted 8 lines
Last time I checked (and ran some benchmarks) it was only a minute slowdown so I say we kill lazy mode if it means significant code complexity drop.
Can I also emulate Greg here and suggest that Pentium 3 people should buy newer hw? They should think about the environment, if nothing else.
:-P
I went back as far as Athon64 and the CR0 manipulation and CR0 faults are overly expensive there too.
Ok, you guys convinced me, I'll do a patch for this in tip:x86/fpu, and then people can benchmark it.
Thanks,
Ingo
Re: [PATCH 02/19] x86, fpu: Wrap get_xsave_addr() to make it safer
In reply to Andy LutomirskiQuoted 10 lines · Andy Lutomirski
Quoted 9 lines · Dave Hansen
Quoted 4 lines · Ingo Molnar
But the real question is: can we support in-use MPX with asynchronous lazy restore, while it's still semantically correct? I don't think so, unless you add MPX specific synchronous restore to the context switch path, which isn't such a good idea IMHO.
Right now, we assume that the first use of the FPU gets an #ND exception to tell us that someone is using the FPU. MPX doesn't generate #ND, thus the need to do it eagerly.
Basically MPX is not really a vector operation, it just uses the xstate (as in 'extended CPU state') context area to do easy saves/restores on context switches. MPX is an MMU-ish feature.
That's an entirely sensible design approach, which reduces the support code needed for MPX, and it's not surprising that MPX accesses were not made conditional on CR0::TS.
Quoted 4 lines
Quoted 4 lines
On CPUs that support it we could, instead, do an xgetbv during the context switch to ensure that all things having an xstate/xfeature but that do not generate #ND exceptions are in their init state. If they are not in their init state, we exit lazy mode.
Yeah, no, we don't need to do anything complex here.
This property is something we know when MPX gets enabled, so for MPX tasks we should either simply set _TIF_WORK_CTXSW and let __switch_to_xtra() handle it, or should slightly modify the eagerfpu choice code to always do eager restores when switching to an MPX task.
Nothing complex is needed to support the mixed lazy/eager model, the current FPU code handles it just fine, because it's already a mixed lazy/eager model :-)
Quoted 16 lines
Quoted 5 lines
We could theoretically use the same kind of thing with the compacted xsave format to ensure that we only allocate enough space for what we *need* in the xsave buffer and not allocate for the worst-case. AVX512 has 32x512-bit registers (2kbytes) and it would be a bit of a shame to need to allocate ~3k of space.
I understand the point of this type of optimization (except that I really don't like the idea of sending SIGBUS or whatever if we fail an allocation at context switch time), but why are we even considering trying to support MPX and lazy fpu at the same time? Judging from all the bug reports, it seems like it's a giant mess, and the code to support lazy restore is not exactly pretty.
I would propose that we take the opposite approach and just ban eagerfpu=off when MPX is enabled. We could then take the next step and default eagerfpu=on for everyone and, if nothing breaks, then just delete lazy mode entirely.
Quoted 5 lines
I suspect we'd have to go back to Pentium 3 or earlier to find a CPU on which lazy mode is actually a good idea. Fiddling with CR0 and handling exceptions is really slow, and I think we should trust CPUs with XSAVEOPT support to do their job and let the older CPUs take the small performance hit, if it even is a performance hit.
It's not that simple, because the decision is not 'lazy versus eager', but 'mixed lazy/eager versus eager-only':
Even on modern machines, if a task is not using the FPU (it's doing integer only work, with short sleeps just shuffling around requests, etc.) then context switches get up to 5-10% faster with lazy FPU restores.
So we have this dynamic measurement code in place in the lazy case that opportunistically enables eagerfpu handling on a per task basis, and that method works pretty efficiently and has a good hit rate in isolating FPU-users from integer-users.
So it's not 'lazy restores versus eager restores', but:
- optimized, mixed lazy and eager use
vs.
- eager-only use
Which is a lot less clear-cut choice.
It's true that right now we forcibly use eagerfpu on all modern CPUs (XSAVE supporting ones - in essence modern Intel CPUs) which hides all this - but if you re-enable it it's measurable even on Intel systems. On AMD systems it's the current state of affairs right now.
Also, I'd like to point out that the FPU code is a lot less of a mess in the latest x86/fpu tree! ;-)
I'd not give up on lazy restores just yet - or at least not without much better measurements backing it all up...
Thanks,
Ingo
Re: [PATCH 02/19] x86, fpu: Wrap get_xsave_addr() to make it safer
Quoted 27 lines · Ingo Molnar
Quoted 10 lines · Andy Lutomirski
Quoted 9 lines · Dave Hansen
Quoted 4 lines · Ingo Molnar
But the real question is: can we support in-use MPX with asynchronous lazy restore, while it's still semantically correct? I don't think so, unless you add MPX specific synchronous restore to the context switch path, which isn't such a good idea IMHO.
Right now, we assume that the first use of the FPU gets an #ND exception to tell us that someone is using the FPU. MPX doesn't generate #ND, thus the need to do it eagerly.
Basically MPX is not really a vector operation, it just uses the xstate (as in 'extended CPU state') context area to do easy saves/restores on context switches. MPX is an MMU-ish feature.
That's an entirely sensible design approach, which reduces the support code needed for MPX, and it's not surprising that MPX accesses were not made conditional on
CR0::TS.Quoted 4 lines
Quoted 4 lines
On CPUs that support it we could, instead, do an xgetbv during the context switch to ensure that all things having an xstate/xfeature but that do not generate #ND exceptions are in their init state. If they are not in their init state, we exit lazy mode.
Quoted 7 lines
Yeah, no, we don't need to do anything complex here.
This property is something we know when MPX gets enabled, so for MPX tasks we should either simply set
_TIF_WORK_CTXSWand let__switch_to_xtra()handle it, or should slightly modify the eagerfpu choice code to always do eager restores when switching to an MPX task.
Do we actually know which tasks use MPX, or do we merely know which tasks use kernel-assisted MPX?
Quoted 25 lines
Nothing complex is needed to support the mixed lazy/eager model, the current FPU code handles it just fine, because it's already a mixed lazy/eager model :-)
Quoted 16 lines
Quoted 5 lines
We could theoretically use the same kind of thing with the compacted xsave format to ensure that we only allocate enough space for what we *need* in the xsave buffer and not allocate for the worst-case. AVX512 has 32x512-bit registers (2kbytes) and it would be a bit of a shame to need to allocate ~3k of space.
I understand the point of this type of optimization (except that I really don't like the idea of sending SIGBUS or whatever if we fail an allocation at context switch time), but why are we even considering trying to support MPX and lazy fpu at the same time? Judging from all the bug reports, it seems like it's a giant mess, and the code to support lazy restore is not exactly pretty.
I would propose that we take the opposite approach and just ban eagerfpu=off when MPX is enabled. We could then take the next step and default eagerfpu=on for everyone and, if nothing breaks, then just delete lazy mode entirely.
Quoted 5 lines
I suspect we'd have to go back to Pentium 3 or earlier to find a CPU on which lazy mode is actually a good idea. Fiddling with CR0 and handling exceptions is really slow, and I think we should trust CPUs with XSAVEOPT support to do their job and let the older CPUs take the small performance hit, if it even is a performance hit.
Quoted 6 lines
It's not that simple, because the decision is not 'lazy versus eager', but 'mixed lazy/eager versus eager-only':
Even on modern machines, if a task is not using the FPU (it's doing integer only work, with short sleeps just shuffling around requests, etc.) then context switches get up to 5-10% faster with lazy FPU restores.
That's only sort of true. I'd believe that a context switch between two lazy tasks is 5-10% faster than a context switch between two eager tasks. I bet that a context switch between a lazy task and an eager task is a whole lot slower than a context switch between two eager tasks because manipulating CR0.TS is incredibly slow on all modern CPUs AFAICT. It's even worse in a VM guest.
In other words, with lazy restore, we save the XRSTOR(S) and possibly a subsequent XSAVEOPT/XSAVES, but the cost is a MOV to CR0 and possibly a CLTS, and the MOV to CR0 is much, much slower than even a worst-case XRSTOR(S). In the worst lazy-restore case, we also pay a full exception roundtrip, and everything pales in comparison. If we're a guest, then there's probably a handful of exits thrown in for good measure.
For true integer-only tasks, I think we should instead convince glibc to add things like vzeroall in convenient places to force as much xstate as possible to the init state, thus speeding up the optimized save/restore variants.
I think the fundamental issue here is that CPU designers care about xstate save/restore/optimize performance, but they don't care at all about TS performance, so TS manipulations are probably microcoded and serializing.
Quoted 14 lines
So we have this dynamic measurement code in place in the lazy case that opportunistically enables eagerfpu handling on a per task basis, and that method works pretty efficiently and has a good hit rate in isolating FPU-users from integer-users.
So it's not 'lazy restores versus eager restores', but:
- optimized, mixed lazy and eager use
vs.
- eager-only useWhich is a lot less clear-cut choice.
Quoted 7 lines
It's true that right now we forcibly use eagerfpu on all modern CPUs (XSAVE supporting ones - in essence modern Intel CPUs) which hides all this - but if you re-enable it it's measurable even on Intel systems. On AMD systems it's the current state of affairs right now.
Also, I'd like to point out that the FPU code is a lot less of a mess in the latest x86/fpu tree! ;-)
That part's certainly true.
Quoted 3 lines
I'd not give up on lazy restores just yet - or at least not without much better measurements backing it all up...
Fair enough. I suspect that the only workloads on which it will win are old 32-bit distros, though -- even integer-only 64-bit workloads are likely to use SSE2 for things like memcpy.
--Andy
Re: [PATCH 02/19] x86, fpu: Wrap get_xsave_addr() to make it safer
Quoted 20 lines · Andy Lutomirski
Quoted 6 lines
It's not that simple, because the decision is not 'lazy versus eager', but 'mixed lazy/eager versus eager-only':
Even on modern machines, if a task is not using the FPU (it's doing integer only work, with short sleeps just shuffling around requests, etc.) then context switches get up to 5-10% faster with lazy FPU restores.
That's only sort of true. I'd believe that a context switch between two lazy tasks is 5-10% faster than a context switch between two eager tasks. I bet that a context switch between a lazy task and an eager task is a whole lot slower than a context switch between two eager tasks because manipulating CR0.TS is incredibly slow on all modern CPUs AFAICT. It's even worse in a VM guest.
In other words, with lazy restore, we save the XRSTOR(S) and possibly a subsequent XSAVEOPT/XSAVES, but the cost is a MOV to CR0 and possibly a CLTS, and the MOV to CR0 is much, much slower than even a worst-case XRSTOR(S). In the worst lazy-restore case, we also pay a full exception roundtrip, and everything pales in comparison. If we're a guest, then there's probably a handful of exits thrown in for good measure.
Quoted 7 lines
For true integer-only tasks, I think we should instead convince glibc to add things like vzeroall in convenient places to force as much xstate as possible to the init state, thus speeding up the optimized save/restore variants.
I think the fundamental issue here is that CPU designers care about xstate save/restore/optimize performance, but they don't care at all about TS performance, so TS manipulations are probably microcoded and serializing.
That's definitely true.
Btw., potentially being able to get rid of lazy restores was why I wrote the FPU-benchmarking code, and it gives these results on reasonably recent Intel CPUs:
CR0 reads are reasonably fast:
[ 0.519287] x86/fpu: Cost of: CR0 read : 4 cycles
but we can cache that so it doesn't help us.
writes are bad:
[ 0.528643] x86/fpu: Cost of: CR0 write : 208 cycles
and we cannot cache it, so that hurts us.
and a CR0::TS fault cost is horrible:
[ 0.538042] x86/fpu: Cost of: CR0::TS fault : 1156 cycles
and this is hurting us too.
Since the first version I have extended the benchmark with a cache-cold column as well - in the cache cold case the difference is even more striking, and in may cases context switches are cache cold.
Interestingly, this kind of high cost of CR0 related accesses is true even on pretty old, 10+ years old x86 CPUs, per my measurements, so it's not limited to modern x86 microarchitectures.
So yes, it would be nice to standardize on synchronous context switching of all CPU state.
Thanks,
Ingo
Re: [PATCH 02/19] x86, fpu: Wrap get_xsave_addr() to make it safer
In reply to Ingo MolnarQuoted 15 lines · Ingo Molnar
Quoted 4 lines
Quoted 4 lines
arch/x86/kernel/fpu/xstate.c renamed from arch/x86/kernel/fpu/xstate.c~tsk_get_xsave_addr +4
-0+union fpregs_state *xstate; + +if (!current->thread.fpu.fpstate_active) +return NULL ;Quoted 8 lines
arch/x86/kernel/fpu/xstate.c void *get_xsave_addr(struct xregs_state · line 461
Quoted 8 lines
arch/x86/kernel/fpu/xstate.c renamed from arch/x86/kernel/fpu/xstate.c~tsk_get_xsave_addr +8
-0+/* +* fpu__save() takes the CPU's xstate registers +* and saves them off to the 'fpu memory buffer. +*/ +fpu__save (¤t->thread.fpu); + xstate = ¤t->thread.fpu.state; + +return get_xsave_addr (&xstate->xsave, xsave_state);Small nit, this would become a lot shorter if you introduced a helper local variable:
Quoted 7 lines
struct fpu *fpu = ¤t->thread.fpu;But more importantly, for a generic
get_xsave_field_ptr()API,fpu__save()is not
enough:fpu__save()will only save FPU registers into memory if necessary (i.e. if the FPU is already in use), and if you call it on a task with no FPU state then it will still have an !fpu->fpstate_active FPU state after the call, with random, invalid data in the xsave area.
But why does this matter? We just did a !fpu.fpstate_active check, so we can't have a !fpu.fpstate_active before or after the call.
Re: [PATCH 02/19] x86, fpu: Wrap get_xsave_addr() to make it safer
Quoted 18 lines · Dave Hansen
Quoted 17 lines · Ingo Molnar
Quoted 4 lines
Quoted 4 lines
arch/x86/kernel/fpu/xstate.c renamed from arch/x86/kernel/fpu/xstate.c~tsk_get_xsave_addr +4
-0+union fpregs_state *xstate; + +if (!current->thread.fpu.fpstate_active) +return NULL ;Quoted 8 lines
arch/x86/kernel/fpu/xstate.c void *get_xsave_addr(struct xregs_state · line 461
Quoted 8 lines
arch/x86/kernel/fpu/xstate.c renamed from arch/x86/kernel/fpu/xstate.c~tsk_get_xsave_addr +8
-0+/* +* fpu__save() takes the CPU's xstate registers +* and saves them off to the 'fpu memory buffer. +*/ +fpu__save (¤t->thread.fpu); + xstate = ¤t->thread.fpu.state; + +return get_xsave_addr (&xstate->xsave, xsave_state);Small nit, this would become a lot shorter if you introduced a helper local variable:
struct fpu *fpu = ¤t->thread.fpu;
Quoted 8 lines
Quoted 5 lines
But more importantly, for a generic
get_xsave_field_ptr()API,fpu__save()is not enough:fpu__save()will only save FPU registers into memory if necessary (i.e. if the FPU is already in use), and if you call it on a task with no FPU state then it will still have an !fpu->fpstate_active FPU state after the call, with random, invalid data in the xsave area.But why does this matter? We just did a !fpu.fpstate_active check, so we can't have a !fpu.fpstate_active before or after the call.
Ah yes, you are right, I missed this:
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
arch/x86/kernel/fpu/xstate.c void *get_xsave_addr(struct xregs_state · line 453
Quoted 2 lines
arch/x86/kernel/fpu/xstate.c renamed from arch/x86/kernel/fpu/xstate.c~tsk_get_xsave_addr +2
-0+if (!current->thread.fpu.fpstate_active) +return NULL ;
because the usual pattern is:
if (!fpu->fpstate_active) return NULL;
:-)
So your variant is fine too.
Thanks,
Ingo
PATCH 3/19 x86, mpx: Use new get_xsave_field_ptr()
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
The MPX registers (bndcsr/bndcfgu/bndstatus) are not directly accessible via normal instructions. They essentially act as if they were floating point registers and are saved/restored along with those registers.
There are two main paths in the MPX code where we care about the contents of these registers:
1. #BR (bounds) faults
2. the prctl() code where we are setting MPX up
Both of those paths _might_ be called without the FPU having been used. That means that 'tsk->thread.fpu.state' might never be allocated.
Also, fpu_save_init() is not preempt-safe. It was a bug to call it without disabling preemption. The new get_xsave_addr() calls unlazy_fpu() instead and properly disables preemption.
---
Changes from v21:
* rename get_xsave_field() to get_xsave_field_ptr()
---
b/arch/x86/include/asm/mpx.h | 8 ++++---- b/arch/x86/kernel/traps.c | 15 +++++++-------- b/arch/x86/mm/mpx.c | 24 ++++++++++++------------ 3 files changed, 23 insertions(+), 24 deletions(-)
arch/x86/include/asm/mpx.h renamed from arch/x86/include/asm/mpx.h~use-new-tsk_get_xsave_addr +4 -4
@@ -60,8 +60,8 @@#ifdef CONFIG_X86_INTEL_MPX siginfo_t *mpx_generate_siginfo (struct pt_regs *regs, -struct xregs_state *xsave_buf); -int mpx_handle_bd_fault (struct xregs_state *xsave_buf); +struct task_struct *tsk); +int mpx_handle_bd_fault (struct task_struct *tsk);static inline int kernel_managing_mpx_tables (struct mm_struct *mm) {return (mm->bd_addr !=MPX_INVALID_BOUNDS_DIR );
@@ -78,11 +78,11 @@ void mpx_notify_unmap(struct mm_struct * unsigned long start, unsigned long end);#else static inline siginfo_t *mpx_generate_siginfo (struct pt_regs *regs, -struct xregs_state *xsave_buf) +struct task_struct *tsk) {return NULL ; } -static inline int mpx_handle_bd_fault (struct xregs_state *xsave_buf) +static inline int mpx_handle_bd_fault (struct task_struct *tsk) {return -EINVAL ; }
arch/x86/kernel/traps.c renamed from arch/x86/kernel/traps.c~use-new-tsk_get_xsave_addr +7 -8
@@ -59,6 +59,7 @@#include <asm/fixmap.h> #include <asm/mach_traps.h> #include <asm/alternative.h> +#include <asm/fpu/xstate.h> #include <asm/mpx.h> #ifdef CONFIG_X86_64
@@ -371,7 +372,6 @@ dotraplinkage void do_double_fault(struc dotraplinkage void do_bounds(struct pt_regs *regs, long error_code) {struct task_struct *tsk = current; -struct xregs_state *xsave_buf;enum ctx_state prev_state;struct bndcsr *bndcsr;siginfo_t *info;
@@ -392,12 +392,11 @@ dotraplinkage void do_bounds(struct pt_r/* * We need to look at BNDSTATUS to resolve this exception. -* It is not directly accessible, though, so we need to -* do an xsave and then pull it out of the xsave buffer. +* A NULL here might mean that it is in its 'init state', +* which is all zeros which indicates MPX was not +* responsible for the exception. */ -copy_fpregs_to_fpstate (&tsk->thread.fpu); - xsave_buf = &(tsk->thread.fpu.state.xsave); - bndcsr =get_xsave_addr (xsave_buf,XSTATE_BNDCSR ); + bndcsr =get_xsave_field_ptr (XSTATE_BNDCSR );if (!bndcsr) goto exit_trap;
@@ -408,11 +407,11 @@ dotraplinkage void do_bounds(struct pt_r*/ switch (bndcsr->bndstatus &MPX_BNDSTA_ERROR_CODE ) {case 2 :/* Bound directory has invalid entry. */ -if (mpx_handle_bd_fault (xsave_buf)) +if (mpx_handle_bd_fault (tsk)) goto exit_trap;break ;/* Success, it was handled */ case 1 :/* Bound violation. */ - info =mpx_generate_siginfo (regs, xsave_buf); + info =mpx_generate_siginfo (regs, tsk);if (IS_ERR (info)) {/* * We failed to decode the MPX instruction. Act as if
arch/x86/mm/mpx.c renamed from arch/x86/mm/mpx.c~use-new-tsk_get_xsave_addr +12 -12
@@ -272,7 +272,7 @@ bad_opcode:* The caller is expected to kfree() the returned siginfo_t. */ siginfo_t *mpx_generate_siginfo (struct pt_regs *regs, -struct xregs_state *xsave_buf) +struct task_struct *tsk) {struct bndreg *bndregs, *bndreg;siginfo_t *info =NULL ;
@@ -294,8 +294,8 @@ siginfo_t *mpx_generate_siginfo(struct p err = -EINVAL ; goto err_out; } -/* get the bndregs _area_ of the xsave structure */ - bndregs =get_xsave_addr (xsave_buf,XSTATE_BNDREGS ); +/* get bndregs field from current task's xsave area */ + bndregs =get_xsave_field_ptr (XSTATE_BNDREGS );if (!bndregs) { err = -EINVAL ; goto err_out;
@@ -357,8 +357,7 @@ static __user void *task_get_bounds_dir(* The bounds directory pointer is stored in a register * only accessible if we first do an xsave. */ -copy_fpregs_to_fpstate (&tsk->thread.fpu); - bndcsr =get_xsave_addr (&tsk->thread.fpu.state.xsave,XSTATE_BNDCSR ); + bndcsr =get_xsave_field_ptr (XSTATE_BNDCSR );if (!bndcsr)return MPX_INVALID_BOUNDS_DIR ;
@@ -389,9 +388,10 @@ int mpx_enable_management(struct task_st* directory into XSAVE/XRSTOR Save Area and enable MPX through * XRSTOR instruction. * -* copy_xregs_to_kernel() is expected to be very expensive. Storing the bounds -* directory here means that we do not have to do xsave in the unmap -* path; we can just use mm->bd_addr instead. +* The copy_xregs_to_kernel() beneath get_xsave_field_ptr() is +* expected to be relatively expensive. Storing the bounds +* directory here means that we do not have to do xsave in the +* unmap path; we can just use mm->bd_addr instead. */ bd_base =task_get_bounds_dir (tsk);down_write (&mm->mmap_sem);
@@ -497,12 +497,12 @@ out_unmap:* bound table is 16KB. With 64-bit mode, the size of BD is 2GB, * and the size of each bound table is 4MB. */ -static int do_mpx_bt_fault (struct xregs_state *xsave_buf) +static int do_mpx_bt_fault (struct task_struct *tsk) {unsigned long bd_entry, bd_base;struct bndcsr *bndcsr; - bndcsr =get_xsave_addr (xsave_buf,XSTATE_BNDCSR ); + bndcsr =get_xsave_field_ptr (XSTATE_BNDCSR );if (!bndcsr)return -EINVAL ;/*
@@ -525,7 +525,7 @@ static int do_mpx_bt_fault(struct xregs_return allocate_bt ((long __user *)bd_entry); } -int mpx_handle_bd_fault (struct xregs_state *xsave_buf) +int mpx_handle_bd_fault (struct task_struct *tsk) {/* * Userspace never asked us to manage the bounds tables,
@@ -534,7 +534,7 @@ int mpx_handle_bd_fault(struct xregs_staif (!kernel_managing_mpx_tables (current->mm))return -EINVAL ; -if (do_mpx_bt_fault (xsave_buf)) { +if (do_mpx_bt_fault (tsk)) {force_sig (SIGSEGV , current);/* * The force_sig() is essentially "handling" this
_
PATCH 4/19 x86, mpx: Cleanup: Do not pass task around when unnecessary
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
The MPX code can only work on the current task. You can not, for instance, enable MPX management in another process or thread. You can also not handle a fault for another process or thread.
Despite this, we pass a task_struct around prolifically. This patch removes all of the task struct passing for code paths where the code can not deal with another task (which turns out to be all of them).
This has no functional changes. It's just a cleanup.
---
b/arch/x86/include/asm/mpx.h | 10 ++++------ b/arch/x86/include/asm/processor.h | 12 ++++++------ b/arch/x86/kernel/traps.c | 5 ++--- b/arch/x86/mm/mpx.c | 19 +++++++++---------- b/kernel/sys.c | 8 ++++---- 5 files changed, 25 insertions(+), 29 deletions(-)
arch/x86/include/asm/mpx.h renamed from arch/x86/include/asm/mpx.h~x86-mpx-dont-pass-current-around +4 -6
@@ -59,9 +59,8 @@ MPX_BT_ENTRY_MASK) << MPX_BT_ENTRY_SHIFT)#ifdef CONFIG_X86_INTEL_MPX -siginfo_t *mpx_generate_siginfo (struct pt_regs *regs, -struct task_struct *tsk); -int mpx_handle_bd_fault (struct task_struct *tsk); +siginfo_t *mpx_generate_siginfo (struct pt_regs *regs); +int mpx_handle_bd_fault (void );static inline int kernel_managing_mpx_tables (struct mm_struct *mm) {return (mm->bd_addr !=MPX_INVALID_BOUNDS_DIR );
@@ -77,12 +76,11 @@ static inline void mpx_mm_init(struct mmvoid mpx_notify_unmap (struct mm_struct *mm,struct vm_area_struct *vma,unsigned long start,unsigned long end);#else -static inline siginfo_t *mpx_generate_siginfo (struct pt_regs *regs, -struct task_struct *tsk) +static inline siginfo_t *mpx_generate_siginfo (struct pt_regs *regs) {return NULL ; } -static inline int mpx_handle_bd_fault (struct task_struct *tsk) +static inline int mpx_handle_bd_fault (void ) {return -EINVAL ; }
arch/x86/include/asm/processor.h renamed from arch/x86/include/asm/processor.h~x86-mpx-dont-pass-current-around +6 -6
@@ -802,18 +802,18 @@ extern int get_tsc_mode(unsigned long adextern int set_tsc_mode (unsigned int val);/* Register/unregister a process' MPX related resource */ -#define MPX_ENABLE_MANAGEMENT (tsk) mpx_enable_management((tsk)) -#define MPX_DISABLE_MANAGEMENT (tsk) mpx_disable_management((tsk)) +#define MPX_ENABLE_MANAGEMENT () mpx_enable_management() +#define MPX_DISABLE_MANAGEMENT () mpx_disable_management()#ifdef CONFIG_X86_INTEL_MPX -extern int mpx_enable_management (struct task_struct *tsk); -extern int mpx_disable_management (struct task_struct *tsk); +extern int mpx_enable_management (void ); +extern int mpx_disable_management (void );#else -static inline int mpx_enable_management (struct task_struct *tsk) +static inline int mpx_enable_management (void ) {return -EINVAL ; } -static inline int mpx_disable_management (struct task_struct *tsk) +static inline int mpx_disable_management (void ) {return -EINVAL ; }
arch/x86/kernel/traps.c renamed from arch/x86/kernel/traps.c~x86-mpx-dont-pass-current-around +2 -3
@@ -371,7 +371,6 @@ dotraplinkage void do_double_fault(struc dotraplinkage void do_bounds(struct pt_regs *regs, long error_code) { -struct task_struct *tsk = current;enum ctx_state prev_state;struct bndcsr *bndcsr;siginfo_t *info;
@@ -407,11 +406,11 @@ dotraplinkage void do_bounds(struct pt_r*/ switch (bndcsr->bndstatus &MPX_BNDSTA_ERROR_CODE ) {case 2 :/* Bound directory has invalid entry. */ -if (mpx_handle_bd_fault (tsk)) +if (mpx_handle_bd_fault ()) goto exit_trap;break ;/* Success, it was handled */ case 1 :/* Bound violation. */ - info =mpx_generate_siginfo (regs, tsk); + info =mpx_generate_siginfo (regs);if (IS_ERR (info)) {/* * We failed to decode the MPX instruction. Act as if
arch/x86/mm/mpx.c renamed from arch/x86/mm/mpx.c~x86-mpx-dont-pass-current-around +9 -10
@@ -271,8 +271,7 @@ bad_opcode:* * The caller is expected to kfree() the returned siginfo_t. */ -siginfo_t *mpx_generate_siginfo (struct pt_regs *regs, -struct task_struct *tsk) +siginfo_t *mpx_generate_siginfo (struct pt_regs *regs) {struct bndreg *bndregs, *bndreg;siginfo_t *info =NULL ;
@@ -340,7 +339,7 @@ err_out:return ERR_PTR (err); } -static __user void *task_get_bounds_dir (struct task_struct *tsk) +static __user void *mpx_get_bounds_dir (void ) {struct bndcsr *bndcsr;
@@ -376,10 +375,10 @@ static __user void *task_get_bounds_dir( (bndcsr->bndcfgu &MPX_BNDCFG_ADDR_MASK ); } -int mpx_enable_management (struct task_struct *tsk) +int mpx_enable_management (void ) {void __user *bd_base =MPX_INVALID_BOUNDS_DIR ; -struct mm_struct *mm = tsk->mm; +struct mm_struct *mm = current->mm;int ret =0 ;/*
@@ -393,7 +392,7 @@ int mpx_enable_management(struct task_st* directory here means that we do not have to do xsave in the * unmap path; we can just use mm->bd_addr instead. */ - bd_base =task_get_bounds_dir (tsk); + bd_base =mpx_get_bounds_dir ();down_write (&mm->mmap_sem); mm->bd_addr = bd_base;if (mm->bd_addr ==MPX_INVALID_BOUNDS_DIR )
@@ -403,7 +402,7 @@ int mpx_enable_management(struct task_streturn ret; } -int mpx_disable_management (struct task_struct *tsk) +int mpx_disable_management (void ) {struct mm_struct *mm = current->mm;
@@ -497,7 +496,7 @@ out_unmap:* bound table is 16KB. With 64-bit mode, the size of BD is 2GB, * and the size of each bound table is 4MB. */ -static int do_mpx_bt_fault (struct task_struct *tsk) +static int do_mpx_bt_fault (void ) {unsigned long bd_entry, bd_base;struct bndcsr *bndcsr;
@@ -525,7 +524,7 @@ static int do_mpx_bt_fault(struct task_sreturn allocate_bt ((long __user *)bd_entry); } -int mpx_handle_bd_fault (struct task_struct *tsk) +int mpx_handle_bd_fault (void ) {/* * Userspace never asked us to manage the bounds tables,
@@ -534,7 +533,7 @@ int mpx_handle_bd_fault(struct task_struif (!kernel_managing_mpx_tables (current->mm))return -EINVAL ; -if (do_mpx_bt_fault (tsk)) { +if (do_mpx_bt_fault ()) {force_sig (SIGSEGV , current);/* * The force_sig() is essentially "handling" this
kernel/sys.c renamed from kernel/sys.c~x86-mpx-dont-pass-current-around +4 -4
@@ -92,10 +92,10 @@# define SET_TSC_CTL (a) (-EINVAL)#endif #ifndef MPX_ENABLE_MANAGEMENT -# define MPX_ENABLE_MANAGEMENT (a) (-EINVAL) +# define MPX_ENABLE_MANAGEMENT () (-EINVAL)#endif #ifndef MPX_DISABLE_MANAGEMENT -# define MPX_DISABLE_MANAGEMENT (a) (-EINVAL) +# define MPX_DISABLE_MANAGEMENT () (-EINVAL)#endif #ifndef GET_FP_MODE # define GET_FP_MODE (a) (-EINVAL)
@@ -2230,12 +2230,12 @@ SYSCALL_DEFINE5(prctl, int, option, unsicase PR_MPX_ENABLE_MANAGEMENT :if (arg2 || arg3 || arg4 || arg5)return -EINVAL ; - error =MPX_ENABLE_MANAGEMENT (me); + error =MPX_ENABLE_MANAGEMENT ();break ;case PR_MPX_DISABLE_MANAGEMENT :if (arg2 || arg3 || arg4 || arg5)return -EINVAL ; - error =MPX_DISABLE_MANAGEMENT (me); + error =MPX_DISABLE_MANAGEMENT ();break ;case PR_SET_FP_MODE : error =SET_FP_MODE (me, arg2);
_
PATCH 5/19 x86, mpx: remove redundant MPX_BNDCFG_ADDR_MASK
In reply to Dave HansenFrom: Qiaowei Ren <[email protected]>
MPX_BNDCFG_ADDR_MASK is defined two times, so this patch removes redundant one.
---
b/arch/x86/include/asm/mpx.h | 1 - 1 file changed, 1 deletion(-)
arch/x86/include/asm/mpx.h renamed from arch/x86/include/asm/mpx.h~0001-x86-mpx-remove-redundant-MPX_BNDCFG_ADDR_MASK +0 -1
@@ -45,7 +45,6 @@#define MPX_BNDSTA_TAIL 2#define MPX_BNDCFG_TAIL 12#define MPX_BNDSTA_ADDR_MASK (~((1UL<<MPX_BNDSTA_TAIL)-1)) -#define MPX_BNDCFG_ADDR_MASK (~((1UL<<MPX_BNDCFG_TAIL)-1))#define MPX_BT_ADDR_MASK (~((1UL<<MPX_BD_ENTRY_TAIL)-1))#define MPX_BNDCFG_ADDR_MASK (~((1UL<<MPX_BNDCFG_TAIL)-1))
_
PATCH 6/19 x86, mpx: Restrict mmap size check to bounds tables
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
The comment and code here are confusing. We do not currently allocate the bounds directory in the kernel.
---
b/arch/x86/mm/mpx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
arch/x86/mm/mpx.c renamed from arch/x86/mm/mpx.c~x86-mpx-we-do-not-allocate-the-bounds-directory +2 -2
@@ -46,8 +46,8 @@ static unsigned long mpx_mmap(unsigned lvm_flags_t vm_flags;struct vm_area_struct *vma; -/* Only bounds table and bounds directory can be allocated here */ -if (len !=MPX_BD_SIZE_BYTES && len !=MPX_BT_SIZE_BYTES ) +/* Only bounds table can be allocated here */ +if (len !=MPX_BT_SIZE_BYTES )return -EINVAL ;down_write (&mm->mmap_sem);
_
PATCH 7/19 x86, mpx: boot-time disable
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
MPX has the _potential_ to cause some issues. Say part of your init system tried to protect one of its components from buffer overflows with MPX. If there were a false positive, it's possible that MPX could keep a system from booting.
MPX could also potentially cause performance issues since it is present in hot paths like the unmap path.
Allow it to be disabled at boot time.
---
b/Documentation/kernel-parameters.txt | 4 ++++ b/arch/x86/kernel/cpu/common.c | 16 ++++++++++++++++ 2 files changed, 20 insertions(+)
arch/x86/kernel/cpu/common.c renamed from arch/x86/kernel/cpu/common.c~x86-mpx-disable-boot-time +16 -0
@@ -144,6 +144,22 @@ DEFINE_PER_CPU_PAGE_ALIGNED(struct gdt_p } };EXPORT_PER_CPU_SYMBOL_GPL (gdt_page); +static int __init x86_mpx_setup (char *s) +{ +/* require an exact match without trailing characters */ +if (strlen (s)) +return 0 ; + +/* do not emit a message if the feature is not present */ +if (!boot_cpu_has (X86_FEATURE_MPX )) +return 1 ; + +setup_clear_cpu_cap (X86_FEATURE_MPX ); +pr_info ("nompx: Intel Memory Protection Extensions (MPX) disabled\n" ); +return 1 ; +} +__setup ("nompx" , x86_mpx_setup); +#ifdef CONFIG_X86_32 static int cachesize_override =-1 ;static int disable_x86_serial_nr =1 ;
Documentation/kernel-parameters.txt renamed from Documentation/kernel-parameters.txt~x86-mpx-disable-boot-time +4 -0
@@ -937,6 +937,10 @@ bytes respectively. Such letter suffixes Enable debug messages at boot time. See Documentation/dynamic-debug-howto.txt for details. + nompx [X86] Disables Intel Memory Protection Extensions. + See Documentation/x86/intel_mpx.txt for more + information about the feature. + eagerfpu= [X86] on enable eager fpu restore off disable eager fpu restore
_
PATCH 8/19 x86, mpx: trace #BR exceptions
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
This is the first in a series of MPX tracing patches. I've found these extremely useful in the process of debugging applications and the kernel code itself.
This exception hooks in to the bounds (#BR) exception very early and allows capturing the key registers which would influence how the exception is handled.
Note that bndcfgu/bndstatus are technically still 64-bit registers even in 32-bit mode.
---
b/arch/x86/include/asm/trace/mpx.h | 49 +++++++++++++++++++++++++++++++++++++ b/arch/x86/kernel/traps.c | 2 + b/arch/x86/mm/mpx.c | 3 ++ 3 files changed, 54 insertions(+)
arch/x86/include/asm/trace/mpx.h new file +49 -0
@@ -0,0 +1,49 @@ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM mpx + +#if !defined(_TRACE_MPX_H) || defined(TRACE_HEADER_MULTI_READ ) +#define _TRACE_MPX_H + +#include <linux/tracepoint.h> + +#ifdef CONFIG_X86_INTEL_MPX + +TRACE_EVENT (bounds_exception_mpx, + + TP_PROTO(struct bndcsr *bndcsr), +TP_ARGS (bndcsr), + +TP_STRUCT__entry ( + __field(u64, bndcfgu) +__field (u64, bndstatus) + ), + +TP_fast_assign ( + __entry->bndcfgu = bndcsr->bndcfgu; + __entry->bndstatus = bndcsr->bndstatus; + ), + +TP_printk ("bndcfgu:0x%llx bndstatus:0x%llx" , + __entry->bndcfgu, + __entry->bndstatus) +); + +#else + +/* +* This gets used outside of MPX-specific code, so we need a stub. +*/ +static inline void trace_bounds_exception_mpx (struct bndcsr *bndcsr) +{ +} + +#endif /* CONFIG_X86_INTEL_MPX */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH asm/trace/ +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE mpx +#endif /* _TRACE_MPX_H */ + +/* This part must be outside protection */ +#include <trace/define_trace.h>
arch/x86/kernel/traps.c renamed from arch/x86/kernel/traps.c~x86-br-exception-trace +2 -0
@@ -60,6 +60,7 @@#include <asm/mach_traps.h> #include <asm/alternative.h> #include <asm/fpu/xstate.h> +#include <asm/trace/mpx.h> #include <asm/mpx.h> #ifdef CONFIG_X86_64
@@ -399,6 +400,7 @@ dotraplinkage void do_bounds(struct pt_rif (!bndcsr) goto exit_trap; +trace_bounds_exception_mpx (bndcsr);/* * The error code field of the BNDSTATUS register communicates status * information of a bound range exception #BR or operation involving
arch/x86/mm/mpx.c renamed from arch/x86/mm/mpx.c~x86-br-exception-trace +3 -0
@@ -17,6 +17,9 @@#include <asm/processor.h> #include <asm/fpu/internal.h> +#define CREATE_TRACE_POINTS +#include <asm/trace/mpx.h> +static const char *mpx_mapping_name (struct vm_area_struct *vma) {return "[mpx]" ;
_
PATCH 9/19 x86, mpx: trace entry to bounds exception paths
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
There are two basic things that can happen as the result of a bounds exception (#BR):
1. We allocate a new bounds table
2. We pass up a bounds exception to userspace.
This patch adds a trace point for the case where we are passing the exception up to userspace with a signal.
We are also explicit that we're printing out the inverse of the 'upper' that we encounter. If you want to filter, for instance, you need to ~ the value first. The reason we do this is because of how 'upper' is stored in the bounds table. If a pointer's range is:
0x1000 -> 0x2000
it is stored in the bounds table as (32-bits here for brevity):
lower: 0x00001000 upper: 0xffffdfff
That is so that an all 0's entry:
lower: 0x00000000 upper: 0x00000000
corresponds to the "init" bounds which store a *range* of:
0x00000000 -> 0xffffffff
That is, by far, the common case, and that lets us use the zero page, or deduplicate the memory, etc... The 'upper' stored in the table is gibberish to print by itself, so we print ~upper to get the *actual*, logical, human-readable value printed out.
---
b/arch/x86/include/asm/trace/mpx.h | 34 ++++++++++++++++++++++++++++++++++ b/arch/x86/mm/mpx.c | 1 + 2 files changed, 35 insertions(+)
arch/x86/include/asm/trace/mpx.h renamed from arch/x86/include/asm/trace/mpx.h~x86-mpx-trace-1 +34 -0
@@ -8,6 +8,40 @@#ifdef CONFIG_X86_INTEL_MPX +TRACE_EVENT (mpx_bounds_register_exception, + +TP_PROTO (void *addr_referenced, + struct bndreg *bndreg), +TP_ARGS (addr_referenced, bndreg), + +TP_STRUCT__entry ( + __field(void *, addr_referenced) + __field(u64, lower_bound) +__field (u64, upper_bound) + ), + +TP_fast_assign ( + __entry->addr_referenced = addr_referenced; + __entry->lower_bound = bndreg->lower_bound; + __entry->upper_bound = bndreg->upper_bound; + ), +/* +* Note that we are printing out the '~' of the upper +* bounds register here. It is actually stored in its +* one's complement form so that its 'init' state +* corresponds to all 0's. But, that looks like +* gibberish when printed out, so print out the 1's +* complement instead of the actual value here. Note +* though that you still need to specify filters for the +* actual value, not the displayed one. +*/ +TP_printk ("address referenced: 0x%p bounds: lower: 0x%llx ~upper: 0x%llx" , + __entry->addr_referenced, + __entry->lower_bound, + ~__entry->upper_bound + ) +); + TRACE_EVENT(bounds_exception_mpx, TP_PROTO(struct bndcsr *bndcsr),
arch/x86/mm/mpx.c renamed from arch/x86/mm/mpx.c~x86-mpx-trace-1 +1 -0
@@ -335,6 +335,7 @@ siginfo_t *mpx_generate_siginfo(struct p err = -EINVAL ; goto err_out; } +trace_mpx_bounds_register_exception (info->si_addr, bndreg);return info; err_out:/* info might be NULL, but kfree() handles that */
_
PATCH 10/19 x86, mpx: Trace the attempts to find bounds tables
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
There are two different events being traced here. They are doing similar things so share a trace "EVENT_CLASS" and are presented together.
1. Trace when MPX is zapping pages "mpx_unmap_zap":
When MPX can not free an entire bounds table, it will instead try to zap unused parts of a bounds table to free the backing memory. This decreases RSS (resident set size) without decreasing the virtual space allocated for bounds tables.
2. Trace attempts to find bounds tables "mpx_unmap_search":
This event traces any time we go looking to unmap a bounds table for a given virtual address range. This is useful to ensure that the kernel actually "tried" to free a bounds table versus times it succeeded in finding one.
It might try and fail if it realized that a table was shared with an adjacent VMA which is not being unmapped.
---
b/arch/x86/include/asm/trace/mpx.h | 32 ++++++++++++++++++++++++++++++++ b/arch/x86/mm/mpx.c | 2 ++ 2 files changed, 34 insertions(+)
arch/x86/include/asm/trace/mpx.h renamed from arch/x86/include/asm/trace/mpx.h~mpx-trace_unmap_zap +32 -0
@@ -62,6 +62,38 @@ TRACE_EVENT(bounds_exception_mpx, __entry->bndstatus) ); +DECLARE_EVENT_CLASS (mpx_range_trace, + + TP_PROTO(unsigned long start, + unsigned long end), +TP_ARGS (start, end), + +TP_STRUCT__entry ( + __field(unsigned long, start) + __field(unsigned long, end) + ), + +TP_fast_assign ( + __entry->start = start; + __entry->end = end; + ), + +TP_printk ("[0x%p:0x%p]" , + (void *)__entry->start, + (void *)__entry->end + ) +); + +DEFINE_EVENT (mpx_range_trace, mpx_unmap_zap, + TP_PROTO(unsigned long start, unsigned long end), +TP_ARGS (start, end) +); + +DEFINE_EVENT (mpx_range_trace, mpx_unmap_search, + TP_PROTO(unsigned long start, unsigned long end), +TP_ARGS (start, end) +); +#else /*
arch/x86/mm/mpx.c renamed from arch/x86/mm/mpx.c~mpx-trace_unmap_zap +2 -0
@@ -668,6 +668,7 @@ static int zap_bt_entries(struct mm_stru len =min (vma->vm_end, end) - addr;zap_page_range (vma, addr, len,NULL ); +trace_mpx_unmap_zap (addr, addr+len); vma = vma->vm_next; addr = vma->vm_start;
@@ -840,6 +841,7 @@ static int mpx_unmap_tables(struct mm_stlong __user *bd_entry, *bde_start, *bde_end;unsigned long bt_addr; +trace_mpx_unmap_search (start, end);/* * "Edge" bounds tables are those which are being used by the region * (start -> end), but that may be shared with adjacent areas. If they
_
PATCH 11/19 x86, mpx: trace allocation of new bounds tables
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
Bounds tables are a significant consumer of memory. It is important to know when they are being allocated. Add a trace point to trace whenever an allocation occurs and also its virtual address.
---
b/arch/x86/include/asm/trace/mpx.h | 16 ++++++++++++++++ b/arch/x86/mm/mpx.c | 1 + 2 files changed, 17 insertions(+)
arch/x86/include/asm/trace/mpx.h renamed from arch/x86/include/asm/trace/mpx.h~trace_mpx_new_bounds_table +16 -0
@@ -94,6 +94,22 @@ DEFINE_EVENT(mpx_range_trace, mpx_unmap_TP_ARGS (start, end) ); +TRACE_EVENT (mpx_new_bounds_table, + + TP_PROTO(unsigned long table_vaddr), +TP_ARGS (table_vaddr), + +TP_STRUCT__entry ( + __field(unsigned long, table_vaddr) + ), + +TP_fast_assign ( + __entry->table_vaddr = table_vaddr; + ), + +TP_printk ("table vaddr:%p" , (void *)__entry->table_vaddr) +); +#else /*
arch/x86/mm/mpx.c renamed from arch/x86/mm/mpx.c~trace_mpx_new_bounds_table +1 -0
@@ -483,6 +483,7 @@ static int allocate_bt(long __user *bd_e ret = -EINVAL ; goto out_unmap; } +trace_mpx_new_bounds_table (bt_addr);return 0 ; out_unmap:vm_munmap (bt_addr &MPX_BT_ADDR_MASK ,MPX_BT_SIZE_BYTES );
_
PATCH 12/19 x86: make is_64bit_mm() widely available
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
The uprobes code has a nice helper, is_64bit_mm(), that consults both the runtime and compile-time flags for 32-bit support. Instead of reinventing the wheel, pull it in to an x86 header so we can use it for MPX.
I prefer passing the mm around to test_thread_flag(TIF_IA32) because it makes it explicit where the context is coming from.
---
b/arch/x86/include/asm/mmu_context.h | 13 +++++++++++++ b/arch/x86/kernel/uprobes.c | 10 +--------- 2 files changed, 14 insertions(+), 9 deletions(-)
arch/x86/include/asm/mmu_context.h renamed from arch/x86/include/asm/mmu_context.h~x86-make-is_64bit_mm-available +13 -0
@@ -142,6 +142,19 @@ static inline void arch_exit_mmap(structparavirt_arch_exit_mmap (mm); } +#ifdef CONFIG_X86_64 +static inline bool is_64bit_mm (struct mm_struct *mm) +{ +return !config_enabled (CONFIG_IA32_EMULATION ) || + !(mm->context.ia32_compat ==TIF_IA32 ); +} +#else +static inline bool is_64bit_mm (struct mm_struct *mm) +{ +return false; +} +#endif +static inline void arch_bprm_mm_init (struct mm_struct *mm,struct vm_area_struct *vma) {
arch/x86/kernel/uprobes.c renamed from arch/x86/kernel/uprobes.c~x86-make-is_64bit_mm-available +1 -9
@@ -29,6 +29,7 @@#include <linux/kdebug.h> #include <asm/processor.h> #include <asm/insn.h> +#include <asm/mmu_context.h> /* Post-execution fixups. */
@@ -312,11 +313,6 @@ static int uprobe_init_insn(struct arch_ }#ifdef CONFIG_X86_64 -static inline bool is_64bit_mm (struct mm_struct *mm) -{ -return !config_enabled (CONFIG_IA32_EMULATION ) || - !(mm->context.ia32_compat ==TIF_IA32 ); -}/* * If arch_uprobe->insn doesn't use rip-relative addressing, return * immediately. Otherwise, rewrite the instruction so that it accesses
@@ -497,10 +493,6 @@ static void riprel_post_xol(struct arch_ } }#else /* 32-bit: */ -static inline bool is_64bit_mm (struct mm_struct *mm) -{ -return false; -}/* * No RIP-relative addressing on 32-bit */
_
PATCH 13/19 x86, mpx: Add temporary variable to reduce masking
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
When we allocate a bounds table, we call mmap(), then add a "valid" bit to the value before storing it in to the bounds directory.
If we fail along the way, we go and mask that valid bit _back_ out. That seems a little silly, and this makes it much more clear when we have a plain address versus an actual table _entry_.
---
b/arch/x86/mm/mpx.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-)
arch/x86/mm/mpx.c renamed from arch/x86/mm/mpx.c~mpx-remove-unnecessary-masking +4 -3
@@ -429,6 +429,7 @@ static int allocate_bt(long __user *bd_eunsigned long expected_old_val =0 ;unsigned long actual_old_val =0 ;unsigned long bt_addr; +unsigned long bd_new_entry;int ret =0 ;/*
@@ -441,7 +442,7 @@ static int allocate_bt(long __user *bd_e/* * Set the valid flag (kinda like _PAGE_PRESENT in a pte) */ - bt_addr = bt_addr |MPX_BD_ENTRY_VALID_FLAG ; + bd_new_entry = bt_addr |MPX_BD_ENTRY_VALID_FLAG ;/* * Go poke the address of the new bounds table in to the
@@ -455,7 +456,7 @@ static int allocate_bt(long __user *bd_e* of the MPX code that have to pagefault_disable(). */ ret =user_atomic_cmpxchg_inatomic (&actual_old_val, bd_entry, - expected_old_val, bt_addr); + expected_old_val, bd_new_entry);if (ret) goto out_unmap;
@@ -486,7 +487,7 @@ static int allocate_bt(long __user *bd_etrace_mpx_new_bounds_table (bt_addr);return 0 ; out_unmap: -vm_munmap (bt_addr &MPX_BT_ADDR_MASK ,MPX_BT_SIZE_BYTES ); +vm_munmap (bt_addr,MPX_BT_SIZE_BYTES );return ret; }
_
PATCH 14/19 x86, mpx: new directory entry to addr helper
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
Currently, to get from a bounds directory entry to the virtual address of a bounds table, we simply mask off a few low bits. However, the set of bits we mask off is different for 32 and 64-bit binaries.
This breaks the operation out in to a helper function and also adds a temporary variable to store the result until we are sure we are returning one.
---
b/arch/x86/include/asm/mpx.h | 1 - b/arch/x86/mm/mpx.c | 41 ++++++++++++++++++++++++++++++++++------- 2 files changed, 34 insertions(+), 8 deletions(-)
arch/x86/include/asm/mpx.h renamed from arch/x86/include/asm/mpx.h~mpx-new-entry-to-addr-helper +0 -1
@@ -45,7 +45,6 @@#define MPX_BNDSTA_TAIL 2#define MPX_BNDCFG_TAIL 12#define MPX_BNDSTA_ADDR_MASK (~((1UL<<MPX_BNDSTA_TAIL)-1)) -#define MPX_BT_ADDR_MASK (~((1UL<<MPX_BD_ENTRY_TAIL)-1))#define MPX_BNDCFG_ADDR_MASK (~((1UL<<MPX_BNDCFG_TAIL)-1))#define MPX_BNDSTA_ERROR_CODE 0x3
arch/x86/mm/mpx.c renamed from arch/x86/mm/mpx.c~mpx-new-entry-to-addr-helper +34 -7
84 lines not on this page · raw message
_
PATCH 15/19 x86, mpx: do 32-bit-only cmpxchg for 32-bit apps
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
user_atomic_cmpxchg_inatomic() actually looks at sizeof(*ptr) to figure out how many bytes to copy. If we run it on a 64-bit kernel with a 64-bit pointer, it will copy a 64-bit bounds directory entry. That's fine, except when we have 32-bit programs with 32-bit bounds directory entries and we only *want* 32-bits.
This patch breaks the cmpxchg operation out in to its own function and performs the 32-bit type swizzling in there.
Note, the "64-bit" version of this code _would_ work on a 32-bit-only kernel. The issue this patch addresses is only for when the kernel's 'long' is mismatched from the size of the bounds directory entry of the process we are working on.
The new helper modifies 'actual_old_val' or returns an error. But gcc doesn't know this, so it warns about 'actual_old_val' being unused. Shut it up with an uninitialized_var().
---
Changes from v21:
* correct type of bd_entry_addr from long -> unsigned long
* shorten variable names
* add whitespace after variables
* unconditionally do assignment
---
b/arch/x86/mm/mpx.c | 41 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-)
arch/x86/mm/mpx.c renamed from arch/x86/mm/mpx.c~mpx-variable-sized-userspace-pokes +36 -5
@@ -419,6 +419,35 @@ int mpx_disable_management(void)return 0 ; } +static int mpx_cmpxchg_bd_entry (struct mm_struct *mm, +unsigned long *curval, +unsigned long __user *addr, +unsigned long old_val,unsigned long new_val) +{ +int ret; +/* +* user_atomic_cmpxchg_inatomic() actually uses sizeof() +* the pointer that we pass to it to figure out how much +* data to cmpxchg. We have to be careful here not to +* pass a pointer to a 64-bit data type when we only want +* a 32-bit copy. +*/ +if (is_64bit_mm (mm)) { + ret =user_atomic_cmpxchg_inatomic (curval, + addr, old_val, new_val); + }else { +u32 uninitialized_var (curval_32 ); +u32 old_val_32 = old_val; +u32 new_val_32 = new_val; +u32 __user *addr_32 = (u32 __user *)addr; + + ret =user_atomic_cmpxchg_inatomic (&curval_32, + addr_32, old_val_32, new_val_32); + *curval = curval_32; + } +return ret; +} +/* * With 32-bit mode, MPX_BT_SIZE_BYTES is 4MB, and the size of each * bounds table is 16KB. With 64-bit mode, MPX_BT_SIZE_BYTES is 2GB,
@@ -426,6 +455,7 @@ int mpx_disable_management(void)*/ static int allocate_bt (long __user *bd_entry) { +struct mm_struct *mm = current->mm;unsigned long expected_old_val =0 ;unsigned long actual_old_val =0 ;unsigned long bt_addr;
@@ -455,8 +485,8 @@ static int allocate_bt(long __user *bd_e* mmap_sem at this point, unlike some of the other part * of the MPX code that have to pagefault_disable(). */ - ret =user_atomic_cmpxchg_inatomic (&actual_old_val, bd_entry, - expected_old_val, bd_new_entry); + ret =mpx_cmpxchg_bd_entry (mm, &actual_old_val, bd_entry, + expected_old_val, bd_new_entry);if (ret) goto out_unmap;
@@ -710,15 +740,16 @@ static int unmap_single_bt(struct mm_str long __user *bd_entry, unsigned long bt_addr) {unsigned long expected_old_val = bt_addr |MPX_BD_ENTRY_VALID_FLAG ; -unsigned long actual_old_val =0 ; +unsigned long uninitialized_var (actual_old_val );int ret;while (1 ) {int need_write =1 ; +unsigned long cleared_bd_entry =0 ;pagefault_disable (); - ret =user_atomic_cmpxchg_inatomic (&actual_old_val, bd_entry, - expected_old_val,0 ); + ret =mpx_cmpxchg_bd_entry (mm, &actual_old_val, + bd_entry, expected_old_val, cleared_bd_entry);pagefault_enable ();if (!ret)break ;
_
PATCH 16/19 x86, mpx: support 32-bit binaries on 64-bit kernel
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
Right now, the kernel can only switch between 64-bit and 32-bit binaries at compile time. This patch adds support for 32-bit binaries on 64-bit kernels when we support ia32 emulation.
We essentially choose which set of table sizes to use when doing arithmetic for the bounds table calculations.
This also uses a different approach for calculating the table indexes than before. I think the new one makes it much more clear what is going on, and allows us to share more code between the 32 and 64-bit cases.
--
Changes from v22:
* fix spelling nit
Changes from v21:
* remove old comment
* add a comment about why the masks are OK
* use cpu_info->x86_virt_bits
* add comment about lack of 32-bit hole
Changes from v20:
* Fix macro confusion between BD and BT
* Add accessor for bt_entry_size_bytes()
---
b/arch/x86/include/asm/mpx.h | 66 ++++++++-------- b/arch/x86/mm/mpx.c | 170 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 181 insertions(+), 55 deletions(-)
arch/x86/include/asm/mpx.h renamed from arch/x86/include/asm/mpx.h~0002-x86-mpx-support-32bit-binaries-on-64bit-kernel +32 -34
@@ -13,49 +13,47 @@#define MPX_BNDCFG_ENABLE_FLAG 0x1#define MPX_BD_ENTRY_VALID_FLAG 0x1 -#ifdef CONFIG_X86_64 - -/* upper 28 bits [47:20] of the virtual address in 64-bit used to -* index into bounds directory (BD). +/* +* The upper 28 bits [47:20] of the virtual address in 64-bit +* are used to index into bounds directory (BD). +* +* The directory is 2G (2^31) in size, and with 8-byte entries +* it has 2^28 entries. */ -#define MPX_BD_ENTRY_OFFSET 28 -#define MPX_BD_ENTRY_SHIFT 3 -/* bits [19:3] of the virtual address in 64-bit used to index into -* bounds table (BT). +#define MPX_BD_SIZE_BYTES_64 (1UL<<31) +#define MPX_BD_ENTRY_BYTES_64 8 +#define MPX_BD_NR_ENTRIES_64 (MPX_BD_SIZE_BYTES_64/MPX_BD_ENTRY_BYTES_64) + +/* +* The 32-bit directory is 4MB (2^22) in size, and with 4-byte +* entries it has 2^20 entries. */ -#define MPX_BT_ENTRY_OFFSET 17 -#define MPX_BT_ENTRY_SHIFT 5 -#define MPX_IGN_BITS 3 -#define MPX_BD_ENTRY_TAIL 3 - -#else - -#define MPX_BD_ENTRY_OFFSET 20 -#define MPX_BD_ENTRY_SHIFT 2 -#define MPX_BT_ENTRY_OFFSET 10 -#define MPX_BT_ENTRY_SHIFT 4 -#define MPX_IGN_BITS 2 -#define MPX_BD_ENTRY_TAIL 2 - -#endif - -#define MPX_BD_SIZE_BYTES (1UL<<(MPX_BD_ENTRY_OFFSET+MPX_BD_ENTRY_SHIFT)) -#define MPX_BT_SIZE_BYTES (1UL<<(MPX_BT_ENTRY_OFFSET+MPX_BT_ENTRY_SHIFT)) +#define MPX_BD_SIZE_BYTES_32 (1UL<<22) +#define MPX_BD_ENTRY_BYTES_32 4 +#define MPX_BD_NR_ENTRIES_32 (MPX_BD_SIZE_BYTES_32/MPX_BD_ENTRY_BYTES_32) + +/* +* A 64-bit table is 4MB total in size, and an entry is +* 4 64-bit pointers in size. +*/ +#define MPX_BT_SIZE_BYTES_64 (1UL<<22) +#define MPX_BT_ENTRY_BYTES_64 32 +#define MPX_BT_NR_ENTRIES_64 (MPX_BT_SIZE_BYTES_64/MPX_BT_ENTRY_BYTES_64) + +/* +* A 32-bit table is 16kB total in size, and an entry is +* 4 32-bit pointers in size. +*/ +#define MPX_BT_SIZE_BYTES_32 (1UL<<14) +#define MPX_BT_ENTRY_BYTES_32 16 +#define MPX_BT_NR_ENTRIES_32 (MPX_BT_SIZE_BYTES_32/MPX_BT_ENTRY_BYTES_32)#define MPX_BNDSTA_TAIL 2#define MPX_BNDCFG_TAIL 12#define MPX_BNDSTA_ADDR_MASK (~((1UL<<MPX_BNDSTA_TAIL)-1)) -#define MPX_BNDCFG_ADDR_MASK (~((1UL<<MPX_BNDCFG_TAIL)-1))#define MPX_BNDSTA_ERROR_CODE 0x3 -#define MPX_BD_ENTRY_MASK ((1<<MPX_BD_ENTRY_OFFSET)-1) -#define MPX_BT_ENTRY_MASK ((1<<MPX_BT_ENTRY_OFFSET)-1) -#define MPX_GET_BD_ENTRY_OFFSET (addr) ((((addr)>>(MPX_BT_ENTRY_OFFSET+ \ - MPX_IGN_BITS)) & MPX_BD_ENTRY_MASK) << MPX_BD_ENTRY_SHIFT) -#define MPX_GET_BT_ENTRY_OFFSET (addr) ((((addr)>>MPX_IGN_BITS) & \ - MPX_BT_ENTRY_MASK) << MPX_BT_ENTRY_SHIFT) -#ifdef CONFIG_X86_INTEL_MPX siginfo_t *mpx_generate_siginfo (struct pt_regs *regs);int mpx_handle_bd_fault (void );
arch/x86/mm/mpx.c renamed from arch/x86/mm/mpx.c~0002-x86-mpx-support-32bit-binaries-on-64bit-kernel +149 -21
261 lines not on this page · raw message
_
PATCH 17/19 x86, mpx: rewrite unmap code
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
The MPX code needs to clear out bounds tables for memory which is no longer in use. We do this when a userspace mapping is torn down (unmapped).
There are two modes:
1. An entire bounds table becomes unused, and can be freed and its pointer removed from the bounds directory. This happens either when a large mapping is torn down, or when a small mapping is torn down and it is the last mapping "covered" by a bounds table.
2. Only part of a bounds table becomes unused, in which case we free the backing memory as if MADV_DONTNEED was called.
The old code was a spaghetti mess of "edge" bounds tables where the edges were handled specially, even if we were unmapping an entire one. Non-edge bounds tables are always fully unmapped, but share a different code path from the edge ones. The old code had a bug where it was unmapping too much memory. I worked on fixing it for two days and gave up.
I didn't write the original code. I didn't particularly like it, but it worked, so I left it. After my debug session, I realized it was undebuggagle *and* buggy, so out it went.
I also wrote a new unmapping test program which uncovers bugs pretty nicely.
---
b/arch/x86/mm/mpx.c | 411 +++++++++++++++++++++------------------------------- 1 file changed, 168 insertions(+), 243 deletions(-)
arch/x86/mm/mpx.c renamed from arch/x86/mm/mpx.c~rewrite-unmap-code +168 -243
@@ -704,110 +704,6 @@ static int get_bt_addr(struct mm_structreturn 0 ; } -/* -* Free the backing physical pages of bounds table 'bt_addr'. -* Assume start...end is within that bounds table. -*/ -static int zap_bt_entries (struct mm_struct *mm, -unsigned long bt_addr, -unsigned long start,unsigned long end) -{ -struct vm_area_struct *vma; -unsigned long addr, len; - -/* -* Find the first overlapping vma. If vma->vm_start > start, there -* will be a hole in the bounds table. This -EINVAL return will -* cause a SIGSEGV. -*/ - vma =find_vma (mm, start); -if (!vma || vma->vm_start > start) -return -EINVAL ; - -/* -* A NUMA policy on a VM_MPX VMA could cause this bouds table to -* be split. So we need to look across the entire 'start -> end' -* range of this bounds table, find all of the VM_MPX VMAs, and -* zap only those. -*/ - addr = start; -while (vma && vma->vm_start < end) { -/* -* We followed a bounds directory entry down -* here. If we find a non-MPX VMA, that's bad, -* so stop immediately and return an error. This -* probably results in a SIGSEGV. -*/ -if (!is_mpx_vma (vma)) -return -EINVAL ; - - len =min (vma->vm_end, end) - addr; -zap_page_range (vma, addr, len,NULL ); -trace_mpx_unmap_zap (addr, addr+len); - - vma = vma->vm_next; - addr = vma->vm_start; - } - -return 0 ; -} - -static int unmap_single_bt (struct mm_struct *mm, -long __user *bd_entry,unsigned long bt_addr) -{ -unsigned long expected_old_val = bt_addr |MPX_BD_ENTRY_VALID_FLAG ; -unsigned long uninitialized_var (actual_old_val ); -int ret; - -while (1 ) { -int need_write =1 ; -unsigned long cleared_bd_entry =0 ; - -pagefault_disable (); - ret =mpx_cmpxchg_bd_entry (mm, &actual_old_val, - bd_entry, expected_old_val, cleared_bd_entry); -pagefault_enable (); -if (!ret) -break ; -if (ret == -EFAULT ) - ret =mpx_resolve_fault (bd_entry, need_write); -/* -* If we could not resolve the fault, consider it -* userspace's fault and error out. -*/ -if (ret) -return ret; - } -/* -* The cmpxchg was performed, check the results. -*/ -if (actual_old_val != expected_old_val) { -/* -* Someone else raced with us to unmap the table. -* There was no bounds table pointed to by the -* directory, so declare success. Somebody freed -* it. -*/ -if (!actual_old_val) -return 0 ; -/* -* Something messed with the bounds directory -* entry. We hold mmap_sem for read or write -* here, so it could not be a _new_ bounds table -* that someone just allocated. Something is -* wrong, so pass up the error and SIGSEGV. -*/ -return -EINVAL ; - } - -/* -* Note, we are likely being called under do_munmap() already. To -* avoid recursion, do_munmap() will check whether it comes -* from one bounds table through VM_MPX flag. -*/ -return do_munmap (mm, bt_addr,mpx_bt_size_bytes (mm)); -} -static inline int bt_entry_size_bytes (struct mm_struct *mm) {if (is_64bit_mm (mm))
@@ -872,13 +768,69 @@ static inline unsigned long bd_entry_vir }/* -* Return an offset in terms of bytes in to the bounds -* directory where the bounds directory entry for a given -* virtual address resides. -* -* This has to be in bytes because the directory entries -* are different sizes on 64/32 bit. +* Free the backing physical pages of bounds table 'bt_addr'. +* Assume start...end is within that bounds table. */ +static noinline int zap_bt_entries_mapping(struct mm_struct *mm, +unsigned long bt_addr, +unsigned long start_mapping,unsigned long end_mapping) +{ +struct vm_area_struct *vma; +unsigned long addr, len; +unsigned long start; +unsigned long end; + +/* +* if we 'end' on a boundary, the offset will be 0 which +* is not what we want. Back it up a byte to get the +* last bt entry. Then once we have the entry itself, +* move 'end' back up by the table entry size. +*/ + start = bt_addr +mpx_get_bt_entry_offset_bytes (mm, start_mapping); + end = bt_addr +mpx_get_bt_entry_offset_bytes (mm, end_mapping -1 ); +/* +* Move end back up by one entry. Among other things +* this ensures that it remains page-aligned and does +* not screw up zap_page_range() +*/ + end +=bt_entry_size_bytes (mm); + +/* +* Find the first overlapping vma. If vma->vm_start > start, there +* will be a hole in the bounds table. This -EINVAL return will +* cause a SIGSEGV. +*/ + vma =find_vma (mm, start); +if (!vma || vma->vm_start > start) +return -EINVAL ; + +/* +* A NUMA policy on a VM_MPX VMA could cause this bounds table to +* be split. So we need to look across the entire 'start -> end' +* range of this bounds table, find all of the VM_MPX VMAs, and +* zap only those. +*/ + addr = start; +while (vma && vma->vm_start < end) { +/* +* We followed a bounds directory entry down +* here. If we find a non-MPX VMA, that's bad, +* so stop immediately and return an error. This +* probably results in a SIGSEGV. +*/ +if (!is_mpx_vma (vma)) +return -EINVAL ; + + len =min (vma->vm_end, end) - addr; +zap_page_range (vma, addr, len,NULL ); +trace_mpx_unmap_zap (addr, addr+len); + + vma = vma->vm_next; + addr = vma->vm_start; + } +return 0 ; +} +static unsigned long mpx_get_bd_entry_offset (struct mm_struct *mm,unsigned long addr) {
@@ -916,69 +868,80 @@ static unsigned long mpx_get_bd_entry_of*/ } -/* -* If the bounds table pointed by bounds directory 'bd_entry' is -* not shared, unmap this whole bounds table. Otherwise, only free -* those backing physical pages of bounds table entries covered -* in this virtual address region start...end. -*/ -static int unmap_shared_bt (struct mm_struct *mm, -long __user *bd_entry,unsigned long start, -unsigned long end,bool prev_shared,bool next_shared) +static int unmap_entire_bt (struct mm_struct *mm, +long __user *bd_entry,unsigned long bt_addr) { -unsigned long bt_addr; -unsigned long start_off, end_off; +unsigned long expected_old_val = bt_addr |MPX_BD_ENTRY_VALID_FLAG ; +unsigned long uninitialized_var (actual_old_val );int ret; - ret =get_bt_addr (mm, bd_entry, &bt_addr); +while (1 ) { +int need_write =1 ; +unsigned long cleared_bd_entry =0 ; + +pagefault_disable (); + ret =mpx_cmpxchg_bd_entry (mm, &actual_old_val, + bd_entry, expected_old_val, cleared_bd_entry); +pagefault_enable (); +if (!ret) +break ; +if (ret == -EFAULT ) + ret =mpx_resolve_fault (bd_entry, need_write); +/* +* If we could not resolve the fault, consider it +* userspace's fault and error out. +*/ +if (ret) +return ret; + } +/* +* The cmpxchg was performed, check the results. +*/ +if (actual_old_val != expected_old_val) { +/* +* Someone else raced with us to unmap the table. +* That is OK, since we were both trying to do +* the same thing. Declare success. +*/ +if (!actual_old_val) +return 0 ; +/* +* Something messed with the bounds directory +* entry. We hold mmap_sem for read or write +* here, so it could not be a _new_ bounds table +* that someone just allocated. Something is +* wrong, so pass up the error and SIGSEGV. +*/ +return -EINVAL ; + }/* -* We could see an "error" ret for not-present bounds -* tables (not really an error), or actual errors, but -* stop unmapping either way. +* Note, we are likely being called under do_munmap() already. To +* avoid recursion, do_munmap() will check whether it comes +* from one bounds table through VM_MPX flag. */ -if (ret) -return ret; - - start_off =mpx_get_bt_entry_offset_bytes (mm, start); - end_off =mpx_get_bt_entry_offset_bytes (mm, end); - -if (prev_shared && next_shared) - ret =zap_bt_entries (mm, bt_addr, - bt_addr + start_off, - bt_addr + end_off); -else if (prev_shared) - ret =zap_bt_entries (mm, bt_addr, - bt_addr + start_off, - bt_addr +mpx_bt_size_bytes (mm)); -else if (next_shared) - ret =zap_bt_entries (mm, bt_addr, bt_addr, - bt_addr + end_off); -else - ret =unmap_single_bt (mm, bd_entry, bt_addr); - -return ret; +return do_munmap (mm, bt_addr,mpx_bt_size_bytes (mm)); } -/* -* A virtual address region being munmap()ed might share bounds table -* with adjacent VMAs. We only need to free the backing physical -* memory of these shared bounds tables entries covered in this virtual -* address region. -*/ -static int unmap_edge_bts (struct mm_struct *mm, -unsigned long start,unsigned long end) +static int try_unmap_single_bt (struct mm_struct *mm, +unsigned long start,unsigned long end) { +struct vm_area_struct *next; +struct vm_area_struct *prev; +/* +* "bta" == Bounds Table Area: the area controlled by the +* bounds table that we are unmapping. +*/ +unsigned long bta_start_vaddr = start & ~(bd_entry_virt_space (mm)-1 ); +unsigned long bta_end_vaddr = bta_start_vaddr +bd_entry_virt_space (mm); +unsigned long uninitialized_var (bt_addr ); +void __user *bde_vaddr;int ret; -long __user *bde_start, *bde_end; -struct vm_area_struct *prev, *next; -bool prev_shared = false, next_shared = false; - - bde_start = mm->bd_addr +mpx_get_bd_entry_offset (mm, start); - bde_end = mm->bd_addr +mpx_get_bd_entry_offset (mm, end-1 ); -/* -* Check whether bde_start and bde_end are shared with adjacent -* VMAs. +* We know 'start' and 'end' lie within an area controlled +* by a single bounds table. See if there are any other +* VMAs controlled by that bounds table. If there are not +* then we can "expand" the are we are unmapping to possibly +* cover the entire table. * * We already unliked the VMAs from the mm's rbtree so 'start' * is guaranteed to be in a hole. This gets us the first VMA
@@ -986,102 +949,64 @@ static int unmap_edge_bts(struct mm_stru* in to 'next'. */ next =find_vma_prev (mm, start, &prev); -if (prev && (mm->bd_addr +mpx_get_bd_entry_offset (mm, prev->vm_end-1 )) - == bde_start) - prev_shared = true; -if (next && (mm->bd_addr +mpx_get_bd_entry_offset (mm, next->vm_start)) - == bde_end) - next_shared = true; - -/* -* This virtual address region being munmap()ed is only -* covered by one bounds table. -* -* In this case, if this table is also shared with adjacent -* VMAs, only part of the backing physical memory of the bounds -* table need be freeed. Otherwise the whole bounds table need -* be unmapped. -*/ -if (bde_start == bde_end) { -return unmap_shared_bt (mm, bde_start, start, end, - prev_shared, next_shared); +if ((!prev || prev->vm_end <= bta_start_vaddr) && + (!next || next->vm_start >= bta_end_vaddr)) { +/* +* No neighbor VMAs controlled by same bounds +* table. Try to unmap the whole thing +*/ + start = bta_start_vaddr; + end = bta_end_vaddr; } + bde_vaddr = mm->bd_addr +mpx_get_bd_entry_offset (mm, start); + ret =get_bt_addr (mm, bde_vaddr, &bt_addr);/* -* If more than one bounds tables are covered in this virtual -* address region being munmap()ed, we need to separately check -* whether bde_start and bde_end are shared with adjacent VMAs. +* No bounds table there, so nothing to unmap. */ - ret =unmap_shared_bt (mm, bde_start, start, end, prev_shared, false); -if (ret) -return ret; - ret =unmap_shared_bt (mm, bde_end, start, end, false, next_shared); +if (ret == -ENOENT ) { + ret =0 ; +return 0 ; + }if (ret)return ret; - -return 0 ; +/* +* We are unmapping an entire table. Either because the +* unmap that started this whole process was large enough +* to cover an entire table, or that the unmap was small +* but was the area covered by a bounds table. +*/ +if ((start == bta_start_vaddr) && + (end == bta_end_vaddr)) +return unmap_entire_bt (mm, bde_vaddr, bt_addr); +return zap_bt_entries_mapping (mm, bt_addr, start, end); }static int mpx_unmap_tables (struct mm_struct *mm,unsigned long start,unsigned long end) { -int ret; -long __user *bd_entry, *bde_start, *bde_end; -unsigned long bt_addr; - +unsigned long one_unmap_start;trace_mpx_unmap_search (start, end); -/* -* "Edge" bounds tables are those which are being used by the region -* (start -> end), but that may be shared with adjacent areas. If they -* turn out to be completely unshared, they will be freed. If they are -* shared, we will free the backing store (like an MADV_DONTNEED) for -* areas used by this region. -*/ - ret =unmap_edge_bts (mm, start, end); -switch (ret) { -/* non-present tables are OK */ -case 0 : -case -ENOENT : -/* Success, or no tables to unmap */ -break ; -case -EINVAL : -case -EFAULT : -default : -return ret; - } - -/* -* Only unmap the bounds table that are -* 1. fully covered -* 2. not at the edges of the mapping, even if full aligned -*/ - bde_start = mm->bd_addr +mpx_get_bd_entry_offset (mm, start); - bde_end = mm->bd_addr +mpx_get_bd_entry_offset (mm, end-1 ); -for (bd_entry = bde_start +1 ; bd_entry < bde_end; bd_entry++) { - ret =get_bt_addr (mm, bd_entry, &bt_addr); -switch (ret) { -case 0 : -break ; -case -ENOENT : -/* No table here, try the next one */ -continue ; -case -EINVAL : -case -EFAULT : -default : -/* -* Note: we are being strict here. -* Any time we run in to an issue -* unmapping tables, we stop and -* SIGSEGV. -*/ -return ret; - } - ret =unmap_single_bt (mm, bd_entry, bt_addr); + one_unmap_start = start; +while (one_unmap_start < end) { +int ret; +unsigned long next_unmap_start =ALIGN (one_unmap_start+1 , +bd_entry_virt_space (mm)); +unsigned long one_unmap_end = end; +/* +* if the end is beyond the current bounds table, +* move it back so we only deal with a single one +* at a time +*/ +if (one_unmap_end > next_unmap_start) + one_unmap_end = next_unmap_start; + ret =try_unmap_single_bt (mm, one_unmap_start, one_unmap_end);if (ret)return ret; - } + one_unmap_start = next_unmap_start; + }return 0 ; }
_
PATCH 18/19 x86, mpx: do not count MPX VMAs as neighbors when unmapping
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
The comment pretty much says it all.
I wrote a test program that does lots of random allocations and forces bounds tables to be created. It came up with a layout like this:
.... | BOUNDS DIRECTORY ENTRY COVERS | ....
| BOUNDS TABLE COVERS || BOUNDS TABLE | REAL ALLOC | BOUNDS TABLE |
Unmapping "REAL ALLOC" should have been able to free the bounds table "covering" the "REAL ALLOC" because it was the last real user. But, the neighboring VMA bounds tables were found, considered as real neighbors, and we declined to free the bounds table covering the area.
Doing this over and over left a small but significant number of these orphans. Handling them is fairly straighforward. All we have to do is walk the VMAs and skip all of the MPX ones when looking for neighbors.
---
b/arch/x86/mm/mpx.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-)
arch/x86/mm/mpx.c renamed from arch/x86/mm/mpx.c~mpx-dont-count-mpx-vmas-as-neighbors +19 -5
@@ -937,16 +937,30 @@ static int try_unmap_single_bt(struct mmvoid __user *bde_vaddr;int ret;/* +* We already unliked the VMAs from the mm's rbtree so 'start' +* is guaranteed to be in a hole. This gets us the first VMA +* before the hole in to 'prev' and the next VMA after the hole +* in to 'next'. +*/ + next =find_vma_prev (mm, start, &prev); +/* +* Do not count other MPX bounds table VMAs as neighbors. +* Although theoretically possible, we do not allow bounds +* tables for bounds tables so our heads do not explode. +* If we count them as neighbors here, we may end up with +* lots of tables even though we have no actual table +* entries in use. +*/ +while (next &&is_mpx_vma (next)) + next = next->vm_next; +while (prev &&is_mpx_vma (prev)) + prev = prev->vm_prev; +/* * We know 'start' and 'end' lie within an area controlled * by a single bounds table. See if there are any other * VMAs controlled by that bounds table. If there are not * then we can "expand" the are we are unmapping to possibly * cover the entire table. -* -* We already unliked the VMAs from the mm's rbtree so 'start' -* is guaranteed to be in a hole. This gets us the first VMA -* before the hole in to 'prev' and the next VMA after the hole -* in to 'next'. */ next =find_vma_prev (mm, start, &prev); if ((!prev || prev->vm_end <= bta_start_vaddr) &&
_
PATCH 19/19 x86, mpx: allow mixed binaries again
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
We explicitly disable allowing 32-bit binaries to enable MPX on 64-bit kernels. Re-allow that.
---
b/arch/x86/mm/mpx.c | 6 ------ 1 file changed, 6 deletions(-)
arch/x86/mm/mpx.c renamed from arch/x86/mm/mpx.c~x86-mpx-allow-mixed-binaries-again +0 -6
@@ -367,12 +367,6 @@ static __user void *mpx_get_bounds_dir(vreturn MPX_INVALID_BOUNDS_DIR ;/* -* 32-bit binaries on 64-bit kernels are currently -* unsupported. -*/ -if (IS_ENABLED (CONFIG_X86_64 ) &&test_thread_flag (TIF_IA32 )) -return MPX_INVALID_BOUNDS_DIR ; -/* * The bounds directory pointer is stored in a register * only accessible if we first do an xsave. */
_