diff --git a/src/WinSW.Core/Native/Handle.cs b/src/WinSW.Core/Native/Handle.cs index 1a8ce60..cb14558 100644 --- a/src/WinSW.Core/Native/Handle.cs +++ b/src/WinSW.Core/Native/Handle.cs @@ -5,7 +5,7 @@ using static WinSW.Native.HandleApis; namespace WinSW.Native { [StructLayout(LayoutKind.Sequential)] - internal readonly ref struct Handle + internal readonly struct Handle : IDisposable { private readonly IntPtr handle; diff --git a/src/WinSW.Core/Native/ProcessApis.cs b/src/WinSW.Core/Native/ProcessApis.cs index e54fba5..00ea9e9 100644 --- a/src/WinSW.Core/Native/ProcessApis.cs +++ b/src/WinSW.Core/Native/ProcessApis.cs @@ -34,12 +34,20 @@ namespace WinSW.Native int processInformationLength, IntPtr returnLength = default); + [DllImport(Libraries.Kernel32)] + internal static extern Handle OpenProcess(ProcessAccess desiredAccess, bool inheritHandle, int processId); + [DllImport(Libraries.Advapi32, SetLastError = true)] internal static extern bool OpenProcessToken( IntPtr processHandle, TokenAccessLevels desiredAccess, out Handle tokenHandle); + internal enum ProcessAccess : uint + { + QueryInformation = 0x0400, + } + internal enum PROCESSINFOCLASS { ProcessBasicInformation = 0, diff --git a/src/WinSW.Core/Util/ProcessHelper.cs b/src/WinSW.Core/Util/ProcessHelper.cs index 9031ea0..dd091e0 100644 --- a/src/WinSW.Core/Util/ProcessHelper.cs +++ b/src/WinSW.Core/Util/ProcessHelper.cs @@ -26,7 +26,11 @@ namespace WinSW.Util { foreach (var child in GetChildren(process)) { - StopProcessTree(child, stopTimeout, stopParentProcessFirst); + using (child.Key) + using (child.Value) + { + StopProcessTree(child.Key, stopTimeout, stopParentProcessFirst); + } } } @@ -36,7 +40,11 @@ namespace WinSW.Util { foreach (var child in GetChildren(process)) { - StopProcessTree(child, stopTimeout, stopParentProcessFirst); + using (child.Key) + using (child.Value) + { + StopProcessTree(child.Key, stopTimeout, stopParentProcessFirst); + } } } } @@ -95,47 +103,53 @@ namespace WinSW.Util Logger.Debug($"Process {process.Id} has already exited."); } - private static unsafe List GetChildren(Process process) + // The handle is to keep a reference to the process. + private static unsafe List> GetChildren(Process process) { var startTime = process.StartTime; int processId = process.Id; - var children = new List(); + var children = new List>(); foreach (var other in Process.GetProcesses()) { + var handle = OpenProcess(ProcessAccess.QueryInformation, false, other.Id); + if (handle == IntPtr.Zero) + { + goto Next; + } + try { if (other.StartTime <= startTime) { goto Next; } - - var handle = other.Handle; - - if (NtQueryInformationProcess( - handle, - PROCESSINFOCLASS.ProcessBasicInformation, - out var information, - sizeof(PROCESS_BASIC_INFORMATION)) != 0) - { - goto Next; - } - - if ((int)information.InheritedFromUniqueProcessId == processId) - { - Logger.Debug($"Found child process {other.Id}."); - children.Add(other); - continue; - } - - Next: - other.Dispose(); } catch (Exception e) when (e is InvalidOperationException || e is Win32Exception) { - other.Dispose(); + goto Next; } + + if (NtQueryInformationProcess( + handle, + PROCESSINFOCLASS.ProcessBasicInformation, + out var information, + sizeof(PROCESS_BASIC_INFORMATION)) != 0) + { + goto Next; + } + + if ((int)information.InheritedFromUniqueProcessId == processId) + { + Logger.Debug($"Found child process {other.Id}."); + children.Add(new(other, handle)); + continue; + } + + Next: + other.Dispose(); + handle.Dispose(); } return children; diff --git a/src/WinSW.Plugins/NativeMethods.cs b/src/WinSW.Plugins/NativeMethods.cs index 9a917e9..e3a4d9f 100644 --- a/src/WinSW.Plugins/NativeMethods.cs +++ b/src/WinSW.Plugins/NativeMethods.cs @@ -10,6 +10,9 @@ namespace WinSW.Plugins private const string Kernel32 = "kernel32.dll"; private const string NTDll = "ntdll.dll"; + [DllImport(Kernel32)] + internal static extern bool CloseHandle(IntPtr objectHandle); + [DllImport(Kernel32)] internal static extern int IsWow64Process(IntPtr hProcess, out int Wow64Process); @@ -53,6 +56,14 @@ namespace WinSW.Plugins long BufferSize, long NumberOfBytesRead = default); + [DllImport(Kernel32)] + internal static extern IntPtr OpenProcess(ProcessAccess desiredAccess, bool inheritHandle, int processId); + + internal enum ProcessAccess : uint + { + QueryInformation = 0x0400, + } + internal enum PROCESSINFOCLASS { ProcessBasicInformation = 0, diff --git a/src/WinSW.Plugins/RunawayProcessKillerExtension.cs b/src/WinSW.Plugins/RunawayProcessKillerExtension.cs index 93cc2e0..74c98c8 100644 --- a/src/WinSW.Plugins/RunawayProcessKillerExtension.cs +++ b/src/WinSW.Plugins/RunawayProcessKillerExtension.cs @@ -268,39 +268,54 @@ namespace WinSW.Plugins // Ensure the process references the service string expectedEnvVarName = WinSWSystem.EnvVarNameServiceId; - string? affiliatedServiceId = ReadEnvironmentVariable(proc.Handle, expectedEnvVarName); - if (affiliatedServiceId is null && this.CheckWinSWEnvironmentVariable) - { - Logger.Warn("The process " + pid + " has no " + expectedEnvVarName + " environment variable defined. " - + "The process has not been started by WinSW, hence it won't be terminated."); + var processHandle = OpenProcess(ProcessAccess.QueryInformation, false, pid); + if (processHandle == IntPtr.Zero) + { + Logger.Warn("Cannot get process handle of PID=" + pid + ". Assuming that the process has not been started by WinSW."); return; } - // Check the service ID value - if (this.CheckWinSWEnvironmentVariable && !this.ServiceId.Equals(affiliatedServiceId)) + try { - Logger.Warn("The process " + pid + " has been started by Windows service with ID='" + affiliatedServiceId + "'. " - + "It is another service (current service id is '" + this.ServiceId + "'), hence the process won't be terminated."); - return; - } + string? affiliatedServiceId = ReadEnvironmentVariable(processHandle, expectedEnvVarName); + if (affiliatedServiceId is null && this.CheckWinSWEnvironmentVariable) + { + Logger.Warn("The process " + pid + " has no " + expectedEnvVarName + " environment variable defined. " + + "The process has not been started by WinSW, hence it won't be terminated."); - // Kill the runaway process - var bldr = new StringBuilder("Stopping the runaway process (pid="); - bldr.Append(pid); - bldr.Append(") and its children. Environment was "); - if (!this.CheckWinSWEnvironmentVariable) + return; + } + + // Check the service ID value + if (this.CheckWinSWEnvironmentVariable && !this.ServiceId.Equals(affiliatedServiceId)) + { + Logger.Warn("The process " + pid + " has been started by Windows service with ID='" + affiliatedServiceId + "'. " + + "It is another service (current service id is '" + this.ServiceId + "'), hence the process won't be terminated."); + return; + } + + // Kill the runaway process + var bldr = new StringBuilder("Stopping the runaway process (pid="); + bldr.Append(pid); + bldr.Append(") and its children. Environment was "); + if (!this.CheckWinSWEnvironmentVariable) + { + bldr.Append("not "); + } + + bldr.Append("checked, affiliated service ID: "); + bldr.Append(affiliatedServiceId ?? "undefined"); + bldr.Append(", process to kill: "); + bldr.Append(proc); + + Logger.Warn(bldr.ToString()); + ProcessHelper.StopProcessTree(proc, this.StopTimeout, this.StopParentProcessFirst); + } + finally { - bldr.Append("not "); + _ = CloseHandle(processHandle); } - - bldr.Append("checked, affiliated service ID: "); - bldr.Append(affiliatedServiceId ?? "undefined"); - bldr.Append(", process to kill: "); - bldr.Append(proc); - - Logger.Warn(bldr.ToString()); - ProcessHelper.StopProcessTree(proc, this.StopTimeout, this.StopParentProcessFirst); } ///