mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] s390/syscall: Set -ENOSYS return value before calling syscall_enter_from_user_mode_work()
@ 2026-10-21  8:56 Heiko Carstens
  0 siblings, 0 replies; only message in thread
From: Heiko Carstens @ 2026-10-21  8:56 UTC (permalink / raw)
  To: Alexander Gordeev, Sven Schnelle, Vasily Gorbik,
	Christian Borntraeger, Oleg Nesterov, Michal Suchánek,
	Dmitry V . Levin, Renzo Davoli, Thomas Gleixner, Peter Zijlstra
  Cc: linux-kernel, linux-s390

On s390 gpr2 contains both the system call return value and the system call
number. With [1] it is expected to set gpr2 to -ENOSYS before calling
syscall_enter_from_user_mode_work(), so that a skipped system call has a
defined default return value, and not the system call number like today.

To make that work and stay compatible with existing ptrace users, replace
PIF_SYSCALL_RET_SET with PIF_SYSCALL_ENTRY. PIF_SYSCALL_ENTRY is set before
syscall_enter_from_user_mode_work() is called and indicates that reads and
writes to gpr2 via PTRACE_PEEKUSR, PTRACE_POKEUSR, PTRACE_GETREGSET, and
PTRACE_SETREGSET are redirected to regs->int_code, which contains the
system call number. gpr2, which contains the system call return value, can
only be changed via syscall_set_return_value() (e.g. seccomp).

After syscall_enter_from_user_mode_work() returns, PIF_SYSCALL_ENTRY is
cleared and the code behaves as before.

PIF_SYSCALL_RET_SET was introduced with [2]. The flag was set by
syscall_set_return_value() and allowed seccomp to skip syscalls
and inject a return value despite the gpr2 aliasing.

Kernel seccomp and ptrace selftests, as well as strace selftests, show no
difference with and without this change.

[1] 05c033db7e9a ("entry, treewide: Make syscall_enter_from_user_mode[_work]() indicate syscall execution")
[2] 56e62a737028 ("s390: convert to generic entry")

Fixes: 05c033db7e9a ("entry, treewide: Make syscall_enter_from_user_mode[_work]() indicate syscall execution")
Cc: stable@vger.kernel.org
Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
---
 arch/s390/include/asm/ptrace.h  |  4 ++--
 arch/s390/include/asm/syscall.h |  1 -
 arch/s390/kernel/ptrace.c       | 28 +++++++++++++++++++++++-----
 arch/s390/kernel/syscall.c      | 19 +++++++++----------
 4 files changed, 34 insertions(+), 18 deletions(-)

diff --git a/arch/s390/include/asm/ptrace.h b/arch/s390/include/asm/ptrace.h
index 3e56ef8c268b..d8f3a2dbe689 100644
--- a/arch/s390/include/asm/ptrace.h
+++ b/arch/s390/include/asm/ptrace.h
@@ -15,14 +15,14 @@
 
 #define PIF_SYSCALL			0	/* inside a system call */
 #define PIF_PSW_ADDR_ADJUSTED		1	/* psw address has been adjusted */
-#define PIF_SYSCALL_RET_SET		2	/* return value was set via ptrace */
+#define PIF_SYSCALL_ENTRY		2	/* ptrace gpr2 access redirected to syscall nr */
 #define PIF_FTRACE_FULL_REGS		3	/* all register contents valid (ftrace) */
 #define PIF_WARN_TRAP_SKIPPED		4	/* __WARN_trap() was skipped */
 #define PIF_PGM_CHECK			5	/* inside a program check */
 
 #define _PIF_SYSCALL			BIT(PIF_SYSCALL)
 #define _PIF_ADDR_PSW_ADJUSTED		BIT(PIF_PSW_ADDR_ADJUSTED)
-#define _PIF_SYSCALL_RET_SET		BIT(PIF_SYSCALL_RET_SET)
+#define _PIF_SYSCALL_ENTRY		BIT(PIF_SYSCALL_ENTRY)
 #define _PIF_FTRACE_FULL_REGS		BIT(PIF_FTRACE_FULL_REGS)
 #define _PIF_WARN_TRAP_SKIPPED		BIT(PIF_WARN_TRAP_SKIPPED)
 #define _PIF_PGM_CHECK			BIT(PIF_PGM_CHECK)
diff --git a/arch/s390/include/asm/syscall.h b/arch/s390/include/asm/syscall.h
index 5f310caad1fc..47f66202977f 100644
--- a/arch/s390/include/asm/syscall.h
+++ b/arch/s390/include/asm/syscall.h
@@ -59,7 +59,6 @@ static inline void syscall_set_return_value(struct task_struct *task,
 					    struct pt_regs *regs,
 					    int error, long val)
 {
-	set_pt_regs_flag(regs, PIF_SYSCALL_RET_SET);
 	regs->gprs[2] = error ? error : val;
 }
 
diff --git a/arch/s390/kernel/ptrace.c b/arch/s390/kernel/ptrace.c
index d8d5e843859c..a3ae881bbff6 100644
--- a/arch/s390/kernel/ptrace.c
+++ b/arch/s390/kernel/ptrace.c
@@ -204,15 +204,24 @@ static unsigned long __peek_user(struct task_struct *child, addr_t addr)
 	addr_t offset, tmp;
 
 	if (addr < offsetof(struct user, regs.acrs)) {
-		/*
-		 * psw and gprs are stored on the stack
-		 */
-		tmp = *(addr_t *)((addr_t) &task_pt_regs(child)->psw + addr);
+		struct pt_regs *regs = task_pt_regs(child);
+
+		/* psw and gprs are on the stack. */
+		tmp = *(addr_t *)((addr_t)&regs->psw + addr);
 		if (addr == offsetof(struct user, regs.psw.mask)) {
 			/* Return a clean psw mask. */
 			tmp &= PSW_MASK_USER | PSW_MASK_RI;
 			tmp |= PSW_USER_BITS;
 		}
+		/*
+		 * During system call entry gpr2 contains the return
+		 * value. Redirect reads of gpr2 to int_code so that
+		 * readers see the system call number.
+		 */
+		if (addr == offsetof(struct user, regs.gprs[2])) {
+			if (test_pt_regs_flag(regs, PIF_SYSCALL_ENTRY))
+				tmp = syscall_get_nr(child, regs);
+		}
 
 	} else if (addr < offsetof(struct user, regs.orig_gpr2)) {
 		/*
@@ -347,8 +356,17 @@ static int __poke_user(struct task_struct *child, addr_t addr, addr_t data)
 		}
 
 		if (addr == offsetof(struct user, regs.gprs[2])) {
-			if (test_pt_regs_flag(regs, PIF_SYSCALL))
+			if (test_pt_regs_flag(regs, PIF_SYSCALL_ENTRY)) {
+				/*
+				 * System call entry path: only change system
+				 * call number in int_code, but leave gpr2
+				 * alone, which contains the return value.
+				 */
 				syscall_set_nr(child, regs, data);
+				return 0;
+			} else if (test_pt_regs_flag(regs, PIF_SYSCALL)) {
+				syscall_set_nr(child, regs, data);
+			}
 		}
 		*(addr_t *)((addr_t) &regs->psw + addr) = data;
 	} else if (addr < offsetof(struct user, regs.orig_gpr2)) {
diff --git a/arch/s390/kernel/syscall.c b/arch/s390/kernel/syscall.c
index bcc0b76bd868..923df3a6dc0d 100644
--- a/arch/s390/kernel/syscall.c
+++ b/arch/s390/kernel/syscall.c
@@ -117,24 +117,23 @@ void noinstr __do_syscall(struct pt_regs *regs, unsigned long flags)
 		regs->int_code &= ~0xffffUL;
 		regs->int_code |= nr;
 	}
-	regs->gprs[2] = nr;
 	if (nr == __NR_restart_syscall && !(current->restart_block.arch_data & 1)) {
 		regs->psw.addr = current->restart_block.arch_data;
 		current->restart_block.arch_data = 1;
 	}
 
-	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
-	 * svc instruction itself, or uses gpr1. To make at least skipping syscalls
-	 * work, the ptrace code sets PIF_SYSCALL_RET_SET, which is checked here
-	 * and if set, the syscall will be skipped.
+	 * gpr2 is the return value register. Set it to -ENOSYS so that a
+	 * skipped system call gets a defined default return value.
+	 * Set PIF_SYSCALL_ENTRY so that ptrace reads and writes to gpr2
+	 * are redirected to the system call number in int_code.
 	 */
-	if (unlikely(test_and_clear_pt_regs_flag(regs, PIF_SYSCALL_RET_SET) || !permit))
+	syscall_set_return_value(current, regs, -ENOSYS, 0);
+	set_pt_regs_flag(regs, PIF_SYSCALL_ENTRY);
+	permit = syscall_enter_from_user_mode_work(regs, &nr);
+	clear_pt_regs_flag(regs, PIF_SYSCALL_ENTRY);
+	if (unlikely(!permit))
 		goto out;
-	regs->gprs[2] = -ENOSYS;
 	if (likely(nr < NR_syscalls)) {
 		nr = array_index_nospec(nr, NR_syscalls);
 		regs->gprs[2] = sys_call_table[nr](regs);
-- 
2.53.0


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-10-21  8:57 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-21  8:56 [PATCH] s390/syscall: Set -ENOSYS return value before calling syscall_enter_from_user_mode_work() Heiko Carstens

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®