From 6b46e08a1f9b64cd6d8cb68d6d95f36e9540d27c Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Sat, 1 Aug 2026 18:26:01 +0200 Subject: [PATCH] Configure PR_SET_PDEATHSIG on Linux and Job Objects on Windows to prevent orphaned subprocesses on program crash --- configure.ac | 9 +++------ os/mingw.c | 30 ++++++++++++++++++++++++++++++ os/portable.h | 5 ++++- os/unix.c | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 7 deletions(-) diff --git a/configure.ac b/configure.ac index 9fea8247..88459839 100644 --- a/configure.ac +++ b/configure.ac @@ -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]) diff --git a/os/mingw.c b/os/mingw.c index 16c59e17..afb23ff4 100644 --- a/os/mingw.c +++ b/os/mingw.c @@ -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); diff --git a/os/portable.h b/os/portable.h index 4dc0460e..bd96b64e 100644 --- a/os/portable.h +++ b/os/portable.h @@ -217,6 +217,10 @@ #include #endif +#if HAVE_SYS_PRCTL_H +#include +#endif + #if HAVE_GETOPT_H #include #endif @@ -309,4 +313,3 @@ #endif #endif - diff --git a/os/unix.c b/os/unix.c index 26797044..7467c992 100644 --- a/os/unix.c +++ b/os/unix.c @@ -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)