From: Nicholas Piggin <npiggin@gmail.com> Date: 2020-08-28 10:00:38
This is an attempt to fix a few different related issues around
switching mm, TLB flushing, and lazy tlb mm handling.
This will require all architectures to eventually move to disabling
irqs over activate_mm, but it's possible we could add another arch
call after irqs are re-enabled for those few which can't do their
entire activation with irqs disabled.
I'd like some feedback on the sparc/powerpc vs kthread_use_mm
problem too.
Thanks,
Nick
Nicholas Piggin (4):
mm: fix exec activate_mm vs TLB shootdown and lazy tlb switching race
powerpc: select ARCH_WANT_IRQS_OFF_ACTIVATE_MM
sparc64: remove mm_cpumask clearing to fix kthread_use_mm race
powerpc/64s/radix: Fix mm_cpumask trimming race vs kthread_use_mm
arch/Kconfig | 7 +++
arch/powerpc/Kconfig | 1 +
arch/powerpc/include/asm/mmu_context.h | 2 +-
arch/powerpc/include/asm/tlb.h | 13 ------
arch/powerpc/mm/book3s64/radix_tlb.c | 23 ++++++---
arch/sparc/kernel/smp_64.c | 65 ++++++--------------------
fs/exec.c | 17 ++++++-
7 files changed, 54 insertions(+), 74 deletions(-)
--
2.23.0
From: Nicholas Piggin <npiggin@gmail.com> Date: 2020-08-28 10:00:53
Reading and modifying current->mm and current->active_mm and switching
mm should be done with irqs off, to prevent races seeing an intermediate
state.
This is similar to commit 38cf307c1f20 ("mm: fix kthread_use_mm() vs TLB
invalidate"). At exec-time when the new mm is activated, the old one
should usually be single-threaded and no longer used, unless something
else is holding an mm_users reference (which may be possible).
Absent other mm_users, there is also a race with preemption and lazy tlb
switching. Consider the kernel_execve case where the current thread is
using a lazy tlb active mm:
call_usermodehelper()
kernel_execve()
old_mm = current->mm;
active_mm = current->active_mm;
*** preempt *** --------------------> schedule()
prev->active_mm = NULL;
mmdrop(prev active_mm);
...
<-------------------- schedule()
current->mm = mm;
current->active_mm = mm;
if (!old_mm)
mmdrop(active_mm);
If we switch back to the kernel thread from a different mm, there is a
double free of the old active_mm, and a missing free of the new one.
Closing this race only requires interrupts to be disabled while ->mm
and ->active_mm are being switched, but the TLB problem requires also
holding interrupts off over activate_mm. Unfortunately not all archs
can do that yet, e.g., arm defers the switch if irqs are disabled and
expects finish_arch_post_lock_switch() to be called to complete the
flush; um takes a blocking lock in activate_mm().
So as a first step, disable interrupts across the mm/active_mm updates
to close the lazy tlb preempt race, and provide an arch option to
extend that to activate_mm which allows architectures doing IPI based
TLB shootdowns to close the second race.
This is a bit ugly, but in the interest of fixing the bug and backporting
before all architectures are converted this is a compromise.
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
arch/Kconfig | 7 +++++++
fs/exec.c | 17 +++++++++++++++--
2 files changed, 22 insertions(+), 2 deletions(-)
From: Nicholas Piggin <npiggin@gmail.com> Date: 2020-08-28 10:01:10
The de facto (and apparently uncommented) standard for using an mm had,
thanks to this code in sparc if nothing else, been that you must have a
reference on mm_users *and that reference must have been obtained with
mmget()*, i.e., from a thread with a reference to mm_users that had used
the mm.
The introduction of mmget_not_zero() in commit d2005e3f41d4
("userfaultfd: don't pin the user memory in userfaultfd_file_create()")
allowed mm_count holders to aoperate on user mappings asynchronously
from the actual threads using the mm, but they were not to load those
mappings into their TLB (i.e., walking vmas and page tables is okay,
kthread_use_mm() is not).
io_uring 2b188cc1bb857 ("Add io_uring IO interface") added code which
does a kthread_use_mm() from a mmget_not_zero() refcount.
The problem with this is code which previously assumed mm == current->mm
and mm->mm_users == 1 implies the mm will remain single-threaded at
least until this thread creates another mm_users reference, has now
broken.
arch/sparc/kernel/smp_64.c:
if (atomic_read(&mm->mm_users) == 1) {
cpumask_copy(mm_cpumask(mm), cpumask_of(cpu));
goto local_flush_and_out;
}
vs fs/io_uring.c
if (unlikely(!(ctx->flags & IORING_SETUP_SQPOLL) ||
!mmget_not_zero(ctx->sqo_mm)))
return -EFAULT;
kthread_use_mm(ctx->sqo_mm);
mmget_not_zero() could come in right after the mm_users == 1 test, then
kthread_use_mm() which sets its CPU in the mm_cpumask. That update could
be lost if cpumask_copy() occurs afterward.
I propose we fix this by allowing mmget_not_zero() to be a first-class
reference, and not have this obscure undocumented and unchecked
restriction.
The basic fix for sparc64 is to remove its mm_cpumask clearing code. The
optimisation could be effectively restored by sending IPIs to mm_cpumask
members and having them remove themselves from mm_cpumask. This is more
tricky so I leave it as an exercise for someone with a sparc64 SMP.
powerpc has a (currently similarly broken) example.
Cc: sparclinux@vger.kernel.org
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
arch/sparc/kernel/smp_64.c | 65 ++++++++------------------------------
1 file changed, 14 insertions(+), 51 deletions(-)
@@ -1039,38 +1039,9 @@ void smp_fetch_global_pmu(void)*areflush_tlb_*()routines,andtheserunafterflush_cache_*()*whichperformstheflushw.*-*TheSMPTLBcoherencyschemeweuseworksasfollows:-*-*1)mm->cpu_vm_maskisabitmaskofwhichcpusanaddress-*spacehas(potentially)executedon,thisistheheuristic-*weusetoavoiddoingcrosscalls.-*-*Also,forflushingfromkswapdandalsoforclones,we-*usecpu_vm_maskasthelistofcpustomakeruntheTLB.-*-*2)TLBcontextnumbersaresharedgloballyacrossallprocessors-*inthesystem,thisallowsustoplayseveralgamestoavoid-*crosscalls.-*-*Oneinvariantisthatwhenacpuswitchestoaprocess,and-*thatprocessestsk->active_mm->cpu_vm_maskdoesnothavethe-*currentcpu'sbitset,thattlbcontextisflushedlocally.-*-*Iftheaddressspaceisnon-shared(ie.mm->count==1)weavoid-*crosscallswhenwewanttoflushthecurrentlyrunningprocess's-*tlbstate.Thisisdonebyclearingallcpubitsexceptthecurrent-*processor'sincurrent->mm->cpu_vm_maskandperformingthe-*flushlocallyonly.Thiswillforceanysubsequentcpuswhichrun-*thistasktoflushthecontextfromthelocaltlbiftheprocess-*migratestoanothercpu(again).-*-*3)Forsharedaddressspaces(threads)andswappingwebitethe-*bulletformostcasesandperformthecrosscall(butonlyto-*thecpuslistedincpu_vm_mask).-*-*Theperformancegainfrom"optimizing"awaythecrosscallforthreadsis-*questionable(intheorythebigwinforthreadsisthemassivesharingof-*addressspacestateacrossprocessors).+*mm->cpu_vm_maskisabitmaskofwhichcpusanaddress+*spacehas(potentially)executedon,thisistheheuristic+*weusetolimitcrosscalls.*//* This currently is only used by the hugetlb arch pre-fault
From: Nicholas Piggin <npiggin@gmail.com> Date: 2020-08-28 10:01:17
powerpc uses IPIs in some situations to switch a kernel thread away
from a lazy tlb mm, which is subject to the TLB flushing race
described in the changelog introducing ARCH_WANT_IRQS_OFF_ACTIVATE_MM.
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
arch/powerpc/Kconfig | 1 +
arch/powerpc/include/asm/mmu_context.h | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
From: Nicholas Piggin <npiggin@gmail.com> Date: 2020-08-28 10:01:28
Commit 0cef77c7798a7 ("powerpc/64s/radix: flush remote CPUs out of
single-threaded mm_cpumask") added a mechanism to trim the mm_cpumask of
a process under certain conditions. One of the assumptions is that
mm_users would not be incremented via a reference outside the process
context with mmget_not_zero() then go on to kthread_use_mm() via that
reference.
That invariant was broken by io_uring code (see previous sparc64 fix),
but I'll point Fixes: to the original powerpc commit because we are
changing that assumption going forward, so this will make backports
match up.
Fix this by no longer relying on that assumption, but by having each CPU
check the mm is not being used, and clearing their own bit from the mask
if it's okay. This fix relies on commit 38cf307c1f20 ("mm: fix
kthread_use_mm() vs TLB invalidate") to disable irqs over the mm switch,
and ARCH_WANT_IRQS_OFF_ACTIVATE_MM to be enabled.
Fixes: 0cef77c7798a7 ("powerpc/64s/radix: flush remote CPUs out of single-threaded mm_cpumask")
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
arch/powerpc/include/asm/tlb.h | 13 -------------
arch/powerpc/mm/book3s64/radix_tlb.c | 23 ++++++++++++++++-------
2 files changed, 16 insertions(+), 20 deletions(-)
@@ -645,19 +645,29 @@ static void do_exit_flush_lazy_tlb(void *arg)structmm_struct*mm=arg;unsignedlongpid=mm->context.id;+/*+*Akthreadcouldhavedoneammget_not_zero()aftertheflushingCPU+*checkedmm_users==1,andbeintheprocessofkthread_use_mmwhen+*interruptedhere.Inthatcase,current->mmwillbesettomm,+*becausekthread_use_mm()setting->mmandswitchingtothemmis+*donewithinterruptsoff.+*/if(current->mm==mm)-return;/* Local CPU */+gotoout_flush;if(current->active_mm==mm){-/*-*Mustbeakernelthreadbecausesenderissingle-threaded.-*/-BUG_ON(current->mm);+WARN_ON_ONCE(current->mm!=NULL);+/* Is a kernel thread and is using mm as the lazy tlb */mmgrab(&init_mm);-switch_mm(mm,&init_mm,current);current->active_mm=&init_mm;+switch_mm_irqs_off(mm,&init_mm,current);mmdrop(mm);}++atomic_dec(&mm->context.active_cpus);+cpumask_clear_cpu(smp_processor_id(),mm_cpumask(mm));++out_flush:_tlbiel_pid(pid,RIC_FLUSH_ALL);}
On Fri, Aug 28, 2020 at 08:00:19PM +1000, Nicholas Piggin wrote:
Closing this race only requires interrupts to be disabled while ->mm
and ->active_mm are being switched, but the TLB problem requires also
holding interrupts off over activate_mm. Unfortunately not all archs
can do that yet, e.g., arm defers the switch if irqs are disabled and
expects finish_arch_post_lock_switch() to be called to complete the
flush; um takes a blocking lock in activate_mm().
ARM at least has activate_mm() := switch_mm(), so it could be made to
work.
From: Nicholas Piggin <npiggin@gmail.com> Date: 2020-08-31 01:25:49
Excerpts from peterz@infradead.org's message of August 28, 2020 9:15 pm:
On Fri, Aug 28, 2020 at 08:00:19PM +1000, Nicholas Piggin wrote:
quoted
Closing this race only requires interrupts to be disabled while ->mm
and ->active_mm are being switched, but the TLB problem requires also
holding interrupts off over activate_mm. Unfortunately not all archs
can do that yet, e.g., arm defers the switch if irqs are disabled and
expects finish_arch_post_lock_switch() to be called to complete the
flush; um takes a blocking lock in activate_mm().
ARM at least has activate_mm() := switch_mm(), so it could be made to
work.
Yeah, so long as that post_lock_switch switch did the right thing with
respect to its TLB flushing. It should do because arm doesn't seem to
check ->mm or ->active_mm (and if it was broken, the scheduler context
switch would be suspect too). I don't think the fix would be hard, just
that I don't have a good way to test it and qemu isn't great for testing
this kind of thing.
um too I think could probably defer that lock until after interrupts are
enabled again. I might throw a bunch of arch conversion patches over the
wall if this gets merged and try to move things along.
Thanks,
Nick
From: Michael Ellerman <mpe@ellerman.id.au> Date: 2020-09-01 12:11:42
Nicholas Piggin [off-list ref] writes:
Commit 0cef77c7798a7 ("powerpc/64s/radix: flush remote CPUs out of
single-threaded mm_cpumask") added a mechanism to trim the mm_cpumask of
a process under certain conditions. One of the assumptions is that
mm_users would not be incremented via a reference outside the process
context with mmget_not_zero() then go on to kthread_use_mm() via that
reference.
That invariant was broken by io_uring code (see previous sparc64 fix),
but I'll point Fixes: to the original powerpc commit because we are
changing that assumption going forward, so this will make backports
match up.
Fix this by no longer relying on that assumption, but by having each CPU
check the mm is not being used, and clearing their own bit from the mask
if it's okay. This fix relies on commit 38cf307c1f20 ("mm: fix
kthread_use_mm() vs TLB invalidate") to disable irqs over the mm switch,
and ARCH_WANT_IRQS_OFF_ACTIVATE_MM to be enabled.
You could use:
Depends-on: 38cf307c1f20 ("mm: fix kthread_use_mm() vs TLB invalidate")
Fixes: 0cef77c7798a7 ("powerpc/64s/radix: flush remote CPUs out of single-threaded mm_cpumask")
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
arch/powerpc/include/asm/tlb.h | 13 -------------
arch/powerpc/mm/book3s64/radix_tlb.c | 23 ++++++++++++++++-------
2 files changed, 16 insertions(+), 20 deletions(-)
One minor nit below if you're respinning anyway.
You know this stuff better than me, but I still reviewed it and it seems
good to me.
Reviewed-by: Michael Ellerman <mpe@ellerman.id.au>
^
in mm_is_singlethreaded()
Adding that reference would help join the dots for a new reader I think.
cheers
quoted hunk
+ * interrupted here. In that case, current->mm will be set to mm,
+ * because kthread_use_mm() setting ->mm and switching to the mm is
+ * done with interrupts off.
+ */
if (current->mm == mm)
- return; /* Local CPU */
+ goto out_flush;
if (current->active_mm == mm) {
- /*
- * Must be a kernel thread because sender is single-threaded.
- */
- BUG_ON(current->mm);
+ WARN_ON_ONCE(current->mm != NULL);
+ /* Is a kernel thread and is using mm as the lazy tlb */
mmgrab(&init_mm);
- switch_mm(mm, &init_mm, current);
current->active_mm = &init_mm;
+ switch_mm_irqs_off(mm, &init_mm, current);
mmdrop(mm);
}
+
+ atomic_dec(&mm->context.active_cpus);
+ cpumask_clear_cpu(smp_processor_id(), mm_cpumask(mm));
+
+out_flush:
_tlbiel_pid(pid, RIC_FLUSH_ALL);
}
From: Nicholas Piggin <npiggin@gmail.com> Date: 2020-09-02 09:49:00
Excerpts from Michael Ellerman's message of September 1, 2020 10:00 pm:
Nicholas Piggin [off-list ref] writes:
quoted
Commit 0cef77c7798a7 ("powerpc/64s/radix: flush remote CPUs out of
single-threaded mm_cpumask") added a mechanism to trim the mm_cpumask of
a process under certain conditions. One of the assumptions is that
mm_users would not be incremented via a reference outside the process
context with mmget_not_zero() then go on to kthread_use_mm() via that
reference.
That invariant was broken by io_uring code (see previous sparc64 fix),
but I'll point Fixes: to the original powerpc commit because we are
changing that assumption going forward, so this will make backports
match up.
Fix this by no longer relying on that assumption, but by having each CPU
check the mm is not being used, and clearing their own bit from the mask
if it's okay. This fix relies on commit 38cf307c1f20 ("mm: fix
kthread_use_mm() vs TLB invalidate") to disable irqs over the mm switch,
and ARCH_WANT_IRQS_OFF_ACTIVATE_MM to be enabled.
You could use:
Depends-on: 38cf307c1f20 ("mm: fix kthread_use_mm() vs TLB invalidate")
Good idea I wil.
quoted
Fixes: 0cef77c7798a7 ("powerpc/64s/radix: flush remote CPUs out of single-threaded mm_cpumask")
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
arch/powerpc/include/asm/tlb.h | 13 -------------
arch/powerpc/mm/book3s64/radix_tlb.c | 23 ++++++++++++++++-------
2 files changed, 16 insertions(+), 20 deletions(-)
One minor nit below if you're respinning anyway.
You know this stuff better than me, but I still reviewed it and it seems
good to me.
Reviewed-by: Michael Ellerman <mpe@ellerman.id.au>