opnsense-src/lib/libc/tests/stdlib/system_test.c
Dag-Erling Smørgrav c4c3753542 system(3): Address test robustness issue
Don't assume that SIGINT and SIGQUIT are set to SIG_DFL at the start
of the test.  Instead, retrieve their current dispositions and verify
that they are restored at the end of the test.

MFC after:	1 week
Sponsored by:	Klara, Inc.
Reviewed by:	kevans
Differential Revision:	https://reviews.freebsd.org/D55709

(cherry picked from commit 48368f702423742b2a7dff7ad3191625e8bf26f0)

system(3): Fix brain glitch in previous commit

We were saving SIGINT twice instead of SIGINT and SIGQUIT.

Also restore original order of operations (SIGINT then SIGQUIT), which
matches the order in which they're discussed in the POSIX description
of system(3).

MFC after:	1 week
Sponsored by:	Klara, Inc.
Fixes:		48368f702423 ("system(3): Address test robustness issue")

(cherry picked from commit 863b5c137a98d29dc6964cba0e0c4fe2a8bebab8)
2026-03-11 14:16:10 +01:00

190 lines
4.7 KiB
C

/*-
* Copyright (c) 2026 Klara, Inc.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <atf-c.h>
ATF_TC(system_true);
ATF_TC_HEAD(system_true, tc)
{
atf_tc_set_md_var(tc, "descr", "system(\"true\")");
}
ATF_TC_BODY(system_true, tc)
{
ATF_REQUIRE_EQ(W_EXITCODE(0, 0), system("true"));
}
ATF_TC(system_false);
ATF_TC_HEAD(system_false, tc)
{
atf_tc_set_md_var(tc, "descr", "system(\"false\")");
}
ATF_TC_BODY(system_false, tc)
{
ATF_REQUIRE_EQ(W_EXITCODE(1, 0), system("false"));
}
ATF_TC(system_touch);
ATF_TC_HEAD(system_touch, tc)
{
atf_tc_set_md_var(tc, "descr", "system(\"touch file\")");
}
ATF_TC_BODY(system_touch, tc)
{
/* The file does not exist */
ATF_CHECK_ERRNO(ENOENT, unlink("file"));
/* Run a command that creates it */
ATF_REQUIRE_EQ(W_EXITCODE(0, 0), system("touch file"));
/* Now the file exists */
ATF_CHECK_EQ(0, unlink("file"));
}
ATF_TC(system_null);
ATF_TC_HEAD(system_null, tc)
{
atf_tc_set_md_var(tc, "descr", "system(NULL)");
atf_tc_set_md_var(tc, "require.user", "root");
}
ATF_TC_BODY(system_null, tc)
{
/* First, test in a normal environment */
ATF_REQUIRE_EQ(1, system(NULL));
/* Now enter an empty chroot */
ATF_REQUIRE_EQ(0, chroot("."));
ATF_REQUIRE_EQ(0, chdir("/"));
/* Test again with no shell available */
ATF_REQUIRE_EQ(0, system(NULL));
ATF_REQUIRE_EQ(W_EXITCODE(127, 0), system("true"));
}
/*
* Define PROCMASK_IS_THREADMASK if sigprocmask() gets / sets the thread
* mask in multithreaded programs, which makes it impossible to verify
* that system(3) correctly blocks and unblocks SIGCHLD.
*/
#ifdef __FreeBSD__
#define PROCMASK_IS_THREADMASK 1
#endif
static void *
system_thread(void *arg)
{
char cmd[64];
int i = (int)(intptr_t)arg;
snprintf(cmd, sizeof(cmd), "rm flag%d ; lockf -ns lock%d true", i, i);
return ((void *)(intptr_t)system(cmd));
}
static inline int
sigcmpset(const sigset_t *a, const sigset_t *b)
{
return (memcmp(a, b, sizeof(sigset_t)));
}
ATF_TC(system_concurrent);
ATF_TC_HEAD(system_concurrent, tc)
{
atf_tc_set_md_var(tc, "descr", "Concurrent calls");
}
ATF_TC_BODY(system_concurrent, tc)
{
enum { N = 3 };
struct sigaction sigint, sigquit, sigact;
sigset_t normset, sigset;
pthread_t thr[N];
char fn[8];
int fd[N];
void *arg, *ret;
/* Create and lock the locks */
for (int i = 0; i < N; i++) {
snprintf(fn, sizeof(fn), "lock%d", i);
fd[i] = open(fn, O_CREAT|O_EXCL|O_EXLOCK|O_CLOEXEC, 0644);
ATF_REQUIRE_MSG(fd[i] >= 0, "%s: %m", fn);
}
/* Create the flags */
for (int i = 0; i < N; i++) {
snprintf(fn, sizeof(fn), "flag%d", i);
ATF_REQUIRE_EQ(0, symlink(fn, fn));
}
/* Save the current signal dispositions */
ATF_REQUIRE_EQ(0, sigaction(SIGINT, NULL, &sigint));
ATF_REQUIRE_EQ(0, sigaction(SIGQUIT, NULL, &sigquit));
ATF_REQUIRE_EQ(0, sigprocmask(0, NULL, &normset));
/* Spawn threads which block on these files */
for (int i = 0; i < N; i++) {
arg = (void *)(intptr_t)i;
ATF_REQUIRE_INTEQ(0,
pthread_create(&thr[i], NULL, system_thread, arg));
}
/* Wait until the flags are gone */
for (int i = 0; i < N; i++) {
snprintf(fn, sizeof(fn), "flag%d", i);
while (readlink(fn, fn, sizeof(fn)) > 0)
usleep(10000);
ATF_REQUIRE_EQ(ENOENT, errno);
}
/* Release the locks */
for (int i = 0; i < N; i++) {
/* Check the signal dispositions */
ATF_REQUIRE_EQ(0, sigaction(SIGINT, NULL, &sigact));
ATF_CHECK_EQ(SIG_IGN, sigact.sa_handler);
ATF_REQUIRE_EQ(0, sigaction(SIGQUIT, NULL, &sigact));
ATF_CHECK_EQ(SIG_IGN, sigact.sa_handler);
#ifndef PROCMASK_IS_THREADMASK
ATF_REQUIRE_EQ(0, sigprocmask(0, NULL, &sigset));
ATF_CHECK(sigismember(&sigset, SIGCHLD));
#endif
/* Close the file, releasing the lock */
ATF_REQUIRE_INTEQ(0, close(fd[i]));
/* Join the thread and check the return value */
ATF_CHECK_INTEQ(0, pthread_join(thr[i], &ret));
ATF_CHECK_INTEQ(W_EXITCODE(0, 0), (int)(intptr_t)ret);
}
/* Check the signal dispositions */
ATF_REQUIRE_EQ(0, sigaction(SIGINT, NULL, &sigact));
ATF_CHECK_EQ(sigint.sa_handler, sigact.sa_handler);
ATF_CHECK_EQ(sigint.sa_flags, sigact.sa_flags);
ATF_CHECK_EQ(0, sigcmpset(&sigint.sa_mask, &sigact.sa_mask));
ATF_REQUIRE_EQ(0, sigaction(SIGQUIT, NULL, &sigact));
ATF_CHECK_EQ(sigquit.sa_handler, sigact.sa_handler);
ATF_CHECK_EQ(sigquit.sa_flags, sigact.sa_flags);
ATF_CHECK_EQ(0, sigcmpset(&sigquit.sa_mask, &sigact.sa_mask));
ATF_REQUIRE_EQ(0, sigprocmask(0, NULL, &sigset));
ATF_CHECK_EQ(0, sigcmpset(&sigset, &normset));
}
ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, system_true);
ATF_TP_ADD_TC(tp, system_false);
ATF_TP_ADD_TC(tp, system_touch);
ATF_TP_ADD_TC(tp, system_null);
ATF_TP_ADD_TC(tp, system_concurrent);
return (atf_no_error());
}