Пример #1
0
        public static (Win32Job, List <AccountPrivilege>) AttachToProcess(uint pid, SessionSettings session)
        {
            var currentProcessHandle = Process.GetCurrentProcess().SafeHandle;
            var dbgpriv = AccountPrivilegeModule.EnablePrivileges(currentProcessHandle, new[] { "SeDebugPrivilege" });

            try
            {
                var hProcess = CheckWin32Result(PInvoke.OpenProcess_SafeHandle(
                                                    PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_INFORMATION |
                                                    PROCESS_ACCESS_RIGHTS.PROCESS_SET_QUOTA |
                                                    PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE |
                                                    PROCESS_ACCESS_RIGHTS.PROCESS_TERMINATE, false, pid));

                if (!Win32JobModule.TryOpen(hProcess, pid, session.PropagateOnChildProcesses,
                                            session.ClockTimeLimitInMilliseconds, out var job))
                {
                    job = Win32JobModule.CreateAndAssignToProcess(hProcess, pid,
                                                                  session.PropagateOnChildProcesses, session.ClockTimeLimitInMilliseconds);
                }
                Debug.Assert(job != null);
                Win32JobModule.SetLimits(job, session);

                var privs = AccountPrivilegeModule.EnablePrivileges(hProcess, session.Privileges);

                return(job, privs);
            }
            finally
            {
                AccountPrivilegeModule.RestorePrivileges(currentProcessHandle, dbgpriv);
            }
        }
Пример #2
0
        public void LoadCustomEnvironmentVariablesTest()
        {
            var envVarsFile = Path.GetTempFileName();

            try {
                using (var writer = new StreamWriter(envVarsFile, false)) {
                    writer.WriteLine("TEST=TESTVAL");
                    writer.WriteLine("  TEST2 = TEST VAL2  ");
                }

                var session = new SessionSettings();
                Program.LoadCustomEnvironmentVariables(session, envVarsFile);
                CollectionAssert.AreEqual(new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase)
                {
                    { "TEST", "TESTVAL" },
                    { "TEST2", "TEST VAL2" }
                }, session.AdditionalEnvironmentVars);


                using (var writer = new StreamWriter(envVarsFile, false)) {
                    writer.WriteLine("  = TEST VAL2  ");
                }

                Assert.Throws <ArgumentException>(() => {
                    Program.LoadCustomEnvironmentVariables(session, envVarsFile);
                });
            } finally {
                if (File.Exists(envVarsFile))
                {
                    File.Delete(envVarsFile);
                }
            }
        }
        static unsafe void SetNumaAffinity(Win32Job job, SessionSettings session, ulong systemAffinityMask)
        {
            if (session.NumaNode != 0xffff)
            {
                CheckWin32Result(PInvoke.GetNumaNodeProcessorMaskEx(session.NumaNode, out var affinity));

                /*
                 * NUMA node processor mask looks a bit strange (at least to me). In my test Hyper-V environment,
                 * I set the number of NUMA nodes to four with two cores per node. The results of running the following
                 * loop are presented below:
                 *
                 * GetNumaHighestNodeNumber(out var highestNode);
                 *
                 * for (ulong i = 0; i <= highestNode; i++) {
                 *  Console.WriteLine($"Node: {i:X}");
                 *  GetNumaNodeProcessorMaskEx((ushort)i, out var affinity);
                 *  $"Mask: {affinity.Mask:X}".Dump();
                 * }
                 *
                 * Results:
                 *
                 * Node: 0
                 * Mask: 3
                 * Node: 1
                 * Mask: 12
                 * Node: 2
                 * Mask: 48
                 * Node: 3
                 * Mask: 192
                 */

                var nodeAffinityMask = Convert.ToUInt64(affinity.Mask);
                if (session.CpuAffinityMask != 0)
                {
                    // When CPU affinity is set, we can't simply use
                    // NUMA affinity, but rather need to apply the CPU affinity
                    // settings to the select NUMA node.
                    var firstNonZeroBitPosition = 0;
                    while ((nodeAffinityMask & (1UL << firstNonZeroBitPosition)) == 0)
                    {
                        firstNonZeroBitPosition++;
                    }
                    session.CpuAffinityMask <<= firstNonZeroBitPosition & 0x3f;
                }
                else
                {
                    session.CpuAffinityMask = nodeAffinityMask;
                }
                // NOTE: this could result in an overflow on 32-bit apps, but I can't
                // think of a nice way of handling it here
                affinity.Mask = new UIntPtr(session.CpuAffinityMask & systemAffinityMask);

                var size = (uint)Marshal.SizeOf(affinity);
                CheckWin32Result(PInvoke.SetInformationJobObject(job.JobHandle, JOBOBJECTINFOCLASS.JobObjectGroupInformationEx,
                                                                 &affinity, size));
            }
        }
 static unsafe void SetMaxBandwith(Win32Job job, SessionSettings session)
 {
     if (session.MaxBandwidth > 0)
     {
         var limitInfo = new JOBOBJECT_NET_RATE_CONTROL_INFORMATION
         {
             ControlFlags = JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_ENABLE |
                            JOB_OBJECT_NET_RATE_CONTROL_FLAGS.JOB_OBJECT_NET_RATE_CONTROL_MAX_BANDWIDTH,
             MaxBandwidth = session.MaxBandwidth,
             DscpTag      = 0
         };
         var size = (uint)Marshal.SizeOf(limitInfo);
         CheckWin32Result(PInvoke.SetInformationJobObject(job.JobHandle, JOBOBJECTINFOCLASS.JobObjectNetRateControlInformation,
                                                          &limitInfo, size));
     }
 }
Пример #5
0
        public void PrepareDebuggerCommandStringTest()
        {
            var session = new SessionSettings()
            {
                CpuAffinityMask  = 0x2,
                MaxProcessMemory = 1024 * 1024,
                MaxJobMemory     = 2048 * 1024,
                ProcessUserTimeLimitInMilliseconds = 500,
                JobUserTimeLimitInMilliseconds     = 1000,
                ClockTimeLimitInMilliseconds       = 2000,
                CpuMaxRate                = 90,
                MaxBandwidth              = 100,
                MinWorkingSetSize         = 1024,
                MaxWorkingSetSize         = 1024 * 1024,
                NumaNode                  = 1,
                Privileges                = new[] { "SeDebugPrivilege", "SeShutdownPrivilege" },
                PropagateOnChildProcesses = true,
                SpawnNewConsoleWindow     = true
            };

            session.AdditionalEnvironmentVars.Add("TEST", "TESTVAL");
            session.AdditionalEnvironmentVars.Add("TEST2", "TESTVAL2");

            var appImageExe = Path.GetFileName(@"C:\temp\test.exe");
            var debugger    = Program.PrepareDebuggerCommandString(session, appImageExe, true);

            var envFilePath = Program.GetAppEnvironmentFilePath(appImageExe);

            Assert.True(File.Exists(envFilePath));

            try {
                var txt = File.ReadAllText(envFilePath);
                Assert.AreEqual("TEST=TESTVAL\r\nTEST2=TESTVAL2\r\n", txt);

                var expectedCmdLine =
                    $"\"{Environment.GetCommandLineArgs()[0]}\" --nogui --debugger --env=\"{envFilePath}\" --cpu=0x2 --maxmem=1048576 " +
                    "--maxjobmem=2097152 --maxws=1048576 --minws=1024 --node=1 --cpurate=90 --bandwidth=100 --recursive " +
                    "--timeout=2000 --process-utime=500 --job-utime=1000 --enable-privileges=SeDebugPrivilege,SeShutdownPrivilege --nowait";

                Assert.AreEqual(expectedCmdLine, debugger);
            } finally {
                File.Delete(envFilePath);
            }
        }
        public static void SetLimits(Win32Job job, SessionSettings session)
        {
            if (session.NumaNode != 0xffff)
            {
                CheckWin32Result(PInvoke.GetNumaHighestNodeNumber(out var highestNodeNumber));
                if (session.NumaNode > highestNodeNumber)
                {
                    throw new ArgumentException($"Incorrect NUMA node number. The highest accepted number is {highestNodeNumber}.");
                }
            }

            CheckWin32Result(PInvoke.GetProcessAffinityMask(job.ProcessHandle, out _, out var aff));
            var systemAffinityMask = (ulong)aff;

            SetBasicLimits(job, session, systemAffinityMask);
            SetMaxCpuRate(job, session, systemAffinityMask);
            SetNumaAffinity(job, session, systemAffinityMask);
            SetMaxBandwith(job, session);
        }
        private static unsafe void SetMaxCpuRate(Win32Job job, SessionSettings session, ulong systemAffinityMask)
        {
            if (session.CpuMaxRate > 0)
            {
                // Set CpuRate to a percentage times 100. For example, to let the job use 20% of the CPU,
                // set CpuRate to 20 times 100, or 2,000.
                uint finalCpuRate = session.CpuMaxRate * 100;

                // CPU rate is set for the whole system so includes all the logical CPUs. When
                // we have the CPU affinity set, we will divide the rate accordingly.
                if (session.CpuAffinityMask != 0)
                {
                    ulong affinity = systemAffinityMask & session.CpuAffinityMask;
                    uint  numberOfSelectedCores = 0;
                    for (int i = 0; i < sizeof(ulong) * 8; i++)
                    {
                        numberOfSelectedCores += (affinity & (1UL << i)) == 0 ? 0u : 1u;
                    }
                    Debug.Assert(numberOfSelectedCores < Environment.ProcessorCount);
                    finalCpuRate /= ((uint)Environment.ProcessorCount / numberOfSelectedCores);
                }

                // configure CPU rate limit
                var limitInfo = new JOBOBJECT_CPU_RATE_CONTROL_INFORMATION
                {
                    ControlFlags = JOB_OBJECT_CPU_RATE_CONTROL.JOB_OBJECT_CPU_RATE_CONTROL_ENABLE |
                                   JOB_OBJECT_CPU_RATE_CONTROL.JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP,

                    Anonymous = new JOBOBJECT_CPU_RATE_CONTROL_INFORMATION._Anonymous_e__Union {
                        CpuRate = finalCpuRate
                    }
                };
                var size = (uint)Marshal.SizeOf(limitInfo);
                CheckWin32Result(PInvoke.SetInformationJobObject(job.JobHandle, JOBOBJECTINFOCLASS.JobObjectCpuRateControlInformation,
                                                                 &limitInfo, size));
            }
        }
        private static unsafe void SetBasicLimits(Win32Job job, SessionSettings session, ulong systemAffinityMask)
        {
            var limitInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
            var size      = (uint)Marshal.SizeOf(limitInfo);
            var length    = 0u;

            CheckWin32Result(PInvoke.QueryInformationJobObject(job.JobHandle,
                                                               JOBOBJECTINFOCLASS.JobObjectExtendedLimitInformation, &limitInfo, size, &length));
            Debug.Assert(length == size);

            JOB_OBJECT_LIMIT flags = 0;

            if (!session.PropagateOnChildProcesses)
            {
                flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
            }

            if (session.MaxProcessMemory > 0)
            {
                flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_PROCESS_MEMORY;
                limitInfo.ProcessMemoryLimit = (UIntPtr)session.MaxProcessMemory;
            }

            if (session.MaxJobMemory > 0)
            {
                flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_JOB_MEMORY;
                limitInfo.JobMemoryLimit = (UIntPtr)session.MaxJobMemory;
            }

            if (session.MaxWorkingSetSize > 0)
            {
                flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_WORKINGSET;
                limitInfo.BasicLimitInformation.MaximumWorkingSetSize = (UIntPtr)session.MaxWorkingSetSize;
                limitInfo.BasicLimitInformation.MinimumWorkingSetSize = (UIntPtr)session.MinWorkingSetSize;
            }

            if (session.ProcessUserTimeLimitInMilliseconds > 0)
            {
                flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_PROCESS_TIME;
                limitInfo.BasicLimitInformation.PerProcessUserTimeLimit = 10_000 * session.ProcessUserTimeLimitInMilliseconds; // in 100ns
            }

            if (session.JobUserTimeLimitInMilliseconds > 0)
            {
                flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_JOB_TIME;
                limitInfo.BasicLimitInformation.PerJobUserTimeLimit = 10_000 * session.JobUserTimeLimitInMilliseconds; // in 100ns
            }

            // only if NUMA node is not provided we will set the CPU affinity,
            // otherwise we will set the affinity on a selected NUMA node
            if (session.CpuAffinityMask != 0 && session.NumaNode == 0xffff)
            {
                flags |= JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_AFFINITY;
                // NOTE: this could result in an overflow on 32-bit apps, but I can't
                // think of a nice way of handling it here
                limitInfo.BasicLimitInformation.Affinity = new UIntPtr(systemAffinityMask & session.CpuAffinityMask);
            }

            if (flags != 0)
            {
                limitInfo.BasicLimitInformation.LimitFlags = flags | limitInfo.BasicLimitInformation.LimitFlags;
                CheckWin32Result(PInvoke.SetInformationJobObject(job.JobHandle,
                                                                 JOBOBJECTINFOCLASS.JobObjectExtendedLimitInformation, &limitInfo, size));
            }
        }
Пример #9
0
        public static unsafe (Win32Job, List <AccountPrivilege>) StartProcessUnderDebuggerAndDetach(IList <string> procargs, SessionSettings session)
        {
            var pi = new PROCESS_INFORMATION();
            var si = new STARTUPINFOW();
            var processCreationFlags = PROCESS_CREATION_FLAGS.CREATE_UNICODE_ENVIRONMENT |
                                       PROCESS_CREATION_FLAGS.DEBUG_ONLY_THIS_PROCESS;

            if (session.SpawnNewConsoleWindow)
            {
                processCreationFlags |= PROCESS_CREATION_FLAGS.CREATE_NEW_CONSOLE;
            }

            var args = string.Join(" ", procargs);
            var env  = GetEnvironmentString(session.AdditionalEnvironmentVars);

            fixed(char *pargs = args)
            {
                fixed(char *penv = env)
                {
                    CheckWin32Result(PInvoke.CreateProcess(null, pargs, null, null, false,
                                                           processCreationFlags, penv, null, si, out pi));
                }
            }

            var hProcess = new SafeFileHandle(pi.hProcess, true);

            CheckWin32Result(PInvoke.DebugSetProcessKillOnExit(false));

            var job = Win32JobModule.CreateAndAssignToProcess(hProcess, pi.dwProcessId,
                                                              session.PropagateOnChildProcesses, session.ClockTimeLimitInMilliseconds);

            Win32JobModule.SetLimits(job, session);

            var privs = AccountPrivilegeModule.EnablePrivileges(hProcess, session.Privileges);

            // resume process main thread by detaching from the debuggee
            CheckWin32Result(PInvoke.DebugActiveProcessStop(pi.dwProcessId));
            // and we can close the thread handle
            PInvoke.CloseHandle(pi.hThread);

            return(job, privs);
        }
Пример #10
0
        public static unsafe (Win32Job, List <AccountPrivilege>) StartProcess(IList <string> procargs, SessionSettings session)
        {
            var pi = new PROCESS_INFORMATION();
            var si = new STARTUPINFOW();
            var processCreationFlags = PROCESS_CREATION_FLAGS.CREATE_UNICODE_ENVIRONMENT |
                                       PROCESS_CREATION_FLAGS.CREATE_SUSPENDED;

            if (session.SpawnNewConsoleWindow)
            {
                processCreationFlags |= PROCESS_CREATION_FLAGS.CREATE_NEW_CONSOLE;
            }

            var args = string.Join(" ", procargs.Select((string s) => s.Contains(' ') ? "\"" + s + "\"" : s));
            var env  = GetEnvironmentString(session.AdditionalEnvironmentVars);

            fixed(char *pargs = args)
            {
                fixed(char *penv = env)
                {
                    CheckWin32Result(PInvoke.CreateProcess(null, pargs, null, null, false,
                                                           processCreationFlags, penv, null, si, out pi));
                }
            }

            var hProcess = new SafeFileHandle(pi.hProcess, true);

            try
            {
                var job = Win32JobModule.CreateAndAssignToProcess(hProcess, pi.dwProcessId,
                                                                  session.PropagateOnChildProcesses, session.ClockTimeLimitInMilliseconds);
                Win32JobModule.SetLimits(job, session);

                var privs = AccountPrivilegeModule.EnablePrivileges(hProcess, session.Privileges);

                // resume process main thread
                CheckWin32Result(PInvoke.ResumeThread(pi.hThread));

                return(job, privs);
            }
            catch
            {
                PInvoke.TerminateProcess(hProcess, 1);
                throw;
            }
            finally
            {
                // and we can close the thread handle
                PInvoke.CloseHandle(pi.hThread);
            }
        }