mirror of
https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-10.git
synced 2026-09-09 00:07:04 +08:00
Merge: Fix bugs and performance of kstack offset randomisation
MR: https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-10/-/merge_requests/3071 JIRA: https://redhat.atlassian.net/browse/RHEL-215975 Fix various issues with kstack randomization. Signed-off-by: Mark Salter <msalter@redhat.com> Approved-by: Jennifer Berringer <jberring@redhat.com> Approved-by: Steve Best <sbest@redhat.com> Approved-by: Rafael Aquini <raquini@redhat.com> Approved-by: CKI KWF Bot <cki-ci-bot+kwf-gitlab-com@redhat.com> Merged-by: CKI GitLab Kmaint Pipeline Bot <26919896-cki-kmaint-pipeline-bot@users.noreply.gitlab.com>
This commit is contained in:
+2
-3
@@ -1500,9 +1500,8 @@ config HAVE_ARCH_RANDOMIZE_KSTACK_OFFSET
|
||||
def_bool n
|
||||
help
|
||||
An arch should select this symbol if it can support kernel stack
|
||||
offset randomization with calls to add_random_kstack_offset()
|
||||
during syscall entry and choose_random_kstack_offset() during
|
||||
syscall exit. Careful removal of -fstack-protector-strong and
|
||||
offset randomization with a call to add_random_kstack_offset()
|
||||
during syscall entry. Careful removal of -fstack-protector-strong and
|
||||
-fstack-protector should also be applied to the entry code and
|
||||
closely examined, as the artificial stack bump looks like an array
|
||||
to the compiler, so it will attempt to add canary checks regardless
|
||||
|
||||
@@ -52,17 +52,6 @@ static void invoke_syscall(struct pt_regs *regs, unsigned int scno,
|
||||
}
|
||||
|
||||
syscall_set_return_value(current, regs, 0, ret);
|
||||
|
||||
/*
|
||||
* This value will get limited by KSTACK_OFFSET_MAX(), which is 10
|
||||
* bits. The actual entropy will be further reduced by the compiler
|
||||
* when applying stack alignment constraints: the AAPCS mandates a
|
||||
* 16-byte aligned SP at function boundaries, which will remove the
|
||||
* 4 low bits from any entropy chosen here.
|
||||
*
|
||||
* The resulting 6 bits of entropy is seen in SP[9:4].
|
||||
*/
|
||||
choose_random_kstack_offset(get_random_u16());
|
||||
}
|
||||
|
||||
static inline bool has_syscall_work(unsigned long flags)
|
||||
|
||||
@@ -20,8 +20,6 @@ notrace long system_call_exception(struct pt_regs *regs, unsigned long r0)
|
||||
|
||||
kuap_lock();
|
||||
|
||||
add_random_kstack_offset();
|
||||
|
||||
if (IS_ENABLED(CONFIG_PPC_IRQ_SOFT_MASK_DEBUG))
|
||||
BUG_ON(irq_soft_mask_return() != IRQS_ALL_DISABLED);
|
||||
|
||||
@@ -30,6 +28,8 @@ notrace long system_call_exception(struct pt_regs *regs, unsigned long r0)
|
||||
CT_WARN_ON(ct_state() == CT_STATE_KERNEL);
|
||||
user_exit_irqoff();
|
||||
|
||||
add_random_kstack_offset();
|
||||
|
||||
BUG_ON(regs_is_unrecoverable(regs));
|
||||
BUG_ON(!user_mode(regs));
|
||||
BUG_ON(arch_irq_disabled_regs(regs));
|
||||
@@ -173,17 +173,5 @@ notrace long system_call_exception(struct pt_regs *regs, unsigned long r0)
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Ultimately, this value will get limited by KSTACK_OFFSET_MAX(),
|
||||
* so the maximum stack offset is 1k bytes (10 bits).
|
||||
*
|
||||
* The actual entropy will be further reduced by the compiler when
|
||||
* applying stack alignment constraints: the powerpc architecture
|
||||
* may have two kinds of stack alignment (16-bytes and 8-bytes).
|
||||
*
|
||||
* So the resulting 6 or 7 bits of entropy is seen in SP[9:4] or SP[9:3].
|
||||
*/
|
||||
choose_random_kstack_offset(mftb());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -344,18 +344,6 @@ void do_trap_ecall_u(struct pt_regs *regs)
|
||||
syscall_handler(regs, syscall);
|
||||
}
|
||||
|
||||
/*
|
||||
* Ultimately, this value will get limited by KSTACK_OFFSET_MAX(),
|
||||
* so the maximum stack offset is 1k bytes (10 bits).
|
||||
*
|
||||
* The actual entropy will be further reduced by the compiler when
|
||||
* applying stack alignment constraints: 16-byte (i.e. 4-bit) aligned
|
||||
* for RV32I or RV64I.
|
||||
*
|
||||
* The resulting 6 bits of entropy is seen in SP[9:4].
|
||||
*/
|
||||
choose_random_kstack_offset(get_random_u16());
|
||||
|
||||
syscall_exit_to_user_mode(regs);
|
||||
} else {
|
||||
irqentry_state_t state = irqentry_nmi_enter(regs);
|
||||
|
||||
@@ -51,14 +51,6 @@ static __always_inline void arch_exit_to_user_mode(void)
|
||||
|
||||
#define arch_exit_to_user_mode arch_exit_to_user_mode
|
||||
|
||||
static inline void arch_exit_to_user_mode_prepare(struct pt_regs *regs,
|
||||
unsigned long ti_work)
|
||||
{
|
||||
choose_random_kstack_offset(get_tod_clock_fast());
|
||||
}
|
||||
|
||||
#define arch_exit_to_user_mode_prepare arch_exit_to_user_mode_prepare
|
||||
|
||||
static __always_inline bool arch_in_rcu_eqs(void)
|
||||
{
|
||||
if (IS_ENABLED(CONFIG_KVM))
|
||||
|
||||
@@ -86,8 +86,8 @@ void noinstr __do_syscall(struct pt_regs *regs, int per_trap)
|
||||
{
|
||||
unsigned long nr;
|
||||
|
||||
add_random_kstack_offset();
|
||||
enter_from_user_mode(regs);
|
||||
add_random_kstack_offset();
|
||||
regs->psw = get_lowcore()->svc_old_psw;
|
||||
regs->int_code = get_lowcore()->svc_int_code;
|
||||
update_timer_sys();
|
||||
|
||||
@@ -8,8 +8,10 @@ UBSAN_SANITIZE := n
|
||||
KCOV_INSTRUMENT := n
|
||||
|
||||
CFLAGS_REMOVE_common.o = $(CC_FLAGS_FTRACE)
|
||||
CFLAGS_REMOVE_syscall_64.o = $(CC_FLAGS_FTRACE)
|
||||
|
||||
CFLAGS_common.o += -fno-stack-protector
|
||||
CFLAGS_syscall_64.o += -fno-stack-protector
|
||||
|
||||
obj-y := entry.o entry_$(BITS).o syscall_$(BITS).o
|
||||
obj-y += common.o
|
||||
|
||||
@@ -37,99 +37,6 @@
|
||||
#include <asm/syscall.h>
|
||||
#include <asm/irq_stack.h>
|
||||
|
||||
#ifdef CONFIG_X86_64
|
||||
|
||||
static __always_inline bool do_syscall_x64(struct pt_regs *regs, int nr)
|
||||
{
|
||||
/*
|
||||
* Convert negative numbers to very high and thus out of range
|
||||
* numbers for comparisons.
|
||||
*/
|
||||
unsigned int unr = nr;
|
||||
|
||||
if (likely(unr < NR_syscalls)) {
|
||||
unr = array_index_nospec(unr, NR_syscalls);
|
||||
regs->ax = x64_sys_call(regs, unr);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static __always_inline bool do_syscall_x32(struct pt_regs *regs, int nr)
|
||||
{
|
||||
/*
|
||||
* Adjust the starting offset of the table, and convert numbers
|
||||
* < __X32_SYSCALL_BIT to very high and thus out of range
|
||||
* numbers for comparisons.
|
||||
*/
|
||||
unsigned int xnr = nr - __X32_SYSCALL_BIT;
|
||||
|
||||
if (IS_ENABLED(CONFIG_X86_X32_ABI) && likely(xnr < X32_NR_syscalls)) {
|
||||
xnr = array_index_nospec(xnr, X32_NR_syscalls);
|
||||
regs->ax = x32_sys_call(regs, xnr);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Returns true to return using SYSRET, or false to use IRET */
|
||||
__visible noinstr bool do_syscall_64(struct pt_regs *regs, int nr)
|
||||
{
|
||||
add_random_kstack_offset();
|
||||
nr = syscall_enter_from_user_mode(regs, nr);
|
||||
|
||||
instrumentation_begin();
|
||||
|
||||
if (!do_syscall_x64(regs, nr) && !do_syscall_x32(regs, nr) && nr != -1) {
|
||||
/* Invalid system call, but still a system call. */
|
||||
regs->ax = __x64_sys_ni_syscall(regs);
|
||||
}
|
||||
|
||||
instrumentation_end();
|
||||
syscall_exit_to_user_mode(regs);
|
||||
|
||||
/*
|
||||
* Check that the register state is valid for using SYSRET to exit
|
||||
* to userspace. Otherwise use the slower but fully capable IRET
|
||||
* exit path.
|
||||
*/
|
||||
|
||||
/* XEN PV guests always use the IRET path */
|
||||
if (cpu_feature_enabled(X86_FEATURE_XENPV))
|
||||
return false;
|
||||
|
||||
/* SYSRET requires RCX == RIP and R11 == EFLAGS */
|
||||
if (unlikely(regs->cx != regs->ip || regs->r11 != regs->flags))
|
||||
return false;
|
||||
|
||||
/* CS and SS must match the values set in MSR_STAR */
|
||||
if (unlikely(regs->cs != __USER_CS || regs->ss != __USER_DS))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* On Intel CPUs, SYSRET with non-canonical RCX/RIP will #GP
|
||||
* in kernel space. This essentially lets the user take over
|
||||
* the kernel, since userspace controls RSP.
|
||||
*
|
||||
* TASK_SIZE_MAX covers all user-accessible addresses other than
|
||||
* the deprecated vsyscall page.
|
||||
*/
|
||||
if (unlikely(regs->ip >= TASK_SIZE_MAX))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* SYSRET cannot restore RF. It can restore TF, but unlike IRET,
|
||||
* restoring TF results in a trap from userspace immediately after
|
||||
* SYSRET.
|
||||
*/
|
||||
if (unlikely(regs->flags & (X86_EFLAGS_RF | X86_EFLAGS_TF)))
|
||||
return false;
|
||||
|
||||
/* Use SYSRET to exit to userspace */
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_X86_32) || defined(CONFIG_IA32_EMULATION)
|
||||
static __always_inline int syscall_32_enter(struct pt_regs *regs)
|
||||
{
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* System call table for x86-64. */
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* 64-bit system call dispatch */
|
||||
|
||||
#include <linux/linkage.h>
|
||||
#include <linux/sys.h>
|
||||
#include <linux/cache.h>
|
||||
#include <linux/syscalls.h>
|
||||
#include <linux/entry-common.h>
|
||||
#include <linux/nospec.h>
|
||||
#include <asm/syscall.h>
|
||||
|
||||
#define __SYSCALL(nr, sym) extern long __x64_##sym(const struct pt_regs *);
|
||||
@@ -34,3 +36,93 @@ long x64_sys_call(const struct pt_regs *regs, unsigned int nr)
|
||||
default: return __x64_sys_ni_syscall(regs);
|
||||
}
|
||||
};
|
||||
|
||||
static __always_inline bool do_syscall_x64(struct pt_regs *regs, int nr)
|
||||
{
|
||||
/*
|
||||
* Convert negative numbers to very high and thus out of range
|
||||
* numbers for comparisons.
|
||||
*/
|
||||
unsigned int unr = nr;
|
||||
|
||||
if (likely(unr < NR_syscalls)) {
|
||||
unr = array_index_nospec(unr, NR_syscalls);
|
||||
regs->ax = x64_sys_call(regs, unr);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static __always_inline bool do_syscall_x32(struct pt_regs *regs, int nr)
|
||||
{
|
||||
/*
|
||||
* Adjust the starting offset of the table, and convert numbers
|
||||
* < __X32_SYSCALL_BIT to very high and thus out of range
|
||||
* numbers for comparisons.
|
||||
*/
|
||||
unsigned int xnr = nr - __X32_SYSCALL_BIT;
|
||||
|
||||
if (IS_ENABLED(CONFIG_X86_X32_ABI) && likely(xnr < X32_NR_syscalls)) {
|
||||
xnr = array_index_nospec(xnr, X32_NR_syscalls);
|
||||
regs->ax = x32_sys_call(regs, xnr);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Returns true to return using SYSRET, or false to use IRET */
|
||||
__visible noinstr bool do_syscall_64(struct pt_regs *regs, int nr)
|
||||
{
|
||||
nr = syscall_enter_from_user_mode(regs, nr);
|
||||
|
||||
instrumentation_begin();
|
||||
add_random_kstack_offset();
|
||||
|
||||
if (!do_syscall_x64(regs, nr) && !do_syscall_x32(regs, nr) && nr != -1) {
|
||||
/* Invalid system call, but still a system call. */
|
||||
regs->ax = __x64_sys_ni_syscall(regs);
|
||||
}
|
||||
|
||||
instrumentation_end();
|
||||
syscall_exit_to_user_mode(regs);
|
||||
|
||||
/*
|
||||
* Check that the register state is valid for using SYSRET to exit
|
||||
* to userspace. Otherwise use the slower but fully capable IRET
|
||||
* exit path.
|
||||
*/
|
||||
|
||||
/* XEN PV guests always use the IRET path */
|
||||
if (cpu_feature_enabled(X86_FEATURE_XENPV))
|
||||
return false;
|
||||
|
||||
/* SYSRET requires RCX == RIP and R11 == EFLAGS */
|
||||
if (unlikely(regs->cx != regs->ip || regs->r11 != regs->flags))
|
||||
return false;
|
||||
|
||||
/* CS and SS must match the values set in MSR_STAR */
|
||||
if (unlikely(regs->cs != __USER_CS || regs->ss != __USER_DS))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* On Intel CPUs, SYSRET with non-canonical RCX/RIP will #GP
|
||||
* in kernel space. This essentially lets the user take over
|
||||
* the kernel, since userspace controls RSP.
|
||||
*
|
||||
* TASK_SIZE_MAX covers all user-accessible addresses other than
|
||||
* the deprecated vsyscall page.
|
||||
*/
|
||||
if (unlikely(regs->ip >= TASK_SIZE_MAX))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* SYSRET cannot restore RF. It can restore TF, but unlike IRET,
|
||||
* restoring TF results in a trap from userspace immediately after
|
||||
* SYSRET.
|
||||
*/
|
||||
if (unlikely(regs->flags & (X86_EFLAGS_RF | X86_EFLAGS_TF)))
|
||||
return false;
|
||||
|
||||
/* Use SYSRET to exit to userspace */
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -81,18 +81,6 @@ static inline void arch_exit_to_user_mode_prepare(struct pt_regs *regs,
|
||||
current_thread_info()->status &= ~(TS_COMPAT | TS_I386_REGS_POKED);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This value will get limited by KSTACK_OFFSET_MAX(), which is 10
|
||||
* bits. The actual entropy will be further reduced by the compiler
|
||||
* when applying stack alignment constraints (see cc_stack_align4/8 in
|
||||
* arch/x86/Makefile), which will remove the 3 (x86_64) or 2 (ia32)
|
||||
* low bits from any entropy chosen here.
|
||||
*
|
||||
* Therefore, final stack offset entropy will be 7 (x86_64) or
|
||||
* 8 (ia32) bits.
|
||||
*/
|
||||
choose_random_kstack_offset(rdtsc());
|
||||
|
||||
/* Avoid unnecessary reads of 'x86_ibpb_exit_to_user' */
|
||||
if (cpu_feature_enabled(X86_FEATURE_IBPB_EXIT_TO_USER) &&
|
||||
this_cpu_read(x86_ibpb_exit_to_user)) {
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/jump_label.h>
|
||||
#include <linux/percpu-defs.h>
|
||||
#include <linux/prandom.h>
|
||||
|
||||
DECLARE_STATIC_KEY_MAYBE(CONFIG_RANDOMIZE_KSTACK_OFFSET_DEFAULT,
|
||||
randomize_kstack_offset);
|
||||
DECLARE_PER_CPU(u32, kstack_offset);
|
||||
|
||||
/*
|
||||
* Do not use this anywhere else in the kernel. This is used here because
|
||||
@@ -46,53 +46,39 @@ DECLARE_PER_CPU(u32, kstack_offset);
|
||||
#define KSTACK_OFFSET_MAX(x) ((x) & 0b1111111100)
|
||||
#endif
|
||||
|
||||
DECLARE_PER_CPU(struct rnd_state, kstack_rnd_state);
|
||||
|
||||
static __always_inline u32 get_kstack_offset(void)
|
||||
{
|
||||
struct rnd_state *state;
|
||||
u32 rnd;
|
||||
|
||||
state = &get_cpu_var(kstack_rnd_state);
|
||||
rnd = prandom_u32_state(state);
|
||||
put_cpu_var(kstack_rnd_state);
|
||||
|
||||
return rnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* add_random_kstack_offset - Increase stack utilization by previously
|
||||
* chosen random offset
|
||||
* add_random_kstack_offset - Increase stack utilization by a random offset.
|
||||
*
|
||||
* This should be used in the syscall entry path when interrupts and
|
||||
* preempt are disabled, and after user registers have been stored to
|
||||
* the stack. For testing the resulting entropy, please see:
|
||||
* tools/testing/selftests/lkdtm/stack-entropy.sh
|
||||
* This should be used in the syscall entry path after user registers have been
|
||||
* stored to the stack. Preemption may be enabled. For testing the resulting
|
||||
* entropy, please see: tools/testing/selftests/lkdtm/stack-entropy.sh
|
||||
*/
|
||||
#define add_random_kstack_offset() do { \
|
||||
if (static_branch_maybe(CONFIG_RANDOMIZE_KSTACK_OFFSET_DEFAULT, \
|
||||
&randomize_kstack_offset)) { \
|
||||
u32 offset = raw_cpu_read(kstack_offset); \
|
||||
u32 offset = get_kstack_offset(); \
|
||||
u8 *ptr = __kstack_alloca(KSTACK_OFFSET_MAX(offset)); \
|
||||
/* Keep allocation even after "ptr" loses scope. */ \
|
||||
asm volatile("" :: "r"(ptr) : "memory"); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* choose_random_kstack_offset - Choose the random offset for the next
|
||||
* add_random_kstack_offset()
|
||||
*
|
||||
* This should only be used during syscall exit when interrupts and
|
||||
* preempt are disabled. This position in the syscall flow is done to
|
||||
* frustrate attacks from userspace attempting to learn the next offset:
|
||||
* - Maximize the timing uncertainty visible from userspace: if the
|
||||
* offset is chosen at syscall entry, userspace has much more control
|
||||
* over the timing between choosing offsets. "How long will we be in
|
||||
* kernel mode?" tends to be more difficult to predict than "how long
|
||||
* will we be in user mode?"
|
||||
* - Reduce the lifetime of the new offset sitting in memory during
|
||||
* kernel mode execution. Exposure of "thread-local" memory content
|
||||
* (e.g. current, percpu, etc) tends to be easier than arbitrary
|
||||
* location memory exposure.
|
||||
*/
|
||||
#define choose_random_kstack_offset(rand) do { \
|
||||
if (static_branch_maybe(CONFIG_RANDOMIZE_KSTACK_OFFSET_DEFAULT, \
|
||||
&randomize_kstack_offset)) { \
|
||||
u32 offset = raw_cpu_read(kstack_offset); \
|
||||
offset = ror32(offset, 5) ^ (rand); \
|
||||
raw_cpu_write(kstack_offset, offset); \
|
||||
} \
|
||||
} while (0)
|
||||
#else /* CONFIG_RANDOMIZE_KSTACK_OFFSET */
|
||||
#define add_random_kstack_offset() do { } while (0)
|
||||
#define choose_random_kstack_offset(rand) do { } while (0)
|
||||
#endif /* CONFIG_RANDOMIZE_KSTACK_OFFSET */
|
||||
|
||||
#endif
|
||||
|
||||
+8
-1
@@ -820,7 +820,14 @@ static inline void initcall_debug_enable(void)
|
||||
#ifdef CONFIG_RANDOMIZE_KSTACK_OFFSET
|
||||
DEFINE_STATIC_KEY_MAYBE_RO(CONFIG_RANDOMIZE_KSTACK_OFFSET_DEFAULT,
|
||||
randomize_kstack_offset);
|
||||
DEFINE_PER_CPU(u32, kstack_offset);
|
||||
DEFINE_PER_CPU(struct rnd_state, kstack_rnd_state);
|
||||
|
||||
static int __init random_kstack_init(void)
|
||||
{
|
||||
prandom_seed_full_state(&kstack_rnd_state);
|
||||
return 0;
|
||||
}
|
||||
late_initcall(random_kstack_init);
|
||||
|
||||
static int __init early_randomize_kstack_offset(char *buf)
|
||||
{
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
#include <linux/thread_info.h>
|
||||
#include <linux/stackleak.h>
|
||||
#include <linux/kasan.h>
|
||||
#include <linux/randomize_kstack.h>
|
||||
#include <linux/scs.h>
|
||||
#include <linux/io_uring.h>
|
||||
#include <linux/bpf.h>
|
||||
|
||||
Reference in New Issue
Block a user