SVE-2026-0001
proxyto unreaped idle child
proxyto, the small PROXY-protocol front-end used in front of applications that do not understand PROXY protocol themselves, left its most recently exited child as a zombie while the parent daemon sat idle. The service kept running normally throughout. Fixed; severity Low.
FixedSeverity: LowComponent: saphira-proxyto
At a glance
- Identifier
- SVE-2026-0001
- Title
- proxyto unreaped idle child
- Component
- saphira-proxyto
- Status
- Fixed.
- Severity
- Low.
An SVE is Saphira's own public record. It is not a CVE and does not imply that an official CVE identifier has been assigned.
Summary
proxyto is the small PROXY-protocol front-end used in front of applications that do not understand PROXY protocol themselves. It has been running the public Saphira Gopher service successfully for approximately a week.
During routine inspection of the live service, including CPU, memory and process behaviour, an exited proxyto child was found remaining in zombie state while the parent daemon was idle.
This did not stop the service, expose data or cause a crash. The live daemon continued serving Gopher normally and used only around 700 KiB RSS.
What happened
proxyto forks a child to handle a connection.
The original SIGCHLD handler did not reap the child directly. Instead it set a flag:
static volatile sig_atomic_t do_reap = 0;
static void
sigchld(int sig)
{
(void)sig;
do_reap = 1;
}
The main accept loop later checked that flag and called waitpid(..., WNOHANG).
SIGCHLD was installed with SA_RESTART.
This detail mattered. When the child exited while the parent was blocked in accept(), the signal handler ran, however SA_RESTART caused accept() to resume rather than return to the main loop. The deferred reap therefore did not happen until another connection arrived.
The result was that the most recently exited child could remain as:
proxyto ... Z [proxyto] <defunct>
while an otherwise idle daemon waited for its next connection.
Repeated testing indicated that this was bounded to the most recent child, rather than an endlessly growing collection of zombies: subsequent activity caused the previous child to be reaped while potentially leaving the newest one behind.
Reproduction and proof
The defect was reproduced by building the pre-fix and fixed binaries side by side in /tmp and running the same suite against both. The pre-fix binary failed the idle check, leaving exactly one zombie child while idle; the fixed binary passed the full suite with zero warnings under -Wall -Wextra -Wpedantic.
A regression test now polls /proc/<daemon>/task/<pid>/children for Z-state entries while the daemon is idle, so this behaviour cannot regress silently.
Resolution
Child reaping was moved into the SIGCHLD handler itself:
static void
sigchld(int sig)
{
int saved_errno = errno;
(void)sig;
while (waitpid(-1, NULL, WNOHANG) > 0)
;
errno = saved_errno;
}
waitpid() is async-signal-safe, all exited children are reaped, and errno is preserved for the interrupted parent code.
The main accept loop no longer needs deferred reap bookkeeping.
Additional verification
During the same inspection, proxyto's privilege handling was verified with strace.
The daemon starts privileged so it can bind the Gopher listener on TCP port 70 and then permanently drops privileges to its dedicated package-owned identity:
getuid() = 0
setgid(19) = 0
setuid(19) = 0
getuid() = 19
getgid() = 19
setuid(0) = -1 EPERM
This proves the process cannot regain root after dropping to proxyto:proxyto UID/GID 19.
Impact
No evidence was found of:
- • data disclosure
- • privilege escalation
- • remote code execution
- • service interruption
- • unbounded process-table exhaustion
The defect was nevertheless incorrect process lifecycle handling and left an unnecessary zombie process while the daemon was idle.
Why publish this?
Because Saphira should document its own mistakes with the same care used when discussing everybody else's.
A defect does not need to be catastrophic to deserve understanding.
It was found on the live service, reproduced, explained and fixed.
Transparency means publishing the small ones too.