* [PATCH v2 1/2] sparc64: add seccomp filter support
2026-09-02 7:27 [PATCH v2 0/2] sparc64: add seccomp filter support Stian Halseth
@ 2026-09-02 7:27 ` Stian Halseth
2026-09-02 7:27 ` [PATCH v2 2/2] selftests/seccomp: add sparc64 support Stian Halseth
1 sibling, 0 replies; 3+ messages in thread
From: Stian Halseth @ 2026-09-02 7:27 UTC (permalink / raw)
To: Andreas Larsson, David S . Miller, Kees Cook, sparclinux
Cc: Andy Lutomirski, Will Drewry, Oleg Nesterov, Shuah Khan,
linux-kselftest, linux-kernel, John Paul Adrian Glaubitz,
Stian Halseth
sparc is the last architecture besides alpha without SECCOMP_FILTER,
which systemd, docker, flatpak and the like all want, and userspace
support already exists as a pending libseccomp pull request.
Everything the filter mode needs is in fact already in place:
syscall_get_arch(), syscall_get_arguments() and
syscall_set_return_value() have long been provided for audit and
ptrace, TIF_SECCOMP is already in the syscall-entry work mask, and
strict-mode seccomp has been wired up for years. What is missing is
letting seccomp veto a syscall and honouring the return value it
sets.
Rework the entry hook accordingly: syscall_trace_enter() now runs the
ptrace entry report first and seccomp second (matching the generic
entry code, so a tracer's changes are seen by the filter), and
returns -1 when the syscall has been denied. On denial the return
value and the carry bit have already been set in pt_regs -
syscall_set_return_value() for a ptrace abort, the seccomp core for
SECCOMP_RET_ERRNO/TRAP/TRACE/KILL - so the assembler stubs must no
longer force -ENOSYS; they instead branch to a new linux_syscall_skip
path that advances TPC/TNPC past the trap instruction and returns
through the syscall exit work without storing a new return value.
The old behaviour of a ptrace entry abort (ENOSYS with the carry bit
set, followed by the exit report) is preserved, it is just set up in
C now.
syscall_rollback()'s XXX comment is replaced with an explanation of
why a no-op is correct: every caller runs before the syscall has been
invoked, and on sparc the first argument register is only overwritten
by the return value once the syscall has actually run.
SECCOMP_ARCH_NATIVE/COMPAT are defined so the constant-action bitmap
cache works for both 64-bit and compat 32-bit tasks.
Filter support is 64-bit only for now, as strict mode already was:
the 32-bit kernel's entry path has no equivalent plumbing.
Tested on an UltraSPARC T4-1: the seccomp_bpf selftest passes 95 of
95 (16 skipped for missing optional features such as uprobes), the
libseccomp test suite with its pending SPARC support passes 5190 of
5190 including the live tests, and docker containers run confined by
both the default and custom seccomp profiles.
Link: https://github.com/sparclinux/issues/issues/11
Signed-off-by: Stian Halseth <stian@itx.no>
---
v2: no changes
arch/sparc/Kconfig | 2 +-
arch/sparc/include/asm/seccomp.h | 15 +++++++++++++++
arch/sparc/include/asm/syscall.h | 11 +++++------
arch/sparc/kernel/entry.h | 2 +-
arch/sparc/kernel/ptrace_64.c | 28 +++++++++++++++++++---------
arch/sparc/kernel/syscalls.S | 28 ++++++++++++++++++++++------
6 files changed, 63 insertions(+), 23 deletions(-)
diff --git a/arch/sparc/Kconfig b/arch/sparc/Kconfig
index ab77d3f2536e..8dd15256edf6 100644
--- a/arch/sparc/Kconfig
+++ b/arch/sparc/Kconfig
@@ -23,7 +23,7 @@ config SPARC
select HAVE_ASM_MODVERSIONS
select HAVE_ARCH_KGDB if !SMP || SPARC64
select HAVE_ARCH_TRACEHOOK
- select HAVE_ARCH_SECCOMP if SPARC64
+ select HAVE_ARCH_SECCOMP_FILTER if SPARC64
select HAVE_EXIT_THREAD
select HAVE_PCI
select SYSCTL_EXCEPTION_TRACE
diff --git a/arch/sparc/include/asm/seccomp.h b/arch/sparc/include/asm/seccomp.h
index 62d4579efb1a..1ea70080f5cd 100644
--- a/arch/sparc/include/asm/seccomp.h
+++ b/arch/sparc/include/asm/seccomp.h
@@ -6,6 +6,21 @@
#define __NR_seccomp_sigreturn_32 __NR_sigreturn
+#ifdef CONFIG_SPARC64
+# define SECCOMP_ARCH_NATIVE AUDIT_ARCH_SPARC64
+# define SECCOMP_ARCH_NATIVE_NR NR_syscalls
+# define SECCOMP_ARCH_NATIVE_NAME "sparc64"
+# ifdef CONFIG_COMPAT
+# define SECCOMP_ARCH_COMPAT AUDIT_ARCH_SPARC
+# define SECCOMP_ARCH_COMPAT_NR NR_syscalls
+# define SECCOMP_ARCH_COMPAT_NAME "sparc"
+# endif
+#else
+# define SECCOMP_ARCH_NATIVE AUDIT_ARCH_SPARC
+# define SECCOMP_ARCH_NATIVE_NR NR_syscalls
+# define SECCOMP_ARCH_NATIVE_NAME "sparc"
+#endif
+
#include <asm-generic/seccomp.h>
#endif /* _ASM_SECCOMP_H */
diff --git a/arch/sparc/include/asm/syscall.h b/arch/sparc/include/asm/syscall.h
index b0233924d323..fc225f2ee23c 100644
--- a/arch/sparc/include/asm/syscall.h
+++ b/arch/sparc/include/asm/syscall.h
@@ -40,12 +40,11 @@ static inline void syscall_set_nr(struct task_struct *task,
static inline void syscall_rollback(struct task_struct *task,
struct pt_regs *regs)
{
- /* XXX This needs some thought. On Sparc we don't
- * XXX save away the original %o0 value somewhere.
- * XXX Instead we hold it in register %l5 at the top
- * XXX level trap frame and pass this down to the signal
- * XXX dispatch code which is the only place that value
- * XXX ever was needed.
+ /* Every caller rolls back before the syscall has been invoked
+ * (a ptrace entry abort or a seccomp user notification), and at
+ * that point the arguments in pt_regs are still intact: the
+ * return value only overwrites u_regs[UREG_I0] once the syscall
+ * has actually run. Nothing to undo.
*/
}
diff --git a/arch/sparc/kernel/entry.h b/arch/sparc/kernel/entry.h
index c746c0fd5d6b..734a649301ea 100644
--- a/arch/sparc/kernel/entry.h
+++ b/arch/sparc/kernel/entry.h
@@ -82,7 +82,7 @@ void do_notify_resume(struct pt_regs *regs,
unsigned long orig_i0,
unsigned long thread_info_flags);
-asmlinkage int syscall_trace_enter(struct pt_regs *regs);
+asmlinkage long syscall_trace_enter(struct pt_regs *regs);
asmlinkage void syscall_trace_leave(struct pt_regs *regs);
void bad_trap_tl1(struct pt_regs *regs, long lvl);
diff --git a/arch/sparc/kernel/ptrace_64.c b/arch/sparc/kernel/ptrace_64.c
index 825ddf55fece..6f4a005e674f 100644
--- a/arch/sparc/kernel/ptrace_64.c
+++ b/arch/sparc/kernel/ptrace_64.c
@@ -38,6 +38,7 @@
#include <asm/page.h>
#include <asm/cpudata.h>
#include <asm/cacheflush.h>
+#include <asm/syscall.h>
#define CREATE_TRACE_POINTS
#include <trace/events/syscalls.h>
@@ -1082,18 +1083,27 @@ long arch_ptrace(struct task_struct *child, long request,
return ret;
}
-asmlinkage int syscall_trace_enter(struct pt_regs *regs)
+/*
+ * Returns 0 to let the syscall through, or -1 to skip it. On skip the
+ * return value and the carry bit have already been set in pt_regs; the
+ * assembler caller must return through the syscall exit work without
+ * writing to them.
+ */
+asmlinkage long syscall_trace_enter(struct pt_regs *regs)
{
- int ret = 0;
-
- /* do the secure computing check first */
- secure_computing_strict(regs->u_regs[UREG_G1]);
-
if (test_thread_flag(TIF_NOHZ))
user_exit();
- if (test_thread_flag(TIF_SYSCALL_TRACE))
- ret = !ptrace_report_syscall_permit_entry(regs);
+ if (test_thread_flag(TIF_SYSCALL_TRACE) &&
+ !ptrace_report_syscall_permit_entry(regs)) {
+ /* The tracer aborted the syscall. */
+ syscall_set_return_value(current, regs, -ENOSYS, 0);
+ return -1;
+ }
+
+ /* Do seccomp after ptrace, to catch any tracer changes. */
+ if (!seccomp_permit_syscall())
+ return -1;
if (unlikely(test_thread_flag(TIF_SYSCALL_TRACEPOINT)))
trace_sys_enter(regs, regs->u_regs[UREG_G1]);
@@ -1102,7 +1112,7 @@ asmlinkage int syscall_trace_enter(struct pt_regs *regs)
regs->u_regs[UREG_I1], regs->u_regs[UREG_I2],
regs->u_regs[UREG_I3]);
- return ret;
+ return 0;
}
asmlinkage void syscall_trace_leave(struct pt_regs *regs)
diff --git a/arch/sparc/kernel/syscalls.S b/arch/sparc/kernel/syscalls.S
index 96fe8763d70c..46779bb27e77 100644
--- a/arch/sparc/kernel/syscalls.S
+++ b/arch/sparc/kernel/syscalls.S
@@ -159,11 +159,13 @@ linux_sparc_ni_syscall:
linux_syscall_trace32:
call syscall_trace_enter
add %sp, PTREGS_OFF, %o0
- brnz,pn %o0, 3f
- mov -ENOSYS, %o0
+ /* A negative return means the syscall was denied and the
+ * return value is already set in pt_regs.
+ */
+ brlz,pn %o0, linux_syscall_skip
/* Syscall tracing can modify the registers. */
- ldx [%sp + PTREGS_OFF + PT_V9_G1], %g1
+ ldx [%sp + PTREGS_OFF + PT_V9_G1], %g1
sethi %hi(sys_call_table32), %l7
ldx [%sp + PTREGS_OFF + PT_V9_I0], %i0
or %l7, %lo(sys_call_table32), %l7
@@ -189,11 +191,13 @@ linux_syscall_trace32:
linux_syscall_trace:
call syscall_trace_enter
add %sp, PTREGS_OFF, %o0
- brnz,pn %o0, 3f
- mov -ENOSYS, %o0
+ /* A negative return means the syscall was denied and the
+ * return value is already set in pt_regs.
+ */
+ brlz,pn %o0, linux_syscall_skip
/* Syscall tracing can modify the registers. */
- ldx [%sp + PTREGS_OFF + PT_V9_G1], %g1
+ ldx [%sp + PTREGS_OFF + PT_V9_G1], %g1
sethi %hi(sys_call_table64), %l7
ldx [%sp + PTREGS_OFF + PT_V9_I0], %i0
or %l7, %lo(sys_call_table64), %l7
@@ -307,3 +311,15 @@ linux_syscall_trace2:
stx %l1, [%sp + PTREGS_OFF + PT_V9_TPC]
ba,pt %xcc, rtrap
stx %l2, [%sp + PTREGS_OFF + PT_V9_TNPC]
+
+ /* The syscall was denied at entry by ptrace or seccomp. The
+ * return value and the carry bit are already set in pt_regs;
+ * advance past the trap instruction and run the syscall exit
+ * work without storing a new return value. We can only get
+ * here from the entry trace stubs, so the thread flags in %l0
+ * are known to be non-zero.
+ */
+linux_syscall_skip:
+ ldx [%sp + PTREGS_OFF + PT_V9_TNPC], %l1 ! pc = npc
+ ba,pt %xcc, linux_syscall_trace2
+ add %l1, 0x4, %l2 ! npc = npc+4
--
2.43.0
^ permalink raw reply related [flat|nested] 3+ messages in thread* [PATCH v2 2/2] selftests/seccomp: add sparc64 support
2026-09-02 7:27 [PATCH v2 0/2] sparc64: add seccomp filter support Stian Halseth
2026-09-02 7:27 ` [PATCH v2 1/2] " Stian Halseth
@ 2026-09-02 7:27 ` Stian Halseth
1 sibling, 0 replies; 3+ messages in thread
From: Stian Halseth @ 2026-09-02 7:27 UTC (permalink / raw)
To: Andreas Larsson, David S . Miller, Kees Cook, sparclinux
Cc: Andy Lutomirski, Will Drewry, Oleg Nesterov, Shuah Khan,
linux-kselftest, linux-kernel, John Paul Adrian Glaubitz,
Stian Halseth
sparc64 now selects HAVE_ARCH_SECCOMP_FILTER, so teach seccomp_bpf
how to read and write its registers: the syscall number lives in %g1
and the return value in %o0. The NT_PRSTATUS regset appends the
register window read back from the tracee's stack and has no exported
layout, so use the sparc-specific PTRACE_GETREGS64/PTRACE_SETREGS64
requests instead, which transfer the uapi struct pt_regs directly.
Errors are signaled by the carry bit in tstate with a positive errno
value in %o0, so provide a SYSCALL_RET_SET that maintains both, and
mark the arch SYSCALL_RET_SET_ON_PTRACE_EXIT since a return value
poked at entry would be overwritten by the syscall skip path, as on
powerpc.
Passes 95 of 95 on an UltraSPARC T4-1 (16 skipped for missing
optional features such as uprobes).
Link: https://github.com/sparclinux/issues/issues/11
Signed-off-by: Stian Halseth <stian@itx.no>
---
v2: use PTRACE_GETREGS64/PTRACE_SETREGS64 and the uapi struct pt_regs
instead of a locally defined NT_PRSTATUS regset layout, as
suggested by Kees Cook. Note the historical %g0 omission in that
layout, which shifts the u_regs indices by one.
tools/testing/selftests/seccomp/seccomp_bpf.c | 36 +++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/tools/testing/selftests/seccomp/seccomp_bpf.c b/tools/testing/selftests/seccomp/seccomp_bpf.c
index 0622bc2acad4..3711060c22a7 100644
--- a/tools/testing/selftests/seccomp/seccomp_bpf.c
+++ b/tools/testing/selftests/seccomp/seccomp_bpf.c
@@ -1872,6 +1872,34 @@ TEST_F(TRACE_poke, getpid_runs_normally)
# define ARCH_REGS struct user_regs_struct
# define SYSCALL_NUM(_regs) (_regs).orig_d0
# define SYSCALL_RET(_regs) (_regs).d0
+#elif defined(__sparc__) && defined(__arch64__)
+# include <asm/ptrace.h>
+/*
+ * PTRACE_GETREGS64/PTRACE_SETREGS64 transfer the uapi struct pt_regs,
+ * but in the historical layout that omits %g0: the blob starts at %g1,
+ * so every u_regs index is one lower than the register number suggests.
+ */
+# define ARCH_REGS struct pt_regs
+# define SYSCALL_NUM(_regs) (_regs).u_regs[0] /* %g1 */
+# define SYSCALL_RET(_regs) (_regs).u_regs[7] /* %o0 */
+/*
+ * A syscall error is signaled by the carry bit in tstate, with the
+ * errno held in %o0 as a positive value; the carry can only be
+ * written reliably once the syscall has been skipped or has run.
+ */
+# define SPARC64_TSTATE_CARRY 0x0000001100000000UL /* xcc.c | icc.c */
+# define SYSCALL_RET_SET(_regs, _val) \
+ do { \
+ typeof(_val) _result = (_val); \
+ if (_result < 0) { \
+ SYSCALL_RET(_regs) = -_result; \
+ (_regs).tstate |= SPARC64_TSTATE_CARRY; \
+ } else { \
+ SYSCALL_RET(_regs) = _result; \
+ (_regs).tstate &= ~SPARC64_TSTATE_CARRY; \
+ } \
+ } while (0)
+# define SYSCALL_RET_SET_ON_PTRACE_EXIT
#else
# error "Do not know how to find your architecture's registers and syscalls"
#endif
@@ -1939,6 +1967,14 @@ const bool ptrace_entry_set_syscall_ret =
#if defined(__x86_64__) || defined(__i386__) || defined(__mips__) || defined(__mc68000__)
# define ARCH_GETREGS(_regs) ptrace(PTRACE_GETREGS, tracee, 0, &(_regs))
# define ARCH_SETREGS(_regs) ptrace(PTRACE_SETREGS, tracee, 0, &(_regs))
+#elif defined(__sparc__) && defined(__arch64__)
+/*
+ * The NT_PRSTATUS regset appends the register window, which struct
+ * pt_regs does not carry; the sparc-specific requests transfer
+ * struct pt_regs directly (and take the buffer in addr).
+ */
+# define ARCH_GETREGS(_regs) ptrace(PTRACE_GETREGS64, tracee, &(_regs), 0)
+# define ARCH_SETREGS(_regs) ptrace(PTRACE_SETREGS64, tracee, &(_regs), 0)
#else
# define ARCH_GETREGS(_regs) ({ \
struct iovec __v; \
--
2.43.0
^ permalink raw reply related [flat|nested] 3+ messages in thread