Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

powerpc/kuap: Simplify KUAP lock/unlock on BOOK3S/32

On book3s/32 KUAP is performed at segment level. At the moment,
when enabling userspace access, only current segment is modified.
Then if a write is performed on another user segment, a fault is
taken and all other user segments get enabled for userspace
access. This then require special attention when disabling
userspace access.

Having a userspace write access crossing a segment boundary is
unlikely. Having a userspace write access crossing a segment boundary
back and forth is even more unlikely. So, instead of enabling
userspace access on all segments when a write fault occurs, just
change which segment has userspace access enabled in order to
eliminate the case when more than one segment has userspace access
enabled. That simplifies userspace access deactivation.

There is however a corner case which is even more unlikely but has
to be handled anyway: an unaligned access which is crossing a
segment boundary. That would definitely require at least having
userspace access enabled on the two segments. To avoid complicating
the likely case for a so unlikely happening, handle such situation
like an alignment exception and emulate the store.

Signed-off-by: Christophe Leroy <christophe.leroy@csgroup.eu>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Link: https://msgid.link/8de8580513c1a6e880bad1ba9a69d3efad3d4fa5.1689091022.git.christophe.leroy@csgroup.eu

authored by

Christophe Leroy and committed by
Michael Ellerman
5222a1d5 26e04120

+23 -60
+19 -46
arch/powerpc/include/asm/book3s/32/kup.h
··· 14 14 #include <linux/sched.h> 15 15 16 16 #define KUAP_NONE (~0UL) 17 - #define KUAP_ALL (~1UL) 18 17 19 18 static inline void kuap_lock_one(unsigned long addr) 20 19 { ··· 27 28 isync(); /* Context sync required after mtsr() */ 28 29 } 29 30 30 - static inline void kuap_lock_all(void) 31 - { 32 - update_user_segments(mfsr(0) | SR_KS); 33 - isync(); /* Context sync required after mtsr() */ 34 - } 35 - 36 - static inline void kuap_unlock_all(void) 37 - { 38 - update_user_segments(mfsr(0) & ~SR_KS); 39 - isync(); /* Context sync required after mtsr() */ 40 - } 41 - 42 - void kuap_lock_all_ool(void); 43 - void kuap_unlock_all_ool(void); 44 - 45 - static inline void kuap_lock_addr(unsigned long addr, bool ool) 46 - { 47 - if (likely(addr != KUAP_ALL)) 48 - kuap_lock_one(addr); 49 - else if (!ool) 50 - kuap_lock_all(); 51 - else 52 - kuap_lock_all_ool(); 53 - } 54 - 55 - static inline void kuap_unlock(unsigned long addr, bool ool) 56 - { 57 - if (likely(addr != KUAP_ALL)) 58 - kuap_unlock_one(addr); 59 - else if (!ool) 60 - kuap_unlock_all(); 61 - else 62 - kuap_unlock_all_ool(); 63 - } 64 - 65 31 static inline void __kuap_save_and_lock(struct pt_regs *regs) 66 32 { 67 33 unsigned long kuap = current->thread.kuap; ··· 36 72 return; 37 73 38 74 current->thread.kuap = KUAP_NONE; 39 - kuap_lock_addr(kuap, false); 75 + kuap_lock_one(kuap); 40 76 } 41 77 #define __kuap_save_and_lock __kuap_save_and_lock 42 78 ··· 48 84 { 49 85 if (unlikely(kuap != KUAP_NONE)) { 50 86 current->thread.kuap = KUAP_NONE; 51 - kuap_lock_addr(kuap, false); 87 + kuap_lock_one(kuap); 52 88 } 53 89 54 90 if (likely(regs->kuap == KUAP_NONE)) ··· 56 92 57 93 current->thread.kuap = regs->kuap; 58 94 59 - kuap_unlock(regs->kuap, false); 95 + kuap_unlock_one(regs->kuap); 60 96 } 61 97 62 98 static inline unsigned long __kuap_get_and_assert_locked(void) ··· 91 127 return; 92 128 93 129 current->thread.kuap = KUAP_NONE; 94 - kuap_lock_addr(kuap, true); 130 + kuap_lock_one(kuap); 95 131 } 96 132 97 133 static inline unsigned long __prevent_user_access_return(void) ··· 100 136 101 137 if (flags != KUAP_NONE) { 102 138 current->thread.kuap = KUAP_NONE; 103 - kuap_lock_addr(flags, true); 139 + kuap_lock_one(flags); 104 140 } 105 141 106 142 return flags; ··· 110 146 { 111 147 if (flags != KUAP_NONE) { 112 148 current->thread.kuap = flags; 113 - kuap_unlock(flags, true); 149 + kuap_unlock_one(flags); 114 150 } 115 151 } 116 152 ··· 119 155 { 120 156 unsigned long kuap = regs->kuap; 121 157 122 - if (!is_write || kuap == KUAP_ALL) 158 + if (!is_write) 123 159 return false; 124 160 if (kuap == KUAP_NONE) 125 161 return true; 126 162 127 - /* If faulting address doesn't match unlocked segment, unlock all */ 128 - if ((kuap ^ address) & 0xf0000000) 129 - regs->kuap = KUAP_ALL; 163 + /* 164 + * If faulting address doesn't match unlocked segment, change segment. 165 + * In case of unaligned store crossing two segments, emulate store. 166 + */ 167 + if ((kuap ^ address) & 0xf0000000) { 168 + if (!(kuap & 0x0fffffff) && address > kuap - 4 && fix_alignment(regs)) { 169 + regs_add_return_ip(regs, 4); 170 + emulate_single_step(regs); 171 + } else { 172 + regs->kuap = address; 173 + } 174 + } 130 175 131 176 return false; 132 177 }
+1
arch/powerpc/include/asm/bug.h
··· 120 120 struct pt_regs; 121 121 void hash__do_page_fault(struct pt_regs *); 122 122 void bad_page_fault(struct pt_regs *, int); 123 + void emulate_single_step(struct pt_regs *regs); 123 124 extern void _exception(int, struct pt_regs *, int, unsigned long); 124 125 extern void _exception_pkey(struct pt_regs *, unsigned long, int); 125 126 extern void die(const char *, struct pt_regs *, long);
+1 -1
arch/powerpc/kernel/traps.c
··· 1158 1158 * pretend we got a single-step exception. This was pointed out 1159 1159 * by Kumar Gala. -- paulus 1160 1160 */ 1161 - static void emulate_single_step(struct pt_regs *regs) 1161 + void emulate_single_step(struct pt_regs *regs) 1162 1162 { 1163 1163 if (single_stepping(regs)) 1164 1164 __single_step_exception(regs);
+2 -13
arch/powerpc/mm/book3s32/kuap.c
··· 3 3 #include <asm/kup.h> 4 4 #include <asm/smp.h> 5 5 6 - void kuap_lock_all_ool(void) 7 - { 8 - kuap_lock_all(); 9 - } 10 - EXPORT_SYMBOL(kuap_lock_all_ool); 11 - 12 - void kuap_unlock_all_ool(void) 13 - { 14 - kuap_unlock_all(); 15 - } 16 - EXPORT_SYMBOL(kuap_unlock_all_ool); 17 - 18 6 void setup_kuap(bool disabled) 19 7 { 20 8 if (!disabled) { 21 - kuap_lock_all_ool(); 9 + update_user_segments(mfsr(0) | SR_KS); 10 + isync(); /* Context sync required after mtsr() */ 22 11 init_mm.context.sr0 |= SR_KS; 23 12 current->thread.sr0 |= SR_KS; 24 13 }