diff options
author | Ron Yorston <rmy@pobox.com> | 2024-05-14 12:33:03 +0100 |
---|---|---|
committer | Ron Yorston <rmy@pobox.com> | 2024-05-14 12:33:03 +0100 |
commit | 569de936abb90f4c7cdca9da111a6ea780b135bf (patch) | |
tree | f1965765683e09ca780b39340abac90f349e47a4 | |
parent | bb128070e590234f8b63fb1d67f7621a1b4b3ff3 (diff) | |
download | busybox-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.c | 18 | ||||
-rw-r--r-- | include/mingw.h | 1 | ||||
-rw-r--r-- | win32/process.c | 196 |
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; | |||
54 | static void kill_child(void) | 54 | static 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 | ||
571 | BOOL WINAPI kill_child_ctrl_handler(DWORD dwCtrlType); | 571 | BOOL WINAPI kill_child_ctrl_handler(DWORD dwCtrlType); |
572 | int kill_signal_by_handle(HANDLE process, int sig); | ||
573 | int FAST_FUNC is_valid_signal(int number); | 572 | int FAST_FUNC is_valid_signal(int number); |
574 | int exit_code_to_wait_status(DWORD win_exit_code); | 573 | int exit_code_to_wait_status(DWORD win_exit_code); |
575 | int exit_code_to_posix(DWORD win_exit_code); | 574 | int 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 | */ | ||
692 | static 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(), ¤t_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 | */ | ||
719 | static 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 | |||
739 | static 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 | */ |
693 | typedef int (*kill_callback)(pid_t pid, int sig); | 779 | static int kill_pids(pid_t pid, int sig) |
694 | |||
695 | static 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 | */ | ||
767 | static 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(), ¤t_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 | */ | ||
794 | int 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 | |||
823 | static 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 | */ | ||
844 | static 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 | |||
860 | int FAST_FUNC is_valid_signal(int number) | 846 | int 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 | ||
898 | int kill(pid_t pid, int sig) | 884 | int 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; |