Skip to content

Commit

Permalink
powerpc/32s: Implement Kernel Userspace Access Protection
Browse files Browse the repository at this point in the history
This patch implements Kernel Userspace Access Protection for
book3s/32.

Due to limitations of the processor page protection capabilities,
the protection is only against writing. read protection cannot be
achieved using page protection.

The previous patch modifies the page protection so that RW user
pages are RW for Key 0 and RO for Key 1, and it sets Key 0 for
both user and kernel.

This patch changes userspace segment registers are set to Ku 0
and Ks 1. When kernel needs to write to RW pages, the associated
segment register is then changed to Ks 0 in order to allow write
access to the kernel.

In order to avoid having the read all segment registers when
locking/unlocking the access, some data is kept in the thread_struct
and saved on stack on exceptions. The field identifies both the
first unlocked segment and the first segment following the last
unlocked one. When no segment is unlocked, it contains value 0.

As the hash_page() function is not able to easily determine if a
protfault is due to a bad kernel access to userspace, protfaults
need to be handled by handle_page_fault when KUAP is set.

Signed-off-by: Christophe Leroy <christophe.leroy@c-s.fr>
[mpe: Drop allow_read/write_to/from_user() as they're now in kup.h,
      and adapt allow_user_access() to do nothing when to == NULL]
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
  • Loading branch information
Christophe Leroy authored and Michael Ellerman committed Apr 21, 2019
1 parent f342adc commit a68c31f
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 0 deletions.
103 changes: 103 additions & 0 deletions arch/powerpc/include/asm/book3s/32/kup.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,109 @@
#endif
.endm

#ifdef CONFIG_PPC_KUAP

.macro kuap_update_sr gpr1, gpr2, gpr3 /* NEVER use r0 as gpr2 due to addis */
101: mtsrin \gpr1, \gpr2
addi \gpr1, \gpr1, 0x111 /* next VSID */
rlwinm \gpr1, \gpr1, 0, 0xf0ffffff /* clear VSID overflow */
addis \gpr2, \gpr2, 0x1000 /* address of next segment */
cmplw \gpr2, \gpr3
blt- 101b
isync
.endm

.macro kuap_save_and_lock sp, thread, gpr1, gpr2, gpr3
lwz \gpr2, KUAP(\thread)
rlwinm. \gpr3, \gpr2, 28, 0xf0000000
stw \gpr2, STACK_REGS_KUAP(\sp)
beq+ 102f
li \gpr1, 0
stw \gpr1, KUAP(\thread)
mfsrin \gpr1, \gpr2
oris \gpr1, \gpr1, SR_KS@h /* set Ks */
kuap_update_sr \gpr1, \gpr2, \gpr3
102:
.endm

.macro kuap_restore sp, current, gpr1, gpr2, gpr3
lwz \gpr2, STACK_REGS_KUAP(\sp)
rlwinm. \gpr3, \gpr2, 28, 0xf0000000
stw \gpr2, THREAD + KUAP(\current)
beq+ 102f
mfsrin \gpr1, \gpr2
rlwinm \gpr1, \gpr1, 0, ~SR_KS /* Clear Ks */
kuap_update_sr \gpr1, \gpr2, \gpr3
102:
.endm

.macro kuap_check current, gpr
#ifdef CONFIG_PPC_KUAP_DEBUG
lwz \gpr2, KUAP(thread)
999: twnei \gpr, 0
EMIT_BUG_ENTRY 999b, __FILE__, __LINE__, (BUGFLAG_WARNING | BUGFLAG_ONCE)
#endif
.endm

#endif /* CONFIG_PPC_KUAP */

#else /* !__ASSEMBLY__ */

#ifdef CONFIG_PPC_KUAP

#include <linux/sched.h>

static inline void kuap_update_sr(u32 sr, u32 addr, u32 end)
{
barrier(); /* make sure thread.kuap is updated before playing with SRs */
while (addr < end) {
mtsrin(sr, addr);
sr += 0x111; /* next VSID */
sr &= 0xf0ffffff; /* clear VSID overflow */
addr += 0x10000000; /* address of next segment */
}
isync(); /* Context sync required after mtsrin() */
}

static inline void allow_user_access(void __user *to, const void __user *from, u32 size)
{
u32 addr, end;

if (__builtin_constant_p(to) && to == NULL)
return;

addr = (__force u32)to;

if (!addr || addr >= TASK_SIZE || !size)
return;

end = min(addr + size, TASK_SIZE);
current->thread.kuap = (addr & 0xf0000000) | ((((end - 1) >> 28) + 1) & 0xf);
kuap_update_sr(mfsrin(addr) & ~SR_KS, addr, end); /* Clear Ks */
}

static inline void prevent_user_access(void __user *to, const void __user *from, u32 size)
{
u32 addr = (__force u32)to;
u32 end = min(addr + size, TASK_SIZE);

if (!addr || addr >= TASK_SIZE || !size)
return;

current->thread.kuap = 0;
kuap_update_sr(mfsrin(addr) | SR_KS, addr, end); /* set Ks */
}

static inline bool bad_kuap_fault(struct pt_regs *regs, bool is_write)
{
if (!is_write)
return false;

return WARN(!regs->kuap, "Bug: write fault blocked by segment registers !");
}

#endif /* CONFIG_PPC_KUAP */

#endif /* __ASSEMBLY__ */

#endif /* _ASM_POWERPC_BOOK3S_32_KUP_H */
3 changes: 3 additions & 0 deletions arch/powerpc/include/asm/processor.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ struct thread_struct {
#ifdef CONFIG_PPC_RTAS
unsigned long rtas_sp; /* stack pointer for when in RTAS */
#endif
#endif
#if defined(CONFIG_PPC_BOOK3S_32) && defined(CONFIG_PPC_KUAP)
unsigned long kuap; /* opened segments for user access */
#endif
/* Debug Registers */
struct debug_reg debug;
Expand Down
3 changes: 3 additions & 0 deletions arch/powerpc/kernel/asm-offsets.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ int main(void)
#if defined(CONFIG_KVM) && defined(CONFIG_BOOKE)
OFFSET(THREAD_KVM_VCPU, thread_struct, kvm_vcpu);
#endif
#if defined(CONFIG_PPC_BOOK3S_32) && defined(CONFIG_PPC_KUAP)
OFFSET(KUAP, thread_struct, kuap);
#endif

#ifdef CONFIG_PPC_TRANSACTIONAL_MEM
OFFSET(PACATMSCRATCH, paca_struct, tm_scratch);
Expand Down
11 changes: 11 additions & 0 deletions arch/powerpc/kernel/head_32.S
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,11 @@ DataAccess:
EXCEPTION_PROLOG
mfspr r10,SPRN_DSISR
stw r10,_DSISR(r11)
#ifdef CONFIG_PPC_KUAP
andis. r0,r10,(DSISR_BAD_FAULT_32S | DSISR_DABRMATCH | DSISR_PROTFAULT)@h
#else
andis. r0,r10,(DSISR_BAD_FAULT_32S|DSISR_DABRMATCH)@h
#endif
bne 1f /* if not, try to put a PTE */
mfspr r4,SPRN_DAR /* into the hash table */
rlwinm r3,r10,32-15,21,21 /* DSISR_STORE -> _PAGE_RW */
Expand Down Expand Up @@ -901,6 +905,9 @@ load_up_mmu:
li r3, 0 /* Kp = 0, Ks = 0, VSID = 0 */
#ifdef CONFIG_PPC_KUEP
oris r3, r3, SR_NX@h /* Set Nx */
#endif
#ifdef CONFIG_PPC_KUAP
oris r3, r3, SR_KS@h /* Set Ks */
#endif
li r4,0
3: mtsrin r3,r4
Expand All @@ -910,6 +917,7 @@ load_up_mmu:
li r0, 16 - NUM_USER_SEGMENTS /* load up kernel segment registers */
mtctr r0 /* for context 0 */
rlwinm r3, r3, 0, ~SR_NX /* Nx = 0 */
rlwinm r3, r3, 0, ~SR_KS /* Ks = 0 */
oris r3, r3, SR_KP@h /* Kp = 1 */
3: mtsrin r3, r4
addi r3, r3, 0x111 /* increment VSID */
Expand Down Expand Up @@ -1019,6 +1027,9 @@ _ENTRY(switch_mmu_context)
rlwinm r3,r3,4,8,27 /* VSID = (context & 0xfffff) << 4 */
#ifdef CONFIG_PPC_KUEP
oris r3, r3, SR_NX@h /* Set Nx */
#endif
#ifdef CONFIG_PPC_KUAP
oris r3, r3, SR_KS@h /* Set Ks */
#endif
li r0,NUM_USER_SEGMENTS
mtctr r0
Expand Down
10 changes: 10 additions & 0 deletions arch/powerpc/mm/ppc_mmu_32.c
Original file line number Diff line number Diff line change
Expand Up @@ -407,3 +407,13 @@ void __init setup_kuep(bool disabled)
pr_warn("KUEP cannot be disabled yet on 6xx when compiled in\n");
}
#endif

#ifdef CONFIG_PPC_KUAP
void __init setup_kuap(bool disabled)
{
pr_info("Activating Kernel Userspace Access Protection\n");

if (disabled)
pr_warn("KUAP cannot be disabled yet on 6xx when compiled in\n");
}
#endif
1 change: 1 addition & 0 deletions arch/powerpc/platforms/Kconfig.cputype
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ config PPC_BOOK3S_32
select PPC_FPU
select PPC_HAVE_PMU_SUPPORT
select PPC_HAVE_KUEP
select PPC_HAVE_KUAP

config PPC_85xx
bool "Freescale 85xx"
Expand Down

0 comments on commit a68c31f

Please sign in to comment.