Skip to content

arm64: SIGSYS from SECCOMP_RET_TRAP carries the syscall number in regs[0] instead of arg0 #14974

Description

@scottmas

Description

On arm64, when a seccomp filter returns SECCOMP_RET_TRAP, the ucontext_t handed to the SIGSYS
handler has the syscall number in uc_mcontext.regs[0] where Linux leaves the syscall's
first argument. The first argument appears in no register of the frame (we checked
regs[0]–regs[8]). Arguments 1–5 (regs[1]–regs[5]) are intact, and a return value the
handler writes into regs[0] reaches the trapped caller correctly.

x86_64 is unaffected: under the same release, on both the systrap and ptrace platforms, all six
arguments arrive in rdi/rsi/rdx/r10/r8/r9.

Handlers that emulate a trapped syscall read its arguments from the SIGSYS frame. On arm64 Linux
this works because __seccomp_filter calls syscall_rollback() for SECCOMP_RET_TRAP before
forcing the signal, and arm64's rollback restores regs[0] from orig_x0. On gVisor arm64 the same handler decides on the wrong first argument.
We found this while running a seccomp-trap-based sandbox under runsc. Its first casualty was
rt_sigprocmask: the handler read the how argument as 0x87 and returned EINVAL.

Steps to reproduce

// repro.c — cc -O1 -o repro repro.c   (static or dynamic; no dependencies)
#define _GNU_SOURCE
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <ucontext.h>
#include <unistd.h>

static volatile unsigned long seen[6];

static void h(int s, siginfo_t *si, void *u) {
    ucontext_t *uc = u;
#if defined(__aarch64__)
    for (int i = 0; i < 6; i++) seen[i] = uc->uc_mcontext.regs[i];
    uc->uc_mcontext.regs[0] = 4242;            /* return value */
#elif defined(__x86_64__)
    static const int r[6] = {REG_RDI, REG_RSI, REG_RDX, REG_R10, REG_R8, REG_R9};
    for (int i = 0; i < 6; i++) seen[i] = uc->uc_mcontext.gregs[r[i]];
    uc->uc_mcontext.gregs[REG_RAX] = 4242;
#endif
}

int main(void) {
    struct sigaction sa = {0};
    sa.sa_sigaction = h;
    sa.sa_flags = SA_SIGINFO;
    sigaction(SIGSYS, &sa, 0);

    struct sock_filter f[] = {
        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_getpriority, 0, 1),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_TRAP),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
    };
    struct sock_fprog p = {sizeof f / sizeof f[0], f};
    prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
    if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &p)) { perror("seccomp"); return 1; }

    long r = syscall(__NR_getpriority, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66);
    printf("args=%lx %lx %lx %lx %lx %lx ret=%ld\n",
           seen[0], seen[1], seen[2], seen[3], seen[4], seen[5], r);
    return 0;
}
runsc --root=/tmp/state --platform=systrap --network=none --ignore-cgroups do -- ./repro

Expected (native Linux arm64 and x86_64, and gVisor x86_64 on systrap and ptrace):

args=11 22 33 44 55 66 ret=4242

Actual (gVisor arm64, systrap and ptrace):

args=8d 22 33 44 55 66 ret=4242

0x8d is __NR_getpriority on arm64.

runsc version

release-20260817.0 (checksum-verified binary from storage.googleapis.com/gvisor/releases/release/20260817.0/aarch64)

Environment

  • arm64: runsc nested in a privileged Docker container on an arm64 Linux VM (OrbStack, Apple
    silicon). Both --platform=systrap and --platform=ptrace.
  • x86_64 control: Debian 13 on a Hetzner VM. Both platforms; clean.

We have not read the sentry code. The symptom looks like the arm64 equivalent of Linux's
syscall_rollback() being missing on the trap path, so the frame is built from register state in
which x0 has already been overwritten.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions