Skip to content

Commit 316ec15

Browse files
committed
s390/mm: fix page table upgrade vs 2ndary address mode accesses
A page table upgrade in a kernel section that uses secondary address mode will mess up the kernel instructions as follows: Consider the following scenario: two threads are sharing memory. On CPU1 thread 1 does e.g. strnlen_user(). That gets to old_fs = enable_sacf_uaccess(); len = strnlen_user_srst(src, size); and " la %2,0(%1)\n" " la %3,0(%0,%1)\n" " slgr %0,%0\n" " sacf 256\n" "0: srst %3,%2\n" in strnlen_user_srst(). At that point we are in secondary space mode, control register 1 points to kernel page table and instruction fetching happens via c1, rather than usual c13. Interrupts are not disabled, for obvious reasons. On CPU2 thread 2 does MAP_FIXED mmap(), forcing the upgrade of page table from 3-level to e.g. 4-level one. We'd allocated new top-level table, set it up and now we hit this: notify = 1; spin_unlock_bh(&mm->page_table_lock); } if (notify) on_each_cpu(__crst_table_upgrade, mm, 0); OK, we need to actually change over to use of new page table and we need that to happen in all threads that are currently running. Which happens to include the thread 1. IPI is delivered and we have static void __crst_table_upgrade(void *arg) { struct mm_struct *mm = arg; if (current->active_mm == mm) set_user_asce(mm); __tlb_flush_local(); } run on CPU1. That does static inline void set_user_asce(struct mm_struct *mm) { S390_lowcore.user_asce = mm->context.asce; OK, user page table address updated... __ctl_load(S390_lowcore.user_asce, 1, 1); ... and control register 1 set to it. clear_cpu_flag(CIF_ASCE_PRIMARY); } IPI is run in home space mode, so it's fine - insns are fetched using c13, which always points to kernel page table. But as soon as we return from the interrupt, previous PSW is restored, putting CPU1 back into secondary space mode, at which point we no longer get the kernel instructions from the kernel mapping. The fix is to only fixup the control registers that are currently in use for user processes during the page table update. We must also disable interrupts in enable_sacf_uaccess to synchronize the cr and thread.mm_segment updates against the on_each-cpu. Fixes: 0aaba41 ("s390: remove all code using the access register mode") Cc: [email protected] # 4.15+ Reported-by: Al Viro <[email protected]> Reviewed-by: Gerald Schaefer <[email protected]> References: CVE-2020-11884 Signed-off-by: Christian Borntraeger <[email protected]>
1 parent 7111951 commit 316ec15

File tree

2 files changed

+18
-2
lines changed

2 files changed

+18
-2
lines changed

arch/s390/lib/uaccess.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,13 @@ mm_segment_t enable_sacf_uaccess(void)
6464
{
6565
mm_segment_t old_fs;
6666
unsigned long asce, cr;
67+
unsigned long flags;
6768

6869
old_fs = current->thread.mm_segment;
6970
if (old_fs & 1)
7071
return old_fs;
72+
/* protect against a concurrent page table upgrade */
73+
local_irq_save(flags);
7174
current->thread.mm_segment |= 1;
7275
asce = S390_lowcore.kernel_asce;
7376
if (likely(old_fs == USER_DS)) {
@@ -83,6 +86,7 @@ mm_segment_t enable_sacf_uaccess(void)
8386
__ctl_load(asce, 7, 7);
8487
set_cpu_flag(CIF_ASCE_SECONDARY);
8588
}
89+
local_irq_restore(flags);
8690
return old_fs;
8791
}
8892
EXPORT_SYMBOL(enable_sacf_uaccess);

arch/s390/mm/pgalloc.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,20 @@ static void __crst_table_upgrade(void *arg)
7070
{
7171
struct mm_struct *mm = arg;
7272

73-
if (current->active_mm == mm)
74-
set_user_asce(mm);
73+
/* we must change all active ASCEs to avoid the creation of new TLBs */
74+
if (current->active_mm == mm) {
75+
S390_lowcore.user_asce = mm->context.asce;
76+
if (current->thread.mm_segment == USER_DS) {
77+
__ctl_load(S390_lowcore.user_asce, 1, 1);
78+
/* Mark user-ASCE present in CR1 */
79+
clear_cpu_flag(CIF_ASCE_PRIMARY);
80+
}
81+
if (current->thread.mm_segment == USER_DS_SACF) {
82+
__ctl_load(S390_lowcore.user_asce, 7, 7);
83+
/* enable_sacf_uaccess does all or nothing */
84+
WARN_ON(!test_cpu_flag(CIF_ASCE_SECONDARY));
85+
}
86+
}
7587
__tlb_flush_local();
7688
}
7789

0 commit comments

Comments
 (0)