[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