Skip to content

Commit 923ab38

Browse files
Added deep-freeze experimental support
1 parent 5dc5ae0 commit 923ab38

14 files changed

+293
-270
lines changed

README.md

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ This application allows you to set constraints on Windows processes. It uses [a
2929
- [Set the priority class](#set-the-priority-class)
3030
- [Set additional environment variables for a process](#set-additional-environment-variables-for-a-process)
3131
- [Enable process privileges](#enable-process-privileges)
32+
- [Deep-freeze processes \(experimental\)](#deep-freeze-processes-experimental)
3233
- [Contributions](#contributions)
3334
- [Links](#links)
3435

3536
<!-- /MarkdownTOC -->
3637

37-
## Installation
38+
Installation
39+
------------
3840

3941
You may download the latest version binaries from the [release page](https://github.com/lowleveldesign/process-governor/releases) or install it with [Chocolatey](https://chocolatey.org/) or [winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/):
4042

@@ -44,7 +46,8 @@ choco install procgov
4446
winget install procgov
4547
```
4648

47-
## Understanding procgov run modes
49+
Understanding procgov run modes
50+
-------------------------------
4851

4952
### The command-line application mode (default)
5053

@@ -64,7 +67,8 @@ The ProcessGovernor service monitors starting processes and applies limits prede
6467

6568
To uninstall the service, use the **--uninstall** switch. The service will be removed when you remove the last saved configuration. If you want to remove all saved procgov data, along with the service, use the **--uninstall-all** switch.
6669

67-
## Applying limits on processes
70+
Applying limits on processes
71+
----------------------------
6872

6973
### Setting limits on a single process
7074

@@ -102,7 +106,8 @@ Then we run procgov again with the new CPU limit - procgov will update the exist
102106
procgov.exe --nowait -c 4 -p 1234
103107
```
104108

105-
## Available process constraints
109+
Available process constraints
110+
-----------------------------
106111

107112
### Limit memory of a process
108113

@@ -201,7 +206,8 @@ With the **--timeout** option you may define the maximum time (clock time) the p
201206
202207
The **--process-utime** and **--job-utime** options allow you to set a limit on the maximum user-mode execution time for a process (with the **--recursive** option also all its children) or a job. The latter case will make sense with the **--recursive** option as it will set a limit on the total user-mode execution time for the process and its children.
203208
204-
## Other options
209+
Other options
210+
-------------
205211
206212
### Set the priority class
207213
@@ -234,15 +240,21 @@ procgov.exe --enable-privilege=SeDebugPrivilege --enable-privilege=SeShutdownPri
234240
235241
Keep in mind that in Windows, you can't add new privileges to the process token. You may only enable existing ones. You may check the available process privileges in Process Hacker or Process Explorer. Check the documentation for a given privilege to learn how to make it available for a given user (for example, you may need to update group policies).
236242

237-
## Contributions
243+
### Deep-freeze processes (experimental)
244+
245+
You may use the --freeze and --thaw options to control execution of processes managed by procgov jobs. When launching a new process with the --freeze option, it will remain in a suspended state that occurs even earlier than when using the [CREATE_SUSPENDED flag](https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags). This means that even if there are missing imports, the process won't fail - instead, the main image will be loaded into memory, allowing for any necessary fixes to be made.
246+
247+
Contributions
248+
-------------
238249
239250
Below you may find a list of people who contributed to this project. Thank you!
240251
241252
- @rowandh - an issue with the WS limit not being set
242253
- @beevvy - an issue report and a fix for a bug with the environment variables
243254
- @weidingerhp - an idea of environment variables for a process and CLR profiler setup
244255
245-
## Links
256+
Links
257+
-----
246258
247259
- **2013.11.21** - [Set process memory limit with Process Governor](http://lowleveldesign.wordpress.com/2013/11/21/set-process-memory-limit-with-process-governor)
248260
- **2016.10.21** - [Releasing wtrace 1.0 and procgov 2.0](https://lowleveldesign.wordpress.com/2016/10/21/releasing-wtrace-1-0-and-procgov-2-0/)

procgov-tests/Code/ProgramTests_CmdApp.cs

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
using System.Diagnostics;
55
using System.IO;
66
using System.IO.Pipes;
7+
using System.Linq;
78
using System.Threading;
89
using System.Threading.Tasks;
910
using Windows.Win32;
11+
using Windows.Win32.Foundation;
1012

1113
namespace ProcessGovernor.Tests.Code;
1214

@@ -24,7 +26,8 @@ public static async Task CmdAppProcessStartFailure()
2426
var exception = Assert.CatchAsync<Win32Exception>(async () =>
2527
{
2628
await Program.Execute(new RunAsCmdApp(new JobSettings(), new LaunchProcess(
27-
["____wrong-executable.exe"], false), [], [], LaunchConfig.Quiet, ExitBehavior.WaitForJobCompletion), CancellationToken.None);
29+
["____wrong-executable.exe"], false), [], [], LaunchConfig.Quiet, StartBehavior.None, ExitBehavior.WaitForJobCompletion),
30+
CancellationToken.None);
2831
});
2932
Assert.That(exception?.NativeErrorCode, Is.EqualTo(2));
3033
}
@@ -37,7 +40,7 @@ public static async Task CmdAppLaunchProcessExitCodeForwarding()
3740
using var pipe = await StartMonitor(cts.Token);
3841

3942
var exitCode = await Program.Execute(new RunAsCmdApp(new JobSettings(), new LaunchProcess(
40-
["cmd.exe", "/c", "exit 5"], false), [], [], LaunchConfig.Quiet, ExitBehavior.WaitForJobCompletion), cts.Token);
43+
["cmd.exe", "/c", "exit 5"], false), [], [], LaunchConfig.Quiet, StartBehavior.None, ExitBehavior.WaitForJobCompletion), cts.Token);
4144
Assert.That(exitCode, Is.EqualTo(5));
4245
}
4346

@@ -50,7 +53,8 @@ public static async Task CmdAppAttachProcessExitCodeForwarding()
5053
var (cmd2, cmd2MainThreadHandle) = ProcessModule.CreateSuspendedProcess(["cmd.exe", "/c", "exit 6"], false, []);
5154

5255
var runTask = Task.Run(() => Program.Execute(new RunAsCmdApp(new JobSettings(), new AttachToProcess(
53-
[cmd1.Id, cmd2.Id]), [], [], LaunchConfig.Quiet | LaunchConfig.NoMonitor, ExitBehavior.WaitForJobCompletion), cts.Token));
56+
[cmd1.Id, cmd2.Id]), [], [], LaunchConfig.Quiet | LaunchConfig.NoMonitor, StartBehavior.None, ExitBehavior.WaitForJobCompletion),
57+
cts.Token));
5458

5559
// give time to start for the job
5660
await Task.Delay(1000);
@@ -99,7 +103,7 @@ public static async Task CmdAppAttachProcessAndUpdateJob()
99103
JobSettings jobSettings = new(maxProcessMemory: 1024 * 1024 * 1024);
100104

101105
await Program.Execute(new RunAsCmdApp(jobSettings, new AttachToProcess([cmd.Id]),
102-
[], [], LaunchConfig.Quiet, ExitBehavior.DontWaitForJobCompletion), cts.Token);
106+
[], [], LaunchConfig.Quiet, StartBehavior.None, ExitBehavior.DontWaitForJobCompletion), cts.Token);
103107

104108
// let's give the IOCP some time to arrive
105109
await Task.Delay(500);
@@ -108,7 +112,7 @@ public static async Task CmdAppAttachProcessAndUpdateJob()
108112

109113
jobSettings = new(maxProcessMemory: jobSettings.MaxProcessMemory, cpuMaxRate: 50);
110114
await Program.Execute(new RunAsCmdApp(jobSettings, new AttachToProcess([cmd.Id]),
111-
[], [], LaunchConfig.Quiet, ExitBehavior.DontWaitForJobCompletion), cts.Token);
115+
[], [], LaunchConfig.Quiet, StartBehavior.None, ExitBehavior.DontWaitForJobCompletion), cts.Token);
112116

113117
Assert.That(await SharedApi.TryGetJobSettingsFromMonitor(cmd.Id, cts.Token), Is.EqualTo(jobSettings));
114118

@@ -176,4 +180,29 @@ public static async Task CmdAppUpdateProcessEnvironmentVariablesWow64()
176180

177181
UpdateProcessEnvironmentVariables(proc);
178182
}
183+
184+
[Test]
185+
public static async Task CmdAppLaunchProcessFreezeAndThaw()
186+
{
187+
using var cts = new CancellationTokenSource(10000);
188+
189+
using var pipe = await StartMonitor(cts.Token);
190+
191+
var startTime = DateTime.Now;
192+
var exitCode = await Program.Execute(new RunAsCmdApp(new JobSettings(), new LaunchProcess(
193+
["cmd.exe", "/c", "exit 5"], false), [], [], LaunchConfig.Quiet,
194+
StartBehavior.Freeze, ExitBehavior.DontWaitForJobCompletion), cts.Token);
195+
Assert.That(exitCode, Is.EqualTo(NTSTATUS.STILL_ACTIVE.Value));
196+
197+
var cmd = Process.GetProcessesByName("cmd").FirstOrDefault(p => p.StartTime > startTime);
198+
while (!cts.IsCancellationRequested && cmd == null)
199+
{
200+
cmd = Process.GetProcessesByName("cmd").FirstOrDefault(p => p.StartTime > startTime);
201+
}
202+
Debug.Assert(cmd is not null);
203+
204+
exitCode = await Program.Execute(new RunAsCmdApp(new JobSettings(), new AttachToProcess([(uint)cmd.Id]),
205+
[], [], LaunchConfig.Quiet, StartBehavior.Thaw, ExitBehavior.WaitForJobCompletion), cts.Token);
206+
Assert.That(exitCode, Is.EqualTo(0));
207+
}
179208
}

procgov-tests/Code/ProgramTests_Monitor.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
using Windows.Win32.Foundation;
1313
using Windows.Win32.System.Threading;
1414

15+
using static ProcessGovernor.Win32.Helpers;
16+
1517
namespace ProcessGovernor.Tests.Code;
1618

1719
public static partial class ProgramTests
@@ -325,7 +327,7 @@ static async Task RunAndMonitorProcessUnderJob(Win32Job job, JobSettings jobSett
325327
// job settings check
326328
Assert.That(await SharedApi.TryGetJobSettingsFromMonitor(pid, ct), Is.EqualTo(jobSettings));
327329

328-
NtApi.CheckWin32Result(PInvoke.ResumeThread(threadHandle));
330+
CheckWin32Result(PInvoke.ResumeThread(threadHandle));
329331

330332
// notification listener should finish as the pipe gets diconnected
331333
await notificationListenerTask;
@@ -363,7 +365,7 @@ async Task NotificationListener()
363365

364366
unsafe
365367
{
366-
NtApi.CheckWin32Result(PInvoke.CreateProcess(null, (char*)processArgsPtr, null, null,
368+
CheckWin32Result(PInvoke.CreateProcess(null, (char*)processArgsPtr, null, null,
367369
false, processCreationFlags, null, null, &si, &pi));
368370
}
369371

procgov-tests/Code/ProgramTests_ParseArgs.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,7 @@ public static void ParseArgsPriorityClass()
715715
}
716716

717717
[Test]
718-
public static void ParseEmptyArgs()
718+
public static void ParseArgsEmptyArgs()
719719
{
720720
Assert.That(Program.ParseArgs(RealSystemInfo, []) is ShowSystemInfoAndExit);
721721
}
@@ -737,18 +737,27 @@ public static void ParseArgsUnknownArgument()
737737
}
738738

739739
[Test]
740-
public static void ParseArgsExitBehaviorArguments()
740+
public static void ParseArgsBehavioralArguments()
741741
{
742742
Assert.That(Program.ParseArgs(RealSystemInfo, ["-m=10M", "test.exe"]) is RunAsCmdApp
743743
{
744+
StartBehavior: StartBehavior.None,
744745
ExitBehavior: ExitBehavior.WaitForJobCompletion,
745746
LaunchConfig: LaunchConfig.Default
746747
});
747748

748-
Assert.That(Program.ParseArgs(RealSystemInfo, ["-m=10M", "-q", "--nowait", "test.exe"]) is RunAsCmdApp
749+
Assert.That(Program.ParseArgs(RealSystemInfo, ["-m=10M", "--freeze", "test.exe"]) is RunAsCmdApp
749750
{
751+
StartBehavior: StartBehavior.Freeze,
752+
ExitBehavior: ExitBehavior.WaitForJobCompletion,
753+
LaunchConfig: LaunchConfig.Default
754+
});
755+
756+
Assert.That(Program.ParseArgs(RealSystemInfo, ["-m=10M", "-q", "--nowait", "--thaw", "test.exe"]) is RunAsCmdApp
757+
{
758+
StartBehavior: StartBehavior.Thaw,
750759
ExitBehavior: ExitBehavior.DontWaitForJobCompletion,
751-
LaunchConfig: LaunchConfig.Quiet
760+
LaunchConfig: LaunchConfig.Quiet,
752761
});
753762

754763

@@ -758,6 +767,11 @@ public static void ParseArgsExitBehaviorArguments()
758767
LaunchConfig: LaunchConfig.NoGui | LaunchConfig.NoMonitor
759768
});
760769

770+
Assert.That(Program.ParseArgs(RealSystemInfo, ["-m=10M", "--freeze", "--thaw", "test.exe"]) is ShowHelpAndExit
771+
{
772+
ErrorMessage: "--thaw and --freeze cannot be set at the same time"
773+
});
774+
761775
Assert.That(Program.ParseArgs(RealSystemInfo, ["-m=10M", "--terminate-job-on-exit", "--nowait", "test.exe"]) is ShowHelpAndExit
762776
{
763777
ErrorMessage: "--terminate-job-on-exit and --nowait cannot be used together"

procgov/AccountPrivilegeModule.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using Windows.Win32;
55
using Windows.Win32.Foundation;
66
using Windows.Win32.Security;
7-
using static ProcessGovernor.NtApi;
7+
using static ProcessGovernor.Win32.Helpers;
88

99
namespace ProcessGovernor;
1010

procgov/ExecutionModes.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
namespace ProcessGovernor;
22

3+
enum StartBehavior { Freeze, Thaw, None };
4+
35
enum ExitBehavior { WaitForJobCompletion, DontWaitForJobCompletion, TerminateJobOnExit };
46

57
[Flags]
@@ -23,6 +25,7 @@ record RunAsCmdApp(
2325
Dictionary<string, string> Environment,
2426
List<string> Privileges,
2527
LaunchConfig LaunchConfig,
28+
StartBehavior StartBehavior,
2629
ExitBehavior ExitBehavior) : IExecutionMode;
2730

2831
record RunAsMonitor(TimeSpan MaxMonitorIdleTime, bool NoGui) : IExecutionMode;

procgov/NativeMethods.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ SC_MANAGER_CREATE_SERVICE
101101
SERVICE_ALL_ACCESS
102102
SERVICE_QUERY_STATUS
103103
SERVICE_START_TYPE
104+
STILL_ACTIVE
104105
WIN32_ERROR
105106
WAIT_EVENT
106107
PROCESS_CREATION_FLAGS

0 commit comments

Comments
 (0)