mirror of
https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-10.git
synced 2026-09-09 00:07:04 +08:00
JIRA: https://redhat.atlassian.net/browse/RHEL-215975 Conflicts: arch/loongarch/kernel/syscall.c arch/x86/entry/syscall_32.c Drop these bits not used in RHEL. commit a96ef5848cb096226bf6aff31a90d8b136d99b71 Author: Ryan Roberts <ryan.roberts@arm.com> Date: Tue, 3 Mar 2026 15:08:39 +0000 Previously different architectures were using random sources of differing strength and cost to decide the random kstack offset. A number of architectures (loongarch, powerpc, s390, x86) were using their timestamp counter, at whatever the frequency happened to be. Other arches (arm64, riscv) were using entropy from the crng via get_random_u16(). There have been concerns that in some cases the timestamp counters may be too weak, because they can be easily guessed or influenced by user space. And get_random_u16() has been shown to be too costly for the level of protection kstack offset randomization provides. So let's use a common, architecture-agnostic source of entropy; a per-cpu prng, seeded at boot-time from the crng. This has a few benefits: - We can remove choose_random_kstack_offset(); That was only there to try to make the timestamp counter value a bit harder to influence from user space [*]. - The architecture code is simplified. All it has to do now is call add_random_kstack_offset() in the syscall path. - The strength of the randomness can be reasoned about independently of the architecture. - Arches previously using get_random_u16() now have much faster syscall paths, see below results. [*] Additionally, this gets rid of some redundant work on s390 and x86. Before this patch, those architectures called choose_random_kstack_offset() under arch_exit_to_user_mode_prepare(), which is also called for exception returns to userspace which were *not* syscalls (e.g. regular interrupts). Getting rid of choose_random_kstack_offset() avoids a small amount of redundant work for the non-syscall cases. In some configurations, add_random_kstack_offset() will now call instrumentable code, so for a couple of arches, I have moved the call a bit later to the first point where instrumentation is allowed. This doesn't impact the efficacy of the mechanism. There have been some claims that a prng may be less strong than the timestamp counter if not regularly reseeded. But the prng has a period of about 2^113. So as long as the prng state remains secret, it should not be possible to guess. If the prng state can be accessed, we have bigger problems. Additionally, we are only consuming 6 bits to randomize the stack, so there are only 64 possible random offsets. I assert that it would be trivial for an attacker to brute force by repeating their attack and waiting for the random stack offset to be the desired one. The prng approach seems entirely proportional to this level of protection. Performance data are provided below. The baseline is v6.18 with rndstack on for each respective arch. (I)/(R) indicate statistically significant improvement/regression. arm64 platform is AWS Graviton3 (m7g.metal). x86_64 platform is AWS Sapphire Rapids (m7i.24xlarge): +-----------------+--------------+---------------+---------------+ | Benchmark | Result Class | per-cpu-prng | per-cpu-prng | | | | arm64 (metal) | x86_64 (VM) | +=================+==============+===============+===============+ | syscall/getpid | mean (ns) | (I) -9.50% | (I) -17.65% | | | p99 (ns) | (I) -59.24% | (I) -24.41% | | | p99.9 (ns) | (I) -59.52% | (I) -28.52% | +-----------------+--------------+---------------+---------------+ | syscall/getppid | mean (ns) | (I) -9.52% | (I) -19.24% | | | p99 (ns) | (I) -59.25% | (I) -25.03% | | | p99.9 (ns) | (I) -59.50% | (I) -28.17% | +-----------------+--------------+---------------+---------------+ | syscall/invalid | mean (ns) | (I) -10.31% | (I) -18.56% | | | p99 (ns) | (I) -60.79% | (I) -20.06% | | | p99.9 (ns) | (I) -61.04% | (I) -25.04% | +-----------------+--------------+---------------+---------------+ I tested an earlier version of this change on x86 bare metal and it showed a smaller but still significant improvement. The bare metal system wasn't available this time around so testing was done in a VM instance. I'm guessing the cost of rdtsc is higher for VMs. Acked-by: Mark Rutland <mark.rutland@arm.com> Signed-off-by: Ryan Roberts <ryan.roberts@arm.com> Link: https://patch.msgid.link/20260303150840.3789438-3-ryan.roberts@arm.com Signed-off-by: Kees Cook <kees@kernel.org> Signed-off-by: Mark Salter <msalter@redhat.com>
129 lines
3.4 KiB
C
129 lines
3.4 KiB
C
// 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 *);
|
|
#define __SYSCALL_NORETURN(nr, sym) extern long __noreturn __x64_##sym(const struct pt_regs *);
|
|
#include <asm/syscalls_64.h>
|
|
#undef __SYSCALL
|
|
|
|
#undef __SYSCALL_NORETURN
|
|
#define __SYSCALL_NORETURN __SYSCALL
|
|
|
|
/*
|
|
* The sys_call_table[] is no longer used for system calls, but
|
|
* kernel/trace/trace_syscalls.c still wants to know the system
|
|
* call address.
|
|
*/
|
|
#define __SYSCALL(nr, sym) __x64_##sym,
|
|
const sys_call_ptr_t sys_call_table[] = {
|
|
#include <asm/syscalls_64.h>
|
|
};
|
|
#undef __SYSCALL
|
|
|
|
#define __SYSCALL(nr, sym) case nr: return __x64_##sym(regs);
|
|
long x64_sys_call(const struct pt_regs *regs, unsigned int nr)
|
|
{
|
|
switch (nr) {
|
|
#include <asm/syscalls_64.h>
|
|
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;
|
|
}
|