[PATCH] sh: prevent ptrace from changing privileged SR bits

From: Jérémy Jean

Date: Fri Aug 21 2026 - 06:35:24 EST


PTRACE_POKEUSR writes every word in struct pt_regs, including SR, without
filtering privileged bits. The general register regset setter exposes the
same write through PTRACE_SETREGS and PTRACE_SETREGSET.

The exception return path restores the saved SR with rte after adjusting
only the interrupt mask. An unprivileged same-UID tracer can set SR.MD and
make its tracee resume at a user address in privileged mode.

Preserve the saved non-user SR bits in both write paths and accept only
the bits in SR_USER_MASK. Recompose SR after user_regset_copyin() even on
error because the copy may have updated a prefix of the register set.

Assisted-by: Codex:gpt-5
Signed-off-by: Jérémy Jean <Jeremy.Jean@xxxxxxxxxxxxxxxxx>
---
arch/sh/kernel/ptrace_32.c | 7 +++++++
1 file changed, 7 insertions(+)

diff --git a/arch/sh/kernel/ptrace_32.c b/arch/sh/kernel/ptrace_32.c
index 8794081483fb..bb69c35cb9a7 100644
--- a/arch/sh/kernel/ptrace_32.c
+++ b/arch/sh/kernel/ptrace_32.c
@@ -146,6 +146,7 @@ static int genregs_set(struct task_struct *target,
const void *kbuf, const void __user *ubuf)
{
struct pt_regs *regs = task_pt_regs(target);
+ unsigned long sr = regs->sr & ~SR_USER_MASK;
int ret;

ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
@@ -159,6 +160,7 @@ static int genregs_set(struct task_struct *target,
if (!ret)
user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf,
sizeof(struct pt_regs), -1);
+ regs->sr = (regs->sr & SR_USER_MASK) | sr;

return ret;
}
@@ -391,6 +393,11 @@ long arch_ptrace(struct task_struct *child, long request,
addr > sizeof(struct user) - 3)
break;

+ if (addr == offsetof(struct pt_regs, sr)) {
+ unsigned long sr = get_stack_long(child, addr);
+
+ data = (data & SR_USER_MASK) | (sr & ~SR_USER_MASK);
+ }
if (addr < sizeof(struct pt_regs))
ret = put_stack_long(child, addr, data);
else if (addr >= offsetof(struct user, fpu) &&
--
2.47.3