aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRon Yorston <rmy@pobox.com>2024-05-14 12:33:03 +0100
committerRon Yorston <rmy@pobox.com>2024-05-14 12:33:03 +0100
commit569de936abb90f4c7cdca9da111a6ea780b135bf (patch)
treef1965765683e09ca780b39340abac90f349e47a4
parentbb128070e590234f8b63fb1d67f7621a1b4b3ff3 (diff)
downloadbusybox-w32-569de936abb90f4c7cdca9da111a6ea780b135bf.tar.gz
busybox-w32-569de936abb90f4c7cdca9da111a6ea780b135bf.tar.bz2
busybox-w32-569de936abb90f4c7cdca9da111a6ea780b135bf.zip
kill: killing a zombie process should fail
A process which has exited may still have its process handle held open by its children. Such a process doesn't appear in the process table. It is thus similar to a zombie process in UNIX. Using kill(1) to interact with such a process was seen to succeed, contrary to expectation. The code for "ordinary" signals in kill(2) did check if the process was still active but didn't treat an attempt to kill an inactive process as an error. Furthermore, sending SIGKILL or the fake signal 0 to a process didn't even check if the process was still active. Rearrange the implementation of kill(2) so that an attempt to signal an inactive process is treated as an error. This also consolidates handling of SIGKILL and signal 0 with "ordinary" signals. Saves 96 bytes. (GitHub issue #416)
-rw-r--r--coreutils/timeout.c18
-rw-r--r--include/mingw.h1
-rw-r--r--win32/process.c196
3 files changed, 101 insertions, 114 deletions
diff --git a/coreutils/timeout.c b/coreutils/timeout.c
index ff58a753a..802ddfc07 100644
--- a/coreutils/timeout.c
+++ b/coreutils/timeout.c
@@ -54,7 +54,9 @@ static HANDLE child = INVALID_HANDLE_VALUE;
54static void kill_child(void) 54static void kill_child(void)
55{ 55{
56 if (child != INVALID_HANDLE_VALUE) { 56 if (child != INVALID_HANDLE_VALUE) {
57 kill_signal_by_handle(child, SIGTERM); 57 pid_t pid = (pid_t)GetProcessId(child);
58 if (pid)
59 kill(pid, SIGTERM);
58 } 60 }
59} 61}
60 62
@@ -206,13 +208,15 @@ int timeout_main(int argc UNUSED_PARAM, char **argv)
206 status = signo == SIGKILL ? 137 : 124; 208 status = signo == SIGKILL ? 137 : 124;
207 209
208 pid = (pid_t)GetProcessId(child); 210 pid = (pid_t)GetProcessId(child);
209 kill(pid, signo); 211 if (pid) {
212 kill(pid, signo);
210 213
211 if (kill_timeout > 0) { 214 if (kill_timeout > 0) {
212 if (timeout_wait(kill_timeout, child, &status)) 215 if (timeout_wait(kill_timeout, child, &status))
213 goto finish; 216 goto finish;
214 kill(pid, SIGKILL); 217 kill(pid, SIGKILL);
215 status = 137; 218 status = 137;
219 }
216 } 220 }
217 finish: 221 finish:
218 CloseHandle(child); 222 CloseHandle(child);
diff --git a/include/mingw.h b/include/mingw.h
index 41d4cd940..56a7285e5 100644
--- a/include/mingw.h
+++ b/include/mingw.h
@@ -569,7 +569,6 @@ int mingw_execve(const char *cmd, char *const *argv, char *const *envp);
569#define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':') 569#define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':')
570 570
571BOOL WINAPI kill_child_ctrl_handler(DWORD dwCtrlType); 571BOOL WINAPI kill_child_ctrl_handler(DWORD dwCtrlType);
572int kill_signal_by_handle(HANDLE process, int sig);
573int FAST_FUNC is_valid_signal(int number); 572int FAST_FUNC is_valid_signal(int number);
574int exit_code_to_wait_status(DWORD win_exit_code); 573int exit_code_to_wait_status(DWORD win_exit_code);
575int exit_code_to_posix(DWORD win_exit_code); 574int exit_code_to_posix(DWORD win_exit_code);
diff --git a/win32/process.c b/win32/process.c
index 5be07e54a..b993bb2be 100644
--- a/win32/process.c
+++ b/win32/process.c
@@ -685,14 +685,98 @@ void FAST_FUNC read_cmdline(char *buf, int col, unsigned pid, const char *comm)
685} 685}
686 686
687/** 687/**
688 * Determine whether a process runs in the same architecture as the current
689 * one. That test is required before we assume that GetProcAddress() returns
690 * a valid address *for the target process*.
691 */
692static inline int process_architecture_matches_current(HANDLE process)
693{
694 static BOOL current_is_wow = -1;
695 BOOL is_wow;
696
697 if (current_is_wow == -1 &&
698 !IsWow64Process (GetCurrentProcess(), &current_is_wow))
699 current_is_wow = -2;
700 if (current_is_wow == -2)
701 return 0; /* could not determine current process' WoW-ness */
702 if (!IsWow64Process (process, &is_wow))
703 return 0; /* cannot determine */
704 return is_wow == current_is_wow;
705}
706
707/**
708 * This function tries to terminate a Win32 process, as gently as possible,
709 * by injecting a thread that calls ExitProcess().
710 *
711 * Note: as kernel32.dll is loaded before any process, the other process and
712 * this process will have ExitProcess() at the same address.
713 *
714 * The idea comes from the Dr Dobb's article "A Safer Alternative to
715 * TerminateProcess()" by Andrew Tucker (July 1, 1999),
716 * http://www.drdobbs.com/a-safer-alternative-to-terminateprocess/184416547
717 *
718 */
719static int kill_signal_by_handle(HANDLE process, int sig)
720{
721 DECLARE_PROC_ADDR(DWORD, ExitProcess, LPVOID);
722 PVOID arg = (PVOID)(intptr_t)(sig << 24);
723 DWORD thread_id;
724 HANDLE thread;
725
726 if (!INIT_PROC_ADDR(kernel32, ExitProcess) ||
727 !process_architecture_matches_current(process)) {
728 SetLastError(ERROR_ACCESS_DENIED);
729 return -1;
730 }
731
732 if (sig != 0 && (thread = CreateRemoteThread(process, NULL, 0,
733 ExitProcess, arg, 0, &thread_id))) {
734 CloseHandle(thread);
735 }
736 return 0;
737}
738
739static int kill_signal(pid_t pid, int sig)
740{
741 HANDLE process;
742 int ret = 0;
743 DWORD code;
744
745 if (sig == SIGKILL)
746 process = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
747 else
748 process = OpenProcess(SYNCHRONIZE | PROCESS_CREATE_THREAD |
749 PROCESS_QUERY_INFORMATION |
750 PROCESS_VM_OPERATION | PROCESS_VM_WRITE |
751 PROCESS_VM_READ, FALSE, pid);
752
753 if (!process)
754 return -1;
755
756 if (!GetExitCodeProcess(process, &code) || code != STILL_ACTIVE) {
757 SetLastError(ERROR_INVALID_PARAMETER);
758 ret = -1;
759 } else if (sig == SIGKILL) {
760 /* This way of terminating processes is not gentle: they get no
761 * chance to clean up after themselves (closing file handles,
762 * removing .lock files, terminating spawned processes (if any),
763 * etc). */
764 ret = !TerminateProcess(process, SIGKILL << 24);
765 } else {
766 ret = kill_signal_by_handle(process, sig);
767 }
768 CloseHandle(process);
769
770 return ret;
771}
772
773/**
688 * If the process ID is positive invoke the callback for that process 774 * If the process ID is positive invoke the callback for that process
689 * only. If negative or zero invoke the callback for all descendants 775 * only. If negative or zero invoke the callback for all descendants
690 * of the indicated process. Zero indicates the current process; negative 776 * of the indicated process. Zero indicates the current process; negative
691 * indicates the process with process ID -pid. 777 * indicates the process with process ID -pid.
692 */ 778 */
693typedef int (*kill_callback)(pid_t pid, int sig); 779static int kill_pids(pid_t pid, int sig)
694
695static int kill_pids(pid_t pid, int sig, kill_callback killer)
696{ 780{
697 DWORD pids[16384]; 781 DWORD pids[16384];
698 int max_len = sizeof(pids) / sizeof(*pids), i, len, ret = 0; 782 int max_len = sizeof(pids) / sizeof(*pids), i, len, ret = 0;
@@ -750,7 +834,7 @@ static int kill_pids(pid_t pid, int sig, kill_callback killer)
750 834
751 for (i = len - 1; i >= 0; i--) { 835 for (i = len - 1; i >= 0; i--) {
752 SetLastError(0); 836 SetLastError(0);
753 if (killer(pids[i], sig)) { 837 if (kill_signal(pids[i], sig)) {
754 errno = err_win_to_posix(); 838 errno = err_win_to_posix();
755 ret = -1; 839 ret = -1;
756 } 840 }
@@ -759,104 +843,6 @@ static int kill_pids(pid_t pid, int sig, kill_callback killer)
759 return ret; 843 return ret;
760} 844}
761 845
762/**
763 * Determine whether a process runs in the same architecture as the current
764 * one. That test is required before we assume that GetProcAddress() returns
765 * a valid address *for the target process*.
766 */
767static inline int process_architecture_matches_current(HANDLE process)
768{
769 static BOOL current_is_wow = -1;
770 BOOL is_wow;
771
772 if (current_is_wow == -1 &&
773 !IsWow64Process (GetCurrentProcess(), &current_is_wow))
774 current_is_wow = -2;
775 if (current_is_wow == -2)
776 return 0; /* could not determine current process' WoW-ness */
777 if (!IsWow64Process (process, &is_wow))
778 return 0; /* cannot determine */
779 return is_wow == current_is_wow;
780}
781
782/**
783 * This function tries to terminate a Win32 process, as gently as possible,
784 * by injecting a thread that calls ExitProcess().
785 *
786 * Note: as kernel32.dll is loaded before any process, the other process and
787 * this process will have ExitProcess() at the same address.
788 *
789 * The idea comes from the Dr Dobb's article "A Safer Alternative to
790 * TerminateProcess()" by Andrew Tucker (July 1, 1999),
791 * http://www.drdobbs.com/a-safer-alternative-to-terminateprocess/184416547
792 *
793 */
794int kill_signal_by_handle(HANDLE process, int sig)
795{
796 DWORD code;
797 int ret = 0;
798
799 if (GetExitCodeProcess(process, &code) && code == STILL_ACTIVE) {
800 DECLARE_PROC_ADDR(DWORD, ExitProcess, LPVOID);
801 PVOID arg = (PVOID)(intptr_t)(sig << 24);
802 DWORD thread_id;
803 HANDLE thread;
804
805 if (!INIT_PROC_ADDR(kernel32, ExitProcess) ||
806 !process_architecture_matches_current(process)) {
807 SetLastError(ERROR_ACCESS_DENIED);
808 ret = -1;
809 goto finish;
810 }
811
812 if ((thread = CreateRemoteThread(process, NULL, 0,
813 ExitProcess, arg, 0, &thread_id))) {
814 CloseHandle(thread);
815 }
816 }
817
818 finish:
819 CloseHandle(process);
820 return ret;
821}
822
823static int kill_signal(pid_t pid, int sig)
824{
825 HANDLE process;
826
827 if (!(process = OpenProcess(SYNCHRONIZE | PROCESS_CREATE_THREAD |
828 PROCESS_QUERY_INFORMATION |
829 PROCESS_VM_OPERATION | PROCESS_VM_WRITE |
830 PROCESS_VM_READ, FALSE, pid))) {
831 return -1;
832 }
833
834 return kill_signal_by_handle(process, sig);
835}
836
837/*
838 * This way of terminating processes is not gentle: they get no chance to
839 * clean up after themselves (closing file handles, removing .lock files,
840 * terminating spawned processes (if any), etc).
841 *
842 * If the signal isn't SIGKILL just check if the target process exists.
843 */
844static int kill_SIGKILL(pid_t pid, int sig)
845{
846 HANDLE process;
847 int ret = 0;
848
849 if (!(process=OpenProcess(PROCESS_TERMINATE, FALSE, pid))) {
850 return -1;
851 }
852
853 if (sig == SIGKILL)
854 ret = !TerminateProcess(process, SIGKILL << 24);
855 CloseHandle(process);
856
857 return ret;
858}
859
860int FAST_FUNC is_valid_signal(int number) 846int FAST_FUNC is_valid_signal(int number)
861{ 847{
862 return isalpha(*get_signame(number)); 848 return isalpha(*get_signame(number));
@@ -897,10 +883,8 @@ int exit_code_to_posix(DWORD exit_code)
897 883
898int kill(pid_t pid, int sig) 884int kill(pid_t pid, int sig)
899{ 885{
900 if (sig == SIGKILL || sig == 0) 886 if (is_valid_signal(sig))
901 return kill_pids(pid, sig, kill_SIGKILL); 887 return kill_pids(pid, sig);
902 else if (is_valid_signal(sig))
903 return kill_pids(pid, sig, kill_signal);
904 888
905 errno = EINVAL; 889 errno = EINVAL;
906 return -1; 890 return -1;