commit 21ff3eaed8565e9b130380abf084e761f20e6fea Author: Florian Weimer Date: Sat Dec 9 22:03:26 2017 +0100 pkeys: Support setting access rights for signal handlers The new pkey_alloc flag PKEY_ALLOC_SETSIGNAL requests that the kernel uses the specified access rights as the default when the a signal handler is entered, instead of the access rights specified by init_pkru. This leads to a divergence in the FPU initialization for signal handlers and for exeve, so a for_signal flag is added to fpu__clear. Signed-off-by: Florian Weimer diff --git a/arch/alpha/include/uapi/asm/mman.h b/arch/alpha/include/uapi/asm/mman.h index 2dbdf59258d9..343251fed6fc 100644 --- a/arch/alpha/include/uapi/asm/mman.h +++ b/arch/alpha/include/uapi/asm/mman.h @@ -72,6 +72,8 @@ /* compatibility flags */ #define MAP_FILE 0 +#define PKEY_ALLOC_SETSIGNAL 0x1 + #define PKEY_DISABLE_ACCESS 0x1 #define PKEY_DISABLE_WRITE 0x2 #define PKEY_ACCESS_MASK (PKEY_DISABLE_ACCESS |\ diff --git a/arch/mips/include/uapi/asm/mman.h b/arch/mips/include/uapi/asm/mman.h index 606e02ca4b6c..be1677e31899 100644 --- a/arch/mips/include/uapi/asm/mman.h +++ b/arch/mips/include/uapi/asm/mman.h @@ -99,6 +99,8 @@ /* compatibility flags */ #define MAP_FILE 0 +#define PKEY_ALLOC_SETSIGNAL 0x1 + #define PKEY_DISABLE_ACCESS 0x1 #define PKEY_DISABLE_WRITE 0x2 #define PKEY_ACCESS_MASK (PKEY_DISABLE_ACCESS |\ diff --git a/arch/parisc/include/uapi/asm/mman.h b/arch/parisc/include/uapi/asm/mman.h index 80510ba44c08..2fc9d1bf98eb 100644 --- a/arch/parisc/include/uapi/asm/mman.h +++ b/arch/parisc/include/uapi/asm/mman.h @@ -69,6 +69,8 @@ #define MAP_FILE 0 #define MAP_VARIABLE 0 +#define PKEY_ALLOC_SETSIGNAL 0x1 + #define PKEY_DISABLE_ACCESS 0x1 #define PKEY_DISABLE_WRITE 0x2 #define PKEY_ACCESS_MASK (PKEY_DISABLE_ACCESS |\ diff --git a/arch/x86/include/asm/fpu/internal.h b/arch/x86/include/asm/fpu/internal.h index a38bf5a1e37a..5a7a72dd589c 100644 --- a/arch/x86/include/asm/fpu/internal.h +++ b/arch/x86/include/asm/fpu/internal.h @@ -32,7 +32,7 @@ extern void fpu__restore(struct fpu *fpu); extern int fpu__restore_sig(void __user *buf, int ia32_frame); extern void fpu__drop(struct fpu *fpu); extern int fpu__copy(struct fpu *dst_fpu, struct fpu *src_fpu); -extern void fpu__clear(struct fpu *fpu); +extern void fpu__clear(struct fpu *fpu, bool for_signal); extern int fpu__exception_code(struct fpu *fpu, int trap_nr); extern int dump_fpu(struct pt_regs *ptregs, struct user_i387_struct *fpstate); diff --git a/arch/x86/include/asm/mmu.h b/arch/x86/include/asm/mmu.h index 9ea26f167497..0f041ed10c83 100644 --- a/arch/x86/include/asm/mmu.h +++ b/arch/x86/include/asm/mmu.h @@ -47,6 +47,13 @@ typedef struct { */ u16 pkey_allocation_map; s16 execute_only_pkey; + + /* + * Used to derive the PKRU register value from init_pkru in a + * signal handler. + */ + u32 pkey_signal_mask; + u32 pkey_signal_value; #endif #ifdef CONFIG_X86_INTEL_MPX /* address of the bounds directory */ diff --git a/arch/x86/include/asm/mmu_context.h b/arch/x86/include/asm/mmu_context.h index 6d16d15d09a0..d402496714ad 100644 --- a/arch/x86/include/asm/mmu_context.h +++ b/arch/x86/include/asm/mmu_context.h @@ -141,6 +141,7 @@ static inline int init_new_context(struct task_struct *tsk, mm->context.pkey_allocation_map = 0x1; /* -1 means unallocated or invalid */ mm->context.execute_only_pkey = -1; + mm->context.pkey_signal_mask = ~0U; } #endif return init_new_context_ldt(tsk, mm); diff --git a/arch/x86/include/asm/pkeys.h b/arch/x86/include/asm/pkeys.h index a0ba1ffda0df..a9e20e4f6bb2 100644 --- a/arch/x86/include/asm/pkeys.h +++ b/arch/x86/include/asm/pkeys.h @@ -7,6 +7,10 @@ extern int arch_set_user_pkey_access(struct task_struct *tsk, int pkey, unsigned long init_val); +extern void arch_set_pkey_signal_default(struct mm_struct *mm, + int pkey, u32 rights); +extern void arch_reset_pkey_signal_default(struct mm_struct *mm, int pkey); + /* * Try to dedicate one of the protection keys to be used as an * execute-only protection key. @@ -104,6 +108,6 @@ extern int arch_set_user_pkey_access(struct task_struct *tsk, int pkey, unsigned long init_val); extern int __arch_set_user_pkey_access(struct task_struct *tsk, int pkey, unsigned long init_val); -extern void copy_init_pkru_to_fpregs(void); +extern void copy_init_pkru_to_fpregs(bool for_signal); #endif /*_ASM_X86_PKEYS_H */ diff --git a/arch/x86/kernel/fpu/core.c b/arch/x86/kernel/fpu/core.c index f92a6593de1e..970e5b01ade4 100644 --- a/arch/x86/kernel/fpu/core.c +++ b/arch/x86/kernel/fpu/core.c @@ -362,7 +362,7 @@ void fpu__drop(struct fpu *fpu) * Clear FPU registers by setting them up from * the init fpstate: */ -static inline void copy_init_fpstate_to_fpregs(void) +static inline void copy_init_fpstate_to_fpregs(bool for_signal) { if (use_xsave()) copy_kernel_to_xregs(&init_fpstate.xsave, -1); @@ -372,7 +372,7 @@ static inline void copy_init_fpstate_to_fpregs(void) copy_kernel_to_fregs(&init_fpstate.fsave); if (boot_cpu_has(X86_FEATURE_OSPKE)) - copy_init_pkru_to_fpregs(); + copy_init_pkru_to_fpregs(for_signal); } /* @@ -381,7 +381,7 @@ static inline void copy_init_fpstate_to_fpregs(void) * Called by sys_execve(), by the signal handler code and by various * error paths. */ -void fpu__clear(struct fpu *fpu) +void fpu__clear(struct fpu *fpu, bool for_signal) { WARN_ON_FPU(fpu != ¤t->thread.fpu); /* Almost certainly an anomaly */ @@ -394,7 +394,7 @@ void fpu__clear(struct fpu *fpu) preempt_disable(); fpu__initialize(fpu); user_fpu_begin(); - copy_init_fpstate_to_fpregs(); + copy_init_fpstate_to_fpregs(for_signal); preempt_enable(); } } diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c index 23f1691670b6..8e9709c1270b 100644 --- a/arch/x86/kernel/fpu/signal.c +++ b/arch/x86/kernel/fpu/signal.c @@ -277,7 +277,7 @@ static int __fpu__restore_sig(void __user *buf, void __user *buf_fx, int size) IS_ENABLED(CONFIG_IA32_EMULATION)); if (!buf) { - fpu__clear(fpu); + fpu__clear(fpu, false); return 0; } @@ -358,7 +358,7 @@ static int __fpu__restore_sig(void __user *buf, void __user *buf_fx, int size) */ user_fpu_begin(); if (copy_user_to_fpregs_zeroing(buf_fx, xfeatures, fx_only)) { - fpu__clear(fpu); + fpu__clear(fpu, false); return -1; } } diff --git a/arch/x86/kernel/process.c b/arch/x86/kernel/process.c index bb988a24db92..36d59d8c684a 100644 --- a/arch/x86/kernel/process.c +++ b/arch/x86/kernel/process.c @@ -129,7 +129,7 @@ void flush_thread(void) flush_ptrace_hw_breakpoint(tsk); memset(tsk->thread.tls_array, 0, sizeof(tsk->thread.tls_array)); - fpu__clear(&tsk->thread.fpu); + fpu__clear(&tsk->thread.fpu, false); } void disable_TSC(void) diff --git a/arch/x86/kernel/signal.c b/arch/x86/kernel/signal.c index b9e00e8f1c9b..86e7cf5d38e9 100644 --- a/arch/x86/kernel/signal.c +++ b/arch/x86/kernel/signal.c @@ -757,7 +757,7 @@ handle_signal(struct ksignal *ksig, struct pt_regs *regs) * Ensure the signal handler starts with the new fpu state. */ if (fpu->initialized) - fpu__clear(fpu); + fpu__clear(fpu, true); } signal_setup_done(failed, ksig, stepping); } diff --git a/arch/x86/mm/pkeys.c b/arch/x86/mm/pkeys.c index d7bc0eea20a5..f7447512833c 100644 --- a/arch/x86/mm/pkeys.c +++ b/arch/x86/mm/pkeys.c @@ -19,6 +19,26 @@ #include /* boot_cpu_has, ... */ #include /* vma_pkey() */ +void arch_set_pkey_signal_default(struct mm_struct *mm, int pkey, u32 rights) +{ + int shift = pkey * PKRU_BITS_PER_PKEY; + u32 mask = ~(3U << shift); + u32 value = rights << shift; + + mm->context.pkey_signal_mask &= mask; + mm->context.pkey_signal_value = + (mm->context.pkey_signal_value & mask) | value; +} + +void arch_reset_pkey_signal_default(struct mm_struct *mm, int pkey) +{ + int shift = pkey * PKRU_BITS_PER_PKEY; + u32 mask = 3U << shift; + + mm->context.pkey_signal_mask |= mask; + mm->context.pkey_signal_value &= ~mask; +} + int __execute_only_pkey(struct mm_struct *mm) { bool need_to_set_mm_pkey = false; @@ -142,21 +162,30 @@ u32 init_pkru_value = PKRU_AD_KEY( 1) | PKRU_AD_KEY( 2) | PKRU_AD_KEY( 3) | * we know the FPU regstiers are safe for use and we can use PKRU * directly. */ -void copy_init_pkru_to_fpregs(void) +void copy_init_pkru_to_fpregs(bool for_signal) { u32 init_pkru_value_snapshot = READ_ONCE(init_pkru_value); + u32 desired_pkru; + + if (for_signal) + desired_pkru = (init_pkru_value_snapshot + & current->mm->context.pkey_signal_mask) + | current->mm->context.pkey_signal_value; + else + desired_pkru = init_pkru_value_snapshot; + /* * Any write to PKRU takes it out of the XSAVE 'init * state' which increases context switch cost. Avoid * writing 0 when PKRU was already 0. */ - if (!init_pkru_value_snapshot && !read_pkru()) + if (!desired_pkru && !read_pkru()) return; /* * Override the PKRU state that came from 'init_fpstate' * with the baseline from the process. */ - write_pkru(init_pkru_value_snapshot); + write_pkru(desired_pkru); } static ssize_t init_pkru_read_file(struct file *file, char __user *user_buf, diff --git a/arch/xtensa/include/uapi/asm/mman.h b/arch/xtensa/include/uapi/asm/mman.h index 3e9d01ada81f..5abb66526e3e 100644 --- a/arch/xtensa/include/uapi/asm/mman.h +++ b/arch/xtensa/include/uapi/asm/mman.h @@ -111,6 +111,8 @@ /* compatibility flags */ #define MAP_FILE 0 +#define PKEY_ALLOC_SETSIGNAL 0x1 + #define PKEY_DISABLE_ACCESS 0x1 #define PKEY_DISABLE_WRITE 0x2 #define PKEY_ACCESS_MASK (PKEY_DISABLE_ACCESS |\ diff --git a/include/linux/pkeys.h b/include/linux/pkeys.h index 0794ca78c379..0ab73297b752 100644 --- a/include/linux/pkeys.h +++ b/include/linux/pkeys.h @@ -29,13 +29,23 @@ static inline int mm_pkey_free(struct mm_struct *mm, int pkey) return -EINVAL; } +static inline void arch_set_pkey_signal_default(struct mm_struct *mm, + int pkey, u32 rights) +{ +} + +static inline void arch_reset_pkey_signal_default(struct mm_struct *mm, + int pkey) +{ +} + static inline int arch_set_user_pkey_access(struct task_struct *tsk, int pkey, unsigned long init_val) { return 0; } -static inline void copy_init_pkru_to_fpregs(void) +static inline void copy_init_pkru_to_fpregs(bool for_signal) { } diff --git a/include/uapi/asm-generic/mman-common.h b/include/uapi/asm-generic/mman-common.h index f8b134f5608f..78772266e3cd 100644 --- a/include/uapi/asm-generic/mman-common.h +++ b/include/uapi/asm-generic/mman-common.h @@ -66,6 +66,8 @@ /* compatibility flags */ #define MAP_FILE 0 +#define PKEY_ALLOC_SETSIGNAL 0x1 + #define PKEY_DISABLE_ACCESS 0x1 #define PKEY_DISABLE_WRITE 0x2 #define PKEY_ACCESS_MASK (PKEY_DISABLE_ACCESS |\ diff --git a/mm/mprotect.c b/mm/mprotect.c index ec39f730a0bf..021f1d465649 100644 --- a/mm/mprotect.c +++ b/mm/mprotect.c @@ -523,14 +523,17 @@ SYSCALL_DEFINE4(pkey_mprotect, unsigned long, start, size_t, len, return do_mprotect_pkey(start, len, prot, pkey); } +#define PKEY_ALLOC_FLAGS ((unsigned long) (PKEY_ALLOC_SETSIGNAL)) + SYSCALL_DEFINE2(pkey_alloc, unsigned long, flags, unsigned long, init_val) { int pkey; int ret; - /* No flags supported yet. */ - if (flags) + /* check for unsupported flags */ + if (flags & ~PKEY_ALLOC_FLAGS) return -EINVAL; + /* check for unsupported init values */ if (init_val & ~PKEY_ACCESS_MASK) return -EINVAL; @@ -547,6 +550,10 @@ SYSCALL_DEFINE2(pkey_alloc, unsigned long, flags, unsigned long, init_val) mm_pkey_free(current->mm, pkey); goto out; } + + if (flags & PKEY_ALLOC_SETSIGNAL) + arch_set_pkey_signal_default(current->mm, pkey, init_val); + ret = pkey; out: up_write(¤t->mm->mmap_sem); @@ -559,6 +566,8 @@ SYSCALL_DEFINE1(pkey_free, int, pkey) down_write(¤t->mm->mmap_sem); ret = mm_pkey_free(current->mm, pkey); + if (!ret) + arch_reset_pkey_signal_default(current->mm, pkey); up_write(¤t->mm->mmap_sem); /* diff --git a/tools/include/uapi/asm-generic/mman-common.h b/tools/include/uapi/asm-generic/mman-common.h index f8b134f5608f..78772266e3cd 100644 --- a/tools/include/uapi/asm-generic/mman-common.h +++ b/tools/include/uapi/asm-generic/mman-common.h @@ -66,6 +66,8 @@ /* compatibility flags */ #define MAP_FILE 0 +#define PKEY_ALLOC_SETSIGNAL 0x1 + #define PKEY_DISABLE_ACCESS 0x1 #define PKEY_DISABLE_WRITE 0x2 #define PKEY_ACCESS_MASK (PKEY_DISABLE_ACCESS |\ diff --git a/tools/testing/selftests/x86/protection_keys.c b/tools/testing/selftests/x86/protection_keys.c index bc1b0735bb50..ea161916e378 100644 --- a/tools/testing/selftests/x86/protection_keys.c +++ b/tools/testing/selftests/x86/protection_keys.c @@ -421,6 +421,8 @@ void dumpit(char *f) close(fd); } +#define PKEY_ALLOC_SETSIGNAL 0x1 + #define PKEY_DISABLE_ACCESS 0x1 #define PKEY_DISABLE_WRITE 0x2