NetBSD Problem Report #57527
From mlelstv@serpens.de Sat Jul 15 15:17:00 2023
Return-Path: <mlelstv@serpens.de>
Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200])
(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits))
(Client CN "mail.NetBSD.org", Issuer "mail.NetBSD.org CA" (not verified))
by mollari.NetBSD.org (Postfix) with ESMTPS id 543F81A923E
for <gnats-bugs@gnats.NetBSD.org>; Sat, 15 Jul 2023 15:17:00 +0000 (UTC)
Message-Id: <20230715145830.B47E8CCAE7@tazz.1st.de>
Date: Sat, 15 Jul 2023 16:58:30 +0200 (CEST)
From: mlelstv@netbsd.org
Reply-To: mlelstv@netbsd.org
To: gnats-bugs@NetBSD.org
Subject: system() waits for last child if SIG_CHLD is ignored
X-Send-Pr-Version: 3.95
>Number: 57527
>Category: kern
>Synopsis: system() waits for last child if SIG_CHLD is ignored
>Confidential: no
>Severity: critical
>Priority: medium
>Responsible: kern-bug-people
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Sat Jul 15 15:20:00 +0000 2023
>Last-Modified: Sat Aug 12 12:55:01 +0000 2023
>Originator: Michael van Elst
>Release: NetBSD 10.99.5
>Organization:
--
Michael van Elst
Internet: mlelstv@serpens.de
"A potential Snark may lurk in every tree."
>Environment:
System: NetBSD tazz 10.99.5 NetBSD 10.99.5 (TAZZ) #268: Sat Jul 15 14:39:30 UTC 2023 mlelstv@slowpoke:/scratch2/obj.amd64/scratch/netbsd-current/src/sys/arch/amd64/compile/TAZZ amd64
Architecture: x86_64
Machine: amd64
>Description:
When you ignore SIGCHLD to avoid zombies, then a call to system()
waits until the last child exited.
This is actually wait() or waitpid() waiting (which are called by system()).
>How-To-Repeat:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char *argv[]) {
pid_t pid;
pid = fork();
if (pid == 0) {
sleep(4);
_exit(0);
}
signal(SIGCHLD, SIG_IGN);
system("sleep 1");
return 0;
}
Sleeps 4 seconds on NetBSD and 1 second on Linux.
Tis behaviour comes explicit from exit1() in kern_exit.c:
/*
* If this was the last child of our parent, notify
* parent, so in case he was wait(2)ing, he will
* continue.
*/
if (LIST_FIRST(&old_parent->p_children) == NULL)
cv_broadcast(&old_parent->p_waitcv);
FreeBSD doesn't have such a condition and always wakes the parent.
/*
* Notify parent, so in case he was wait(2)ing or
* executing waitpid(2) with our pid, he will
* continue.
*/
wakeup(pp);
Removing the condition in exit1() fixes the behaviour, but there might
be side effects.
>Fix:
>Audit-Trail:
From: Taylor R Campbell <riastradh@NetBSD.org>
To: mlelstv@NetBSD.org
Cc: gnats-bugs@NetBSD.org
Subject: Re: kern/57527: system() waits for last child if SIG_CHLD is ignored
Date: Sat, 15 Jul 2023 20:04:50 +0000
This is a multi-part message in MIME format.
--=_jTTFFMu/al3E0ztP4Shf9axJ0hgFpHTk
The following clause in POSIX on wait/waitpid that mlelstv found
shortly after filing the bug looks relevant:
If the calling process has SA_NOCLDWAIT set or has SIGCHLD set to
SIG_IGN, and the process has no unwaited for children that were
transformed into zombie processes, the calling thread will block
until all of the children of the process containing the calling
thread terminate, and wait() and waitpid() will fail and set errno
to [ECHILD].
https://pubs.opengroup.org/onlinepubs/7908799/xsh/wait.html
That is, this peculiar behaviour of waitpid that depends on whether
SIGCHLD is set to SIG_IGN or has SA_NOCLDWAIT set is enshrined in
POSIX. So the kernel isn't doing something wrong, except...
> Removing the condition in exit1() fixes the behaviour, but there
> might be side effects.
If issuing more _wakeups_ changes anything substantively, the
_waiting_ side must be broken. I think we're missing a test for
PK_NOCLDWAIT|PK_CHLDSIGIGN in do_sys_waitid to match the conditional
wakeup for this semantics, if we want to follow this semantics
properly. (What we're doing right now is wrong either way!)
FreeBSD, however, appears to have intentionally eliminated this quirky
behaviour of POSIX, in svn revision 191313:
r191313 | kib | 2009-04-20 14:34:55 +0000 (Mon, 20 Apr 2009) | 14 lines
On the exit of the child process which parent either set SA_NOCLDWAIT
or ignored SIGCHLD, unconditionally wake up the parent instead of doing
this only when the child is a last child.
This brings us in line with other U**xes that support SA_NOCLDWAIT. If
the parent called waitpid(childpid), then exit of the child should wake
up the parent immediately instead of forcing it to wait for all children
to exit.
/*
- * If this was the last child of our parent, notify
- * parent, so in case he was wait(2)ing, he will
+ * Notify parent, so in case he was wait(2)ing or
+ * executiing waitpid(2) with our pid, he will
* continue.
*/
- if (LIST_EMPTY(&pp->p_children))
- wakeup(pp);
+ wakeup(pp);
But let's suppose we intend to follow POSIX here.
Under this semantics, it is tempting to say that system(3) should set
SIGCHLD to SIG_DFL while it is blocked, in case the caller had set it
to SIG_IGN. Unfortunately, this has the side effect that if there
were any queued SIGCHLD signals for processes _other_ than the one
that system(3) spawned, those queued SIGCHLD signals will be lost and
not delivered! And we can't just raise(SIGCHLD) because that passes
the wrong siginfo through.
Maybe we could set SIGCHLD to SIG_DFL _only if_ it was already set to
SIG_IGN, as in this note that mlelstv found:
https://www.open-std.org/jtc1/sc22/WG15/docs/rr/9945-2/9945-2-28.html
The rationale section of the wait/waitpid page explains some of the
complications in interaction with system(3):
https://pubs.opengroup.org/onlinepubs/7908799/xsh/wait.html
However, it doesn't seem to address the possibility of changing
SIGCHLD to SIG_DFL if and only if it was already set to SIG_IGN, if
I'm reading it correctly. So maybe the attached patch will resolve
the issue in system(3)?
--=_jTTFFMu/al3E0ztP4Shf9axJ0hgFpHTk
Content-Type: text/plain; charset="ISO-8859-1"; name="pr57527-system"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment; filename="pr57527-system.patch"
From 51954a3b812c57c57835be1b4b06c5c9918c5783 Mon Sep 17 00:00:00 2001
From: Taylor R Campbell <riastradh@NetBSD.org>
Date: Sat, 15 Jul 2023 20:02:28 +0000
Subject: [PATCH] system(3): Change SIG_IGN to SIG_DFL for SIGCHLD while we
work.
PR kern/57527
---
lib/libc/stdlib/system.c | 32 ++++++++++++++++++++++++++++++--
1 file changed, 30 insertions(+), 2 deletions(-)
diff --git a/lib/libc/stdlib/system.c b/lib/libc/stdlib/system.c
index 5a19bc9e0568..4d3e45f1892a 100644
--- a/lib/libc/stdlib/system.c
+++ b/lib/libc/stdlib/system.c
@@ -54,7 +54,7 @@ int
system(const char *command)
{
pid_t pid;
- struct sigaction intsa, quitsa, sa;
+ struct sigaction chldsa, intsa, quitsa, sa;
sigset_t nmask, omask, sigdefault;
int pstat;
const char *argp[] =3D {"sh", "-c", "--", command, NULL};
@@ -90,6 +90,32 @@ system(const char *command)
return -1;
}
=20
+ /*
+ * Make sure SIGCHLD is not set to SIG_IGN, because that means
+ * waitpid will wait until _all_ children have exited, under a
+ * peculiar clause of POSIX enshrining a quirk of SysV
+ * semantics.
+ *
+ * If SIGCHLD was set to anything other than SIG_IGN, we must
+ * not interfere with it -- changing the action may clear any
+ * queued signals for for processes _other_ than the one that
+ * system(3) itself spawns.
+ */
+ if (sigaction(SIGCHLD, NULL, &chldsa) =3D=3D -1) {
+ (void)sigprocmask(SIG_SETMASK, &omask, NULL);
+ sigaction(SIGINT, &intsa, NULL);
+ sigaction(SIGQUIT, &quitsa, NULL);
+ return -1;
+ }
+ if (chldsa.sa_handler =3D=3D SIG_IGN &&
+ sigaction(SIGCHLD, &(struct sigaction){ .sa_handler =3D SIG_DFL },
+ NULL) =3D=3D -1) {
+ (void)sigprocmask(SIG_SETMASK, &omask, NULL);
+ sigaction(SIGINT, &intsa, NULL);
+ sigaction(SIGQUIT, &quitsa, NULL);
+ return -1;
+ }
+
/*
* We arrange to inherit all signal handlers from the caller by
* default, except possibly SIGINT and SIGQUIT. These we have
@@ -133,7 +159,9 @@ system(const char *command)
}
}
=20
-out: sigaction(SIGINT, &intsa, NULL);
+out: if (chldsa.sa_handler =3D=3D SIG_IGN)
+ sigaction(SIGCHLD, &chldsa, NULL);
+ sigaction(SIGINT, &intsa, NULL);
sigaction(SIGQUIT, &quitsa, NULL);
(void)sigprocmask(SIG_SETMASK, &omask, NULL);
=20
--=_jTTFFMu/al3E0ztP4Shf9axJ0hgFpHTk--
From: David Holland <dholland-bugs@netbsd.org>
To: gnats-bugs@netbsd.org
Cc:
Subject: Re: kern/57527: system() waits for last child if SIG_CHLD is ignored
Date: Sat, 15 Jul 2023 20:46:43 +0000
On Sat, Jul 15, 2023 at 08:05:02PM +0000, Taylor R Campbell wrote:
> However, it doesn't seem to address the possibility of changing
> SIGCHLD to SIG_DFL if and only if it was already set to SIG_IGN, if
> I'm reading it correctly. So maybe the attached patch will resolve
> the issue in system(3)?
It's worse than that; you could race with another thread setting
SIG_IGN.
Maybe that gets into "don't do that" territory though.
--
David A. Holland
dholland@netbsd.org
From: Taylor R Campbell <riastradh@NetBSD.org>
To: dholland@NetBSD.org
Cc: gnats-bugs@NetBSD.org
Subject: Re: kern/57527: system() waits for last child if SIG_CHLD is ignored
Date: Sat, 15 Jul 2023 21:48:47 +0000
This is a multi-part message in MIME format.
--=_cMRXY1mpK2XMc2pQZgnqCub6ZuhP0lAV
> Date: Sat, 15 Jul 2023 20:46:43 +0000
> From: David Holland <dholland-bugs@netbsd.org>
>
> On Sat, Jul 15, 2023 at 08:05:02PM +0000, Taylor R Campbell wrote:
> > However, it doesn't seem to address the possibility of changing
> > SIGCHLD to SIG_DFL if and only if it was already set to SIG_IGN, if
> > I'm reading it correctly. So maybe the attached patch will resolve
> > the issue in system(3)?
>
> It's worse than that; you could race with another thread setting
> SIG_IGN.
>
> Maybe that gets into "don't do that" territory though.
Yes, it's in `don't do that' territory. POSIX says:
Using the system() function in more than one thread in a process or
when the SIGCHLD signal is being manipulated by more than one
thread in a process may produce unexpected results.
https://pubs.opengroup.org/onlinepubs/9699919799/functions/system.html
--=_cMRXY1mpK2XMc2pQZgnqCub6ZuhP0lAV
Content-Type: text/plain; charset="ISO-8859-1"; name="pr57527-system"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment; filename="pr57527-system.patch"
From 51954a3b812c57c57835be1b4b06c5c9918c5783 Mon Sep 17 00:00:00 2001
From: Taylor R Campbell <riastradh@NetBSD.org>
Date: Sat, 15 Jul 2023 20:02:28 +0000
Subject: [PATCH] system(3): Change SIG_IGN to SIG_DFL for SIGCHLD while we
work.
PR kern/57527
---
lib/libc/stdlib/system.c | 32 ++++++++++++++++++++++++++++++--
1 file changed, 30 insertions(+), 2 deletions(-)
diff --git a/lib/libc/stdlib/system.c b/lib/libc/stdlib/system.c
index 5a19bc9e0568..4d3e45f1892a 100644
--- a/lib/libc/stdlib/system.c
+++ b/lib/libc/stdlib/system.c
@@ -54,7 +54,7 @@ int
system(const char *command)
{
pid_t pid;
- struct sigaction intsa, quitsa, sa;
+ struct sigaction chldsa, intsa, quitsa, sa;
sigset_t nmask, omask, sigdefault;
int pstat;
const char *argp[] =3D {"sh", "-c", "--", command, NULL};
@@ -90,6 +90,32 @@ system(const char *command)
return -1;
}
=20
+ /*
+ * Make sure SIGCHLD is not set to SIG_IGN, because that means
+ * waitpid will wait until _all_ children have exited, under a
+ * peculiar clause of POSIX enshrining a quirk of SysV
+ * semantics.
+ *
+ * If SIGCHLD was set to anything other than SIG_IGN, we must
+ * not interfere with it -- changing the action may clear any
+ * queued signals for for processes _other_ than the one that
+ * system(3) itself spawns.
+ */
+ if (sigaction(SIGCHLD, NULL, &chldsa) =3D=3D -1) {
+ (void)sigprocmask(SIG_SETMASK, &omask, NULL);
+ sigaction(SIGINT, &intsa, NULL);
+ sigaction(SIGQUIT, &quitsa, NULL);
+ return -1;
+ }
+ if (chldsa.sa_handler =3D=3D SIG_IGN &&
+ sigaction(SIGCHLD, &(struct sigaction){ .sa_handler =3D SIG_DFL },
+ NULL) =3D=3D -1) {
+ (void)sigprocmask(SIG_SETMASK, &omask, NULL);
+ sigaction(SIGINT, &intsa, NULL);
+ sigaction(SIGQUIT, &quitsa, NULL);
+ return -1;
+ }
+
/*
* We arrange to inherit all signal handlers from the caller by
* default, except possibly SIGINT and SIGQUIT. These we have
@@ -133,7 +159,9 @@ system(const char *command)
}
}
=20
-out: sigaction(SIGINT, &intsa, NULL);
+out: if (chldsa.sa_handler =3D=3D SIG_IGN)
+ sigaction(SIGCHLD, &chldsa, NULL);
+ sigaction(SIGINT, &intsa, NULL);
sigaction(SIGQUIT, &quitsa, NULL);
(void)sigprocmask(SIG_SETMASK, &omask, NULL);
=20
--=_cMRXY1mpK2XMc2pQZgnqCub6ZuhP0lAV--
From: Taylor R Campbell <riastradh@NetBSD.org>
To: mlelstv@NetBSD.org
Cc: gnats-bugs@NetBSD.org
Subject: Re: kern/57527: system() waits for last child if SIG_CHLD is ignored
Date: Mon, 17 Jul 2023 12:45:07 +0000
This is a multi-part message in MIME format.
--=_skMWPwotJfxMxufC+Kk6N1dx2rTmwBMC
> Date: Sat, 15 Jul 2023 20:04:50 +0000
> From: Taylor R Campbell <riastradh@NetBSD.org>
>
> The following clause in POSIX on wait/waitpid that mlelstv found
> shortly after filing the bug looks relevant:
>
> If the calling process has SA_NOCLDWAIT set or has SIGCHLD set to
> SIG_IGN, and the process has no unwaited for children that were
> transformed into zombie processes, the calling thread will block
> until all of the children of the process containing the calling
> thread terminate, and wait() and waitpid() will fail and set errno
> to [ECHILD].
>
> https://pubs.opengroup.org/onlinepubs/7908799/xsh/wait.html
Sorry, that's an ancient POSIX URL. Corrected URL that has the quote:
https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html
The attached three-patch series adds automatic tests for these corners
of system(3), and shows that the temporary SIG_IGN -> SIG_DFL change
addresses them.
However, it doesn't address the case of a signal handler function (not
SIG_IGN) with SA_NOCLDWAIT set, as demonstrated in the third patch.
That sounds difficult to reconcile:
- If we leave the signal handler in place, waitpid doesn't work.
- If we touch sigaction, queued signals are lost.
- If we try to requeue signals with raise or kill, siginfo is
destroyed.
What to do? There's some long text in POSIX about whether wait(2)
consumes (`accepts'/`discards') SIGCHLD signals and its interaction
with system(3), but I'm out of time for now to think further about
this.
--=_skMWPwotJfxMxufC+Kk6N1dx2rTmwBMC
Content-Type: text/plain; charset="ISO-8859-1"; name="pr57527-system-v3"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment; filename="pr57527-system-v3.patch"
From 83ffe8640761b46ca49538a438c741f7f24d4acb Mon Sep 17 00:00:00 2001
From: Taylor R Campbell <riastradh@NetBSD.org>
Date: Mon, 17 Jul 2023 11:51:09 +0000
Subject: [PATCH 1/3] system(3): Add test for SA_NOCLDWAIT and SIG_IGN SIGCH=
LD
interaction.
PR 57527
---
tests/lib/libc/stdlib/Makefile | 1 +
tests/lib/libc/stdlib/t_system_sigchld.c | 459 +++++++++++++++++++++++
2 files changed, 460 insertions(+)
create mode 100644 tests/lib/libc/stdlib/t_system_sigchld.c
diff --git a/tests/lib/libc/stdlib/Makefile b/tests/lib/libc/stdlib/Makefile
index a7221b5033fc..493152d47882 100644
--- a/tests/lib/libc/stdlib/Makefile
+++ b/tests/lib/libc/stdlib/Makefile
@@ -20,6 +20,7 @@ TESTS_C+=3D t_strtod
TESTS_C+=3D t_strtol
TESTS_C+=3D t_strtoi
TESTS_C+=3D t_system
+TESTS_C+=3D t_system_sigchld
=20
TESTS_SH+=3D t_atexit
TESTS_SH+=3D t_getopt
diff --git a/tests/lib/libc/stdlib/t_system_sigchld.c b/tests/lib/libc/stdl=
ib/t_system_sigchld.c
new file mode 100644
index 000000000000..9463fc5994ec
--- /dev/null
+++ b/tests/lib/libc/stdlib/t_system_sigchld.c
@@ -0,0 +1,459 @@
+/* $NetBSD$ */
+
+/*-
+ * Copyright (c) 2023 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTO=
RS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIM=
ITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICU=
LAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTO=
RS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF =
THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_system.c,v 1.1 2011/09/11 10:32:23 jruoho Exp $");
+
+#include <sys/wait.h>
+
+#include <atf-c.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "h_macros.h"
+
+static void
+emptyhandler(int signo)
+{
+}
+
+static bool woken;
+
+static void
+wakeup(int signo)
+{
+
+ woken =3D true;
+}
+
+static bool sigchld;
+
+static void
+sigchldhandler(int signo)
+{
+
+ sigchld =3D true;
+}
+
+static siginfo_t sigchldinfo;
+
+static void
+sigchldaction(int signo, siginfo_t *info, void *ctx)
+{
+
+ sigchld =3D true;
+ sigchldinfo =3D *info;
+}
+
+static void
+go(bool dowait)
+{
+ int pfd[2], ffd;
+ char c =3D 1;
+ ssize_t n;
+ int status =3D 0;
+ pid_t child, waitret;
+
+ /*
+ * Create a pipe for communication with the asynchronous child
+ * process.
+ */
+ RL(pipe(pfd));
+
+ /*
+ * Fork an asynchronous child process that will wait until
+ * we've written to the pipe before exiting.
+ */
+ RL(child =3D fork());
+ if (child =3D=3D 0) {
+ if ((n =3D read(pfd[0], &c, 1)) =3D=3D -1)
+ _exit(1);
+ if (n !=3D 1)
+ _exit(1);
+ _exit(0);
+ }
+
+ /*
+ * Set an alarm to kill the process if system(3) hasn't
+ * returned in 5sec.
+ */
+ ATF_REQUIRE_MSG(signal(SIGALRM, &wakeup) !=3D SIG_ERR,
+ "signal(SIGALRM): %s", strerror(errno));
+ RL((int)alarm(2));
+
+ /*
+ * Run a synchronous child process. This should complete
+ * promptly without error. Paranoia: make sure the output file
+ * is not exist already.
+ */
+ (void)unlink("ok");
+ RL(system("touch ok"));
+
+ /*
+ * Clear the alarm. Fail if it had woken us already.
+ */
+ RL((int)alarm(0));
+ ATF_REQUIRE(!woken);
+
+ /*
+ * Make sure the synchronous child process took effect by
+ * creating a file `ok'.
+ */
+ RL(ffd =3D open("ok", O_RDONLY));
+ RL(close(ffd));
+
+ /*
+ * Wake the asynchronous child process so it can exit.
+ */
+ RL(n =3D write(pfd[1], &c, 1));
+ ATF_REQUIRE_MSG(n =3D=3D 1, "n=3D%zd", n);
+
+ /*
+ * Wait for the asynchronous child process -- or expect waitpid
+ * to fail with ECHILD if we can't. Set an alarm so the wait
+ * can't take too long.
+ */
+ RL((int)alarm(2));
+ waitret =3D waitpid(child, &status, 0);
+ if (dowait) {
+ ATF_REQUIRE_EQ_MSG(waitret, child,
+ "waitret=3D%ld status=3D0x%x child=3D%ld",
+ (long)waitret, status, (long)child);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE_EQ_MSG(WEXITSTATUS(status), 0,
+ "status=3D0x%x WEXITSTATUS(status)=3D%d",
+ status, WEXITSTATUS(status));
+ } else {
+ ATF_REQUIRE_EQ_MSG(waitret, -1,
+ "waitret=3D%ld status=3D0x%x child=3D%ld",
+ (long)waitret, status, (long)child);
+ ATF_REQUIRE_EQ_MSG(errno, ECHILD, "errno=3D%d %s",
+ errno, strerror(errno));
+ }
+ RL((int)alarm(0));
+ ATF_REQUIRE(!woken);
+
+ RL(close(pfd[0]));
+ RL(close(pfd[1]));
+}
+
+static void
+goqueued(bool dowait)
+{
+ sigset_t block, omask;
+ pid_t child0, waitret0;
+ int status0 =3D 0;
+
+ /*
+ * Block SIGCHLD for most of the test -- we are going to make
+ * sure that the signal remains queued.
+ */
+ RL(sigemptyset(&block));
+ RL(sigaddset(&block, SIGCHLD));
+ RL(sigprocmask(SIG_BLOCK, &block, &omask));
+
+ /*
+ * Fork a child that immediately exits -- this should promptly
+ * deliver a queued SIGCHLD.
+ */
+ RL(child0 =3D fork());
+ if (child0 =3D=3D 0)
+ _exit(0);
+
+ /*
+ * Wait for the child and verify it exited cleanly. This
+ * should ensure the SIGCHLD is queued before we proceed.
+ */
+ waitret0 =3D waitpid(child0, &status0, 0);
+ if (dowait) {
+ ATF_REQUIRE_EQ_MSG(waitret0, child0,
+ "waitret0=3D%ld status0=3D0x%x child0=3D%ld",
+ (long)waitret0, status0, (long)child0);
+ ATF_REQUIRE(WIFEXITED(status0));
+ ATF_REQUIRE_EQ_MSG(WEXITSTATUS(status0), 0,
+ "status0=3D0x%x WEXITSTATUS(status0)=3D%d",
+ status0, WEXITSTATUS(status0));
+ } else {
+ ATF_REQUIRE_EQ_MSG(waitret0, -1,
+ "waitret0=3D%ld status0=3D0x%x child0=3D%ld",
+ (long)waitret0, status0, (long)child0);
+ ATF_REQUIRE_EQ_MSG(errno, ECHILD, "errno=3D%d %s",
+ errno, strerror(errno));
+ }
+
+ /*
+ * Do the main test.
+ */
+ go(dowait);
+
+ /*
+ * Unblock SIGCHLD and verify that we got the signal.
+ */
+ RL(sigprocmask(SIG_SETMASK, &omask, NULL));
+ ATF_REQUIRE(sigchld);
+}
+
+ATF_TC(system_default);
+ATF_TC_HEAD(system_default, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "system(3) with asynchronous child");
+ atf_tc_set_md_var(tc, "require.progs", "touch");
+}
+ATF_TC_BODY(system_default, tc)
+{
+ go(/*dowait*/true);
+}
+
+ATF_TC(system_nocldwait);
+ATF_TC_HEAD(system_nocldwait, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "system(3) with asynchronous child using SA_NOCLDWAIT");
+ atf_tc_set_md_var(tc, "require.progs", "touch");
+}
+ATF_TC_BODY(system_nocldwait, tc)
+{
+ struct sigaction sa =3D {
+ .sa_handler =3D &emptyhandler,
+ .sa_flags =3D SA_NOCLDWAIT,
+ };
+ struct sigaction nsa;
+
+ atf_tc_expect_fail("PR 57527");
+
+ RL(sigaction(SIGCHLD, &sa, NULL));
+
+ /*
+ * With SIGCHLD set with SA_NOCLDWAIT, the asynchronous child
+ * process will be reaped without waiting.
+ */
+ go(/*dowait*/false);
+
+ /*
+ * Verify the signal configuration hasn't changed.
+ */
+ RL(sigaction(SIGCHLD, NULL, &nsa));
+ ATF_REQUIRE_EQ_MSG(nsa.sa_handler, &emptyhandler, "%p",
+ nsa.sa_handler);
+ ATF_REQUIRE_EQ_MSG(nsa.sa_flags, SA_NOCLDWAIT, "0x%x", nsa.sa_flags);
+}
+
+ATF_TC(system_sigign);
+ATF_TC_HEAD(system_sigign, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "system(3) with asynchronous child using SIG_IGN");
+ atf_tc_set_md_var(tc, "require.progs", "touch");
+}
+ATF_TC_BODY(system_sigign, tc)
+{
+ void (*h)(int);
+
+ atf_tc_expect_fail("PR 57527");
+
+ ATF_REQUIRE_MSG(signal(SIGCHLD, SIG_IGN) !=3D SIG_ERR,
+ "signal(SIGCHLD): %s", strerror(errno));
+
+ /*
+ * With SIGCHLD set to SIG_IGN, the asynchronous child process
+ * will be reaped without waiting.
+ */
+ go(/*dowait*/false);
+
+ /*
+ * Verify the signal configuration hasn't changed.
+ */
+ h =3D signal(SIGCHLD, SIG_DFL);
+ ATF_REQUIRE_EQ_MSG(h, SIG_IGN, "%p", h);
+}
+
+ATF_TC(system_sigign_nocldwait);
+ATF_TC_HEAD(system_sigign_nocldwait, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "system(3) with asynchronous child"
+ " using SIG_IGN and SA_NOCLDWAIT");
+ atf_tc_set_md_var(tc, "require.progs", "touch");
+}
+ATF_TC_BODY(system_sigign_nocldwait, tc)
+{
+ struct sigaction sa =3D {
+ .sa_handler =3D SIG_IGN,
+ .sa_flags =3D SA_NOCLDWAIT,
+ };
+ struct sigaction nsa;
+
+ atf_tc_expect_fail("PR 57527");
+
+ RL(sigaction(SIGCHLD, &sa, NULL));
+
+ /*
+ * With SIGCHLD set to SIG_IGN and/or SA_NOCLDWAIT set, the
+ * asynchronous child process will be reaped without waiting.
+ */
+ go(/*dowait*/false);
+
+ RL(sigaction(SIGCHLD, NULL, &nsa));
+ ATF_REQUIRE_EQ_MSG(nsa.sa_handler, SIG_IGN, "%p", nsa.sa_handler);
+ ATF_REQUIRE_EQ_MSG(nsa.sa_flags, SA_NOCLDWAIT, "0x%x", nsa.sa_flags);
+}
+
+ATF_TC(system_queued);
+ATF_TC_HEAD(system_queued, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "system(3) with asynchronous child and a queued SIGCHLD");
+ atf_tc_set_md_var(tc, "require.progs", "touch");
+}
+ATF_TC_BODY(system_queued, tc)
+{
+ void (*h)(int);
+
+ /*
+ * Set up a signal handler to set a flag when SIGCHLD is
+ * delivered.
+ */
+ ATF_REQUIRE_MSG(signal(SIGCHLD, &sigchldhandler) !=3D SIG_ERR,
+ "signal(SIGCHLD): %s", strerror(errno));
+
+ goqueued(/*dowait*/true);
+
+ /*
+ * Verify the signal configuration hasn't changed.
+ */
+ h =3D signal(SIGCHLD, SIG_DFL);
+ ATF_REQUIRE_EQ_MSG(h, &sigchldhandler, "%p", h);
+}
+
+ATF_TC(system_queued_action);
+ATF_TC_HEAD(system_queued_action, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "system(3) with asynchronous child and a queued SIGCHLD,"
+ " with sigaction");
+ atf_tc_set_md_var(tc, "require.progs", "touch");
+}
+ATF_TC_BODY(system_queued_action, tc)
+{
+ struct sigaction sa =3D {
+ .sa_sigaction =3D &sigchldaction,
+ .sa_flags =3D SA_SIGINFO,
+ };
+ struct sigaction nsa;
+
+ /*
+ * Set up a signal action to set a flag when SIGCHLD is
+ * delivered.
+ */
+ RL(sigaction(SIGCHLD, &sa, NULL));
+
+ goqueued(/*dowait*/true);
+
+ /*
+ * Verify we got the right siginfo.
+ */
+ ATF_CHECK_EQ_MSG(sigchldinfo.si_signo, SIGCHLD, "%d",
+ sigchldinfo.si_signo);
+ ATF_CHECK_EQ_MSG(sigchldinfo.si_errno, 0, "%d",
+ sigchldinfo.si_errno);
+ ATF_CHECK_EQ_MSG(sigchldinfo.si_code, CLD_EXITED, "%d",
+ sigchldinfo.si_code);
+
+ /*
+ * Verify the signal configuration hasn't changed.
+ */
+ RL(sigaction(SIGCHLD, NULL, &nsa));
+ ATF_REQUIRE_EQ_MSG(nsa.sa_sigaction, &sigchldaction,
+ "%p", nsa.sa_sigaction);
+ ATF_REQUIRE_EQ_MSG(nsa.sa_flags, SA_SIGINFO,
+ "0x%x", nsa.sa_flags);
+}
+
+ATF_TC(system_queued_nocldwait);
+ATF_TC_HEAD(system_queued_nocldwait, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "system(3) with asynchronous child and a queued SIGCHLD,"
+ " with SA_NOCLDWAIT");
+ atf_tc_set_md_var(tc, "require.progs", "touch");
+}
+ATF_TC_BODY(system_queued_nocldwait, tc)
+{
+ struct sigaction sa =3D {
+ .sa_sigaction =3D &sigchldaction,
+ .sa_flags =3D SA_NOCLDWAIT|SA_SIGINFO,
+ };
+ struct sigaction nsa;
+
+ atf_tc_expect_fail("PR 57527");
+
+ /*
+ * Set up a signal action to set a flag when SIGCHLD is
+ * delivered.
+ */
+ RL(sigaction(SIGCHLD, &sa, NULL));
+
+ goqueued(/*dowait*/false);
+
+ /*
+ * Verify we got the right siginfo.
+ */
+ ATF_CHECK_EQ_MSG(sigchldinfo.si_signo, SIGCHLD, "%d",
+ sigchldinfo.si_signo);
+ ATF_CHECK_EQ_MSG(sigchldinfo.si_errno, 0, "%d",
+ sigchldinfo.si_errno);
+ ATF_CHECK_EQ_MSG(sigchldinfo.si_code, CLD_EXITED, "%d",
+ sigchldinfo.si_code);
+
+ /*
+ * Verify the signal configuration hasn't changed.
+ */
+ RL(sigaction(SIGCHLD, NULL, &nsa));
+ ATF_REQUIRE_EQ_MSG(nsa.sa_sigaction, &sigchldaction,
+ "%p", nsa.sa_sigaction);
+ ATF_REQUIRE_EQ_MSG(nsa.sa_flags, SA_NOCLDWAIT|SA_SIGINFO,
+ "0x%x", nsa.sa_flags);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, system_default);
+ ATF_TP_ADD_TC(tp, system_nocldwait);
+ ATF_TP_ADD_TC(tp, system_queued);
+ ATF_TP_ADD_TC(tp, system_queued_action);
+ ATF_TP_ADD_TC(tp, system_queued_nocldwait);
+ ATF_TP_ADD_TC(tp, system_sigign);
+ ATF_TP_ADD_TC(tp, system_sigign_nocldwait);
+
+ return atf_no_error();
+}
From daf08fb3988bd6de0ec12348c12a6131ddc279e5 Mon Sep 17 00:00:00 2001
From: Taylor R Campbell <riastradh@NetBSD.org>
Date: Sat, 15 Jul 2023 20:02:28 +0000
Subject: [PATCH 2/3] system(3): Change SIG_IGN to SIG_DFL for SIGCHLD while=
we
work.
Note: This doesn't address the scenario that SIGCHLD is set to a
handler function, not SIG_IGN, but has the SA_NOCLDWAIT flag set.
PR 57527
---
lib/libc/stdlib/system.c | 42 ++++++++++++++++++++++--
tests/lib/libc/stdlib/t_system_sigchld.c | 4 ---
2 files changed, 40 insertions(+), 6 deletions(-)
diff --git a/lib/libc/stdlib/system.c b/lib/libc/stdlib/system.c
index 5a19bc9e0568..8101278c1bd3 100644
--- a/lib/libc/stdlib/system.c
+++ b/lib/libc/stdlib/system.c
@@ -54,7 +54,7 @@ int
system(const char *command)
{
pid_t pid;
- struct sigaction intsa, quitsa, sa;
+ struct sigaction chldsa, intsa, quitsa, sa;
sigset_t nmask, omask, sigdefault;
int pstat;
const char *argp[] =3D {"sh", "-c", "--", command, NULL};
@@ -90,6 +90,42 @@ system(const char *command)
return -1;
}
=20
+ /*
+ * Make sure SIGCHLD is not set to SIG_IGN, because that means
+ * waitpid will wait until _all_ children have exited, under a
+ * peculiar clause of POSIX enshrining a quirk of SysV
+ * semantics in wait/waitpid:
+ *
+ * If the calling process has SA_NOCLDWAIT set or has
+ * SIGCHLD set to SIG_IGN, and the process has no unwaited
+ * for children that were transformed into zombie processes,
+ * the calling thread will block until all of the children
+ * of the process containing the calling thread terminate,
+ * and wait() and waitpid() will fail and set errno to
+ * [ECHILD].
+ *
+ * https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html
+ *
+ * If SIGCHLD was set to anything other than SIG_IGN, we must
+ * not interfere with it -- changing the action may clear any
+ * queued signals for for processes _other_ than the one that
+ * system(3) itself spawns.
+ */
+ if (sigaction(SIGCHLD, NULL, &chldsa) =3D=3D -1) {
+ (void)sigprocmask(SIG_SETMASK, &omask, NULL);
+ sigaction(SIGINT, &intsa, NULL);
+ sigaction(SIGQUIT, &quitsa, NULL);
+ return -1;
+ }
+ if (chldsa.sa_handler =3D=3D SIG_IGN &&
+ sigaction(SIGCHLD, &(struct sigaction){ .sa_handler =3D SIG_DFL },
+ NULL) =3D=3D -1) {
+ (void)sigprocmask(SIG_SETMASK, &omask, NULL);
+ sigaction(SIGINT, &intsa, NULL);
+ sigaction(SIGQUIT, &quitsa, NULL);
+ return -1;
+ }
+
/*
* We arrange to inherit all signal handlers from the caller by
* default, except possibly SIGINT and SIGQUIT. These we have
@@ -133,7 +169,9 @@ system(const char *command)
}
}
=20
-out: sigaction(SIGINT, &intsa, NULL);
+out: if (chldsa.sa_handler =3D=3D SIG_IGN)
+ sigaction(SIGCHLD, &chldsa, NULL);
+ sigaction(SIGINT, &intsa, NULL);
sigaction(SIGQUIT, &quitsa, NULL);
(void)sigprocmask(SIG_SETMASK, &omask, NULL);
=20
diff --git a/tests/lib/libc/stdlib/t_system_sigchld.c b/tests/lib/libc/stdl=
ib/t_system_sigchld.c
index 9463fc5994ec..78d44a8bd797 100644
--- a/tests/lib/libc/stdlib/t_system_sigchld.c
+++ b/tests/lib/libc/stdlib/t_system_sigchld.c
@@ -278,8 +278,6 @@ ATF_TC_BODY(system_sigign, tc)
{
void (*h)(int);
=20
- atf_tc_expect_fail("PR 57527");
-
ATF_REQUIRE_MSG(signal(SIGCHLD, SIG_IGN) !=3D SIG_ERR,
"signal(SIGCHLD): %s", strerror(errno));
=20
@@ -312,8 +310,6 @@ ATF_TC_BODY(system_sigign_nocldwait, tc)
};
struct sigaction nsa;
=20
- atf_tc_expect_fail("PR 57527");
-
RL(sigaction(SIGCHLD, &sa, NULL));
=20
/*
From 57a2d906999c44e409cdf3bc50d84615b390a0c0 Mon Sep 17 00:00:00 2001
From: Taylor R Campbell <riastradh@NetBSD.org>
Date: Mon, 17 Jul 2023 12:37:06 +0000
Subject: [PATCH 3/3] WIP: system(3): defend against SA_NOCLDWAIT too
---
lib/libc/stdlib/system.c | 4 ++--
tests/lib/libc/stdlib/t_system_sigchld.c | 2 --
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/lib/libc/stdlib/system.c b/lib/libc/stdlib/system.c
index 8101278c1bd3..256324a0869e 100644
--- a/lib/libc/stdlib/system.c
+++ b/lib/libc/stdlib/system.c
@@ -117,7 +117,7 @@ system(const char *command)
sigaction(SIGQUIT, &quitsa, NULL);
return -1;
}
- if (chldsa.sa_handler =3D=3D SIG_IGN &&
+ if ((chldsa.sa_handler =3D=3D SIG_IGN || chldsa.sa_flags & SA_NOCLDWAIT) =
&&
sigaction(SIGCHLD, &(struct sigaction){ .sa_handler =3D SIG_DFL },
NULL) =3D=3D -1) {
(void)sigprocmask(SIG_SETMASK, &omask, NULL);
@@ -169,7 +169,7 @@ system(const char *command)
}
}
=20
-out: if (chldsa.sa_handler =3D=3D SIG_IGN)
+out: if (chldsa.sa_handler =3D=3D SIG_IGN || chldsa.sa_flags & SA_NOCLDWAI=
T)
sigaction(SIGCHLD, &chldsa, NULL);
sigaction(SIGINT, &intsa, NULL);
sigaction(SIGQUIT, &quitsa, NULL);
diff --git a/tests/lib/libc/stdlib/t_system_sigchld.c b/tests/lib/libc/stdl=
ib/t_system_sigchld.c
index 78d44a8bd797..88ad880efef2 100644
--- a/tests/lib/libc/stdlib/t_system_sigchld.c
+++ b/tests/lib/libc/stdlib/t_system_sigchld.c
@@ -248,8 +248,6 @@ ATF_TC_BODY(system_nocldwait, tc)
};
struct sigaction nsa;
=20
- atf_tc_expect_fail("PR 57527");
-
RL(sigaction(SIGCHLD, &sa, NULL));
=20
/*
--=_skMWPwotJfxMxufC+Kk6N1dx2rTmwBMC--
From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/57527 CVS commit: src/share/man/man7
Date: Mon, 17 Jul 2023 14:20:19 +0000
Module Name: src
Committed By: riastradh
Date: Mon Jul 17 14:20:19 UTC 2023
Modified Files:
src/share/man/man7: signal.7
Log Message:
signal(7): Clarify semantics of SIGCHLD with SIG_IGN or SA_NOCLDWAIT.
The semantics is not just a nonportable hack for SysV compatibility;
it is enshrined in POSIX.
Related: PR 57527
To generate a diff of this commit:
cvs rdiff -u -r1.27 -r1.28 src/share/man/man7/signal.7
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
From: "Martin Husemann" <martin@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/57527 CVS commit: [netbsd-10] src/share/man/man7
Date: Sat, 12 Aug 2023 12:53:18 +0000
Module Name: src
Committed By: martin
Date: Sat Aug 12 12:53:18 UTC 2023
Modified Files:
src/share/man/man7 [netbsd-10]: signal.7
Log Message:
Pull up following revision(s) (requested by riastradh in ticket #325):
share/man/man7/signal.7: revision 1.27
share/man/man7/signal.7: revision 1.28
signal(7): Clarify quirky SysV-inspired SIGCHLD semantics.
Suggest a portable alternative approach for detaching subprocesses.
Break wall of text into paragraphs while here.
signal(7): Clarify semantics of SIGCHLD with SIG_IGN or SA_NOCLDWAIT.
The semantics is not just a nonportable hack for SysV compatibility;
it is enshrined in POSIX.
Related: PR 57527
To generate a diff of this commit:
cvs rdiff -u -r1.26 -r1.26.4.1 src/share/man/man7/signal.7
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
(Contact us)
$NetBSD: query-full-pr,v 1.47 2022/09/11 19:34:41 kim Exp $
$NetBSD: gnats_config.sh,v 1.9 2014/08/02 14:16:04 spz Exp $
Copyright © 1994-2023
The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.