Configure PR_SET_PDEATHSIG on Linux and Job Objects on Windows to prevent orphaned subprocesses on program crash

This commit is contained in:
Andrea Mazzoleni 2026-08-01 18:26:01 +02:00
parent 0ca7c65049
commit 6b46e08a1f
4 changed files with 76 additions and 7 deletions

View file

@ -70,12 +70,9 @@ dnl Checks for header files.
AC_HEADER_ASSERT
AC_HEADER_DIRENT
AC_HEADER_SYS_WAIT
AC_CHECK_HEADERS([fcntl.h stddef.h stdint.h stdlib.h string.h limits.h time.h sys/time.h])
AC_CHECK_HEADERS([unistd.h getopt.h io.h inttypes.h byteswap.h])
AC_CHECK_HEADERS([pthread.h math.h syslog.h grp.h pwd.h])
AC_CHECK_HEADERS([sys/file.h sys/ioctl.h sys/sysmacros.h sys/mkdev.h sys/mount.h])
AC_CHECK_HEADERS([linux/fiemap.h linux/fs.h linux/btrfs.h execinfo.h])
AC_CHECK_HEADERS([mach/mach_time.h sys/sysctl.h])
AC_CHECK_HEADERS([stdint.h inttypes.h unistd.h math.h execinfo.h strings.h getopt.h syslog.h grp.h pwd.h io.h])
AC_CHECK_HEADERS([sys/file.h sys/sysctl.h sys/ioctl.h sys/time.h sys/types.h sys/mkdev.h sys/sysmacros.h sys/stat.h sys/prctl.h sys/sysinfo.h sys/utsname.h])
AC_CHECK_HEADERS([linux/fs.h linux/btrfs.h linux/fiemap.h mach/mach_time.h])
# Check for the close_range(...,CLOSE_RANGE_CLOEXEC)
AC_CHECK_HEADERS([linux/close_range.h])

View file

@ -2531,6 +2531,8 @@ static int argcat(WCHAR* cmd, int size, int pos, const WCHAR* arg)
return pos;
}
static HANDLE os_job_handle = 0;
pid_t os_spawn(char** argv, int* stdout_read_int, int* stderr_read_int, const char* run_as_user)
{
wchar_t conv[CONV_MAX];
@ -2743,6 +2745,17 @@ pid_t os_spawn(char** argv, int* stdout_read_int, int* stderr_read_int, const ch
return -1;
}
/*
* Assign spawned process to Job Object configured with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE.
* This ensures Windows automatically terminates child processes if the daemon crashes unexpectedly.
*/
if (os_job_handle != 0) {
if (!AssignProcessToJobObject(os_job_handle, pi.hProcess)) {
windows_errno(GetLastError());
os_syslog(OS_LVL_INFO, "failed to assign process to job object, errno=%s(%d)", strerror(errno), errno);
}
}
/* close the write end of the pipes in the parent */
if (has_out) {
CloseHandle(stdout_write_handle);
@ -3550,6 +3563,18 @@ void os_init(unsigned opt)
os_exit();
}
/* initialize job object to terminate child processes if daemon dies */
os_job_handle = CreateJobObjectW(0, 0);
if (os_job_handle != 0) {
JOBOBJECT_EXTENDED_LIMIT_INFORMATION info;
ZeroMemory(&info, sizeof(info));
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
if (!SetInformationJobObject(os_job_handle, JobObjectExtendedLimitInformation, &info, sizeof(info))) {
CloseHandle(os_job_handle);
os_job_handle = 0;
}
}
/* check for Wine presence */
is_wine = GetProcAddress(ntdll, "wine_get_version") != 0;
@ -3572,6 +3597,11 @@ void os_init(unsigned opt)
void os_done(void)
{
if (os_job_handle != 0) {
CloseHandle(os_job_handle);
os_job_handle = 0;
}
/* delete the thread local storage for strerror() */
windows_key_delete(last_error);

View file

@ -217,6 +217,10 @@
#include <sys/wait.h>
#endif
#if HAVE_SYS_PRCTL_H
#include <sys/prctl.h>
#endif
#if HAVE_GETOPT_H
#include <getopt.h>
#endif
@ -309,4 +313,3 @@
#endif
#endif

View file

@ -765,6 +765,10 @@ int os_script(char** argv, char** envp, const char* run_as_user)
start = os_tick_sec();
#if defined(__linux__) && defined(PR_SET_PDEATHSIG)
pid_t ppid = getpid();
#endif
pid = fork();
if (pid < 0) {
os_syslog(OS_LVL_INFO, "failed to fork script=%s, errno=%s(%d)", resolved_path, strerror(errno), errno);
@ -802,6 +806,15 @@ int os_script(char** argv, char** envp, const char* run_as_user)
_exit(126);
}
#if defined(__linux__) && defined(PR_SET_PDEATHSIG)
/* Send SIGKILL to child if parent daemon dies unexpectedly */
prctl(PR_SET_PDEATHSIG, SIGKILL);
/* exit if parent died before prctl to avoid becoming orphaned */
if (getppid() != ppid) {
_exit(127);
}
#endif
/* io sandboxing */
int null_fd = open("/dev/null", O_RDWR);
if (null_fd == -1)
@ -933,6 +946,10 @@ int os_command(const char* command, const char* run_as_user, const char* stdin_t
start = os_tick_sec();
#if defined(__linux__) && defined(PR_SET_PDEATHSIG)
pid_t ppid = getpid();
#endif
pid = fork();
if (pid < 0) {
os_syslog(OS_LVL_INFO, "failed to fork command=%s, errno=%s(%d)", command, strerror(errno), errno);
@ -976,6 +993,15 @@ int os_command(const char* command, const char* run_as_user, const char* stdin_t
_exit(126);
}
#if defined(__linux__) && defined(PR_SET_PDEATHSIG)
/* Send SIGKILL to child if parent daemon dies unexpectedly */
prctl(PR_SET_PDEATHSIG, SIGKILL);
/* exit if parent died before prctl to avoid becoming orphaned */
if (getppid() != ppid) {
_exit(127);
}
#endif
/* io sandboxing */
int null_fd = open("/dev/null", O_RDWR);
if (null_fd == -1)
@ -1157,6 +1183,10 @@ pid_t os_spawn(char** argv, int* stdout_read_fd, int* stderr_read_fd, const char
}
}
#if defined(__linux__) && defined(PR_SET_PDEATHSIG)
pid_t ppid = getpid();
#endif
pid = fork();
if (pid < 0) {
os_syslog(OS_LVL_INFO, "failed to fork path=%s, errno=%s(%d)", resolved_path, strerror(errno), errno);
@ -1195,6 +1225,15 @@ pid_t os_spawn(char** argv, int* stdout_read_fd, int* stderr_read_fd, const char
_exit(126);
}
#if defined(__linux__) && defined(PR_SET_PDEATHSIG)
/* Send SIGKILL to child if parent daemon dies unexpectedly */
prctl(PR_SET_PDEATHSIG, SIGKILL);
/* exit if parent died before prctl to avoid becoming orphaned */
if (getppid() != ppid) {
_exit(127);
}
#endif
/* io sandboxing */
int null_fd = open("/dev/null", O_RDWR | O_CLOEXEC);
if (null_fd < 0)