matu3ba / sandboxamples

Structured collection of sandbox programs including tests (fs, net access, permissions, process groups [if available]) and system setup programs. No VM stuff.
BSD Zero Clause License
0 stars 0 forks source link

windows: add method to wait for process group termination #9

Closed matu3ba closed 3 months ago

matu3ba commented 3 months ago

See CI, which can be locally reproduced with a few attempts and/or under heavy system load:

test
+- run main_job_api failure
hasProcessPrefix err: error.PartialCopy
error: ProcessHasUnwantedPrefix
D:\a\sandboxamples\sandboxamples\test\win\main_job_api.zig:151:21: 0xcf1b34 in behavior (main_job_api.exe.obj)
    if (has_prefix) return error.ProcessHasUnwantedPrefix;
                    ^
D:\a\sandboxamples\sandboxamples\test\win\main_job_api.zig:37:5: 0xcf327a in main (main_job_api.exe.obj)
    try behavior(gpa);
    ^
error: the following command exited with code 1 (expected exited with code 0):
D:\a\sandboxamples\sandboxamples\zig-out\bin\main_job_api.exe D:\a\sandboxamples\sandboxamples\zig-out\bin\evildescendent_child_job_api.exe 
test
+- run main_acl stderr
sec_info: sec.os.win.SecurityInfo{ .sid_owner = ?*sec.os.win.PSID__opaque_4421@1fc558c2374, .sid_group = null, .dacl = null, .sacl = null, .sec_descr = ?*anyopaque@1fc558c2360 }
Build Summary: 24/26 steps succeeded; 1 failed
test transitive failure
+- run main_min success 10ms MaxRSS:4M
|  +- zig build-exe main_min Debug native success 2s MaxRSS:154M
|  +- zig build-exe child_min Debug native success 1s MaxRSS:134M
+- run main_explicit_handle_inheritance success 93ms MaxRSS:5M
|  +- zig build-exe main_explicit_handle_inheritance Debug native success 41s MaxRSS:164M
|  +- zig build-exe child_explicit_handle_inheritance Debug native success 2s MaxRSS:134M
|  +- install success
|     +- install child_explicit_handle_inheritance success
|     |  +- zig build-exe child_explicit_handle_inheritance Debug native (reused)
|     +- install main_explicit_handle_inheritance success
|     |  +- zig build-exe main_explicit_handle_inheritance Debug native (reused)
|     +- install child_win32k_mitigation success
|     |  +- zig build-exe child_win32k_mitigation Debug native success 2s MaxRSS:136M
|     +- install main_win32k_mitigation success
|     |  +- zig build-exe main_win32k_mitigation Debug native success 41s MaxRSS:153M
|     +- install evildescendent_child_job_api success
|     |  +- zig build-exe evildescendent_child_job_api Debug native success 41s MaxRSS:143M
|     +- install main_job_api success
|     |  +- zig build-exe main_job_api Debug native success 2s MaxRSS:155M
|     +- install main_getsecurityinfo_c success
|     |  +- zig build-exe main_getsecurityinfo_c Debug native success 41s MaxRSS:129M
|     +- install main_acl success
|        +- zig build-exe main_acl Debug native success 2s MaxRSS:151M
+- run main_win32k_mitigation success 62ms MaxRSS:4M
|  +- zig build-exe main_win32k_mitigation Debug native (reused)
|  +- zig build-exe child_win32k_mitigation Debug native (reused)
|  +- install (+8 more reused dependencies)
+- run main_job_api failure
|  +- zig build-exe main_job_api Debug native (reused)
|  +- zig build-exe evildescendent_child_job_api Debug native (reused)
|  +- install (+8 more reused dependencies)
+- run main_getsecurityinfo_c success 43ms MaxRSS:4M
|  +- zig build-exe main_getsecurityinfo_c Debug native (reused)
|  +- install (+8 more reused dependencies)
+- run main_acl success 25ms MaxRSS:5M
   +- zig build-exe main_acl Debug native (reused)
   +- install (+8 more reused dependencies)
error: the following build command failed with exit code 1:
D:\a\sandboxamples\sandboxamples\zig-cache\o\ef63a89db80e75a4dd49f5ce93c545b0\build.exe C:\hostedtoolcache\windows\zig\master\x64\zig.exe D:\a\sandboxamples\sandboxamples D:\a\sandboxamples\sandboxamples\zig-cache C:\Users\runneradmin\AppData\Local\zig --seed 0x615abec9 -Z016111adb887a881 test --summary all
Error: Process completed with exit code 1.

Probably Windows has a dedicated method to wait for process group termination.

matu3ba commented 3 months ago

From https://devblogs.microsoft.com/oldnewthing/20130405-00/?p=4743

#define [_UNICODE](http://blogs.msdn.com/b/oldnewthing/archive/2004/02/12/71851.aspx)
#define STRICT
#include <windows.h>
#include <stdio.h>
#include <atlbase.h>
#include <atlalloc.h>
#include <shlwapi.h>

int __cdecl wmain(int argc, PWSTR argv[])
{
 CHandle Job(CreateJobObject(nullptr, nullptr));
 if (!Job) {
  wprintf(L”CreateJobObject, error %d\n”, GetLastError());
  return 0;
 }

 CHandle IOPort(CreateIoCompletionPort(INVALID_HANDLE_VALUE,
                                       nullptr, 0, 1));
 if (!IOPort) {
  wprintf(L”CreateIoCompletionPort, error %d\n”,
          GetLastError());
  return 0;
 }

 JOBOBJECT_ASSOCIATE_COMPLETION_PORT Port;
 Port.CompletionKey = Job;
 Port.CompletionPort = IOPort;
 if (!SetInformationJobObject(Job,
       JobObjectAssociateCompletionPortInformation,
       &Port, sizeof(Port))) {
  wprintf(L”SetInformation, error %d\n”, GetLastError());
  return 0;
 }

 PROCESS_INFORMATION ProcessInformation;
 STARTUPINFO StartupInfo = { sizeof(StartupInfo) };
 PWSTR [CommandLine](http://blogs.msdn.com/b/oldnewthing/archive/2011/08/15/10195600.aspx) = PathGetArgs(GetCommandLine());

 if (!CreateProcess(nullptr, CommandLine, nullptr, nullptr,
                    FALSE, CREATE_SUSPENDED, nullptr, nullptr,
                    &StartupInfo, &ProcessInformation)) {
  wprintf(L”CreateProcess, error %d\n”, GetLastError());
  return 0;
 }

 if (!AssignProcessToJobObject(Job,
         ProcessInformation.hProcess)) {
  wprintf(L”Assign, error %d\n”, GetLastError());
  return 0;
 }

 ResumeThread(ProcessInformation.hThread);
 CloseHandle(ProcessInformation.hThread);
 CloseHandle(ProcessInformation.hProcess);

 DWORD CompletionCode;
 ULONG_PTR CompletionKey;
 LPOVERLAPPED Overlapped;

 while (GetQueuedCompletionStatus(IOPort, &CompletionCode,
          &CompletionKey, &Overlapped, INFINITE) &&
          !((HANDLE)CompletionKey == Job &&
           CompletionCode == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO)) {
  wprintf(L”Still waiting…\n”);
 }

 wprintf(L”All done\n”);

 return 0;
}
matu3ba commented 3 months ago

In depth explanation of non-scaling alternatives with some practice tips https://www.microsoftpressstore.com/articles/article.aspx?p=2224047&seqNum=5

Thread association by usage of I/O completion port https://devblogs.microsoft.com/oldnewthing/20210120-00/?p=104740

https://devblogs.microsoft.com/oldnewthing/20130405-00/?p=4743

https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-jobobject_associate_completion_port?redirectedfrom=MSDN "Note that, except for limits set with the JobObjectNotificationLimitInformation information class, messages are intended only as notifications and their delivery to the completion port is not guaranteed. The failure of a message to arrive at the completion port does not necessarily mean that the event did not occur. Notifications for limits set with JobObjectNotificationLimitInformation are guaranteed to arrive at the completion port."

Afaiu, there is no guarantee that a notification queue is not full due to extensive I/O action and the notification JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO on the job object is lost. https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getqueuedcompletionstatus

assumption: process events (termination etc) are too unlikely to get lost

matu3ba commented 3 months ago

documented for others and implemented. logic is evident from reading code meaning upper bound needed or executed trusted to not stall execution. closing.