diff --git a/src/Plugins/RunawayProcessKiller/NativeMethods.cs b/src/Plugins/RunawayProcessKiller/NativeMethods.cs
new file mode 100644
index 0000000..3b92382
--- /dev/null
+++ b/src/Plugins/RunawayProcessKiller/NativeMethods.cs
@@ -0,0 +1,180 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace winsw.Plugins.RunawayProcessKiller
+{
+ public partial class RunawayProcessKillerExtension
+ {
+ internal static class NativeMethods
+ {
+ private const string Kernel32 = "kernel32.dll";
+ private const string NTDll = "ntdll.dll";
+
+ [DllImport(Kernel32)]
+ internal static extern int IsWow64Process(IntPtr hProcess, out int Wow64Process);
+
+ [DllImport(NTDll)]
+ internal static extern int NtQueryInformationProcess(
+ IntPtr ProcessHandle,
+ PROCESSINFOCLASS ProcessInformationClass,
+ out PROCESS_BASIC_INFORMATION32 ProcessInformation,
+ int ProcessInformationLength,
+ IntPtr ReturnLength = default);
+
+ [DllImport(NTDll)]
+ internal static extern int NtQueryInformationProcess(
+ IntPtr ProcessHandle,
+ PROCESSINFOCLASS ProcessInformationClass,
+ out PROCESS_BASIC_INFORMATION64 ProcessInformation,
+ int ProcessInformationLength,
+ IntPtr ReturnLength = default);
+
+ [DllImport(NTDll)]
+ internal static extern unsafe int NtReadVirtualMemory(
+ IntPtr ProcessHandle,
+ IntPtr BaseAddress,
+ void* Buffer,
+ IntPtr BufferSize,
+ IntPtr NumberOfBytesRead = default);
+
+ [DllImport(NTDll)]
+ internal static extern int NtWow64QueryInformationProcess64(
+ IntPtr ProcessHandle,
+ PROCESSINFOCLASS ProcessInformationClass,
+ out PROCESS_BASIC_INFORMATION64 ProcessInformation,
+ int ProcessInformationLength,
+ IntPtr ReturnLength = default);
+
+ [DllImport(NTDll)]
+ internal static extern unsafe int NtWow64ReadVirtualMemory64(
+ IntPtr ProcessHandle,
+ long BaseAddress,
+ void* Buffer,
+ long BufferSize,
+ long NumberOfBytesRead = default);
+
+ internal enum PROCESSINFOCLASS
+ {
+ ProcessBasicInformation = 0,
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal readonly struct MEMORY_BASIC_INFORMATION
+ {
+ public readonly IntPtr BaseAddress;
+ private readonly IntPtr AllocationBase;
+ private readonly uint AllocationProtect;
+ public readonly IntPtr RegionSize;
+ private readonly uint State;
+ private readonly uint Protect;
+ private readonly uint Type;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct PROCESS_BASIC_INFORMATION32
+ {
+ private readonly int Reserved1;
+ public readonly int PebBaseAddress;
+ private fixed int Reserved2[2];
+ private readonly uint UniqueProcessId;
+ private readonly int Reserved3;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct PROCESS_BASIC_INFORMATION64
+ {
+ private readonly long Reserved1;
+ public readonly long PebBaseAddress;
+ private fixed long Reserved2[2];
+ private readonly ulong UniqueProcessId;
+ private readonly long Reserved3;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct PEB32
+ {
+ private fixed byte Reserved1[2];
+ private readonly byte BeingDebugged;
+ private fixed byte Reserved2[1];
+ private fixed int Reserved3[2];
+ private readonly int Ldr;
+ public readonly int ProcessParameters;
+ private fixed int Reserved4[3];
+ private readonly int AtlThunkSListPtr;
+ private readonly int Reserved5;
+ private readonly uint Reserved6;
+ private readonly int Reserved7;
+ private readonly uint Reserved8;
+ private readonly uint AtlThunkSListPtr32;
+ private fixed int Reserved9[45];
+ private fixed byte Reserved10[96];
+ private readonly int PostProcessInitRoutine;
+ private fixed byte Reserved11[128];
+ private fixed int Reserved12[1];
+ private readonly uint SessionId;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct PEB64
+ {
+ private fixed byte Reserved1[2];
+ private readonly byte BeingDebugged;
+ private fixed byte Reserved2[1];
+ private fixed long Reserved3[2];
+ private readonly long Ldr;
+ public readonly long ProcessParameters;
+ private fixed long Reserved4[3];
+ private readonly long AtlThunkSListPtr;
+ private readonly long Reserved5;
+ private readonly uint Reserved6;
+ private readonly long Reserved7;
+ private readonly uint Reserved8;
+ private readonly uint AtlThunkSListPtr32;
+ private fixed long Reserved9[45];
+ private fixed byte Reserved10[96];
+ private readonly long PostProcessInitRoutine;
+ private fixed byte Reserved11[128];
+ private fixed long Reserved12[1];
+ private readonly uint SessionId;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct RTL_USER_PROCESS_PARAMETERS32
+ {
+ private fixed byte Reserved1[16];
+ private fixed int Reserved2[10];
+ private readonly UNICODE_STRING32 ImagePathName;
+ private readonly UNICODE_STRING32 CommandLine;
+
+ internal readonly int Environment;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct RTL_USER_PROCESS_PARAMETERS64
+ {
+ private fixed byte Reserved1[16];
+ private fixed long Reserved2[10];
+ private readonly UNICODE_STRING64 ImagePathName;
+ private readonly UNICODE_STRING64 CommandLine;
+
+ internal readonly long Environment;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal readonly struct UNICODE_STRING32
+ {
+ private readonly ushort Length;
+ private readonly ushort MaximumLength;
+ private readonly int Buffer;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal readonly struct UNICODE_STRING64
+ {
+ private readonly ushort Length;
+ private readonly ushort MaximumLength;
+ private readonly long Buffer;
+ }
+ }
+ }
+}
diff --git a/src/Plugins/RunawayProcessKiller/RunawayProcessKiller.csproj b/src/Plugins/RunawayProcessKiller/RunawayProcessKiller.csproj
index 7618eac..0c03e9c 100644
--- a/src/Plugins/RunawayProcessKiller/RunawayProcessKiller.csproj
+++ b/src/Plugins/RunawayProcessKiller/RunawayProcessKiller.csproj
@@ -4,6 +4,7 @@
net20;net40;net461;netcoreapp3.1
latest
enable
+ true
winsw.Plugins.RunawayProcessKiller
true
diff --git a/src/Plugins/RunawayProcessKiller/RunawayProcessKillerExtension.cs b/src/Plugins/RunawayProcessKiller/RunawayProcessKillerExtension.cs
index 9906c62..a842bd4 100644
--- a/src/Plugins/RunawayProcessKiller/RunawayProcessKillerExtension.cs
+++ b/src/Plugins/RunawayProcessKiller/RunawayProcessKillerExtension.cs
@@ -7,10 +7,11 @@ using System.Xml;
using log4net;
using winsw.Extensions;
using winsw.Util;
+using static winsw.Plugins.RunawayProcessKiller.RunawayProcessKillerExtension.NativeMethods;
namespace winsw.Plugins.RunawayProcessKiller
{
- public class RunawayProcessKillerExtension : AbstractWinSWExtension
+ public partial class RunawayProcessKillerExtension : AbstractWinSWExtension
{
///
/// Absolute path to the PID file, which stores ID of the previously launched process.
@@ -57,6 +58,127 @@ namespace winsw.Plugins.RunawayProcessKiller
this.CheckWinSWEnvironmentVariable = checkWinSWEnvironmentVariable;
}
+ private static unsafe string? ReadEnvironmentVariable(IntPtr processHandle, string variable)
+ {
+ if (Environment.Is64BitOperatingSystem)
+ {
+ if (Environment.Is64BitProcess)
+ {
+ return SearchEnvironmentVariable(
+ processHandle,
+ variable,
+ GetEnvironmentAddress64(processHandle).ToInt64(),
+ (handle, address, buffer, size) => NtReadVirtualMemory(handle, new IntPtr(address), buffer, new IntPtr(size)));
+ }
+
+ if (IsWow64Process(processHandle, out int isWow64) == 0 || isWow64 == 0)
+ {
+ return SearchEnvironmentVariable(
+ processHandle,
+ variable,
+ GetEnvironmentAddressWow64(processHandle),
+ (handle, address, buffer, size) => NtWow64ReadVirtualMemory64(handle, address, buffer, size));
+ }
+ }
+
+ return SearchEnvironmentVariable(
+ processHandle,
+ variable,
+ GetEnvironmentAddress32(processHandle).ToInt64(),
+ (handle, address, buffer, size) => NtReadVirtualMemory(handle, new IntPtr(address), buffer, new IntPtr(size)));
+ }
+
+ private unsafe delegate int ReadMemoryCallback(IntPtr processHandle, long baseAddress, void* buffer, int bufferSize);
+
+ private static unsafe string? SearchEnvironmentVariable(IntPtr processHandle, string variable, long address, ReadMemoryCallback reader)
+ {
+ const int BaseBufferSize = 0x1000;
+ string variableKey = '\0' + variable + '=';
+ string buffer = new string('\0', BaseBufferSize + variableKey.Length);
+ fixed (char* bufferPtr = buffer)
+ {
+ long startAddress = address;
+ for (; ; )
+ {
+ int status = reader(processHandle, address, bufferPtr, buffer.Length * sizeof(char));
+ int index = buffer.IndexOf("\0\0");
+ if (index >= 0)
+ {
+ break;
+ }
+
+ address += BaseBufferSize * sizeof(char);
+ }
+
+ for (; ; )
+ {
+ int variableIndex = buffer.IndexOf(variableKey);
+ if (variableIndex >= 0)
+ {
+ int valueStartIndex = variableIndex + variableKey.Length;
+ int valueEndIndex = buffer.IndexOf('\0', valueStartIndex);
+ string value = buffer.Substring(valueStartIndex, valueEndIndex - valueStartIndex);
+ return value;
+ }
+
+ address -= BaseBufferSize * sizeof(char);
+ if (address < startAddress)
+ {
+ break;
+ }
+
+ int status = reader(processHandle, address, bufferPtr, buffer.Length * sizeof(char));
+ }
+ }
+
+ return null;
+ }
+
+ private static unsafe IntPtr GetEnvironmentAddress32(IntPtr processHandle)
+ {
+ _ = NtQueryInformationProcess(
+ processHandle,
+ PROCESSINFOCLASS.ProcessBasicInformation,
+ out PROCESS_BASIC_INFORMATION32 information,
+ sizeof(PROCESS_BASIC_INFORMATION32));
+
+ PEB32 peb;
+ _ = NtReadVirtualMemory(processHandle, new IntPtr(information.PebBaseAddress), &peb, new IntPtr(sizeof(PEB32)));
+ RTL_USER_PROCESS_PARAMETERS32 parameters;
+ _ = NtReadVirtualMemory(processHandle, new IntPtr(peb.ProcessParameters), ¶meters, new IntPtr(sizeof(RTL_USER_PROCESS_PARAMETERS32)));
+ return new IntPtr(parameters.Environment);
+ }
+
+ private static unsafe IntPtr GetEnvironmentAddress64(IntPtr processHandle)
+ {
+ _ = NtQueryInformationProcess(
+ processHandle,
+ PROCESSINFOCLASS.ProcessBasicInformation,
+ out PROCESS_BASIC_INFORMATION64 information,
+ sizeof(PROCESS_BASIC_INFORMATION64));
+
+ PEB64 peb;
+ _ = NtReadVirtualMemory(processHandle, new IntPtr(information.PebBaseAddress), &peb, new IntPtr(sizeof(PEB64)));
+ RTL_USER_PROCESS_PARAMETERS64 parameters;
+ _ = NtReadVirtualMemory(processHandle, new IntPtr(peb.ProcessParameters), ¶meters, new IntPtr(sizeof(RTL_USER_PROCESS_PARAMETERS64)));
+ return new IntPtr(parameters.Environment);
+ }
+
+ private static unsafe long GetEnvironmentAddressWow64(IntPtr processHandle)
+ {
+ _ = NtWow64QueryInformationProcess64(
+ processHandle,
+ PROCESSINFOCLASS.ProcessBasicInformation,
+ out PROCESS_BASIC_INFORMATION64 information,
+ sizeof(PROCESS_BASIC_INFORMATION64));
+
+ PEB64 peb;
+ _ = NtWow64ReadVirtualMemory64(processHandle, information.PebBaseAddress, &peb, sizeof(PEB64));
+ RTL_USER_PROCESS_PARAMETERS64 parameters;
+ _ = NtWow64ReadVirtualMemory64(processHandle, peb.ProcessParameters, ¶meters, sizeof(RTL_USER_PROCESS_PARAMETERS64));
+ return parameters.Environment;
+ }
+
public override void Configure(ServiceDescriptor descriptor, XmlNode node)
{
// We expect the upper logic to process any errors
@@ -121,35 +243,15 @@ namespace winsw.Plugins.RunawayProcessKiller
}
// Ensure the process references the service
- string? affiliatedServiceId;
- // TODO: This method is not ideal since it works only for vars explicitly mentioned in the start info
- // No Windows 10- compatible solution for EnvVars retrieval, see https://blog.gapotchenko.com/eazfuscator.net/reading-environment-variables
- StringDictionary previousProcessEnvVars = proc.StartInfo.EnvironmentVariables;
string expectedEnvVarName = WinSWSystem.ENVVAR_NAME_SERVICE_ID;
- if (previousProcessEnvVars.ContainsKey(expectedEnvVarName))
- {
- // StringDictionary is case-insensitive, hence it will fetch variable definitions in any case
- affiliatedServiceId = previousProcessEnvVars[expectedEnvVarName];
- }
- else if (CheckWinSWEnvironmentVariable)
+ string? affiliatedServiceId = ReadEnvironmentVariable(proc.Handle, expectedEnvVarName);
+ if (affiliatedServiceId is null && 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.");
- if (Logger.IsDebugEnabled)
- {
- // TODO replace by String.Join() in .NET 4
- string[] keys = new string[previousProcessEnvVars.Count];
- previousProcessEnvVars.Keys.CopyTo(keys, 0);
- Logger.DebugFormat("Env vars of the process with PID={0}: {1}", new object[] { pid, string.Join(",", keys) });
- }
return;
}
- else
- {
- // We just skip this check
- affiliatedServiceId = null;
- }
// Check the service ID value
if (CheckWinSWEnvironmentVariable && !ServiceId.Equals(affiliatedServiceId))
diff --git a/src/Test/winswTests/Extensions/RunawayProcessKillerTest.cs b/src/Test/winswTests/Extensions/RunawayProcessKillerTest.cs
index 7b67b32..3bfa09d 100644
--- a/src/Test/winswTests/Extensions/RunawayProcessKillerTest.cs
+++ b/src/Test/winswTests/Extensions/RunawayProcessKillerTest.cs
@@ -65,7 +65,6 @@ namespace winswTests.Extensions
}
[Test]
- [Ignore(nameof(RunawayProcessKillerExtension) + "isn't working.")]
public void ShouldKillTheSpawnedProcess()
{
var winswId = "myAppWithRunaway";