From: Michael Ellerman <mpe@ellerman.id.au> Date: 2021-02-11 14:01:42
In the past we had a fallback definition for _PAGE_KERNEL_ROX, but we
removed that in commit d82fd29c5a8c ("powerpc/mm: Distribute platform
specific PAGE and PMD flags and definitions") and added definitions
for each MMU family.
However we missed adding a definition for 64s, which was not really a
bug because it's currently not used.
But we'd like to use PAGE_KERNEL_ROX in a future patch so add a
definition now.
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
---
arch/powerpc/include/asm/book3s/64/pgtable.h | 1 +
1 file changed, 1 insertion(+)
From: Michael Ellerman <mpe@ellerman.id.au> Date: 2021-02-11 13:56:30
In hash__mark_rodata_ro() we pass the raw PP_RXXX value to
hash__change_memory_range(). That has the effect of setting the key to
zero, because PP_RXXX contains no key value.
Fix it by using htab_convert_pte_flags(), which knows how to convert a
pgprot into a pp value, including the key.
Fixes: d94b827e89dc ("powerpc/book3s64/kuap: Use Key 3 for kernel mapping with hash translation")
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
---
arch/powerpc/mm/book3s64/hash_pgtable.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
From: Michael Ellerman <mpe@ellerman.id.au> Date: 2021-02-11 14:06:41
The flags argument to plpar_pte_protect() (aka. H_PROTECT), includes
the key in bits 9-13, but currently we always set those bits to zero.
In the past that hasn't been a problem because we always used key 0
for the kernel, and updateboltedpp() is only used for kernel mappings.
However since commit d94b827e89dc ("powerpc/book3s64/kuap: Use Key 3
for kernel mapping with hash translation") we are now inadvertently
changing the key (to zero) when we call plpar_pte_protect().
That hasn't broken anything because updateboltedpp() is only used for
STRICT_KERNEL_RWX, which is currently disabled on 64s due to other
bugs.
But we want to fix that, so first we need to pass the key correctly to
plpar_pte_protect(). In the `newpp` value the low 3 bits of the key
are already in the correct spot, but the high 2 bits of the key need
to be shifted down.
Fixes: d94b827e89dc ("powerpc/book3s64/kuap: Use Key 3 for kernel mapping with hash translation")
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
---
arch/powerpc/platforms/pseries/lpar.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
From: Michael Ellerman <mpe@ellerman.id.au> Date: 2021-02-11 14:11:44
Pull the loop calling hpte_updateboltedpp() out of
hash__change_memory_range() into a helper function. We need it to be a
separate function for the next patch.
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
---
arch/powerpc/mm/book3s64/hash_pgtable.c | 23 +++++++++++++++--------
1 file changed, 15 insertions(+), 8 deletions(-)
@@ -400,10 +400,23 @@ EXPORT_SYMBOL_GPL(hash__has_transparent_hugepage);#endif /* CONFIG_TRANSPARENT_HUGEPAGE */#ifdef CONFIG_STRICT_KERNEL_RWX+staticvoidchange_memory_range(unsignedlongstart,unsignedlongend,+unsignedintstep,unsignedlongnewpp)+{+unsignedlongidx;++pr_debug("Changing page protection on range 0x%lx-0x%lx, to 0x%lx, step 0x%x\n",+start,end,newpp,step);++for(idx=start;idx<end;idx+=step)+/* Not sure if we can do much with the return value */+mmu_hash_ops.hpte_updateboltedpp(newpp,idx,mmu_linear_psize,+mmu_kernel_ssize);+}+staticboolhash__change_memory_range(unsignedlongstart,unsignedlongend,unsignedlongnewpp){-unsignedlongidx;unsignedintstep,shift;shift=mmu_psize_defs[mmu_linear_psize].shift;
@@ -415,13 +428,7 @@ static bool hash__change_memory_range(unsigned long start, unsigned long end,if(start>=end)returnfalse;-pr_debug("Changing page protection on range 0x%lx-0x%lx, to 0x%lx, step 0x%x\n",-start,end,newpp,step);--for(idx=start;idx<end;idx+=step)-/* Not sure if we can do much with the return value */-mmu_hash_ops.hpte_updateboltedpp(newpp,idx,mmu_linear_psize,-mmu_kernel_ssize);+change_memory_range(start,end,step,newpp);returntrue;}
From: Michael Ellerman <mpe@ellerman.id.au> Date: 2021-02-11 14:15:28
When we enabled STRICT_KERNEL_RWX we received some reports of boot
failures when using the Hash MMU and running under phyp. The crashes
are intermittent, and often exhibit as a completely unresponsive
system, or possibly an oops.
One example, which was caught in xmon:
[ 14.068327][ T1] devtmpfs: mounted
[ 14.069302][ T1] Freeing unused kernel memory: 5568K
[ 14.142060][ T347] BUG: Unable to handle kernel instruction fetch
[ 14.142063][ T1] Run /sbin/init as init process
[ 14.142074][ T347] Faulting instruction address: 0xc000000000004400
cpu 0x2: Vector: 400 (Instruction Access) at [c00000000c7475e0]
pc: c000000000004400: exc_virt_0x4400_instruction_access+0x0/0x80
lr: c0000000001862d4: update_rq_clock+0x44/0x110
sp: c00000000c747880
msr: 8000000040001031
current = 0xc00000000c60d380
paca = 0xc00000001ec9de80 irqmask: 0x03 irq_happened: 0x01
pid = 347, comm = kworker/2:1
...
enter ? for help
[c00000000c747880] c0000000001862d4 update_rq_clock+0x44/0x110 (unreliable)
[c00000000c7478f0] c000000000198794 update_blocked_averages+0xb4/0x6d0
[c00000000c7479f0] c000000000198e40 update_nohz_stats+0x90/0xd0
[c00000000c747a20] c0000000001a13b4 _nohz_idle_balance+0x164/0x390
[c00000000c747b10] c0000000001a1af8 newidle_balance+0x478/0x610
[c00000000c747be0] c0000000001a1d48 pick_next_task_fair+0x58/0x480
[c00000000c747c40] c000000000eaab5c __schedule+0x12c/0x950
[c00000000c747cd0] c000000000eab3e8 schedule+0x68/0x120
[c00000000c747d00] c00000000016b730 worker_thread+0x130/0x640
[c00000000c747da0] c000000000174d50 kthread+0x1a0/0x1b0
[c00000000c747e10] c00000000000e0f0 ret_from_kernel_thread+0x5c/0x6c
This shows that CPU 2, which was idle, woke up and then appears to
randomly take an instruction fault on a completely valid area of
kernel text.
The cause turns out to be the call to hash__mark_rodata_ro(), late in
boot. Due to the way we layout text and rodata, that function actually
changes the permissions for all of text and rodata to read-only plus
execute.
To do the permission change we use a hypervisor call, H_PROTECT. On
phyp that appears to be implemented by briefly removing the mapping of
the kernel text, before putting it back with the updated permissions.
If any other CPU is executing during that window, it will see spurious
faults on the kernel text and/or data, leading to crashes.
To fix it we use stop machine to collect all other CPUs, and then have
them drop into real mode (MMU off), while we change the mapping. That
way they are unaffected by the mapping temporarily disappearing.
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
---
arch/powerpc/mm/book3s64/hash_pgtable.c | 105 +++++++++++++++++++++++-
1 file changed, 104 insertions(+), 1 deletion(-)
@@ -400,6 +401,19 @@ EXPORT_SYMBOL_GPL(hash__has_transparent_hugepage);#endif /* CONFIG_TRANSPARENT_HUGEPAGE */#ifdef CONFIG_STRICT_KERNEL_RWX++structchange_memory_parms{+unsignedlongstart,end,newpp;+unsignedintstep,nr_cpus,master_cpu;+atomic_tcpu_counter;+};++// We'd rather this was on the stack but it has to be in the RMO+staticstructchange_memory_parmschmem_parms;++// And therefore we need a lock to protect it from concurrent use+staticDEFINE_MUTEX(chmem_lock);+staticvoidchange_memory_range(unsignedlongstart,unsignedlongend,unsignedintstep,unsignedlongnewpp){
@@ -414,6 +428,73 @@ static void change_memory_range(unsigned long start, unsigned long end,mmu_kernel_ssize);}+staticintnotracechmem_secondary_loop(structchange_memory_parms*parms)+{+unsignedlongmsr,tmp,flags;+int*p;++p=&parms->cpu_counter.counter;++local_irq_save(flags);+__hard_EE_RI_disable();++asmvolatile(+// Switch to real mode and leave interrupts off+"mfmsr %[msr] ;"+"li %[tmp], %[MSR_IR_DR] ;"+"andc %[tmp], %[msr], %[tmp] ;"+"mtmsrd %[tmp] ;"++// Tell the master we are in real mode+"1: "+"lwarx %[tmp], 0, %[p] ;"+"addic %[tmp], %[tmp], -1 ;"+"stwcx. %[tmp], 0, %[p] ;"+"bne- 1b ;"++// Spin until the counter goes to zero+"2: ;"+"lwz %[tmp], 0(%[p]) ;"+"cmpwi %[tmp], 0 ;"+"bne- 2b ;"++// Switch back to virtual mode+"mtmsrd %[msr] ;"++:// outputs+[msr]"=&r"(msr),[tmp]"=&b"(tmp),"+m"(*p)+:// inputs+[p]"b"(p),[MSR_IR_DR]"i"(MSR_IR|MSR_DR)+:// clobbers+"cc","xer"+);++local_irq_restore(flags);++return0;+}++staticintchange_memory_range_fn(void*data)+{+structchange_memory_parms*parms=data;++if(parms->master_cpu!=smp_processor_id())+returnchmem_secondary_loop(parms);++// Wait for all but one CPU (this one) to call-in+while(atomic_read(&parms->cpu_counter)>1)+barrier();++change_memory_range(parms->start,parms->end,parms->step,parms->newpp);++mb();++// Signal the other CPUs that we're done+atomic_dec(&parms->cpu_counter);++return0;+}+staticboolhash__change_memory_range(unsignedlongstart,unsignedlongend,unsignedlongnewpp){
@@ -428,7 +509,29 @@ static bool hash__change_memory_range(unsigned long start, unsigned long end,if(start>=end)returnfalse;-change_memory_range(start,end,step,newpp);+if(firmware_has_feature(FW_FEATURE_LPAR)){+mutex_lock(&chmem_lock);++chmem_parms.start=start;+chmem_parms.end=end;+chmem_parms.step=step;+chmem_parms.newpp=newpp;+chmem_parms.master_cpu=smp_processor_id();++cpus_read_lock();++atomic_set(&chmem_parms.cpu_counter,num_online_cpus());++// Ensure state is consistent before we call the other CPUs+mb();++stop_machine_cpuslocked(change_memory_range_fn,&chmem_parms,+cpu_online_mask);++cpus_read_unlock();+mutex_unlock(&chmem_lock);+}else+change_memory_range(start,end,step,newpp);returntrue;}
From: Michael Ellerman <mpe@ellerman.id.au> Date: 2021-02-11 14:19:52
We have now fixed the known bugs in STRICT_KERNEL_RWX for Book3S
64-bit Hash and Radix MMUs, see preceding commits, so allow the
option to be selected again.
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
---
arch/powerpc/Kconfig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
From: Nicholas Piggin <npiggin@gmail.com> Date: 2021-02-11 23:19:22
Excerpts from Michael Ellerman's message of February 11, 2021 11:51 pm:
quoted hunk
When we enabled STRICT_KERNEL_RWX we received some reports of boot
failures when using the Hash MMU and running under phyp. The crashes
are intermittent, and often exhibit as a completely unresponsive
system, or possibly an oops.
One example, which was caught in xmon:
[ 14.068327][ T1] devtmpfs: mounted
[ 14.069302][ T1] Freeing unused kernel memory: 5568K
[ 14.142060][ T347] BUG: Unable to handle kernel instruction fetch
[ 14.142063][ T1] Run /sbin/init as init process
[ 14.142074][ T347] Faulting instruction address: 0xc000000000004400
cpu 0x2: Vector: 400 (Instruction Access) at [c00000000c7475e0]
pc: c000000000004400: exc_virt_0x4400_instruction_access+0x0/0x80
lr: c0000000001862d4: update_rq_clock+0x44/0x110
sp: c00000000c747880
msr: 8000000040001031
current = 0xc00000000c60d380
paca = 0xc00000001ec9de80 irqmask: 0x03 irq_happened: 0x01
pid = 347, comm = kworker/2:1
...
enter ? for help
[c00000000c747880] c0000000001862d4 update_rq_clock+0x44/0x110 (unreliable)
[c00000000c7478f0] c000000000198794 update_blocked_averages+0xb4/0x6d0
[c00000000c7479f0] c000000000198e40 update_nohz_stats+0x90/0xd0
[c00000000c747a20] c0000000001a13b4 _nohz_idle_balance+0x164/0x390
[c00000000c747b10] c0000000001a1af8 newidle_balance+0x478/0x610
[c00000000c747be0] c0000000001a1d48 pick_next_task_fair+0x58/0x480
[c00000000c747c40] c000000000eaab5c __schedule+0x12c/0x950
[c00000000c747cd0] c000000000eab3e8 schedule+0x68/0x120
[c00000000c747d00] c00000000016b730 worker_thread+0x130/0x640
[c00000000c747da0] c000000000174d50 kthread+0x1a0/0x1b0
[c00000000c747e10] c00000000000e0f0 ret_from_kernel_thread+0x5c/0x6c
This shows that CPU 2, which was idle, woke up and then appears to
randomly take an instruction fault on a completely valid area of
kernel text.
The cause turns out to be the call to hash__mark_rodata_ro(), late in
boot. Due to the way we layout text and rodata, that function actually
changes the permissions for all of text and rodata to read-only plus
execute.
To do the permission change we use a hypervisor call, H_PROTECT. On
phyp that appears to be implemented by briefly removing the mapping of
the kernel text, before putting it back with the updated permissions.
If any other CPU is executing during that window, it will see spurious
faults on the kernel text and/or data, leading to crashes.
To fix it we use stop machine to collect all other CPUs, and then have
them drop into real mode (MMU off), while we change the mapping. That
way they are unaffected by the mapping temporarily disappearing.
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
---
arch/powerpc/mm/book3s64/hash_pgtable.c | 105 +++++++++++++++++++++++-
1 file changed, 104 insertions(+), 1 deletion(-)
@@ -400,6 +401,19 @@ EXPORT_SYMBOL_GPL(hash__has_transparent_hugepage);#endif /* CONFIG_TRANSPARENT_HUGEPAGE */#ifdef CONFIG_STRICT_KERNEL_RWX++structchange_memory_parms{+unsignedlongstart,end,newpp;+unsignedintstep,nr_cpus,master_cpu;+atomic_tcpu_counter;+};++// We'd rather this was on the stack but it has to be in the RMO+staticstructchange_memory_parmschmem_parms;++// And therefore we need a lock to protect it from concurrent use+staticDEFINE_MUTEX(chmem_lock);+staticvoidchange_memory_range(unsignedlongstart,unsignedlongend,unsignedintstep,unsignedlongnewpp){
@@ -414,6 +428,73 @@ static void change_memory_range(unsigned long start, unsigned long end,mmu_kernel_ssize);}+staticintnotracechmem_secondary_loop(structchange_memory_parms*parms)+{+unsignedlongmsr,tmp,flags;+int*p;++p=&parms->cpu_counter.counter;++local_irq_save(flags);+__hard_EE_RI_disable();++asmvolatile(+// Switch to real mode and leave interrupts off+"mfmsr %[msr] ;"+"li %[tmp], %[MSR_IR_DR] ;"+"andc %[tmp], %[msr], %[tmp] ;"+"mtmsrd %[tmp] ;"++// Tell the master we are in real mode+"1: "+"lwarx %[tmp], 0, %[p] ;"+"addic %[tmp], %[tmp], -1 ;"+"stwcx. %[tmp], 0, %[p] ;"+"bne- 1b ;"++// Spin until the counter goes to zero+"2: ;"+"lwz %[tmp], 0(%[p]) ;"+"cmpwi %[tmp], 0 ;"+"bne- 2b ;"++// Switch back to virtual mode+"mtmsrd %[msr] ;"++:// outputs+[msr]"=&r"(msr),[tmp]"=&b"(tmp),"+m"(*p)+:// inputs+[p]"b"(p),[MSR_IR_DR]"i"(MSR_IR|MSR_DR)+:// clobbers+"cc","xer"+);++local_irq_restore(flags);
Hmm. __hard_EE_RI_disable won't get restored by this because it doesn't
set the HARD_DIS flag. Also we don't want RI disabled here because
tracing will get called first (which might take SLB or HPTE fault).
But it's also slightly rude to ever enable EE under an irq soft mask,
because you don't know if it had been disabled by the masked interrupt
handler. It's not strictly a problem AFAIK because the interrupt would
just get masked again, but if we try to maintain a good pattern would
be good. Hmm that means we should add a check for irqs soft masked in
__hard_irq_enable(), I'm not sure if all existing users would follow
this rule.
Might be better to call hard_irq_disable(); after the local_irq_save();
and then clear and reset RI inside that region (could just do it at the
same time as disabling MMU).
You could possibly pass old_msr and new_msr into asm directly and do
mfmsr() in C?
Clearing RI here unfortuantely I don't think will prevent interrupt
handlers (sreset or MCE) from trying to go into virtual mode if they
hit here. It only prevents them trying to return.
Thanks,
Nick
From: Nicholas Piggin <npiggin@gmail.com> Date: 2021-02-12 00:54:12
Excerpts from Michael Ellerman's message of February 11, 2021 11:51 pm:
quoted hunk
When we enabled STRICT_KERNEL_RWX we received some reports of boot
failures when using the Hash MMU and running under phyp. The crashes
are intermittent, and often exhibit as a completely unresponsive
system, or possibly an oops.
One example, which was caught in xmon:
[ 14.068327][ T1] devtmpfs: mounted
[ 14.069302][ T1] Freeing unused kernel memory: 5568K
[ 14.142060][ T347] BUG: Unable to handle kernel instruction fetch
[ 14.142063][ T1] Run /sbin/init as init process
[ 14.142074][ T347] Faulting instruction address: 0xc000000000004400
cpu 0x2: Vector: 400 (Instruction Access) at [c00000000c7475e0]
pc: c000000000004400: exc_virt_0x4400_instruction_access+0x0/0x80
lr: c0000000001862d4: update_rq_clock+0x44/0x110
sp: c00000000c747880
msr: 8000000040001031
current = 0xc00000000c60d380
paca = 0xc00000001ec9de80 irqmask: 0x03 irq_happened: 0x01
pid = 347, comm = kworker/2:1
...
enter ? for help
[c00000000c747880] c0000000001862d4 update_rq_clock+0x44/0x110 (unreliable)
[c00000000c7478f0] c000000000198794 update_blocked_averages+0xb4/0x6d0
[c00000000c7479f0] c000000000198e40 update_nohz_stats+0x90/0xd0
[c00000000c747a20] c0000000001a13b4 _nohz_idle_balance+0x164/0x390
[c00000000c747b10] c0000000001a1af8 newidle_balance+0x478/0x610
[c00000000c747be0] c0000000001a1d48 pick_next_task_fair+0x58/0x480
[c00000000c747c40] c000000000eaab5c __schedule+0x12c/0x950
[c00000000c747cd0] c000000000eab3e8 schedule+0x68/0x120
[c00000000c747d00] c00000000016b730 worker_thread+0x130/0x640
[c00000000c747da0] c000000000174d50 kthread+0x1a0/0x1b0
[c00000000c747e10] c00000000000e0f0 ret_from_kernel_thread+0x5c/0x6c
This shows that CPU 2, which was idle, woke up and then appears to
randomly take an instruction fault on a completely valid area of
kernel text.
The cause turns out to be the call to hash__mark_rodata_ro(), late in
boot. Due to the way we layout text and rodata, that function actually
changes the permissions for all of text and rodata to read-only plus
execute.
To do the permission change we use a hypervisor call, H_PROTECT. On
phyp that appears to be implemented by briefly removing the mapping of
the kernel text, before putting it back with the updated permissions.
If any other CPU is executing during that window, it will see spurious
faults on the kernel text and/or data, leading to crashes.
To fix it we use stop machine to collect all other CPUs, and then have
them drop into real mode (MMU off), while we change the mapping. That
way they are unaffected by the mapping temporarily disappearing.
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
---
arch/powerpc/mm/book3s64/hash_pgtable.c | 105 +++++++++++++++++++++++-
1 file changed, 104 insertions(+), 1 deletion(-)
@@ -400,6 +401,19 @@ EXPORT_SYMBOL_GPL(hash__has_transparent_hugepage);#endif /* CONFIG_TRANSPARENT_HUGEPAGE */#ifdef CONFIG_STRICT_KERNEL_RWX++structchange_memory_parms{+unsignedlongstart,end,newpp;+unsignedintstep,nr_cpus,master_cpu;+atomic_tcpu_counter;+};++// We'd rather this was on the stack but it has to be in the RMO+staticstructchange_memory_parmschmem_parms;++// And therefore we need a lock to protect it from concurrent use+staticDEFINE_MUTEX(chmem_lock);+staticvoidchange_memory_range(unsignedlongstart,unsignedlongend,unsignedintstep,unsignedlongnewpp){
@@ -414,6 +428,73 @@ static void change_memory_range(unsigned long start, unsigned long end,mmu_kernel_ssize);}+staticintnotracechmem_secondary_loop(structchange_memory_parms*parms)+{+unsignedlongmsr,tmp,flags;+int*p;++p=&parms->cpu_counter.counter;++local_irq_save(flags);+__hard_EE_RI_disable();++asmvolatile(+// Switch to real mode and leave interrupts off+"mfmsr %[msr] ;"+"li %[tmp], %[MSR_IR_DR] ;"+"andc %[tmp], %[msr], %[tmp] ;"+"mtmsrd %[tmp] ;"++// Tell the master we are in real mode+"1: "+"lwarx %[tmp], 0, %[p] ;"+"addic %[tmp], %[tmp], -1 ;"+"stwcx. %[tmp], 0, %[p] ;"+"bne- 1b ;"++// Spin until the counter goes to zero+"2: ;"+"lwz %[tmp], 0(%[p]) ;"+"cmpwi %[tmp], 0 ;"+"bne- 2b ;"++// Switch back to virtual mode+"mtmsrd %[msr] ;"
Pity we don't have something that can switch to emergency stack and
so we can write this stuff in C.
How's something like this suit you?
---
arch/powerpc/kernel/misc_64.S | 22 +++++++++++++++++++++
arch/powerpc/kernel/process.c | 37 +++++++++++++++++++++++++++++++++++
2 files changed, 59 insertions(+)
@@ -2197,6 +2197,43 @@ void show_stack(struct task_struct *tsk, unsigned long *stack,put_task_stack(tsk);}+#ifdef CONFIG_PPC_BOOK3S_64+int__call_realmode(int(*fn)(void*arg),void*arg,void*sp);++/* XXX: find a better place for this+*ExecutingCcodeinreal-modeingeneralBook3S-64codecanonlybedone+*viathisfunctionthatswitchesthestacktooneinsidethereal-mode-area,+*whichmaycoveronlyasmallfirstpartofrealmemoryonhashguestLPARs.+*fnmustbeNOKPROBES,mustnotaccessvmallocoranythingoutsidetheRMA,+*probablyshouldn'tenabletheMMUorinterrupts,etc,andbeverycareful+*aboutcallingothergenerickernelorpowerpcfunctions.+*/+intcall_realmode(int(*fn)(void*arg),void*arg)+{+unsignedlongflags;+void*cursp,*emsp;+intret;++/* Stack switch is only really required for HPT LPAR, but do it for all to help test coverage of tricky code */+cursp=(void*)(current_stack_pointer&~(THREAD_SIZE-1));+emsp=(void*)(local_paca->emergency_sp-THREAD_SIZE);++/* XXX check_stack_overflow(); */++if(WARN_ON_ONCE(cursp==emsp))+return-EBUSY;++local_irq_save(flags);+hard_irq_disable();++ret=__call_realmode(fn,arg,emsp);++local_irq_restore(flags);++returnret;+}+#endif+#ifdef CONFIG_PPC64/* Called with hard IRQs off */voidnotrace__ppc64_runlatch_on(void)
From: Daniel Axtens <hidden> Date: 2021-02-16 05:40:27
Michael Ellerman [off-list ref] writes:
quoted hunk
The flags argument to plpar_pte_protect() (aka. H_PROTECT), includes
the key in bits 9-13, but currently we always set those bits to zero.
In the past that hasn't been a problem because we always used key 0
for the kernel, and updateboltedpp() is only used for kernel mappings.
However since commit d94b827e89dc ("powerpc/book3s64/kuap: Use Key 3
for kernel mapping with hash translation") we are now inadvertently
changing the key (to zero) when we call plpar_pte_protect().
That hasn't broken anything because updateboltedpp() is only used for
STRICT_KERNEL_RWX, which is currently disabled on 64s due to other
bugs.
But we want to fix that, so first we need to pass the key correctly to
plpar_pte_protect(). In the `newpp` value the low 3 bits of the key
are already in the correct spot, but the high 2 bits of the key need
to be shifted down.
Fixes: d94b827e89dc ("powerpc/book3s64/kuap: Use Key 3 for kernel mapping with hash translation")
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
---
arch/powerpc/platforms/pseries/lpar.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
@@ -976,11 +976,13 @@ static void pSeries_lpar_hpte_updateboltedpp(unsigned long newpp,slot=pSeries_lpar_hpte_find(vpn,psize,ssize);BUG_ON(slot==-1);-flags=newpp&7;+flags=newpp&(HPTE_R_PP|HPTE_R_N);if(mmu_has_feature(MMU_FTR_KERNEL_RO))/* Move pp0 into bit 8 (IBM 55) */flags|=(newpp&HPTE_R_PP0)>>55;+flags|=((newpp&HPTE_R_KEY_HI)>>48)|(newpp&HPTE_R_KEY_LO);+
I'm really confused about how these bits are getting packed into the
flags parameter. It seems to match how they are unpacked in
kvmppc_h_pr_protect, but I cannot figure out why they are packed in that
order, and the LoPAR doesn't seem especially illuminating on this topic
- although I may have missed the relevant section.
Kind regards,
Daniel
From: Daniel Axtens <hidden> Date: 2021-02-16 05:51:26
Hi Michael,
In hash__mark_rodata_ro() we pass the raw PP_RXXX value to
hash__change_memory_range(). That has the effect of setting the key to
zero, because PP_RXXX contains no key value.
Fix it by using htab_convert_pte_flags(), which knows how to convert a
pgprot into a pp value, including the key.
So far as I can tell by chasing the definitions around, this appears
to do what it claims to do.
So, for what it's worth:
Reviewed-by: Daniel Axtens <redacted>
Kind regards,
Daniel
quoted hunk
Fixes: d94b827e89dc ("powerpc/book3s64/kuap: Use Key 3 for kernel mapping with hash translation")
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
---
arch/powerpc/mm/book3s64/hash_pgtable.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
From: Michael Ellerman <mpe@ellerman.id.au> Date: 2021-02-18 23:26:28
Daniel Axtens [off-list ref] writes:
Michael Ellerman [off-list ref] writes:
quoted
The flags argument to plpar_pte_protect() (aka. H_PROTECT), includes
the key in bits 9-13, but currently we always set those bits to zero.
In the past that hasn't been a problem because we always used key 0
for the kernel, and updateboltedpp() is only used for kernel mappings.
However since commit d94b827e89dc ("powerpc/book3s64/kuap: Use Key 3
for kernel mapping with hash translation") we are now inadvertently
changing the key (to zero) when we call plpar_pte_protect().
That hasn't broken anything because updateboltedpp() is only used for
STRICT_KERNEL_RWX, which is currently disabled on 64s due to other
bugs.
But we want to fix that, so first we need to pass the key correctly to
plpar_pte_protect(). In the `newpp` value the low 3 bits of the key
are already in the correct spot, but the high 2 bits of the key need
to be shifted down.
Fixes: d94b827e89dc ("powerpc/book3s64/kuap: Use Key 3 for kernel mapping with hash translation")
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
---
arch/powerpc/platforms/pseries/lpar.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
@@ -976,11 +976,13 @@ static void pSeries_lpar_hpte_updateboltedpp(unsigned long newpp,slot=pSeries_lpar_hpte_find(vpn,psize,ssize);BUG_ON(slot==-1);-flags=newpp&7;+flags=newpp&(HPTE_R_PP|HPTE_R_N);if(mmu_has_feature(MMU_FTR_KERNEL_RO))/* Move pp0 into bit 8 (IBM 55) */flags|=(newpp&HPTE_R_PP0)>>55;+flags|=((newpp&HPTE_R_KEY_HI)>>48)|(newpp&HPTE_R_KEY_LO);+
I'm really confused about how these bits are getting packed into the
flags parameter. It seems to match how they are unpacked in
kvmppc_h_pr_protect, but I cannot figure out why they are packed in that
order, and the LoPAR doesn't seem especially illuminating on this topic
- although I may have missed the relevant section.
Yeah I agree it's not very clearly specified.
The hcall we're using here is H_PROTECT, which is specified in section
14.5.4.1.6 of LoPAPR v1.1.
It takes a `flags` parameter, and the description for flags says:
* flags: AVPN, pp0, pp1, pp2, key0-key4, n, and for the CMO
option: CMO Option flags as defined in Table 189‚
If you then go to the start of the parent section, 14.5.4.1, on page
405, it says:
Register Linkage (For hcall() tokens 0x04 - 0x18)
* On Call
* R3 function call token
* R4 flags (see Table 178‚ “Page Frame Table Access flags field definition‚” on page 401)
Then you have to go to section 14.5.3, and on page 394 there is a list
of hcalls and their tokens (table 176), and there you can see that
H_PROTECT == 0x18.
Finally you can look at table 178, on page 401, where it specifies the
layout of the bits for the key:
Bit Function
------------------
50-54 | key0-key4
Those are big-endian bit numbers, converting to normal bit numbers you
get bits 9-13, or 0x3e00.
If you look at the kernel source we have:
#define HPTE_R_KEY_HI ASM_CONST(0x3000000000000000)
#define HPTE_R_KEY_LO ASM_CONST(0x0000000000000e00)
So the LO bits are already in the right place, and the HI bits just need
to be shifted down by 48.
Hope that makes it clearer :)
cheers
From: Daniel Axtens <hidden> Date: 2021-02-19 02:09:18
Michael Ellerman [off-list ref] writes:
quoted hunk
Pull the loop calling hpte_updateboltedpp() out of
hash__change_memory_range() into a helper function. We need it to be a
separate function for the next patch.
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
---
arch/powerpc/mm/book3s64/hash_pgtable.c | 23 +++++++++++++++--------
1 file changed, 15 insertions(+), 8 deletions(-)
Looking at the call paths, this gets called only in bare metal, not
virtualised: should the name reflect that?
+{
+ unsigned long idx;
+
+ pr_debug("Changing page protection on range 0x%lx-0x%lx, to 0x%lx, step 0x%x\n",
+ start, end, newpp, step);
+
+ for (idx = start; idx < end; idx += step)
+ /* Not sure if we can do much with the return value */
Hmm, I realise this comment isn't changed, but it did make me wonder
what the return value!
It turns out that the function doesn't actually return anything.
Tracking back the history of hpte_updateboltedpp, it looks like it has
not had a return value since the start of git history:
^1da177e4c3f4 include/asm-ppc64/machdep.h void (*hpte_updateboltedpp)(unsigned long newpp,
3c726f8dee6f5 include/asm-powerpc/machdep.h unsigned long ea,
1189be6508d45 include/asm-powerpc/machdep.h int psize, int ssize);
The comment comes from commit cd65d6971334 ("powerpc/mm/hash: Implement
mark_rodata_ro() for hash") where Balbir added the comment, but again I
can't figure out what sort of return value there would be to ignore.
Should we drop the comment? (or return something from hpte_updateboltedpp)
quoted hunk
+ mmu_hash_ops.hpte_updateboltedpp(newpp, idx, mmu_linear_psize,
+ mmu_kernel_ssize);
+}
+
static bool hash__change_memory_range(unsigned long start, unsigned long end,
unsigned long newpp)
{
- unsigned long idx;
unsigned int step, shift;
shift = mmu_psize_defs[mmu_linear_psize].shift;
@@ -415,13 +428,7 @@ static bool hash__change_memory_range(unsigned long start, unsigned long end, if (start >= end) return false;- pr_debug("Changing page protection on range 0x%lx-0x%lx, to 0x%lx, step 0x%x\n",- start, end, newpp, step);-- for (idx = start; idx < end; idx += step)- /* Not sure if we can do much with the return value */- mmu_hash_ops.hpte_updateboltedpp(newpp, idx, mmu_linear_psize,- mmu_kernel_ssize);+ change_memory_range(start, end, step, newpp);
Looking at how change_memory_range is called, step is derived by:
shift = mmu_psize_defs[mmu_linear_psize].shift;
step = 1 << shift;
We probably therefore don't really need to pass step in to
change_memory_range. Having said that, I'm not sure it would really be that
much tidier to compute step in change_memory_range, especially since we
also need step for the other branch in hash__change_memory_range.
Beyond that it all looks reasonable to me!
I also checked that the loop operations made sense, I think they do - we
cover from start inclusive to end exclusive and the alignment is done
before we call into change_memory_range.
Regards,
Daniel
From: Daniel Axtens <hidden> Date: 2021-02-19 02:43:58
Michael Ellerman [off-list ref] writes:
When we enabled STRICT_KERNEL_RWX we received some reports of boot
failures when using the Hash MMU and running under phyp. The crashes
are intermittent, and often exhibit as a completely unresponsive
system, or possibly an oops.
One example, which was caught in xmon:
[ 14.068327][ T1] devtmpfs: mounted
[ 14.069302][ T1] Freeing unused kernel memory: 5568K
[ 14.142060][ T347] BUG: Unable to handle kernel instruction fetch
[ 14.142063][ T1] Run /sbin/init as init process
[ 14.142074][ T347] Faulting instruction address: 0xc000000000004400
cpu 0x2: Vector: 400 (Instruction Access) at [c00000000c7475e0]
pc: c000000000004400: exc_virt_0x4400_instruction_access+0x0/0x80
lr: c0000000001862d4: update_rq_clock+0x44/0x110
sp: c00000000c747880
msr: 8000000040001031
current = 0xc00000000c60d380
paca = 0xc00000001ec9de80 irqmask: 0x03 irq_happened: 0x01
pid = 347, comm = kworker/2:1
...
enter ? for help
[c00000000c747880] c0000000001862d4 update_rq_clock+0x44/0x110 (unreliable)
[c00000000c7478f0] c000000000198794 update_blocked_averages+0xb4/0x6d0
[c00000000c7479f0] c000000000198e40 update_nohz_stats+0x90/0xd0
[c00000000c747a20] c0000000001a13b4 _nohz_idle_balance+0x164/0x390
[c00000000c747b10] c0000000001a1af8 newidle_balance+0x478/0x610
[c00000000c747be0] c0000000001a1d48 pick_next_task_fair+0x58/0x480
[c00000000c747c40] c000000000eaab5c __schedule+0x12c/0x950
[c00000000c747cd0] c000000000eab3e8 schedule+0x68/0x120
[c00000000c747d00] c00000000016b730 worker_thread+0x130/0x640
[c00000000c747da0] c000000000174d50 kthread+0x1a0/0x1b0
[c00000000c747e10] c00000000000e0f0 ret_from_kernel_thread+0x5c/0x6c
This shows that CPU 2, which was idle, woke up and then appears to
randomly take an instruction fault on a completely valid area of
kernel text.
The cause turns out to be the call to hash__mark_rodata_ro(), late in
boot. Due to the way we layout text and rodata, that function actually
changes the permissions for all of text and rodata to read-only plus
execute.
To do the permission change we use a hypervisor call, H_PROTECT. On
phyp that appears to be implemented by briefly removing the mapping of
the kernel text, before putting it back with the updated permissions.
If any other CPU is executing during that window, it will see spurious
faults on the kernel text and/or data, leading to crashes.
Jordan asked why we saw this on phyp but not under KVM? We had a look at
book3s_hv_rm_mmu.c but the code is a bit too obtuse for me to reason
about!
Nick suggests that the KVM hypervisor is invalidating the HPTE, but
because we run guests in VPM mode, the hypervisor would catch the page
fault and not reflect it down to the guest. It looks like Linux-as-a-HV
will take HPTE_V_HVLOCK, and then because it's running in VPM mode, the
hypervisor will catch the fault and not pass it to the guest. But if
phyp runs with VPM mode off, the guest will see the fault before the
hypervisor. (we think this is what's going on anyway.)
We spent a while pondering if phyp is doing something buggy or not...
Looking at the PAPR definition of H_PROTECT, that claims the hypervisor
will do the 'architected “Modifying a Page Table Entry General Case”
sequence'. s 5.10.1.2 of Book IIIS of the ISAv3 defines that, and the
non-atomic hardware sequence does indeed modify the PTE by going through
the invalid state. So it looks like if phyp is running without VPM mode
it's technically not buggy.
Hopefully I'll get to have a look at the rest of the patch shortly!
Kind regards,
Daniel
quoted hunk
To fix it we use stop machine to collect all other CPUs, and then have
them drop into real mode (MMU off), while we change the mapping. That
way they are unaffected by the mapping temporarily disappearing.
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
---
arch/powerpc/mm/book3s64/hash_pgtable.c | 105 +++++++++++++++++++++++-
1 file changed, 104 insertions(+), 1 deletion(-)
@@ -400,6 +401,19 @@ EXPORT_SYMBOL_GPL(hash__has_transparent_hugepage);#endif /* CONFIG_TRANSPARENT_HUGEPAGE */#ifdef CONFIG_STRICT_KERNEL_RWX++structchange_memory_parms{+unsignedlongstart,end,newpp;+unsignedintstep,nr_cpus,master_cpu;+atomic_tcpu_counter;+};++// We'd rather this was on the stack but it has to be in the RMO+staticstructchange_memory_parmschmem_parms;++// And therefore we need a lock to protect it from concurrent use+staticDEFINE_MUTEX(chmem_lock);+staticvoidchange_memory_range(unsignedlongstart,unsignedlongend,unsignedintstep,unsignedlongnewpp){
@@ -414,6 +428,73 @@ static void change_memory_range(unsigned long start, unsigned long end,mmu_kernel_ssize);}+staticintnotracechmem_secondary_loop(structchange_memory_parms*parms)+{+unsignedlongmsr,tmp,flags;+int*p;++p=&parms->cpu_counter.counter;++local_irq_save(flags);+__hard_EE_RI_disable();++asmvolatile(+// Switch to real mode and leave interrupts off+"mfmsr %[msr] ;"+"li %[tmp], %[MSR_IR_DR] ;"+"andc %[tmp], %[msr], %[tmp] ;"+"mtmsrd %[tmp] ;"++// Tell the master we are in real mode+"1: "+"lwarx %[tmp], 0, %[p] ;"+"addic %[tmp], %[tmp], -1 ;"+"stwcx. %[tmp], 0, %[p] ;"+"bne- 1b ;"++// Spin until the counter goes to zero+"2: ;"+"lwz %[tmp], 0(%[p]) ;"+"cmpwi %[tmp], 0 ;"+"bne- 2b ;"++// Switch back to virtual mode+"mtmsrd %[msr] ;"++:// outputs+[msr]"=&r"(msr),[tmp]"=&b"(tmp),"+m"(*p)+:// inputs+[p]"b"(p),[MSR_IR_DR]"i"(MSR_IR|MSR_DR)+:// clobbers+"cc","xer"+);++local_irq_restore(flags);++return0;+}++staticintchange_memory_range_fn(void*data)+{+structchange_memory_parms*parms=data;++if(parms->master_cpu!=smp_processor_id())+returnchmem_secondary_loop(parms);++// Wait for all but one CPU (this one) to call-in+while(atomic_read(&parms->cpu_counter)>1)+barrier();++change_memory_range(parms->start,parms->end,parms->step,parms->newpp);++mb();++// Signal the other CPUs that we're done+atomic_dec(&parms->cpu_counter);++return0;+}+staticboolhash__change_memory_range(unsignedlongstart,unsignedlongend,unsignedlongnewpp){
@@ -428,7 +509,29 @@ static bool hash__change_memory_range(unsigned long start, unsigned long end,if(start>=end)returnfalse;-change_memory_range(start,end,step,newpp);+if(firmware_has_feature(FW_FEATURE_LPAR)){+mutex_lock(&chmem_lock);++chmem_parms.start=start;+chmem_parms.end=end;+chmem_parms.step=step;+chmem_parms.newpp=newpp;+chmem_parms.master_cpu=smp_processor_id();++cpus_read_lock();++atomic_set(&chmem_parms.cpu_counter,num_online_cpus());++// Ensure state is consistent before we call the other CPUs+mb();++stop_machine_cpuslocked(change_memory_range_fn,&chmem_parms,+cpu_online_mask);++cpus_read_unlock();+mutex_unlock(&chmem_lock);+}else+change_memory_range(start,end,step,newpp);returntrue;}
From: Michael Ellerman <mpe@ellerman.id.au> Date: 2021-03-16 06:31:00
Daniel Axtens [off-list ref] writes:
Michael Ellerman [off-list ref] writes:
quoted
Pull the loop calling hpte_updateboltedpp() out of
hash__change_memory_range() into a helper function. We need it to be a
separate function for the next patch.
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
---
arch/powerpc/mm/book3s64/hash_pgtable.c | 23 +++++++++++++++--------
1 file changed, 15 insertions(+), 8 deletions(-)
Looking at the call paths, this gets called only in bare metal, not
virtualised: should the name reflect that?
It's also called on bare metal:
static bool hash__change_memory_range(unsigned long start, unsigned long end,
unsigned long newpp)
{
...
if (firmware_has_feature(FW_FEATURE_LPAR)) {
...
stop_machine_cpuslocked(change_memory_range_fn, &chmem_parms,
cpu_online_mask);
...
} else
change_memory_range(start, end, step, newpp);
^^^^^^^^^^^^^^^^^^^
quoted
+{
+ unsigned long idx;
+
+ pr_debug("Changing page protection on range 0x%lx-0x%lx, to 0x%lx, step 0x%x\n",
+ start, end, newpp, step);
+
+ for (idx = start; idx < end; idx += step)
+ /* Not sure if we can do much with the return value */
Hmm, I realise this comment isn't changed, but it did make me wonder
what the return value!
It turns out that the function doesn't actually return anything.
Tracking back the history of hpte_updateboltedpp, it looks like it has
not had a return value since the start of git history:
^1da177e4c3f4 include/asm-ppc64/machdep.h void (*hpte_updateboltedpp)(unsigned long newpp,
3c726f8dee6f5 include/asm-powerpc/machdep.h unsigned long ea,
1189be6508d45 include/asm-powerpc/machdep.h int psize, int ssize);
The comment comes from commit cd65d6971334 ("powerpc/mm/hash: Implement
mark_rodata_ro() for hash") where Balbir added the comment, but again I
can't figure out what sort of return value there would be to ignore.
I suspect he just assumed there was a return value, and the comment is
saying we aren't really allowed to fail here, so what could we do?
In general these routines changing the kernel map permissions aren't
allowed to fail, because the callers don't cope with a failure, and at
least in some cases eg. RW -> RX the permission change is not optional.
Should we drop the comment? (or return something from hpte_updateboltedpp)
I'll leave the comment for now, but we could probably drop it.
It would be good if hpte_updateboltedpp() could fail and return an
error. Currently pSeries_lpar_hpte_updateboltedpp() BUGs if the hcall
fails, because the only error cases are due to bad input on our part.
And similarly native_hpte_updateboltedpp() panics if we give it bad
input.
We may still need to panic() at a higher level, ie. adding execute to a
mapping is not optional. But possibly for some changes, like RW->RO we
could WARN and continue.
And I guess for modules we could eventually plumb the error all the way
out and fail the module load.
quoted
+ mmu_hash_ops.hpte_updateboltedpp(newpp, idx, mmu_linear_psize,
+ mmu_kernel_ssize);
+}
+
static bool hash__change_memory_range(unsigned long start, unsigned long end,
unsigned long newpp)
{
- unsigned long idx;
unsigned int step, shift;
shift = mmu_psize_defs[mmu_linear_psize].shift;
@@ -415,13 +428,7 @@ static bool hash__change_memory_range(unsigned long start, unsigned long end, if (start >= end) return false;- pr_debug("Changing page protection on range 0x%lx-0x%lx, to 0x%lx, step 0x%x\n",- start, end, newpp, step);-- for (idx = start; idx < end; idx += step)- /* Not sure if we can do much with the return value */- mmu_hash_ops.hpte_updateboltedpp(newpp, idx, mmu_linear_psize,- mmu_kernel_ssize);+ change_memory_range(start, end, step, newpp);
Looking at how change_memory_range is called, step is derived by:
shift = mmu_psize_defs[mmu_linear_psize].shift;
step = 1 << shift;
We probably therefore don't really need to pass step in to
change_memory_range. Having said that, I'm not sure it would really be that
much tidier to compute step in change_memory_range, especially since we
also need step for the other branch in hash__change_memory_range.
Hmm yeah, swings and roundabouts. I think I'll leave it as is, so that
we're only calculating step in one place.
Beyond that it all looks reasonable to me!
I also checked that the loop operations made sense, I think they do - we
cover from start inclusive to end exclusive and the alignment is done
before we call into change_memory_range.
From: Michael Ellerman <mpe@ellerman.id.au> Date: 2021-03-16 06:41:27
Nicholas Piggin [off-list ref] writes:
Excerpts from Michael Ellerman's message of February 11, 2021 11:51 pm:
quoted
When we enabled STRICT_KERNEL_RWX we received some reports of boot
failures when using the Hash MMU and running under phyp. The crashes
are intermittent, and often exhibit as a completely unresponsive
system, or possibly an oops.
@@ -414,6 +428,73 @@ static void change_memory_range(unsigned long start, unsigned long end,mmu_kernel_ssize);}+staticintnotracechmem_secondary_loop(structchange_memory_parms*parms)+{+unsignedlongmsr,tmp,flags;+int*p;++p=&parms->cpu_counter.counter;++local_irq_save(flags);+__hard_EE_RI_disable();++asmvolatile(+// Switch to real mode and leave interrupts off+"mfmsr %[msr] ;"+"li %[tmp], %[MSR_IR_DR] ;"+"andc %[tmp], %[msr], %[tmp] ;"+"mtmsrd %[tmp] ;"++// Tell the master we are in real mode+"1: "+"lwarx %[tmp], 0, %[p] ;"+"addic %[tmp], %[tmp], -1 ;"+"stwcx. %[tmp], 0, %[p] ;"+"bne- 1b ;"++// Spin until the counter goes to zero+"2: ;"+"lwz %[tmp], 0(%[p]) ;"+"cmpwi %[tmp], 0 ;"+"bne- 2b ;"++// Switch back to virtual mode+"mtmsrd %[msr] ;"
Pity we don't have something that can switch to emergency stack and
so we can write this stuff in C.
How's something like this suit you?
It looks like it would be really good for writing exploits :)
I think at the very least we would want the asm part to load the SP
from the paca itself, rather than taking it as a parameter.
But I'm not sure writing these type of things in C is a big win, because
you have to be so careful about what you call anyway. It's almost better
in asm because it's so restrictive.
Obviously having said that, my first attempt got the IRQ save/restore
wrong, so maybe we should at least have some macros to help with it.
Did you have another user for this in mind? The only one that I can
think of at the moment is the subcore stuff.
cheers
@@ -2197,6 +2197,43 @@ void show_stack(struct task_struct *tsk, unsigned long *stack,put_task_stack(tsk);}+#ifdef CONFIG_PPC_BOOK3S_64+int__call_realmode(int(*fn)(void*arg),void*arg,void*sp);++/* XXX: find a better place for this+*ExecutingCcodeinreal-modeingeneralBook3S-64codecanonlybedone+*viathisfunctionthatswitchesthestacktooneinsidethereal-mode-area,+*whichmaycoveronlyasmallfirstpartofrealmemoryonhashguestLPARs.+*fnmustbeNOKPROBES,mustnotaccessvmallocoranythingoutsidetheRMA,+*probablyshouldn'tenabletheMMUorinterrupts,etc,andbeverycareful+*aboutcallingothergenerickernelorpowerpcfunctions.+*/+intcall_realmode(int(*fn)(void*arg),void*arg)+{+unsignedlongflags;+void*cursp,*emsp;+intret;++/* Stack switch is only really required for HPT LPAR, but do it for all to help test coverage of tricky code */+cursp=(void*)(current_stack_pointer&~(THREAD_SIZE-1));+emsp=(void*)(local_paca->emergency_sp-THREAD_SIZE);++/* XXX check_stack_overflow(); */++if(WARN_ON_ONCE(cursp==emsp))+return-EBUSY;++local_irq_save(flags);+hard_irq_disable();++ret=__call_realmode(fn,arg,emsp);++local_irq_restore(flags);++returnret;+}+#endif+#ifdef CONFIG_PPC64/* Called with hard IRQs off */voidnotrace__ppc64_runlatch_on(void)
From: Michael Ellerman <mpe@ellerman.id.au> Date: 2021-03-19 11:57:16
Daniel Axtens [off-list ref] writes:
Michael Ellerman [off-list ref] writes:
quoted
When we enabled STRICT_KERNEL_RWX we received some reports of boot
failures when using the Hash MMU and running under phyp. The crashes
are intermittent, and often exhibit as a completely unresponsive
system, or possibly an oops.
One example, which was caught in xmon:
[ 14.068327][ T1] devtmpfs: mounted
[ 14.069302][ T1] Freeing unused kernel memory: 5568K
[ 14.142060][ T347] BUG: Unable to handle kernel instruction fetch
[ 14.142063][ T1] Run /sbin/init as init process
[ 14.142074][ T347] Faulting instruction address: 0xc000000000004400
cpu 0x2: Vector: 400 (Instruction Access) at [c00000000c7475e0]
pc: c000000000004400: exc_virt_0x4400_instruction_access+0x0/0x80
lr: c0000000001862d4: update_rq_clock+0x44/0x110
sp: c00000000c747880
msr: 8000000040001031
current = 0xc00000000c60d380
paca = 0xc00000001ec9de80 irqmask: 0x03 irq_happened: 0x01
pid = 347, comm = kworker/2:1
...
enter ? for help
[c00000000c747880] c0000000001862d4 update_rq_clock+0x44/0x110 (unreliable)
[c00000000c7478f0] c000000000198794 update_blocked_averages+0xb4/0x6d0
[c00000000c7479f0] c000000000198e40 update_nohz_stats+0x90/0xd0
[c00000000c747a20] c0000000001a13b4 _nohz_idle_balance+0x164/0x390
[c00000000c747b10] c0000000001a1af8 newidle_balance+0x478/0x610
[c00000000c747be0] c0000000001a1d48 pick_next_task_fair+0x58/0x480
[c00000000c747c40] c000000000eaab5c __schedule+0x12c/0x950
[c00000000c747cd0] c000000000eab3e8 schedule+0x68/0x120
[c00000000c747d00] c00000000016b730 worker_thread+0x130/0x640
[c00000000c747da0] c000000000174d50 kthread+0x1a0/0x1b0
[c00000000c747e10] c00000000000e0f0 ret_from_kernel_thread+0x5c/0x6c
This shows that CPU 2, which was idle, woke up and then appears to
randomly take an instruction fault on a completely valid area of
kernel text.
The cause turns out to be the call to hash__mark_rodata_ro(), late in
boot. Due to the way we layout text and rodata, that function actually
changes the permissions for all of text and rodata to read-only plus
execute.
To do the permission change we use a hypervisor call, H_PROTECT. On
phyp that appears to be implemented by briefly removing the mapping of
the kernel text, before putting it back with the updated permissions.
If any other CPU is executing during that window, it will see spurious
faults on the kernel text and/or data, leading to crashes.
Jordan asked why we saw this on phyp but not under KVM? We had a look at
book3s_hv_rm_mmu.c but the code is a bit too obtuse for me to reason
about!
Nick suggests that the KVM hypervisor is invalidating the HPTE, but
because we run guests in VPM mode, the hypervisor would catch the page
fault and not reflect it down to the guest. It looks like Linux-as-a-HV
will take HPTE_V_HVLOCK, and then because it's running in VPM mode, the
hypervisor will catch the fault and not pass it to the guest.
Yep.
But if phyp runs with VPM mode off, the guest will see the fault
before the hypervisor. (we think this is what's going on anyway.)
Yeah. I assumed phyp always ran with VPM=1, but apparently it can run
with it off or on, depending on various configuration settings.
So I'm fairly sure what we're hitting here is VPM=0, where the faults go
straight to the guest.
cheers
@@ -414,6 +428,73 @@ static void change_memory_range(unsigned long start, unsigned long end,mmu_kernel_ssize);}+staticintnotracechmem_secondary_loop(structchange_memory_parms*parms)+{+unsignedlongmsr,tmp,flags;+int*p;++p=&parms->cpu_counter.counter;++local_irq_save(flags);+__hard_EE_RI_disable();++asmvolatile(+// Switch to real mode and leave interrupts off+"mfmsr %[msr] ;"+"li %[tmp], %[MSR_IR_DR] ;"+"andc %[tmp], %[msr], %[tmp] ;"+"mtmsrd %[tmp] ;"++// Tell the master we are in real mode+"1: "+"lwarx %[tmp], 0, %[p] ;"+"addic %[tmp], %[tmp], -1 ;"+"stwcx. %[tmp], 0, %[p] ;"+"bne- 1b ;"++// Spin until the counter goes to zero+"2: ;"+"lwz %[tmp], 0(%[p]) ;"+"cmpwi %[tmp], 0 ;"+"bne- 2b ;"++// Switch back to virtual mode+"mtmsrd %[msr] ;"++:// outputs+[msr]"=&r"(msr),[tmp]"=&b"(tmp),"+m"(*p)+:// inputs+[p]"b"(p),[MSR_IR_DR]"i"(MSR_IR|MSR_DR)+:// clobbers+"cc","xer"+);++local_irq_restore(flags);
Hmm. __hard_EE_RI_disable won't get restored by this because it doesn't
set the HARD_DIS flag. Also we don't want RI disabled here because
tracing will get called first (which might take SLB or HPTE fault).
Thanks for noticing. I originally wrote hard_irq_disable() but then
thought disabling RI also would be good.
But it's also slightly rude to ever enable EE under an irq soft mask,
because you don't know if it had been disabled by the masked interrupt
handler. It's not strictly a problem AFAIK because the interrupt would
just get masked again, but if we try to maintain a good pattern would
be good. Hmm that means we should add a check for irqs soft masked in
__hard_irq_enable(), I'm not sure if all existing users would follow
this rule.
Might be better to call hard_irq_disable(); after the local_irq_save();
and then clear and reset RI inside that region (could just do it at the
same time as disabling MMU).
Thinking about it more, there's no real reason to disable RI.
We should be able to return from an interrupt in there, it's just that
if we do take one we'll probably die before we get a chance to return
because the mapping of text will be missing.
So disabling RI doesn't really gain us anything I don't think.
cheers
@@ -414,6 +428,73 @@ static void change_memory_range(unsigned long start, unsigned long end,mmu_kernel_ssize);}+staticintnotracechmem_secondary_loop(structchange_memory_parms*parms)+{+unsignedlongmsr,tmp,flags;+int*p;++p=&parms->cpu_counter.counter;++local_irq_save(flags);+__hard_EE_RI_disable();++asmvolatile(+// Switch to real mode and leave interrupts off+"mfmsr %[msr] ;"+"li %[tmp], %[MSR_IR_DR] ;"+"andc %[tmp], %[msr], %[tmp] ;"+"mtmsrd %[tmp] ;"++// Tell the master we are in real mode+"1: "+"lwarx %[tmp], 0, %[p] ;"+"addic %[tmp], %[tmp], -1 ;"+"stwcx. %[tmp], 0, %[p] ;"+"bne- 1b ;"++// Spin until the counter goes to zero+"2: ;"+"lwz %[tmp], 0(%[p]) ;"+"cmpwi %[tmp], 0 ;"+"bne- 2b ;"++// Switch back to virtual mode+"mtmsrd %[msr] ;"++:// outputs+[msr]"=&r"(msr),[tmp]"=&b"(tmp),"+m"(*p)+:// inputs+[p]"b"(p),[MSR_IR_DR]"i"(MSR_IR|MSR_DR)+:// clobbers+"cc","xer"+);++local_irq_restore(flags);
Hmm. __hard_EE_RI_disable won't get restored by this because it doesn't
set the HARD_DIS flag. Also we don't want RI disabled here because
tracing will get called first (which might take SLB or HPTE fault).
Thanks for noticing. I originally wrote hard_irq_disable() but then
thought disabling RI also would be good.
quoted
But it's also slightly rude to ever enable EE under an irq soft mask,
because you don't know if it had been disabled by the masked interrupt
handler. It's not strictly a problem AFAIK because the interrupt would
just get masked again, but if we try to maintain a good pattern would
be good. Hmm that means we should add a check for irqs soft masked in
__hard_irq_enable(), I'm not sure if all existing users would follow
this rule.
Might be better to call hard_irq_disable(); after the local_irq_save();
and then clear and reset RI inside that region (could just do it at the
same time as disabling MMU).
Thinking about it more, there's no real reason to disable RI.
We should be able to return from an interrupt in there, it's just that
if we do take one we'll probably die before we get a chance to return
because the mapping of text will be missing.
Yeah it probably will because the pseries hash machine check handler has
some hacks in it that require turning the MMU on. We might never fix
that if we're moving to radix, but if we did then in theory we'd be able
to take a MCE here and recover.
So disabling RI doesn't really gain us anything I don't think.
Yeah I probably agree. So local_irq_save(flags); hard_irq_disable();
should do the trick.
Thanks,
Nick
From: Nicholas Piggin <npiggin@gmail.com> Date: 2021-03-22 03:09:41
Excerpts from Michael Ellerman's message of March 16, 2021 4:40 pm:
Nicholas Piggin [off-list ref] writes:
quoted
Excerpts from Michael Ellerman's message of February 11, 2021 11:51 pm:
quoted
When we enabled STRICT_KERNEL_RWX we received some reports of boot
failures when using the Hash MMU and running under phyp. The crashes
are intermittent, and often exhibit as a completely unresponsive
system, or possibly an oops.
@@ -414,6 +428,73 @@ static void change_memory_range(unsigned long start, unsigned long end,mmu_kernel_ssize);}+staticintnotracechmem_secondary_loop(structchange_memory_parms*parms)+{+unsignedlongmsr,tmp,flags;+int*p;++p=&parms->cpu_counter.counter;++local_irq_save(flags);+__hard_EE_RI_disable();++asmvolatile(+// Switch to real mode and leave interrupts off+"mfmsr %[msr] ;"+"li %[tmp], %[MSR_IR_DR] ;"+"andc %[tmp], %[msr], %[tmp] ;"+"mtmsrd %[tmp] ;"++// Tell the master we are in real mode+"1: "+"lwarx %[tmp], 0, %[p] ;"+"addic %[tmp], %[tmp], -1 ;"+"stwcx. %[tmp], 0, %[p] ;"+"bne- 1b ;"++// Spin until the counter goes to zero+"2: ;"+"lwz %[tmp], 0(%[p]) ;"+"cmpwi %[tmp], 0 ;"+"bne- 2b ;"++// Switch back to virtual mode+"mtmsrd %[msr] ;"
Pity we don't have something that can switch to emergency stack and
so we can write this stuff in C.
How's something like this suit you?
It looks like it would be really good for writing exploits :)
Hmm. In that case maybe the callee function could be inlined into it
like the interrupt wrappers, and the asm real-mode entry/exit gets
added around it rather than have this little exploit stub. So similar to
yours but with a stack switch as well so you can come back up in real
mode.
I think at the very least we would want the asm part to load the SP
from the paca itself, rather than taking it as a parameter.
But I'm not sure writing these type of things in C is a big win, because
you have to be so careful about what you call anyway. It's almost better
in asm because it's so restrictive.
Obviously having said that, my first attempt got the IRQ save/restore
wrong, so maybe we should at least have some macros to help with it.
Did you have another user for this in mind? The only one that I can
think of at the moment is the subcore stuff.
Possibly rtas entry/exit (although that has other issues). But I guess
it's not a huge amount of asm compared with what I'm dealing with.
I'm okay if you just put your thing in at the moment, we might or might
not get keen and c-ify it later.
Thanks,
Nick
From: Michael Ellerman <mpe@ellerman.id.au> Date: 2021-03-22 09:07:58
Nicholas Piggin [off-list ref] writes:
Excerpts from Michael Ellerman's message of March 16, 2021 4:40 pm:
quoted
Nicholas Piggin [off-list ref] writes:
quoted
Excerpts from Michael Ellerman's message of February 11, 2021 11:51 pm:
quoted
When we enabled STRICT_KERNEL_RWX we received some reports of boot
failures when using the Hash MMU and running under phyp. The crashes
are intermittent, and often exhibit as a completely unresponsive
system, or possibly an oops.
@@ -414,6 +428,73 @@ static void change_memory_range(unsigned long start, unsigned long end,mmu_kernel_ssize);}+staticintnotracechmem_secondary_loop(structchange_memory_parms*parms)+{+unsignedlongmsr,tmp,flags;+int*p;++p=&parms->cpu_counter.counter;++local_irq_save(flags);+__hard_EE_RI_disable();++asmvolatile(+// Switch to real mode and leave interrupts off+"mfmsr %[msr] ;"+"li %[tmp], %[MSR_IR_DR] ;"+"andc %[tmp], %[msr], %[tmp] ;"+"mtmsrd %[tmp] ;"++// Tell the master we are in real mode+"1: "+"lwarx %[tmp], 0, %[p] ;"+"addic %[tmp], %[tmp], -1 ;"+"stwcx. %[tmp], 0, %[p] ;"+"bne- 1b ;"++// Spin until the counter goes to zero+"2: ;"+"lwz %[tmp], 0(%[p]) ;"+"cmpwi %[tmp], 0 ;"+"bne- 2b ;"++// Switch back to virtual mode+"mtmsrd %[msr] ;"
Pity we don't have something that can switch to emergency stack and
so we can write this stuff in C.
How's something like this suit you?
It looks like it would be really good for writing exploits :)
Hmm. In that case maybe the callee function could be inlined into it
like the interrupt wrappers, and the asm real-mode entry/exit gets
added around it rather than have this little exploit stub. So similar to
yours but with a stack switch as well so you can come back up in real
mode.
Yeah inlining as much as possible would reduce the risk.
quoted
I think at the very least we would want the asm part to load the SP
from the paca itself, rather than taking it as a parameter.
But I'm not sure writing these type of things in C is a big win, because
you have to be so careful about what you call anyway. It's almost better
in asm because it's so restrictive.
Obviously having said that, my first attempt got the IRQ save/restore
wrong, so maybe we should at least have some macros to help with it.
Did you have another user for this in mind? The only one that I can
think of at the moment is the subcore stuff.
Possibly rtas entry/exit (although that has other issues). But I guess
it's not a huge amount of asm compared with what I'm dealing with.
Ah yep, I hadn't thought of RTAS.
I'm okay if you just put your thing in at the moment, we might or might
not get keen and c-ify it later.
From: Michael Ellerman <hidden> Date: 2021-04-10 14:32:59
On Fri, 12 Feb 2021 00:51:25 +1100, Michael Ellerman wrote:
In the past we had a fallback definition for _PAGE_KERNEL_ROX, but we
removed that in commit d82fd29c5a8c ("powerpc/mm: Distribute platform
specific PAGE and PMD flags and definitions") and added definitions
for each MMU family.
However we missed adding a definition for 64s, which was not really a
bug because it's currently not used.
[...]
From: Michael Ellerman <mpe@ellerman.id.au> Date: 2021-04-19 05:17:56
Michael Ellerman [off-list ref] writes:
On Fri, 12 Feb 2021 00:51:25 +1100, Michael Ellerman wrote:
quoted
In the past we had a fallback definition for _PAGE_KERNEL_ROX, but we
removed that in commit d82fd29c5a8c ("powerpc/mm: Distribute platform
specific PAGE and PMD flags and definitions") and added definitions
for each MMU family.
However we missed adding a definition for 64s, which was not really a
bug because it's currently not used.
[...]