start-stop-daemon: use vfork to avoid races

While running `rc-service start docker` on Gentoo,
I found that the command does not start the service 90% of the time,
with an enigmatic 'service crashed' message.

The root cause of this is apparently rc-service spawning a pty,
running start-stop-daemon inside that pty, and exitting,
before start-stop-daemon child process calls setsid(),
which results in the child process being killed with SIGHUP (SI_KERNEL).

Theoretically this bug was present ever since the file was created in
5af58b4514 ("Rewrite the core parts in C. We now provide...")
(or even before that), but it should have been only a minor issue before
45bd125dcc ("Use a pty for prefixed output instead of pipes for...").
Not sure why nobody has had the issue so far (it has been present for
almost 15 years).

As here setsid() is the last call before execve(), the most natural
locking mechanism is vfork(), as it gives back control to parent
process only after execve() or process termination.
So this way the bug can be fixed by adding a single letter. :-)

Another way to ensure this would be using an O_CLOEXEC file descriptor
or some custom lock, which would need to be released not before setsid().

Fixes: 5af58b4514 ("Rewrite the core parts in C. We now provide...")
Fixes #532.
This commit is contained in:
Arusekk 2022-06-16 19:08:08 +02:00 committed by William Hubbs
parent 95dc83bfbc
commit 9e5ce59a21

View File

@ -864,8 +864,8 @@ int main(int argc, char **argv)
if (background) if (background)
signal_setup(SIGCHLD, handle_signal); signal_setup(SIGCHLD, handle_signal);
if ((pid = fork()) == -1) if ((pid = vfork()) == -1)
eerrorx("%s: fork: %s", applet, strerror(errno)); eerrorx("%s: vfork: %s", applet, strerror(errno));
/* Child process - lets go! */ /* Child process - lets go! */
if (pid == 0) { if (pid == 0) {