[Bug 4036] New: seccomp sandbox: sshd-auth killed with SIGSYS on restart_syscall after the process is frozen (cgroup freezer, SIGSTOP)

bugzilla-daemon at mindrot.org bugzilla-daemon at mindrot.org
Sat Oct 17 01:40:14 AEDT 2026


https://bugzilla.mindrot.org/show_bug.cgi?id=4036

            Bug ID: 4036
           Summary: seccomp sandbox: sshd-auth killed with SIGSYS on
                    restart_syscall after the process is frozen (cgroup
                    freezer, SIGSTOP)
           Product: Portable OpenSSH
           Version: 10.2p1
          Hardware: amd64
                OS: Linux
            Status: NEW
          Severity: normal
          Priority: P5
         Component: sshd
          Assignee: unassigned-bugs at mindrot.org
          Reporter: web122077 at gmail.com

Created attachment 4005
  --> https://bugzilla.mindrot.org/attachment.cgi?id=4005&action=edit
Allow restart_syscall in the preauth seccomp sandbox

The preauth child (sshd-auth) is killed by its own seccomp filter if it
is
frozen and thawed while inside a sleeping syscall. On resume the kernel
re-enters the interrupted call as restart_syscall(2). That syscall is
not in
preauth_insns, so the default SECCOMP_RET_KILL fires.

Seen with OpenSSH 10.2p1 (Ubuntu 1:10.2p1-2ubuntu3.6 and -3.8), glibc
2.43,
Linux 7.0 x86_64, with sshd in an unprivileged LXC container. The
allowlist in
sandbox-seccomp-filter.c on master (6545e6a55d4a) has the same gap.

Trigger: ensure_minimum_time_since() in auth2.c calls nanosleep() after
every
failed userauth request, and also after postponed and successful
publickey
requests (here: "delaying 1.5ms" .. "delaying 6.5ms"). If the process
is
frozen during that sleep, clock_nanosleep returns
-ERESTART_RESTARTBLOCK.
No signal handler runs, so on thaw the kernel resumes it via
restart_syscall
(x86_64 nr 219). Things that freeze a process this way:
  - the cgroup v2 freezer (cgroup.freeze). Proxmox VE freezes every
running
    container for each storage-replication run, snapshot and
snapshot-mode
    backup.
  - SIGSTOP/SIGCONT.
  - CRIU checkpointing.
A poll() with a timeout would go down the same restart path.

What it looks like:

  sshd-session[N]: error: mm_reap: child terminated by signal 31
  sshd[P]: Session process N unpriv child crash for connection from A
to B
  sshd[P]: srclimit_penalise: ipv4: new A/32 active penalty of 90
seconds for penalty: caused crash
  audit: type=SECCOMP ... comm="sshd-auth"
exe="/usr/lib/openssh/sshd-auth" sig=31 arch=c000003e syscall=219 ...
SYSCALL=restart_syscall

With the default PerSourcePenalties crash:90, each kill also locks the
client's address out. We hit this on a busy git server whose container
is
replicated every minute: about one killed login per day, and each one
locked
out every client behind that address for 90+ seconds.

Reproduction (root, systemd, cgroup v2). Only a throwaway sshd is
frozen:

  systemd-run --unit=sshd-test /usr/sbin/sshd -D -e -p 2222 \
      -o ListenAddress=127.0.0.1 -o PidFile=none -o LogLevel=DEBUG3
  F=/sys/fs/cgroup/system.slice/sshd-test.service/cgroup.freeze
  # brief freeze/thaw at random 2-20 ms intervals, for 30 s
  python3 -c 'import sys,time,random
  f=sys.argv[1]; end=time.monotonic()+30
  while time.monotonic()<end:
      open(f,"w").write("1"); time.sleep(0.001); open(f,"w").write("0")
      time.sleep(random.uniform(0.002,0.02))' $F &
  ssh-keygen -q -t ed25519 -N '' -f ./k
  for i in $(seq 200); do
    ssh -n -p 2222 -i ./k -o IdentitiesOnly=yes -o BatchMode=yes \
      -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
      nosuchuser at 127.0.0.1 true >/dev/null 2>&1 &
    [ $((i % 8)) -eq 0 ] && wait
  done; wait
  journalctl -u sshd-test | grep -c 'terminated by signal 31'

Results:
  - local clients: 4/200 children killed
  - clients connecting over a network tunnel (longer userauth round
trips):
    103/148 killed
  - freezer loop not running: 0/200 killed
All 117 audit records were comm=sshd-auth sig=31 syscall=219. Each kill
came
about 3 ms after a "[preauth] ensure_minimum_time_since: ... delaying
6.5ms"
debug line.

The kernel half on its own:
  systemd-run --unit=p sleep 60      # /proc/PID/syscall: 230
(clock_nanosleep)
  echo 1 > /sys/fs/cgroup/system.slice/p.service/cgroup.freeze
  echo 0 > /sys/fs/cgroup/system.slice/p.service/cgroup.freeze
                                     # /proc/PID/syscall: 219
(restart_syscall)

Suggested fix (patch attached): allow restart_syscall in preauth_insns.
It
only continues the restart block left behind by a syscall the filter
already
allows (nanosleep/clock_nanosleep/poll/futex here). With no pending
block it
returns -EINTR, so it does not widen what the sandbox permits. Docker's
default seccomp profile and systemd's @default syscall set allow it for
the
same reason. Bug 3478 covers the more general fragility of the default
KILL
action.

-- 
You are receiving this mail because:
You are watching the assignee of the bug.


More information about the openssh-bugs mailing list