[PATCH] crond: reap orphaned grandchildren to prevent zombie buildup
Valentin Lab
valentin.lab_busybox at kalysto.org
Sat May 31 03:53:05 UTC 2025
This patch fixes a long-standing zombie-process leak in crond that
appears whenever crond itself is PID 1 (typical in minimal BusyBox
containers).
Reproducer
==========
mkdir /tmp/test-root-crontabs -p
echo "* * * * * sh -c 'sleep 1 &'" > "/tmp/test-root-crontabs/root"
sudo chown root:root -R /tmp/test-root-crontabs
## Run busybox crond PID 1
docker run -d --rm --name busybox \
-v /tmp/test-root-crontabs/root:/var/spool/cron/crontabs/root:ro \
busybox:1.37.0-uclibc \
crond -f -d 0 > /dev/null
watch -n 1 '
echo "Expecting more zombies in $((60 - 10#$(date +%S)))s (Ctrl-C to quit):"
ps -ax -o pid,ppid,stat,comm | egrep "\s+Z\s+"
'
echo "Cleaning busybox container"
docker stop busybox
You should see one new "sleep" zombie each minute.
If crond is *not* PID 1 (e.g. started via a wrapper shell), the leak
does not appear because the wrapper—now PID 1—reaps the orphans.
Root cause
==========
crond only calls `waitpid()` for PIDs it tracks. Background tasks
(`sleep 5 &`) become orphans; the kernel re-parents them to PID 1.
When crond *is* PID 1, it inherits these orphans but never waits for
them, so they persist as zombies.
Fix
===
Add a tiny reaper loop:
while (waitpid(-1, NULL, WNOHANG) > 0);
Placed at the end of `check_completions()`, it collects any remaining
children. On systems where crond is **not** PID 1 the loop returns
`-ECHILD` immediately, so behaviour and overhead are unchanged.
Testing
=======
* Reproduced the leak on `busybox:1.37.0-uclibc` and current git master.
* Applied the patch, rebuilt BusyBox statically (with only crond),
bind-mounted the binary into a fresh container; zombie count stays at
0 after X min.
Suggested docker commmand for testing with compiled (static) binary in CWD:
docker run --rm --name busybox \
-v /tmp/test-root-crontabs/root:/var/spool/cron/crontabs/root:ro \
-v $PWD/busybox:/bin/crond \
busybox:1.37.0-uclibc \
crond -f -d 0
* Verified crond still exits cleanly and runs scheduled jobs normally.
Binary size impact (gcc 13.3.0, x86-64, `-Os`, static): +17 bytes.
Thanks for your time and for maintaining BusyBox!
Regards,
Valentin Lab
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0001-crond-reap-orphaned-grandchildren-to-prevent-zombie-.patch
Type: text/x-diff
Size: 1662 bytes
Desc: not available
URL: <http://lists.busybox.net/pipermail/busybox/attachments/20250531/3f382a56/attachment.bin>
More information about the busybox
mailing list