summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/core-api/entry.rst45
-rw-r--r--arch/loongarch/kernel/syscall.c14
-rw-r--r--arch/powerpc/kernel/syscall.c3
-rw-r--r--arch/riscv/kernel/traps.c11
-rw-r--r--arch/s390/kernel/syscall.c7
-rw-r--r--arch/x86/entry/syscall_32.c25
-rw-r--r--arch/x86/entry/syscall_64.c12
-rw-r--r--include/linux/entry-common.h32
8 files changed, 88 insertions, 61 deletions
diff --git a/Documentation/core-api/entry.rst b/Documentation/core-api/entry.rst
index 44e9608038ea..79fdaed954d9 100644
--- a/Documentation/core-api/entry.rst
+++ b/Documentation/core-api/entry.rst
@@ -58,26 +58,51 @@ 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_randomize_stack(regs, nr);
+ 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);
+ }
- instrumentation_begin();
- if (!invoke_syscall(regs, nr) && nr != -1)
- result_reg(regs) = __sys_ni_syscall(regs);
- instrumentation_end();
+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:
diff --git a/arch/loongarch/kernel/syscall.c b/arch/loongarch/kernel/syscall.c
index 142a9c20292c..62264e422467 100644
--- a/arch/loongarch/kernel/syscall.c
+++ b/arch/loongarch/kernel/syscall.c
@@ -57,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 */
@@ -69,12 +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_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]);
+ 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);
diff --git a/arch/powerpc/kernel/syscall.c b/arch/powerpc/kernel/syscall.c
index b4d0159e29f8..1440dcabe052 100644
--- a/arch/powerpc/kernel/syscall.c
+++ b/arch/powerpc/kernel/syscall.c
@@ -18,7 +18,8 @@ notrace long system_call_exception(struct pt_regs *regs, unsigned long r0)
long ret;
syscall_fn f;
- r0 = syscall_enter_from_user_mode_randomize_stack(regs, r0);
+ if (unlikely(!syscall_enter_from_user_mode_randomize_stack(regs, &r0)))
+ return syscall_get_error(current, regs);
if (unlikely(r0 >= NR_syscalls)) {
if (unlikely(trap_is_unsupported_scv(regs))) {
diff --git a/arch/riscv/kernel/traps.c b/arch/riscv/kernel/traps.c
index bb180116ef0a..2f57fd48ec28 100644
--- a/arch/riscv/kernel/traps.c
+++ b/arch/riscv/kernel/traps.c
@@ -332,13 +332,12 @@ void do_trap_ecall_u(struct pt_regs *regs)
riscv_v_vstate_discard(regs);
- syscall = 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);
+ 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);
diff --git a/arch/s390/kernel/syscall.c b/arch/s390/kernel/syscall.c
index 73abda2a9080..4ac80e2478d7 100644
--- a/arch/s390/kernel/syscall.c
+++ b/arch/s390/kernel/syscall.c
@@ -96,6 +96,7 @@ 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);
@@ -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)) {
diff --git a/arch/x86/entry/syscall_32.c b/arch/x86/entry/syscall_32.c
index 92369d84c0c8..91123a90710c 100644
--- a/arch/x86/entry/syscall_32.c
+++ b/arch/x86/entry/syscall_32.c
@@ -161,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);
@@ -223,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);
@@ -243,13 +244,13 @@ __visible noinstr void do_int80_syscall_32(struct pt_regs *regs)
* orig_ax, the int return value truncates it. This matches
* the semantics of syscall_get_nr().
*/
- nr = syscall_enter_from_user_mode_randomize_stack(regs, nr);
-
- instrumentation_begin();
+ if (likely(syscall_enter_from_user_mode_randomize_stack(regs, &nr))) {
+ instrumentation_begin();
- 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 */
@@ -286,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);
diff --git a/arch/x86/entry/syscall_64.c b/arch/x86/entry/syscall_64.c
index 9a2762e32ae8..966d3d1b7586 100644
--- a/arch/x86/entry/syscall_64.c
+++ b/arch/x86/entry/syscall_64.c
@@ -78,14 +78,14 @@ static __always_inline void do_syscall_x32(struct pt_regs *regs, unsigned long n
/* Returns true to return using SYSRET, or false to use IRET */
__visible noinstr bool do_syscall_64(struct pt_regs *regs, long nr)
{
- nr = syscall_enter_from_user_mode_randomize_stack(regs, nr);
+ if (likely(syscall_enter_from_user_mode_randomize_stack(regs, &nr))) {
+ instrumentation_begin();
- instrumentation_begin();
+ if (!do_syscall_x64(regs, nr))
+ do_syscall_x32(regs, nr);
- if (!do_syscall_x64(regs, nr))
- do_syscall_x32(regs, nr);
-
- instrumentation_end();
+ instrumentation_end();
+ }
syscall_exit_to_user_mode(regs);
/*
diff --git a/include/linux/entry-common.h b/include/linux/entry-common.h
index df221d919e76..6574b7183c01 100644
--- a/include/linux/entry-common.h
+++ b/include/linux/entry-common.h
@@ -114,16 +114,15 @@ 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:
*
@@ -131,19 +130,20 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l
* 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) {
- if (!syscall_trace_enter(regs, work, syscall))
- return -1L;
+ if (!(work & SYSCALL_WORK_ENTER))
+ return true;
- /* Reread the syscall number as it might have been modified */
- syscall = syscall_get_nr(current, regs);
- }
+ if (unlikely(!syscall_trace_enter(regs, work, *syscall)))
+ return false;
- return syscall;
+ /* Reread the syscall number as it might have been modified */
+ *syscall = syscall_get_nr(current, regs);
+
+ return true;
}
/**