Generic entry code updates:

- Make syscall user dispatching configurable
 
     Not all architectures can makes use of syscall user dispatching. Allow
     them to disable the feature completely.
 
   - Consolidate stack randomization for the generic entry code and the
     architectures using it.
 
     Stack randomization on syscall entry was sprinkled throughout the
     architecture specific low level entry code and in some cases at the
     wrong points, e.g. before establishing state, which violates the
     non-instrumentable constraints of that code.
 
     Clean this up by integrating stack randomization into the generic entry
     code helpers so that it is invoked at the earliest possible point right
     after establishing state and converting all generic entry code using
     architecture over.
 
   - Clean up the syscall number handling in the generic entry code. It
     works correctly for architectures which have a separate return value
     storage in pt_regs, but fails to distinguish the case where user space
     handed in -1 as syscall number from the case where the entry code
     rejects it by returning -1 to the callers. Aside of that the return
     value functionality of those interfaces is not really intuitive.
 
     Fix this by separating the decision to reject a syscall (user dispatch,
     ptrace, seccomp ...) from the potential modification of the syscall
     number through these mechanisms.
 
     This solves most of the problems for architectures which do not have a
     separate return value storage in pt_regs except for the case where a
     tracepoint has a BPF script or a probe attached which overwrite both
     the syscall number and the return value. But that's a problem which
     cannot be solved in the generic code, that only can be addressed by
     separating the storage model in the affected architectures.
 -----BEGIN PGP SIGNATURE-----
 
 iQJEBAABCgAuFiEEQp8+kY+LLUocC4bMphj1TA10mKEFAmqCs10QHHRnbHhAa2Vy
 bmVsLm9yZwAKCRCmGPVMDXSYoaf6D/0ZBG1Yb0/C/6lrI185qPu38aGOROuAcxP+
 RV1O1x6C83w2hCLBH8LeswY2x4/iGbdftne/hfmvu8eNCE5MzBfYvXhLL4If75Tc
 IJ6C8uummnDmrT1TFuWHryTAfjyF28gt0+GGq0Zy5Hyz9b4CTJqOMx5u6KV4cZuJ
 odoNQpE/GlWo40wCSTYP/Tt5xONrogk2pMQtFyV8JEoaXkdYSj/V815yojEmofYU
 fmgPPO5/vOnZzE4b29gZyndXnU1Boah7r1l5fg7c9za376yCEEzh/ApPhovHyY0A
 t8zjnrtooZ27IUKbcsyycrAM14asfcmViDNDgaCj8ttBioQaCnxO1BpKWjVxEZhE
 AbM6q3Q66ER4Df6GNhZjPqT5Lr7E7+vLLarhXLWztsGQklIx4AFbrsa73hA20UC9
 1PSeMd45JSxH3yA8vMauXAGHFK1tD1V8Lgofu69+2Z3jtKB+aU0fqWeL1jesSEM0
 oCGhUb3hIC1pz3KVA0MGmNTm0yyQJYTGZL7wADYNV5NbxJVqXgo37qa/0n94Gf/4
 TG3OwY4Sb/H/sve7v/eY4IvxVh+xs3dLZP8ZoqMlPCp9JIxc6iNoe6VHqPI7PFnM
 fXwDtsy+bRF/SKnB/32qxnR7UJqmdNH3XIjd+lXWliKt6UYoC79/MEKN5DmJcO9P
 CykZUWa72A==
 =XUd9
 -----END PGP SIGNATURE-----

Merge tag 'core-entry-2026-08-17' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull generic entry code updates from Thomas Gleixner:

 - Make syscall user dispatching configurable

   Not all architectures can makes use of syscall user dispatching.
   Allow them to disable the feature completely.

 - Consolidate stack randomization for the generic entry code and the
   architectures using it.

   Stack randomization on syscall entry was sprinkled throughout the
   architecture specific low level entry code and in some cases at the
   wrong points, e.g. before establishing state, which violates the
   non-instrumentable constraints of that code.

   Clean this up by integrating stack randomization into the generic
   entry code helpers so that it is invoked at the earliest possible
   point right after establishing state and converting all generic entry
   code using architecture over.

 - Clean up the syscall number handling in the generic entry code. It
   works correctly for architectures which have a separate return value
   storage in pt_regs, but fails to distinguish the case where user
   space handed in -1 as syscall number from the case where the entry
   code rejects it by returning -1 to the callers. Aside of that the
   return value functionality of those interfaces is not really
   intuitive.

   Fix this by separating the decision to reject a syscall (user
   dispatch, ptrace, seccomp ...) from the potential modification of the
   syscall number through these mechanisms.

   This solves most of the problems for architectures which do not have
   a separate return value storage in pt_regs except for the case where
   a tracepoint has a BPF script or a probe attached which overwrite
   both the syscall number and the return value. But that's a problem
   which cannot be solved in the generic code, that only can be
   addressed by separating the storage model in the affected
   architectures.

* tag 'core-entry-2026-08-17' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (23 commits)
  entry, treewide: Make syscall_enter_from_user_mode[_work]() indicate syscall execution
  entry: Make return type of syscall_trace_enter() bool
  entry: Rework trace_syscall_enter()
  entry: Rework syscall_audit_enter()
  syscall_user_dispatch: Introduce ARCH_SUPPORTS_SYSCALL_USER_DISPATCH
  entry: Fix seccomp bypass after ptrace with TSYNC
  x86/entry: Simplify the syscall number logic
  x86/entry: Get rid of the sys_ni_syscall() indirection
  x86/entry: Make syscall functions static
  ptrace, treewide: Rename ptrace_report_syscall_entry() to ptrace_report_syscall_permit_entry()
  seccomp, treewide: Rename and convert __secure_computing() to return boolean
  entry: Use syscall number instead of rereading it
  entry: Remove syscall_enter_from_user_mode()
  x86/syscall: Use [syscall_]enter_from_user_mode_randomize_stack()
  s390/syscall: Use enter_from_user_mode_randomize_stack()
  riscv/syscall: Use syscall_enter_from_user_mode_randomize_stack()
  powerpc/syscall: Use syscall_enter_from_user_mode_randomize_stack()
  loongarch/syscall: Use syscall_enter_from_user_mode_randomize_stack()
  entry: Provide [syscall_]enter_from_user_mode_randomize_stack()
  randomize_kstack: Provide add_random_kstack_offset_irqsoff()
  ...
This commit is contained in:
Linus Torvalds 2026-08-18 15:00:56 -07:00
commit 3424d8c18a
48 changed files with 408 additions and 323 deletions

View File

@ -1402,6 +1402,23 @@ Note that if you change this from 0 to 1, already created segments
without users and with a dead originative process will be destroyed.
syscall_user_dispatch
=====================
Controls whether userspace may arm Syscall User Dispatch via
``prctl(PR_SET_SYSCALL_USER_DISPATCH, ...)`` or the
``PTRACE_SET_SYSCALL_USER_DISPATCH_CONFIG`` ptrace request:
== ===================================================================
0 Arming syscall user dispatch is denied with ``-EPERM``. Tasks that
already armed it keep it, and disabling it is always permitted.
1 (default) Arming syscall user dispatch is permitted.
== ===================================================================
Only present when the kernel is built with ``CONFIG_SYSCALL_USER_DISPATCH``
and ``CONFIG_PROC_SYSCTL``.
sysctl_writes_strict
====================

View File

@ -58,32 +58,59 @@ state transitions must run with interrupts disabled.
Syscalls
--------
Syscall-entry code starts in assembly code and calls out into low-level C code
after establishing low-level architecture-specific state and stack frames. This
low-level C code must not be instrumented. A typical syscall handling function
invoked from low-level assembly code looks like this:
Syscall-entry code starts in assembly code and calls out into low-level C
code after establishing low-level architecture-specific state and stack
frames. This low-level C code must not be instrumented. The recommended
syscall handling function invoked from low-level assembly code looks like
this:
.. code-block:: c
noinstr void syscall(struct pt_regs *regs, int nr)
noinstr void syscall(struct pt_regs *regs, long nr)
{
arch_syscall_enter(regs);
nr = syscall_enter_from_user_mode(regs, nr);
instrumentation_begin();
if (!invoke_syscall(regs, nr) && nr != -1)
result_reg(regs) = __sys_ni_syscall(regs);
instrumentation_end();
result_reg(regs) = -ENOSYS;
if (syscall_enter_from_user_mode_randomize_stack(regs, &nr)) {
instrumentation_begin();
if (valid(nr)
result_reg(regs) = invoke_syscall(regs, nr);
instrumentation_end();
}
syscall_exit_to_user_mode(regs);
}
syscall_enter_from_user_mode() first invokes enter_from_user_mode() which
establishes state in the following order:
This is the most resilent variant as it has always a guaranteed valid
return code. The alternative variant is:
.. code-block:: c
noinstr void syscall(struct pt_regs *regs, long nr)
{
arch_syscall_enter(regs);
if (syscall_enter_from_user_mode_randomize_stack(regs, &nr)) {
instrumentation_begin();
if (valid(nr)
result_reg(regs) = invoke_syscall(regs, nr);
else
result_reg(regs) = -ENOSYS;
instrumentation_end();
}
syscall_exit_to_user_mode(regs);
}
That works for most situations except when a probe/BPF attached to the
syscall tracepoint sets an invalid syscall number e.g. -1 and also modifies
the result register. So this variant will obviously overwrite the modified
result with -ENOSYS.
syscall_enter_from_user_mode_randomize_stack() first invokes
enter_from_user_mode_randomize_stack() which establishes state in the
following order:
* Lockdep
* RCU / Context tracking
* Tracing
* Apply stack randomization
and then invokes the various entry work functions like ptrace, seccomp, audit,
syscall tracing, etc. After all that is done, the instrumentable invoke_syscall
@ -99,10 +126,11 @@ transition in the reverse order:
* RCU / Context tracking
* Lockdep
syscall_enter_from_user_mode() and syscall_exit_to_user_mode() are also
available as fine grained subfunctions in cases where the architecture code
has to do extra work between the various steps. In such cases it has to
ensure that enter_from_user_mode() is called first on entry and
syscall_enter_from_user_mode_randomize_stack() and
syscall_exit_to_user_mode() are also available as fine grained subfunctions
in cases where the architecture code has to do extra work between the
various steps. In such cases it has to ensure that
enter_from_user_mode_randomize_stack() is called first on entry and
exit_to_user_mode() is called last on exit.
Do not nest syscalls. Nested syscalls will cause RCU and/or context tracking

View File

@ -114,6 +114,20 @@ config GENERIC_ENTRY
select GENERIC_IRQ_ENTRY
select GENERIC_SYSCALL
config ARCH_SUPPORTS_SYSCALL_USER_DISPATCH
bool
config SYSCALL_USER_DISPATCH
bool "Syscall User Dispatch"
depends on ARCH_SUPPORTS_SYSCALL_USER_DISPATCH
depends on GENERIC_ENTRY
default y
help
Syscall User Dispatch lets a thread have its own system calls
intercepted and redirected to a userspace signal handler based
on a prctl() configured instruction pointer range.
If unsure, say Y.
config KPROBES
bool "Kprobes"
depends on HAVE_KPROBES
@ -637,7 +651,7 @@ config HAVE_ARCH_SECCOMP_FILTER
- syscall_set_return_value()
- SIGSYS siginfo_t support
- secure_computing is called from a ptrace_event()-safe context
- secure_computing return value is checked and a return value of -1
- secure_computing return value is checked and if false it
results in the system call being skipped immediately.
- seccomp syscall wired up
- if !HAVE_SPARSE_SYSCALL_NR, have SECCOMP_ARCH_NATIVE,

View File

@ -375,7 +375,7 @@ asmlinkage unsigned long syscall_trace_enter(void)
struct pt_regs *regs = current_pt_regs();
if (test_thread_flag(TIF_SYSCALL_TRACE) &&
ptrace_report_syscall_entry(regs)) {
!ptrace_report_syscall_permit_entry(regs)) {
syscall_set_nr(current, regs, -1);
if (regs->r19 == 0 && regs->r0 == (unsigned long)-1)
syscall_set_return_value(current, regs, -ENOSYS, 0);
@ -387,7 +387,7 @@ asmlinkage unsigned long syscall_trace_enter(void)
* If this fails, seccomp may already have set up the return value
* (e.g. SECCOMP_RET_ERRNO / TRACE).
*/
if (secure_computing() == -1) {
if (!seccomp_permit_syscall()) {
if (regs->r19 == 0 && regs->r0 == (unsigned long)-1)
syscall_set_return_value(current, regs, -ENOSYS, 0);
syscall_set_nr(current, regs, -1);

View File

@ -342,7 +342,7 @@ long arch_ptrace(struct task_struct *child, long request,
asmlinkage int syscall_trace_enter(struct pt_regs *regs)
{
if (test_thread_flag(TIF_SYSCALL_TRACE))
if (ptrace_report_syscall_entry(regs))
if (!ptrace_report_syscall_permit_entry(regs))
return ULONG_MAX;
#ifdef CONFIG_HAVE_SYSCALL_TRACEPOINTS

View File

@ -840,7 +840,7 @@ static void report_syscall(struct pt_regs *regs, enum ptrace_syscall_dir dir)
if (dir == PTRACE_SYSCALL_EXIT)
ptrace_report_syscall_exit(regs, 0);
else if (ptrace_report_syscall_entry(regs))
else if (!ptrace_report_syscall_permit_entry(regs))
current_thread_info()->abi_syscall = -1;
regs->ARM_ip = ip;
@ -855,7 +855,7 @@ asmlinkage int syscall_trace_enter(struct pt_regs *regs)
/* Do seccomp after ptrace; syscall may have changed. */
#ifdef CONFIG_HAVE_ARCH_SECCOMP_FILTER
if (secure_computing() == -1)
if (!seccomp_permit_syscall())
return -1;
#else
/* XXX: remove this once OABI gets fixed */

View File

@ -2429,7 +2429,7 @@ static int report_syscall_entry(struct pt_regs *regs)
int regno, ret;
saved_reg = ptrace_save_reg(regs, PTRACE_SYSCALL_ENTER, &regno);
ret = ptrace_report_syscall_entry(regs);
ret = !ptrace_report_syscall_permit_entry(regs);
if (ret)
forget_syscall(regs);
regs->regs[regno] = saved_reg;
@ -2470,7 +2470,7 @@ int syscall_trace_enter(struct pt_regs *regs)
}
/* Do the secure computing after ptrace; failures should be fast. */
if (secure_computing() == -1)
if (!seccomp_permit_syscall())
return NO_SYSCALL;
if (test_thread_flag(TIF_SYSCALL_TRACEPOINT))

View File

@ -320,10 +320,10 @@ long arch_ptrace(struct task_struct *child, long request,
asmlinkage int syscall_trace_enter(struct pt_regs *regs)
{
if (test_thread_flag(TIF_SYSCALL_TRACE))
if (ptrace_report_syscall_entry(regs))
if (!ptrace_report_syscall_permit_entry(regs))
return -1;
if (secure_computing() == -1)
if (!seccomp_permit_syscall())
return -1;
if (test_thread_flag(TIF_SYSCALL_TRACEPOINT))

View File

@ -345,7 +345,7 @@ void do_trap0(struct pt_regs *regs)
/* allow strace to catch syscall args */
if (unlikely(test_thread_flag(TIF_SYSCALL_TRACE) &&
ptrace_report_syscall_entry(regs)))
!ptrace_report_syscall_permit_entry(regs)))
return; /* return -ENOSYS somewhere? */
/* Interrupts should be re-enabled for syscall processing */

View File

@ -84,10 +84,4 @@ static inline int syscall_get_arch(struct task_struct *task)
return AUDIT_ARCH_LOONGARCH64;
#endif
}
static inline bool arch_syscall_is_vdso_sigreturn(struct pt_regs *regs)
{
return false;
}
#endif /* __ASM_LOONGARCH_SYSCALL_H */

View File

@ -11,7 +11,6 @@
#include <linux/linkage.h>
#include <linux/nospec.h>
#include <linux/objtool.h>
#include <linux/randomize_kstack.h>
#include <linux/syscalls.h>
#include <linux/unistd.h>
@ -58,8 +57,8 @@ typedef long (*sys_call_fn)(unsigned long, unsigned long,
void noinstr __no_stack_protector do_syscall(struct pt_regs *regs)
{
unsigned long nr;
sys_call_fn syscall_fn;
unsigned long nr;
nr = regs->regs[11];
/* Set for syscall restarting */
@ -70,14 +69,12 @@ void noinstr __no_stack_protector do_syscall(struct pt_regs *regs)
regs->orig_a0 = regs->regs[4];
regs->regs[4] = -ENOSYS;
nr = syscall_enter_from_user_mode(regs, nr);
add_random_kstack_offset();
if (nr < NR_syscalls) {
syscall_fn = sys_call_table[array_index_nospec(nr, NR_syscalls)];
regs->regs[4] = syscall_fn(regs->orig_a0, regs->regs[5], regs->regs[6],
regs->regs[7], regs->regs[8], regs->regs[9]);
if (likely(syscall_enter_from_user_mode_randomize_stack(regs, &nr))) {
if (nr < NR_syscalls) {
syscall_fn = sys_call_table[array_index_nospec(nr, NR_syscalls)];
regs->regs[4] = syscall_fn(regs->orig_a0, regs->regs[5], regs->regs[6],
regs->regs[7], regs->regs[8], regs->regs[9]);
}
}
syscall_exit_to_user_mode(regs);

View File

@ -279,9 +279,9 @@ asmlinkage int syscall_trace_enter(void)
int ret = 0;
if (test_thread_flag(TIF_SYSCALL_TRACE))
ret = ptrace_report_syscall_entry(task_pt_regs(current));
ret = !ptrace_report_syscall_permit_entry(task_pt_regs(current));
if (secure_computing() == -1)
if (!seccomp_permit_syscall())
return -1;
return ret;

View File

@ -139,7 +139,7 @@ asmlinkage unsigned long do_syscall_trace_enter(struct pt_regs *regs)
secure_computing_strict(regs->r12);
if (test_thread_flag(TIF_SYSCALL_TRACE) &&
ptrace_report_syscall_entry(regs))
!ptrace_report_syscall_permit_entry(regs))
/*
* Tracing decided this syscall should not happen.
* We'll return a bogus call number to get an ENOSYS

View File

@ -1324,11 +1324,11 @@ asmlinkage long syscall_trace_enter(struct pt_regs *regs)
user_exit();
if (test_thread_flag(TIF_SYSCALL_TRACE)) {
if (ptrace_report_syscall_entry(regs))
if (!ptrace_report_syscall_permit_entry(regs))
return -1;
}
if (secure_computing())
if (!seccomp_permit_syscall())
return -1;
if (unlikely(test_thread_flag(TIF_SYSCALL_TRACEPOINT)))

View File

@ -133,7 +133,7 @@ asmlinkage int do_syscall_trace_enter(void)
int ret = 0;
if (test_thread_flag(TIF_SYSCALL_TRACE))
ret = ptrace_report_syscall_entry(task_pt_regs(current));
ret = !ptrace_report_syscall_permit_entry(task_pt_regs(current));
return ret;
}

View File

@ -293,7 +293,7 @@ asmlinkage long do_syscall_trace_enter(struct pt_regs *regs)
long ret = 0;
if (test_thread_flag(TIF_SYSCALL_TRACE) &&
ptrace_report_syscall_entry(regs))
!ptrace_report_syscall_permit_entry(regs))
/*
* Tracing decided this syscall should not happen.
* We'll return a bogus call number to get an ENOSYS

View File

@ -326,7 +326,7 @@ long compat_arch_ptrace(struct task_struct *child, compat_long_t request,
long do_syscall_trace_enter(struct pt_regs *regs)
{
if (test_thread_flag(TIF_SYSCALL_TRACE)) {
int rc = ptrace_report_syscall_entry(regs);
bool permit = ptrace_report_syscall_permit_entry(regs);
/*
* As tracesys_next does not set %r28 to -ENOSYS
@ -334,12 +334,10 @@ long do_syscall_trace_enter(struct pt_regs *regs)
*/
regs->gr[28] = -ENOSYS;
if (rc) {
if (!permit) {
/*
* A nonzero return code from
* ptrace_report_syscall_entry() tells us
* to prevent the syscall execution. Skip
* the syscall call and the syscall restart handling.
* Skip the syscall call and the syscall restart
* handling.
*
* Note that the tracer may also just change
* regs->gr[20] to an invalid syscall number,
@ -351,7 +349,7 @@ long do_syscall_trace_enter(struct pt_regs *regs)
}
/* Do the secure computing check after ptrace. */
if (secure_computing() == -1)
if (!seccomp_permit_syscall())
return -1;
#ifdef CONFIG_HAVE_SYSCALL_TRACEPOINTS

View File

@ -145,9 +145,4 @@ static inline int syscall_get_arch(struct task_struct *task)
else
return AUDIT_ARCH_PPC64;
}
static inline bool arch_syscall_is_vdso_sigreturn(struct pt_regs *regs)
{
return false;
}
#endif /* _ASM_SYSCALL_H */

View File

@ -2,7 +2,6 @@
#include <linux/compat.h>
#include <linux/context_tracking.h>
#include <linux/randomize_kstack.h>
#include <linux/entry-common.h>
#include <asm/interrupt.h>
@ -19,8 +18,8 @@ notrace long system_call_exception(struct pt_regs *regs, unsigned long r0)
long ret;
syscall_fn f;
add_random_kstack_offset();
r0 = syscall_enter_from_user_mode(regs, r0);
if (unlikely(!syscall_enter_from_user_mode_randomize_stack(regs, &r0)))
return syscall_get_error(current, regs);
if (unlikely(test_and_clear_thread_flag(TIF_SYSCALL_RET)))
return syscall_get_error(current, regs);

View File

@ -112,11 +112,6 @@ static inline void syscall_handler(struct pt_regs *regs, ulong syscall)
regs->a0 = fn(regs);
}
static inline bool arch_syscall_is_vdso_sigreturn(struct pt_regs *regs)
{
return false;
}
asmlinkage long sys_riscv_flush_icache(uintptr_t, uintptr_t, uintptr_t);
asmlinkage long sys_riscv_hwprobe(struct riscv_hwprobe *, size_t, size_t,

View File

@ -7,7 +7,6 @@
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/irqflags.h>
#include <linux/randomize_kstack.h>
#include <linux/sched.h>
#include <linux/sched/debug.h>
#include <linux/sched/signal.h>
@ -333,15 +332,12 @@ void do_trap_ecall_u(struct pt_regs *regs)
riscv_v_vstate_discard(regs);
syscall = syscall_enter_from_user_mode(regs, syscall);
add_random_kstack_offset();
if (syscall >= 0 && syscall < NR_syscalls) {
syscall = array_index_nospec(syscall, NR_syscalls);
syscall_handler(regs, syscall);
if (likely(syscall_enter_from_user_mode_randomize_stack(regs, &syscall))) {
if (syscall >= 0 && syscall < NR_syscalls) {
syscall = array_index_nospec(syscall, NR_syscalls);
syscall_handler(regs, syscall);
}
}
syscall_exit_to_user_mode(regs);
} else {
irqentry_state_t state = irqentry_nmi_enter(regs);

View File

@ -89,11 +89,6 @@ static inline int syscall_get_arch(struct task_struct *task)
return AUDIT_ARCH_S390X;
}
static inline bool arch_syscall_is_vdso_sigreturn(struct pt_regs *regs)
{
return false;
}
#define SYSCALL_FMT_0
#define SYSCALL_FMT_1 , "0" (r2)
#define SYSCALL_FMT_2 , "d" (r3) SYSCALL_FMT_1

View File

@ -96,9 +96,10 @@ SYSCALL_DEFINE0(ni_syscall)
void noinstr __do_syscall(struct pt_regs *regs, int per_trap)
{
unsigned long nr;
bool permit;
enter_from_user_mode_randomize_stack(regs);
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();
@ -121,7 +122,9 @@ void noinstr __do_syscall(struct pt_regs *regs, int per_trap)
regs->psw.addr = current->restart_block.arch_data;
current->restart_block.arch_data = 1;
}
nr = syscall_enter_from_user_mode_work(regs, nr);
permit = syscall_enter_from_user_mode_work(regs, &nr);
/*
* In the s390 ptrace ABI, both the syscall number and the return value
* use gpr2. However, userspace puts the syscall number either in the
@ -129,7 +132,7 @@ void noinstr __do_syscall(struct pt_regs *regs, int per_trap)
* work, the ptrace code sets PIF_SYSCALL_RET_SET, which is checked here
* and if set, the syscall will be skipped.
*/
if (unlikely(test_and_clear_pt_regs_flag(regs, PIF_SYSCALL_RET_SET)))
if (unlikely(test_and_clear_pt_regs_flag(regs, PIF_SYSCALL_RET_SET) || !permit))
goto out;
regs->gprs[2] = -ENOSYS;
if (likely(nr < NR_syscalls)) {

View File

@ -455,12 +455,12 @@ long arch_ptrace(struct task_struct *child, long request,
asmlinkage long do_syscall_trace_enter(struct pt_regs *regs)
{
if (test_thread_flag(TIF_SYSCALL_TRACE) &&
ptrace_report_syscall_entry(regs)) {
!ptrace_report_syscall_permit_entry(regs)) {
regs->regs[0] = -ENOSYS;
return -1;
}
if (secure_computing() == -1)
if (!seccomp_permit_syscall())
return -1;
if (unlikely(test_thread_flag(TIF_SYSCALL_TRACEPOINT)))

View File

@ -441,7 +441,7 @@ asmlinkage int syscall_trace(struct pt_regs *regs, int syscall_exit_p)
if (syscall_exit_p)
ptrace_report_syscall_exit(regs, 0);
else
ret = ptrace_report_syscall_entry(regs);
ret = !ptrace_report_syscall_permit_entry(regs);
}
return ret;

View File

@ -1093,7 +1093,7 @@ asmlinkage int syscall_trace_enter(struct pt_regs *regs)
user_exit();
if (test_thread_flag(TIF_SYSCALL_TRACE))
ret = ptrace_report_syscall_entry(regs);
ret = !ptrace_report_syscall_permit_entry(regs);
if (unlikely(test_thread_flag(TIF_SYSCALL_TRACEPOINT)))
trace_sys_enter(regs, regs->u_regs[UREG_G1]);

View File

@ -135,7 +135,7 @@ int syscall_trace_enter(struct pt_regs *regs)
if (!test_thread_flag(TIF_SYSCALL_TRACE))
return 0;
return ptrace_report_syscall_entry(regs);
return !ptrace_report_syscall_permit_entry(regs);
}
void syscall_trace_leave(struct pt_regs *regs)

View File

@ -27,7 +27,7 @@ void handle_syscall(struct uml_pt_regs *r)
goto out;
/* Do the seccomp check after ptrace; failures should be fast. */
if (secure_computing() == -1)
if (!seccomp_permit_syscall())
goto out;
syscall = UPT_SYSCALL_NR(r);

View File

@ -336,6 +336,7 @@ config X86
select SCHED_SMT if SMP
select ARCH_SUPPORTS_SCHED_CLUSTER if SMP
select ARCH_SUPPORTS_SCHED_MC if SMP
select ARCH_SUPPORTS_SYSCALL_USER_DISPATCH
select HAVE_SINGLE_FTRACE_DIRECT_OPS if X86_64 && DYNAMIC_FTRACE_WITH_DIRECT_CALLS
config INSTRUCTION_DECODER

View File

@ -41,7 +41,9 @@ const sys_call_ptr_t sys_call_table[] = {
#endif
#define __SYSCALL(nr, sym) case nr: return __ia32_##sym(regs);
long ia32_sys_call(const struct pt_regs *regs, unsigned int nr)
/* The unsigned int @nr argument is intentional as it creates denser code in a 64-bit build */
static noinline long ia32_sys_call(const struct pt_regs *regs, unsigned int nr)
{
switch (nr) {
#include <asm/syscalls_32.h>
@ -49,7 +51,7 @@ long ia32_sys_call(const struct pt_regs *regs, unsigned int nr)
}
}
static __always_inline int syscall_32_enter(struct pt_regs *regs)
static __always_inline long syscall_32_enter(struct pt_regs *regs)
{
if (IS_ENABLED(CONFIG_IA32_EMULATION))
current_thread_info()->status |= TS_COMPAT;
@ -70,19 +72,11 @@ early_param("ia32_emulation", ia32_emulation_override_cmdline);
/*
* Invoke a 32-bit syscall. Called with IRQs on in CT_STATE_KERNEL.
*/
static __always_inline void do_syscall_32_irqs_on(struct pt_regs *regs, int nr)
static __always_inline void do_syscall_32_irqs_on(struct pt_regs *regs, unsigned long nr)
{
/*
* Convert negative numbers to very high and thus out of range
* numbers for comparisons.
*/
unsigned int unr = nr;
if (likely(unr < IA32_NR_syscalls)) {
unr = array_index_nospec(unr, IA32_NR_syscalls);
regs->ax = ia32_sys_call(regs, unr);
} else if (nr != -1) {
regs->ax = __ia32_sys_ni_syscall(regs);
if (likely(nr < IA32_NR_syscalls)) {
nr = array_index_nospec(nr, IA32_NR_syscalls);
regs->ax = ia32_sys_call(regs, (unsigned int)nr);
}
}
@ -128,7 +122,7 @@ static __always_inline bool int80_is_external(void)
*/
__visible noinstr void do_int80_emulation(struct pt_regs *regs)
{
int nr;
long nr;
/* Kernel does not use INT $0x80! */
if (unlikely(!user_mode(regs))) {
@ -142,10 +136,9 @@ __visible noinstr void do_int80_emulation(struct pt_regs *regs)
* int80_is_external() below which calls into the APIC driver.
* Identical for soft and external interrupts.
*/
enter_from_user_mode(regs);
enter_from_user_mode_randomize_stack(regs);
instrumentation_begin();
add_random_kstack_offset();
/* Validate that this is a soft interrupt to the extent possible */
if (unlikely(int80_is_external()))
@ -168,8 +161,9 @@ __visible noinstr void do_int80_emulation(struct pt_regs *regs)
nr = syscall_32_enter(regs);
local_irq_enable();
nr = syscall_enter_from_user_mode_work(regs, nr);
do_syscall_32_irqs_on(regs, nr);
if (likely(syscall_enter_from_user_mode_work(regs, &nr)))
do_syscall_32_irqs_on(regs, nr);
instrumentation_end();
syscall_exit_to_user_mode(regs);
@ -208,13 +202,11 @@ __visible noinstr void do_int80_emulation(struct pt_regs *regs)
*/
DEFINE_FREDENTRY_RAW(int80_emulation)
{
int nr;
long nr;
enter_from_user_mode(regs);
enter_from_user_mode_randomize_stack(regs);
instrumentation_begin();
add_random_kstack_offset();
/*
* FRED pushed 0 into regs::orig_ax and regs::ax contains the
* syscall number.
@ -232,8 +224,8 @@ DEFINE_FREDENTRY_RAW(int80_emulation)
nr = syscall_32_enter(regs);
local_irq_enable();
nr = syscall_enter_from_user_mode_work(regs, nr);
do_syscall_32_irqs_on(regs, nr);
if (likely(syscall_enter_from_user_mode_work(regs, &nr)))
do_syscall_32_irqs_on(regs, nr);
instrumentation_end();
syscall_exit_to_user_mode(regs);
@ -245,38 +237,32 @@ DEFINE_FREDENTRY_RAW(int80_emulation)
/* Handles int $0x80 on a 32bit kernel */
__visible noinstr void do_int80_syscall_32(struct pt_regs *regs)
{
int nr = syscall_32_enter(regs);
long nr = syscall_32_enter(regs);
/*
* Subtlety here: if ptrace pokes something larger than 2^31-1 into
* orig_ax, the int return value truncates it. This matches
* the semantics of syscall_get_nr().
*/
nr = syscall_enter_from_user_mode(regs, nr);
instrumentation_begin();
if (likely(syscall_enter_from_user_mode_randomize_stack(regs, &nr))) {
instrumentation_begin();
add_random_kstack_offset();
do_syscall_32_irqs_on(regs, nr);
do_syscall_32_irqs_on(regs, nr);
instrumentation_end();
instrumentation_end();
}
syscall_exit_to_user_mode(regs);
}
#endif /* !CONFIG_IA32_EMULATION */
static noinstr bool __do_fast_syscall_32(struct pt_regs *regs)
{
int nr = syscall_32_enter(regs);
long nr = syscall_32_enter(regs);
int res;
/*
* This cannot use syscall_enter_from_user_mode() as it has to
* fetch EBP before invoking any of the syscall entry work
* functions.
*/
enter_from_user_mode(regs);
enter_from_user_mode_randomize_stack(regs);
instrumentation_begin();
add_random_kstack_offset();
local_irq_enable();
/* Fetch EBP from where the vDSO stashed it. */
if (IS_ENABLED(CONFIG_X86_64)) {
@ -301,10 +287,8 @@ static noinstr bool __do_fast_syscall_32(struct pt_regs *regs)
return false;
}
nr = syscall_enter_from_user_mode_work(regs, nr);
/* Now this is just like a normal syscall. */
do_syscall_32_irqs_on(regs, nr);
if (likely(syscall_enter_from_user_mode_work(regs, &nr)))
do_syscall_32_irqs_on(regs, nr);
instrumentation_end();
syscall_exit_to_user_mode(regs);

View File

@ -32,7 +32,9 @@ const sys_call_ptr_t sys_call_table[] = {
#undef __SYSCALL
#define __SYSCALL(nr, sym) case nr: return __x64_##sym(regs);
long x64_sys_call(const struct pt_regs *regs, unsigned int nr)
/* The unsigned int @nr argument is intentional as it creates denser code */
static noinline long x64_sys_call(const struct pt_regs *regs, unsigned int nr)
{
switch (nr) {
#include <asm/syscalls_64.h>
@ -40,63 +42,50 @@ long x64_sys_call(const struct pt_regs *regs, unsigned int nr)
}
}
#ifdef CONFIG_X86_X32_ABI
long x32_sys_call(const struct pt_regs *regs, unsigned int nr)
static noinline long x32_sys_call(const struct pt_regs *regs, unsigned int nr)
{
#ifdef CONFIG_X86_X32_ABI
switch (nr) {
#include <asm/syscalls_x32.h>
default: return __x64_sys_ni_syscall(regs);
}
}
#else
return -ENOSYS;
#endif
}
static __always_inline bool do_syscall_x64(struct pt_regs *regs, int nr)
static __always_inline bool do_syscall_x64(struct pt_regs *regs, unsigned long 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);
if (likely(nr < NR_syscalls)) {
nr = array_index_nospec(nr, NR_syscalls);
regs->ax = x64_sys_call(regs, (unsigned int)nr);
return true;
}
return false;
}
static __always_inline bool do_syscall_x32(struct pt_regs *regs, int nr)
static __always_inline void do_syscall_x32(struct pt_regs *regs, unsigned long 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;
/* Adjust the starting offset of the table */
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;
if (IS_ENABLED(CONFIG_X86_X32_ABI) && likely(nr < X32_NR_syscalls)) {
nr = array_index_nospec(nr, X32_NR_syscalls);
regs->ax = x32_sys_call(regs, (unsigned int)nr);
}
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)
__visible noinstr bool do_syscall_64(struct pt_regs *regs, long nr)
{
nr = syscall_enter_from_user_mode(regs, nr);
if (likely(syscall_enter_from_user_mode_randomize_stack(regs, &nr))) {
instrumentation_begin();
instrumentation_begin();
add_random_kstack_offset();
if (!do_syscall_x64(regs, nr))
do_syscall_x32(regs, nr);
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();
}
instrumentation_end();
syscall_exit_to_user_mode(regs);
/*

View File

@ -118,10 +118,10 @@ static bool write_ok_or_segv(unsigned long ptr, size_t size)
static bool __emulate_vsyscall(struct pt_regs *regs, unsigned long address)
{
unsigned long caller;
int vsyscall_nr, syscall_nr, tmp;
unsigned long caller, orig_dx;
int vsyscall_nr, syscall_nr;
bool skip;
long ret;
unsigned long orig_dx;
/* Confirm that the fault happened in 64-bit user mode */
if (!user_64bit_mode(regs))
@ -197,16 +197,16 @@ static bool __emulate_vsyscall(struct pt_regs *regs, unsigned long address)
*/
regs->orig_ax = syscall_nr;
regs->ax = -ENOSYS;
tmp = secure_computing();
if ((!tmp && regs->orig_ax != syscall_nr) || regs->ip != address) {
skip = !seccomp_permit_syscall();
if ((!skip && regs->orig_ax != syscall_nr) || regs->ip != address) {
warn_bad_vsyscall(KERN_DEBUG, regs,
"seccomp tried to change syscall nr or ip");
force_exit_sig(SIGSYS);
return true;
}
regs->orig_ax = -1;
if (tmp)
goto do_ret; /* skip requested */
if (skip)
goto do_ret;
/*
* With a real vsyscall, page faults cause SIGSEGV.

View File

@ -2,7 +2,6 @@
#ifndef _ASM_X86_ENTRY_COMMON_H
#define _ASM_X86_ENTRY_COMMON_H
#include <linux/randomize_kstack.h>
#include <linux/user-return-notifier.h>
#include <asm/nospec-branch.h>

View File

@ -20,14 +20,6 @@
typedef long (*sys_call_ptr_t)(const struct pt_regs *);
extern const sys_call_ptr_t sys_call_table[];
/*
* These may not exist, but still put the prototypes in so we
* can use IS_ENABLED().
*/
extern long ia32_sys_call(const struct pt_regs *, unsigned int nr);
extern long x32_sys_call(const struct pt_regs *, unsigned int nr);
extern long x64_sys_call(const struct pt_regs *, unsigned int nr);
/*
* Only the low 32 bits of orig_ax are meaningful, so we return int.
* This importantly ignores the high bits on 64-bit, so comparisons
@ -172,7 +164,7 @@ static inline int syscall_get_arch(struct task_struct *task)
? AUDIT_ARCH_I386 : AUDIT_ARCH_X86_64;
}
bool do_syscall_64(struct pt_regs *regs, int nr);
bool do_syscall_64(struct pt_regs *regs, long nr);
void do_int80_emulation(struct pt_regs *regs);
#endif /* CONFIG_X86_32 */

View File

@ -547,14 +547,13 @@ int do_syscall_trace_enter(struct pt_regs *regs)
regs->areg[2] = -ENOSYS;
if (test_thread_flag(TIF_SYSCALL_TRACE) &&
ptrace_report_syscall_entry(regs)) {
!ptrace_report_syscall_permit_entry(regs)) {
regs->areg[2] = -ENOSYS;
regs->syscall = NO_SYSCALL;
return 0;
}
if (regs->syscall == NO_SYSCALL ||
secure_computing() == -1) {
if (regs->syscall == NO_SYSCALL || !seccomp_permit_syscall()) {
do_syscall_trace_leave(regs);
return 0;
}

View File

@ -58,8 +58,8 @@ void syscall_set_nr(struct task_struct *task, struct pt_regs *regs, int nr);
*
* It's only valid to call this when @task is stopped for system
* call exit tracing (due to %SYSCALL_WORK_SYSCALL_TRACE or
* %SYSCALL_WORK_SYSCALL_AUDIT), after ptrace_report_syscall_entry()
* returned nonzero to prevent the system call from taking place.
* %SYSCALL_WORK_SYSCALL_AUDIT), after ptrace_report_syscall_permit_entry()
* returned False to prevent the system call from taking place.
*
* This rolls back the register state in @regs so it's as if the
* system call instruction was a no-op. The registers containing

View File

@ -6,9 +6,11 @@
#include <linux/irq-entry-common.h>
#include <linux/livepatch.h>
#include <linux/ptrace.h>
#include <linux/randomize_kstack.h>
#include <linux/resume_user_mode.h>
#include <linux/seccomp.h>
#include <linux/sched.h>
#include <linux/syscall_user_dispatch.h>
#include <asm/entry-common.h>
#include <asm/syscall.h>
@ -18,7 +20,7 @@
#endif
/*
* SYSCALL_WORK flags handled in syscall_enter_from_user_mode()
* SYSCALL_WORK flags handled in syscall_enter_from_user_mode_work()
*/
#define SYSCALL_WORK_ENTER (SYSCALL_WORK_SECCOMP | \
SYSCALL_WORK_SYSCALL_TRACEPOINT | \
@ -37,42 +39,32 @@
SYSCALL_WORK_SYSCALL_EXIT_TRAP)
/**
* arch_ptrace_report_syscall_entry - Architecture specific ptrace_report_syscall_entry() wrapper
* arch_ptrace_report_syscall_permit_entry - Architecture specific wrapper for
* ptrace_report_syscall_permit_entry()
* @regs: Pointer to the register state at syscall entry
*
* Invoked from syscall_trace_enter() to wrap ptrace_report_syscall_entry().
* Invoked from syscall_trace_enter() to wrap ptrace_report_syscall_permit_entry().
*
* This allows architecture specific ptrace_report_syscall_entry()
* This allows architecture specific ptrace_report_syscall_permit_entry()
* implementations. If not defined by the architecture this falls back to
* to ptrace_report_syscall_entry().
* to ptrace_report_syscall_permit_entry().
*/
static __always_inline int arch_ptrace_report_syscall_entry(struct pt_regs *regs);
static __always_inline bool arch_ptrace_report_syscall_permit_entry(struct pt_regs *regs);
#ifndef arch_ptrace_report_syscall_entry
static __always_inline int arch_ptrace_report_syscall_entry(struct pt_regs *regs)
#ifndef arch_ptrace_report_syscall_permit_entry
static __always_inline bool arch_ptrace_report_syscall_permit_entry(struct pt_regs *regs)
{
return ptrace_report_syscall_entry(regs);
return ptrace_report_syscall_permit_entry(regs);
}
#endif
bool syscall_user_dispatch(struct pt_regs *regs);
long trace_syscall_enter(struct pt_regs *regs, long syscall);
void trace_syscall_enter(struct pt_regs *regs);
void trace_syscall_exit(struct pt_regs *regs, long ret);
void syscall_enter_audit(struct pt_regs *regs);
static inline void syscall_enter_audit(struct pt_regs *regs, long syscall)
static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned long work,
long syscall)
{
if (unlikely(audit_context())) {
unsigned long args[6];
syscall_get_arguments(current, regs, args);
audit_syscall_entry(syscall, args[0], args[1], args[2], args[3]);
}
}
static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned long work)
{
long syscall, ret = 0;
/*
* Handle Syscall User Dispatch. This must comes first, since
* the ABI here can be something that doesn't make sense for
@ -80,7 +72,7 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l
*/
if (work & SYSCALL_WORK_SYSCALL_USER_DISPATCH) {
if (syscall_user_dispatch(regs))
return -1L;
return false;
}
/*
@ -89,31 +81,31 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l
* through hrtimer_interrupt().
*/
if (work & SYSCALL_WORK_SYSCALL_RSEQ_SLICE)
rseq_syscall_enter_work(syscall_get_nr(current, regs));
rseq_syscall_enter_work(syscall);
/* Handle ptrace */
if (work & (SYSCALL_WORK_SYSCALL_TRACE | SYSCALL_WORK_SYSCALL_EMU)) {
ret = arch_ptrace_report_syscall_entry(regs);
if (ret || (work & SYSCALL_WORK_SYSCALL_EMU))
return -1L;
if (!arch_ptrace_report_syscall_permit_entry(regs) ||
(work & SYSCALL_WORK_SYSCALL_EMU))
return false;
/* ptrace might have changed work flags */
work = READ_ONCE(current_thread_info()->syscall_work);
}
/* Do seccomp after ptrace, to catch any tracer changes. */
if (work & SYSCALL_WORK_SECCOMP) {
ret = __secure_computing();
if (ret == -1L)
return ret;
if (!__seccomp_permit_syscall())
return false;
}
/* Either of the above might have changed the syscall number */
syscall = syscall_get_nr(current, regs);
if (unlikely(work & SYSCALL_WORK_SYSCALL_TRACEPOINT))
syscall = trace_syscall_enter(regs, syscall);
trace_syscall_enter(regs);
syscall_enter_audit(regs, syscall);
if (unlikely(audit_context()))
syscall_enter_audit(regs);
return ret ? : syscall;
return true;
}
/**
@ -122,36 +114,63 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l
* @regs: Pointer to currents pt_regs
* @syscall: The syscall number
*
* Invoked from architecture specific syscall entry code with interrupts
* enabled after invoking enter_from_user_mode(), enabling interrupts and
* extra architecture specific work.
* Invoked from architecture specific syscall entry code with interrupts enabled
* after invoking enter_from_user_mode(), enabling interrupts and extra
* architecture specific work with the syscall return value preset to -ENOSYS.
*
* Returns: The original or a modified syscall number
* Returns: True if the syscall should be invoked, False otherwise.
*
* If the returned syscall number is -1 then the syscall should be
* skipped. In this case the caller may invoke syscall_set_error() or
* syscall_set_return_value() first. If neither of those are called and -1
* is returned, then the syscall will fail with ENOSYS.
* If the return value is false, the caller must skip the syscall and leave the
* syscall return value unmodified as it might have been set by one of the entry
* work functions.
*
* It handles the following work items:
*
* 1) syscall_work flag dependent invocations of
* ptrace_report_syscall_entry(), __secure_computing(), trace_sys_enter()
* ptrace_report_syscall_permit_entry(), __seccomp_permit_syscall(), trace_sys_enter()
* 2) Invocation of audit_syscall_entry()
*/
static __always_inline long syscall_enter_from_user_mode_work(struct pt_regs *regs, long syscall)
static __always_inline bool syscall_enter_from_user_mode_work(struct pt_regs *regs, long *syscall)
{
unsigned long work = READ_ONCE(current_thread_info()->syscall_work);
if (work & SYSCALL_WORK_ENTER)
syscall = syscall_trace_enter(regs, work);
if (!(work & SYSCALL_WORK_ENTER))
return true;
return syscall;
if (unlikely(!syscall_trace_enter(regs, work, *syscall)))
return false;
/* Reread the syscall number as it might have been modified */
*syscall = syscall_get_nr(current, regs);
return true;
}
/**
* syscall_enter_from_user_mode - Establish state and check and handle work
* before invoking a syscall
* enter_from_user_mode_randomize_stack - Establish state and add stack randomization
* before invoking syscall_enter_from_user_mode_work()
* @regs: Pointer to currents pt_regs
*
* Invoked from architecture specific syscall entry code with interrupts
* disabled. The calling code has to be non-instrumentable. When the function
* returns all state is correct, interrupts are still disabled and the
* subsequent functions can be instrumented.
*
* Implemented as a macro so that the stack randomization is effective
* throughout the function in which it is invoked. An inline would only make it
* effective in the scope of the inline function.
*/
#define enter_from_user_mode_randomize_stack(regs) \
do { \
enter_from_user_mode(regs); \
instrumentation_begin(); \
add_random_kstack_offset_irqsoff(); \
instrumentation_end(); \
} while (0)
/**
* syscall_enter_from_user_mode_randomize_stack - Establish state and check and handle work
* before invoking a syscall
* @regs: Pointer to currents pt_regs
* @syscall: The syscall number
*
@ -160,31 +179,32 @@ static __always_inline long syscall_enter_from_user_mode_work(struct pt_regs *re
* function returns all state is correct, interrupts are enabled and the
* subsequent functions can be instrumented.
*
* This is the combination of enter_from_user_mode() and
* This is the combination of enter_from_user_mode_randomize_stack() and
* syscall_enter_from_user_mode_work() to be used when there is no
* architecture specific work to be done between the two.
*
* Returns: The original or a modified syscall number. See
* syscall_enter_from_user_mode_work() for further explanation.
*
* Implemented as a macro to make stack randomization effective in the calling
* scope.
*/
static __always_inline long syscall_enter_from_user_mode(struct pt_regs *regs, long syscall)
{
long ret;
enter_from_user_mode(regs);
instrumentation_begin();
local_irq_enable();
ret = syscall_enter_from_user_mode_work(regs, syscall);
instrumentation_end();
return ret;
}
#define syscall_enter_from_user_mode_randomize_stack(regs, syscall) \
({ \
enter_from_user_mode_randomize_stack(regs); \
\
instrumentation_begin(); \
local_irq_enable(); \
long _ret = syscall_enter_from_user_mode_work(regs, syscall); \
instrumentation_end(); \
\
_ret; \
})
/*
* If SYSCALL_EMU is set, then the only reason to report is when
* SINGLESTEP is set (i.e. PTRACE_SYSEMU_SINGLESTEP). This syscall
* instruction has been already reported in syscall_enter_from_user_mode().
* If SYSCALL_EMU is set, then the only reason to report is when SINGLESTEP is
* set (i.e. PTRACE_SYSEMU_SINGLESTEP). This syscall instruction has been
* already reported in syscall_enter_from_user_mode_work().
*/
static __always_inline bool report_single_step(unsigned long work)
{
@ -232,10 +252,8 @@ static __always_inline void syscall_exit_work(struct pt_regs *regs, unsigned lon
* of these syscalls is unknown.
*/
if (work & SYSCALL_WORK_SYSCALL_USER_DISPATCH) {
if (unlikely(current->syscall_dispatch.on_dispatch)) {
current->syscall_dispatch.on_dispatch = false;
if (syscall_user_dispatch_clear_on_dispatch())
return;
}
}
audit_syscall_exit(regs);

View File

@ -49,9 +49,9 @@
* Defaults to an empty implementation. Can be replaced by architecture
* specific code.
*
* Invoked from syscall_enter_from_user_mode() in the non-instrumentable
* section. Use __always_inline so the compiler cannot push it out of line
* and make it instrumentable.
* Invoked from enter_from_user_mode() in the non-instrumentable section. Use
* __always_inline so the compiler cannot push it out of line and make it
* instrumentable.
*/
static __always_inline void arch_enter_from_user_mode(struct pt_regs *regs);

View File

@ -405,13 +405,13 @@ extern void sigaction_compat_abi(struct k_sigaction *act, struct k_sigaction *oa
/*
* ptrace report for syscall entry and exit looks identical.
*/
static inline int ptrace_report_syscall(unsigned long message)
static inline bool ptrace_report_syscall(unsigned long message)
{
int ptrace = current->ptrace;
int signr;
if (!(ptrace & PT_PTRACED))
return 0;
return true;
signr = ptrace_notify(SIGTRAP | ((ptrace & PT_TRACESYSGOOD) ? 0x80 : 0),
message);
@ -424,11 +424,11 @@ static inline int ptrace_report_syscall(unsigned long message)
if (signr)
send_sig(signr, current, 1);
return fatal_signal_pending(current);
return !fatal_signal_pending(current);
}
/**
* ptrace_report_syscall_entry - task is about to attempt a system call
* ptrace_report_syscall_permit_entry - task is about to attempt a system call
* @regs: user register state of current task
*
* This will be called if %SYSCALL_WORK_SYSCALL_TRACE or
@ -438,7 +438,7 @@ static inline int ptrace_report_syscall(unsigned long message)
* call number and arguments to be tried. It is safe to block here,
* preventing the system call from beginning.
*
* Returns zero normally, or nonzero if the calling arch code should abort
* Returns True normally, or False if the calling architecture code should abort
* the system call. That must prevent normal entry so no system call is
* made. If @task ever returns to user mode after this, its register state
* is unspecified, but should be something harmless like an %ENOSYS error
@ -447,8 +447,7 @@ static inline int ptrace_report_syscall(unsigned long message)
*
* Called without locks, just after entering kernel mode.
*/
static inline __must_check int ptrace_report_syscall_entry(
struct pt_regs *regs)
static inline __must_check bool ptrace_report_syscall_permit_entry(struct pt_regs *regs)
{
return ptrace_report_syscall(PTRACE_EVENTMSG_SYSCALL_ENTRY);
}

View File

@ -77,8 +77,27 @@ static __always_inline u32 get_kstack_offset(void)
} \
} while (0)
/**
* add_random_kstack_offset_irqsoff - Increase stack utilization by a random offset.
*
* This should be used in the syscall entry path after user registers have been
* stored to the stack. Interrupts must be still disabled.
*/
#define add_random_kstack_offset_irqsoff() \
do { \
lockdep_assert_irqs_disabled(); \
if (static_branch_maybe(CONFIG_RANDOMIZE_KSTACK_OFFSET_DEFAULT, \
&randomize_kstack_offset)) { \
u32 offset = prandom_u32_state(raw_cpu_ptr(&kstack_rnd_state)); \
u8 *ptr = __kstack_alloca(KSTACK_OFFSET_MAX(offset)); \
/* Keep allocation even after "ptr" loses scope. */ \
asm volatile("" :: "r"(ptr) : "memory"); \
} \
} while (0)
#else /* CONFIG_RANDOMIZE_KSTACK_OFFSET */
#define add_random_kstack_offset() do { } while (0)
#define add_random_kstack_offset_irqsoff() do { } while (0)
#endif /* CONFIG_RANDOMIZE_KSTACK_OFFSET */
#endif

View File

@ -22,14 +22,14 @@
#include <linux/atomic.h>
#include <asm/seccomp.h>
extern int __secure_computing(void);
extern bool __seccomp_permit_syscall(void);
#ifdef CONFIG_HAVE_ARCH_SECCOMP_FILTER
static inline int secure_computing(void)
static __always_inline bool seccomp_permit_syscall(void)
{
if (unlikely(test_syscall_work(SECCOMP)))
return __secure_computing();
return 0;
return __seccomp_permit_syscall();
return true;
}
#else
extern void secure_computing_strict(int this_syscall);
@ -50,11 +50,11 @@ static inline int seccomp_mode(struct seccomp *s)
struct seccomp_data;
#ifdef CONFIG_HAVE_ARCH_SECCOMP_FILTER
static inline int secure_computing(void) { return 0; }
static inline bool seccomp_permit_syscall(void) { return true; }
#else
static inline void secure_computing_strict(int this_syscall) { return; }
#endif
static inline int __secure_computing(void) { return 0; }
static inline bool __seccomp_permit_syscall(void) { return true; }
static inline long prctl_get_seccomp(void)
{

View File

@ -6,9 +6,23 @@
#define _SYSCALL_USER_DISPATCH_H
#include <linux/thread_info.h>
#include <linux/sched.h>
#include <linux/syscall_user_dispatch_types.h>
#ifdef CONFIG_GENERIC_ENTRY
struct pt_regs;
#ifdef CONFIG_SYSCALL_USER_DISPATCH
bool syscall_user_dispatch(struct pt_regs *regs);
static __always_inline bool syscall_user_dispatch_clear_on_dispatch(void)
{
if (likely(!current->syscall_dispatch.on_dispatch))
return false;
current->syscall_dispatch.on_dispatch = false;
return true;
}
int set_syscall_user_dispatch(unsigned long mode, unsigned long offset,
unsigned long len, char __user *selector);
@ -24,6 +38,16 @@ int syscall_user_dispatch_set_config(struct task_struct *task, unsigned long siz
#else
static __always_inline bool syscall_user_dispatch(struct pt_regs *regs)
{
return false;
}
static __always_inline bool syscall_user_dispatch_clear_on_dispatch(void)
{
return false;
}
static inline int set_syscall_user_dispatch(unsigned long mode, unsigned long offset,
unsigned long len, char __user *selector)
{
@ -46,6 +70,6 @@ static inline int syscall_user_dispatch_set_config(struct task_struct *task,
return -EINVAL;
}
#endif /* CONFIG_GENERIC_ENTRY */
#endif /* CONFIG_SYSCALL_USER_DISPATCH */
#endif /* _SYSCALL_USER_DISPATCH_H */

View File

@ -4,7 +4,7 @@
#include <linux/types.h>
#ifdef CONFIG_GENERIC_ENTRY
#ifdef CONFIG_SYSCALL_USER_DISPATCH
struct syscall_user_dispatch {
char __user *selector;

View File

@ -13,5 +13,6 @@ CFLAGS_REMOVE_common.o = -fstack-protector -fstack-protector-strong
CFLAGS_common.o += -fno-stack-protector
obj-$(CONFIG_GENERIC_IRQ_ENTRY) += common.o
obj-$(CONFIG_GENERIC_SYSCALL) += syscall-common.o syscall_user_dispatch.o
obj-$(CONFIG_GENERIC_SYSCALL) += syscall-common.o
obj-$(CONFIG_SYSCALL_USER_DISPATCH) += syscall_user_dispatch.o
obj-$(CONFIG_VIRT_XFER_TO_GUEST_WORK) += virt.o

View File

@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/audit.h>
#include <linux/entry-common.h>
#define CREATE_TRACE_POINTS
@ -7,17 +8,23 @@
/* Out of line to prevent tracepoint code duplication */
long trace_syscall_enter(struct pt_regs *regs, long syscall)
void trace_syscall_enter(struct pt_regs *regs)
{
trace_sys_enter(regs, syscall);
/*
* Probes or BPF hooks in the tracepoint may have changed the
* system call number. Reread it.
*/
return syscall_get_nr(current, regs);
trace_sys_enter(regs, syscall_get_nr(current, regs));
}
void trace_syscall_exit(struct pt_regs *regs, long ret)
{
trace_sys_exit(regs, ret);
}
#ifdef CONFIG_AUDITSYSCALL
void syscall_enter_audit(struct pt_regs *regs)
{
long syscall = syscall_get_nr(current, regs);
unsigned long args[6];
syscall_get_arguments(current, regs, args);
__audit_syscall_entry(syscall, args[0], args[1], args[2], args[3]);
}
#endif

View File

@ -2,21 +2,22 @@
/*
* Copyright (C) 2020 Collabora Ltd.
*/
#include <linux/elf.h>
#include <linux/entry-common.h>
#include <linux/sched.h>
#include <linux/prctl.h>
#include <linux/ptrace.h>
#include <linux/syscall_user_dispatch.h>
#include <linux/uaccess.h>
#include <linux/signal.h>
#include <linux/elf.h>
#include <linux/sched.h>
#include <linux/sched/signal.h>
#include <linux/sched/task_stack.h>
#include <linux/signal.h>
#include <linux/syscall_user_dispatch.h>
#include <linux/sysctl.h>
#include <linux/uaccess.h>
#include <asm/syscall.h>
static bool syscall_user_dispatch_allowed __read_mostly = true;
static void trigger_sigsys(struct pt_regs *regs)
{
struct kernel_siginfo info;
@ -102,6 +103,10 @@ static int task_set_syscall_user_dispatch(struct task_struct *task, unsigned lon
return -EINVAL;
}
/* Arming can be denied at runtime via sysctl, disarming is allowed */
if (mode != PR_SYS_DISPATCH_OFF && !syscall_user_dispatch_allowed)
return -EPERM;
/*
* access_ok() will clear memory tags for tagged addresses
* if current has memory tagging enabled.
@ -172,3 +177,22 @@ int syscall_user_dispatch_set_config(struct task_struct *task, unsigned long siz
return task_set_syscall_user_dispatch(task, cfg.mode, cfg.offset, cfg.len,
(char __user *)(uintptr_t)cfg.selector);
}
#ifdef CONFIG_PROC_SYSCTL
static const struct ctl_table syscall_user_dispatch_sysctls[] = {
{
.procname = "syscall_user_dispatch",
.data = &syscall_user_dispatch_allowed,
.maxlen = sizeof(syscall_user_dispatch_allowed),
.mode = 0644,
.proc_handler = proc_dobool,
},
};
static int __init syscall_user_dispatch_sysctl_init(void)
{
register_sysctl_init("kernel", syscall_user_dispatch_sysctls);
return 0;
}
late_initcall(syscall_user_dispatch_sysctl_init);
#endif /* CONFIG_PROC_SYSCTL */

View File

@ -1100,12 +1100,13 @@ void secure_computing_strict(int this_syscall)
else
BUG();
}
int __secure_computing(void)
bool __seccomp_permit_syscall(void)
{
int this_syscall = syscall_get_nr(current, current_pt_regs());
secure_computing_strict(this_syscall);
return 0;
return true;
}
#else
@ -1256,7 +1257,7 @@ static int seccomp_do_user_notification(int this_syscall,
return -1;
}
static int __seccomp_filter(int this_syscall, const bool recheck_after_trace)
static bool __seccomp_filter(int this_syscall, const bool recheck_after_trace)
{
u32 filter_ret, action;
struct seccomp_data sd;
@ -1294,7 +1295,7 @@ static int __seccomp_filter(int this_syscall, const bool recheck_after_trace)
case SECCOMP_RET_TRACE:
/* We've been put in this state by the ptracer already. */
if (recheck_after_trace)
return 0;
return true;
/* ENOSYS these calls if there is no tracer attached. */
if (!ptrace_event_enabled(current, PTRACE_EVENT_SECCOMP)) {
@ -1329,20 +1330,17 @@ static int __seccomp_filter(int this_syscall, const bool recheck_after_trace)
* a reload of all registers. This does not goto skip since
* a skip would have already been reported.
*/
if (__seccomp_filter(this_syscall, true))
return -1;
return 0;
return __seccomp_filter(this_syscall, true);
case SECCOMP_RET_USER_NOTIF:
if (seccomp_do_user_notification(this_syscall, match, &sd))
goto skip;
return 0;
return true;
case SECCOMP_RET_LOG:
seccomp_log(this_syscall, 0, action, true);
return 0;
return true;
case SECCOMP_RET_ALLOW:
/*
@ -1350,7 +1348,7 @@ static int __seccomp_filter(int this_syscall, const bool recheck_after_trace)
* this action since SECCOMP_RET_ALLOW is the starting
* state in seccomp_run_filters().
*/
return 0;
return true;
case SECCOMP_RET_KILL_THREAD:
case SECCOMP_RET_KILL_PROCESS:
@ -1367,46 +1365,46 @@ static int __seccomp_filter(int this_syscall, const bool recheck_after_trace)
} else {
do_exit(SIGSYS);
}
return -1; /* skip the syscall go directly to signal handling */
return false; /* skip the syscall go directly to signal handling */
}
unreachable();
skip:
seccomp_log(this_syscall, 0, action, match ? match->log : false);
return -1;
return false;
}
#else
static int __seccomp_filter(int this_syscall, const bool recheck_after_trace)
static bool __seccomp_filter(int this_syscall, const bool recheck_after_trace)
{
BUG();
return -1;
return false;
}
#endif
int __secure_computing(void)
bool __seccomp_permit_syscall(void)
{
int mode = current->seccomp.mode;
int this_syscall;
if (IS_ENABLED(CONFIG_CHECKPOINT_RESTORE) &&
unlikely(current->ptrace & PT_SUSPEND_SECCOMP))
return 0;
return true;
this_syscall = syscall_get_nr(current, current_pt_regs());
switch (mode) {
case SECCOMP_MODE_STRICT:
__secure_computing_strict(this_syscall); /* may call do_exit */
return 0;
return true;
case SECCOMP_MODE_FILTER:
return __seccomp_filter(this_syscall, false);
/* Surviving SECCOMP_RET_KILL_* must be proactively impossible. */
case SECCOMP_MODE_DEAD:
WARN_ON_ONCE(1);
do_exit(SIGKILL);
return -1;
return false;
default:
BUG();
}

View File

@ -1 +1 @@
CONFIG_GENERIC_ENTRY=y
CONFIG_SYSCALL_USER_DISPATCH=y