Zine.net online

Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc
w Szukaj

mgrzeg.net - Admin on Rails :)

Job objects

Jednym z ciekawszych obiektów jądra, dostępnym od Windows 2000 jest job object (w polskim tłumaczeniu: zadanie). Niestety rzadko używany popadł w zapomnienie, co postaram się tym wpisem (i być może następnymi) nieco zmienić. Prawdopodobnie największy wpływ na ten stan rzeczy ma praktycznie całkowity brak jakichkolwiek narzędzi systemowych do zarządzania zadaniami (był ponoć jakiś toolek w wersji Windows 2000 Datacenter Server i na tym chyba koniec). Brak przykładów kodu ilustrujących sposób użycia również nie sprzyja popularyzacji job objects.

Garść przykładów z życia

Jak się jednak okazuje, w systemie roi się od obiektów zadań. Poniżej kilka przykładów.

1. Adobe Reader
Ze względu na swoją architekturę job objects mogą służyć jako podstawa przy tworzeniu wydajnych i bezpiecznych sandboxów (*). Adobe podjęło w 2010 roku wysiłek stworzenia trybu chronionego w Adobe Readerze, wykorzystując do tego m.in. właśnie obiekty zadań.

Rys 1. Proces Adobe Readera w zadaniu

Nadrzędny proces tworzy obiekt zadania, który następnie przypisuje do właściwego procesu AcroRd32.exe otwierającego plik PDF.

Rys 2. Ograniczenia narzucone na AcroRd32.exe

2. Chrome
Chrome, podobnie jak to jest w przypadku Adobe Readera również implementuje swoją piaskownicę m.in. w oparciu o obiekty zadań. Proces raz umieszczony w obiekcie zadania nie ma możliwości opuszczenia zadania, ani zmiany ustawień ‘swojego’ zadania, a więc jakikolwiek kod działający w ramach procesu objętego jobem jest ‘uwięziony’ przez to zadanie.

Rys 3. Ograniczenia narzucone na chrome.exe

Przyglądając się ograniczeniom narzuconym na chrome widzimy zatem, że szkodliwy kod, który może być wykonany przez chrome (np. po udanym ataku na jakąś podatność) nie będzie miał możliwości ponownego uruchomienia komputera (Exit Windows), odczytania i modyfikacji schowka (Read & Write Clipboard), etc.

3. Program Compatibility Assistant (PCA)
Zadania utworzone przez PCA można odnaleźć pośród innych obiektów globalnych w /BasedNamedObjects, wykorzystywane są głównie do utrzymywania kontroli nad procesami, co do których PCA nie ma pewności, czy należy im ufać :) Do ciekawych przypadków należy tutaj Visual Studio (devenv.exe), które uruchomione w trybie zgodności, ma automatycznie przypisany obiekt zadania.

Rys 4. Job object powiązany z devenv.exe oraz debuggowanym procesem

Domyślnie każdy proces potomny jest automatycznie dodawany do tego zadania, co jest problemem, gdy chcemy debuggować nasze zabawy z obiektami zadań. Na szczęście, dzięki ustawionej fladze ‘Breakaway OK’ PCA umożliwia uwolnienie się spod jarzma joba (np. poprzez dodatkową flagę w CreateProcess, lub wpis w HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Compatibility Assistant).

4. WMI
Usługa Instrumentacji zarządzania Windows tworząc nowe obiekty COM do obsługi zapytań WMI (WMI Provider Host) w ramach ochrony zasobów systemowych dodaje wszystkie procesy hosta do joba \BaseNamedObjects\WmiProviderSubSystemHostJob, który narzuca ograniczenia na procesy (zrzut z mojego systemu):
- max 32 procesy;
- ogranicznie pamięci dla wszystkich procesów: 1GB;
- ograniczenie pamięci dla każdego z procesów: 512MB

Rys 5. Ograniczenia dla WmiPrvSE.exe

Ciekawe tutaj jest także to, że procesy hosta tworzone są przez usługę DcomLaunch, hostowaną w innym procesie, niż winmgmt (patrz rysunek).

Rys 6. Procesy obsługujące żądania WMI oraz zarządzająca nimi usługa

Kilka słów wprowadzenia

Zadanie może mieć nazwę, domyślnie podczas tworzenia nowego obiektu zadania z przypisaną doń nazwą object manager umieszcza go w katalogu BasedNamedObjects danej sesji. Takie obiekty zadań możemy znaleźć wędrując po zasobach object managera, jednak w przypadku anonimowych zadań znalezienie ich możliwe jest jedynie z poziomu jądra.

Rys 7. Obiekty zadań w WinObj

Dla przykładu, Process Hacker bez włączonego własnego sterownika w ogóle nie pokazuje obiektów zadań (nawet nazwanych).
Bezpańskie i osierocone zadanie, niczym Ronin jądra żyje do momentu śmierci ostatniego procesu z nim powiązanego, po czym zostaje przez system uśmiercone. Jeśli chcemy zatem mieć wpływ na obiekt zadania i nie chcemy bawić się w sterowniki, musimy utrzymać przy życiu proces, który tworzy zadanie.

W dokumentacji MSDN opisane są następujące funkcje do zabaw z zadaniami: CreateJobObject, OpenJobObject, SetInformationJobObject, QueryInformationJobObject, TerminateJobObject (do unicestwienia zadania i wszystkich powiązanych z nim procesów w jednej chwili) oraz AssignProcessToJobObject służąca do przypisywania procesu do zadania. W przypadku, gdy chcemy jednak utworzyć (lub otworzyć) obiekt zadania znajdujący się w innym miejscu w drzewie object managera (np. w ramach innej sesji, lub z katalogu globalnego), musimy sięgnąć po odpowiednie funkcje Nt* z biblioteki ntdll.dll (zamiast wspomnianych wcześniej znajdujących się w kernel32.dll). Problem w tym, że funkcje te są kompletnie nieudokumentowane i nawet wujek google o nich milczy.

//JobObjectSecurityAndAccessRights -> see
//http://msdn.microsoft.com/en-us/library/windows/desktop/ms684164(v=vs.85).aspx
[DllImport("ntdll.dll", EntryPoint = "NtOpenJobObject", SetLastError = true)]
public static extern Int32 NtOpenJobObject(out IntPtr JobHandle, JobObjectSecurityAndAccessRights DesiredAccess, ref OBJECT_ATTRIBUTES ObjectAttributes);

Wesoło zaczyna się w momencie, gdy chcemy użyć funkcji QueryInformationJobObject, która wymaga wcześniejszego przygotowania odpowiedniej struktury zaalokowanej na stosie. Wobec mnogości dostępnych opcji przydatna może się okazać funkcja podobna do poniższej.

private static T GetJobInformation<T>(string jobPath, JOBOBJECTINFOCLASS infoClass) where T: struct
{
  T jobInfo = new T();
  IntPtr hJob;
  Win32.OBJECT_ATTRIBUTES attr = new Win32.OBJECT_ATTRIBUTES(jobPath, 0);

  Win32.NtOpenJobObject(out hJob, JobObjectSecurityAndAccessRights.JOB_OBJECT_QUERY, ref attr);

  unsafe
  {
    int cb = (int)Marshal.SizeOf(typeof(T));
    byte* jobObjectInfoVoid = stackalloc byte[cb];
    IntPtr jobObjectInfo = (IntPtr)jobObjectInfoVoid;
    int retNum = 0;
    Win32.QueryInformationJobObject(hJob, infoClass, jobObjectInfo, (uint)cb, ref retNum);
    jobInfo = (T)Marshal.PtrToStructure(jobObjectInfo, typeof(T));
  }

  Win32.CloseHandle(hJob);
  return jobInfo;
}

Funkcja QueryInformationJobObject jest bardzo czuła na niewłaściwy rozmiar zaalokowanej struktury i bez opanowanej umiejętności liczenia na paluszkach możemy zapomnieć o wersji 64-bit (tu polecam zwrócenie szczególnej uwagi na to, co wypluwa P/Invoke Interop Assistant, który tłumaczy np. ULONG_PTR na unsigned int, czyli UInt32, zamiast UInt64)

Fajerwerki!

Uzbrojeni w podstawową wiedzę dotyczącą zadań możemy przystąpić do działania. Wraz z Windows 8 pojawiło się sporo nowości w ramach job objects, w tym możliwość narzucania jawnego ograniczenia na % wykorzystania czasu procesora (cpu rate). W Windows 7 można było ograniczyć cpu rate na poziomie całego systemu, sesji oraz danego użytkownika (poprzez wpisy CpuRateLimit w kluczu HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Quota System), a także na poziomie procesu poprzez funkcję NtSetInformationProcess. To drugie rozwiązanie nie odbiera możliwości procesowi samodzielnej zmiany swojego cpu rate, co w przypadku pracy w jobie jest niemożliwe.
Wszystkim tym, którzy zastanawiają się nad przydatnością takiego ograniczenia niech posłuży przykład - CPU Throttling w IIS 8 dla app pools wykorzystuje właśnie ten mechanizm do ograniczania przydziału czasu procesora dla worker procesów w3wp.exe.
System wykorzystuje mniej, lub bardziej skomplikowane algorytmy i statystyki i w zależności od nich decyduje o tym, czy może przeznaczyć jeszcze nieco czasu na wątki z procesu dołączonego do zadania z narzuconym odgórnie maksymalnym cpu rate, czy też nie.

Napisałem przykładowy system killer, który bez względu na to, ile mamy procesorów, i tak jest je w stanie wszystkie zarżnąć. W efekcie mamy wszystkie procesory obciążone w 100% i niewiele czasu procesora na cokolwiek innego. Po narzuceniu na ten proces ograniczenia na maksymalny cpu rate system odzyskuje oddech :)

Rys 8. Ograniczanie CPU rate w działaniu

Zobaczmy, jak job wygląda w WinDbg

kd> !process 0n3728
Searching for Process with Cid == e90
PROCESS ffffe0000160f900
    SessionId: 1  Cid: 0e90    Peb: 7ff5ffb3f000  ParentCid: 0318
    DirBase: 3c01f000  ObjectTable: ffffc00009d94580  HandleCount: <Data Not Accessible>
    Image: JobWorker.exe
    VadRoot ffffe00002784d90 Vads 94 Clone 0 Private 567. Modified 0. Locked 1.
    DeviceMap ffffc00001e60130
    Token                             ffffc00006bde060
    ElapsedTime                       00:04:40.807
    UserTime                          00:00:00.000
    KernelTime                        00:00:00.000
    QuotaPoolUsage[PagedPool]         168048
    QuotaPoolUsage[NonPagedPool]      24608
    Working Set Sizes (now,min,max)  (2754, 50, 345) (11016KB, 200KB, 1380KB)
    PeakWorkingSetSize                2772
    VirtualSize                       499 Mb
    PeakVirtualSize                   515 Mb
    PageFaultCount                    2816
    MemoryPriority                    BACKGROUND
    BasePriority                      8
    CommitCharge                      2311
    Job                               ffffe00001f43b50

Struktura _EJOB zawiera informacje o obiekcie zadania, w tym również limitu cpu rate (w ramach wspomnianej wcześniej struktury _JOB_CPU_RATE_CONTROL)

kd> dt nt!_ejob ffffe00001f43b50
   +0x000 Event            : _KEVENT
   +0x018 JobLinks         : _LIST_ENTRY [ 0xffffe000`01076b68 - 0xffffe000`0264eb68 ]
   +0x028 ProcessListHead  : _LIST_ENTRY [ 0xffffe000`0160fd58 - 0xffffe000`0160fd58 ]
   +0x038 JobLock          : _ERESOURCE
   +0x0a0 TotalUserTime    : _LARGE_INTEGER 0x0
   +0x0a8 TotalKernelTime  : _LARGE_INTEGER 0x0
   +0x0b0 TotalCycleTime   : _LARGE_INTEGER 0x0
   +0x0b8 ThisPeriodTotalUserTime : _LARGE_INTEGER 0x0
   +0x0c0 ThisPeriodTotalKernelTime : _LARGE_INTEGER 0x0
   +0x0c8 TotalContextSwitches : 0
   +0x0d0 TotalPageFaultCount : 0
   +0x0d4 TotalProcesses   : 1
   +0x0d8 ActiveProcesses  : 1
   +0x0dc TotalTerminatedProcesses : 0
   +0x0e0 PerProcessUserTimeLimit : _LARGE_INTEGER 0x0
   +0x0e8 PerJobUserTimeLimit : _LARGE_INTEGER 0x0
   +0x0f0 MinimumWorkingSetSize : 0
   +0x0f8 MaximumWorkingSetSize : 0
   +0x100 LimitFlags       : 0
   +0x104 ActiveProcessLimit : 0
   +0x108 Affinity         : _KAFFINITY_EX
   +0x1b0 AccessState      : (null)
   +0x1b8 AccessStateQuotaReference : (null)
   +0x1c0 UIRestrictionsClass : 0
   +0x1c4 EndOfJobTimeAction : 0
   +0x1c8 CompletionPort   : (null)
   +0x1d0 CompletionKey    : (null)
   +0x1d8 CompletionCount  : 0
   +0x1e0 SessionId        : 1
   +0x1e4 SchedulingClass  : 5
   +0x1e8 ReadOperationCount : 0
   +0x1f0 WriteOperationCount : 0
   +0x1f8 OtherOperationCount : 0
   +0x200 ReadTransferCount : 0
   +0x208 WriteTransferCount : 0
   +0x210 OtherTransferCount : 0
   +0x218 DiskIoInfo       : _PROCESS_DISK_COUNTERS
   +0x240 ProcessMemoryLimit : 0
   +0x248 JobMemoryLimit   : 0
   +0x250 PeakProcessMemoryUsed : 0x91b
   +0x258 PeakJobMemoryUsed : 0x91b
   +0x260 EffectiveAffinity : _KAFFINITY_EX
   +0x308 EffectivePerProcessUserTimeLimit : _LARGE_INTEGER 0x0
   +0x310 EffectiveMinimumWorkingSetSize : 0
   +0x318 EffectiveMaximumWorkingSetSize : 0
   +0x320 EffectiveProcessMemoryLimit : 0
   +0x328 EffectiveProcessMemoryLimitJob : (null)
   +0x330 EffectivePerProcessUserTimeLimitJob : (null)
   +0x338 EffectiveLimitFlags : 0
   +0x33c EffectiveSchedulingClass : 0xa
   +0x340 EffectiveFreezeCount : 0
   +0x344 EffectiveBackgroundCount : 0
   +0x348 EffectiveSwapCount : 0
   +0x34c EffectiveNotificationLimitCount : 0
   +0x350 EffectivePriorityClass : 0 ''
   +0x351 PriorityClass    : 0 ''
   +0x352 Reserved1        : [2]  ""
   +0x354 CompletionFilter : 0x1ffe
   +0x358 WakeChannel      : _WNF_STATE_NAME
   +0x358 WakeInfo         : _PS_WAKE_INFORMATION
   +0x390 WakeFilter       : _JOBOBJECT_WAKE_FILTER
   +0x398 LowEdgeLatchFilter : 0
   +0x39c OwnedHighEdgeFilters : 0
   +0x3a0 NotificationLink : (null)
   +0x3a8 CurrentJobMemoryUsed : 0x907
   +0x3b0 NotificationInfo : (null)
   +0x3b8 NotificationInfoQuotaReference : (null)
   +0x3c0 NotificationPacket : (null)
   +0x3c8 CpuRateControl   : 0xffffe000`01e08240 _JOB_CPU_RATE_CONTROL
   +0x3d0 EffectiveSchedulingGroup : 0xffffe000`01e082c0 Void
   +0x3d8 ReadyTime        : 0
   +0x3e0 MemoryLimitsLock : _EX_PUSH_LOCK
   +0x3e8 SiblingJobLinks  : _LIST_ENTRY [ 0xffffe000`01f43f38 - 0xffffe000`01f43f38 ]
   +0x3f8 ChildJobListHead : _LIST_ENTRY [ 0xffffe000`01f43f48 - 0xffffe000`01f43f48 ]
   +0x408 ParentJob        : (null)
   +0x410 RootJob          : 0xffffe000`01f43b50 _EJOB
   +0x418 IteratorListHead : _LIST_ENTRY [ 0xffffe000`01f43f68 - 0xffffe000`01f43f68 ]
   +0x428 AncestorCount    : 0
   +0x430 Ancestors        : (null)
   +0x438 Accounting       : _EPROCESS_VALUES
   +0x488 ShadowActiveProcessCount : 1
   +0x48c SequenceNumber   : 0
   +0x490 TimerListLock    : 0
   +0x498 TimerListHead    : _LIST_ENTRY [ 0xffffe000`01f43fe8 - 0xffffe000`01f43fe8 ]
   +0x4a8 JobFlags         : 0x1800060
   +0x4a8 CloseDone        : 0y0
   +0x4a8 MultiGroup       : 0y0
   +0x4a8 OutstandingNotification : 0y0
   +0x4a8 NotificationInProgress : 0y0
   +0x4a8 UILimits         : 0y0
   +0x4a8 CpuRateControlActive : 0y1
   +0x4a8 OwnCpuRateControl : 0y1
   +0x4a8 Terminating      : 0y0
   +0x4a8 WorkingSetLock   : 0y0
   +0x4a8 JobFrozen        : 0y0
   +0x4a8 Background       : 0y0
   +0x4a8 WakeNotificationAllocated : 0y0
   +0x4a8 WakeNotificationEnabled : 0y0
   +0x4a8 WakeNotificationPending : 0y0
   +0x4a8 LimitNotificationRequired : 0y0
   +0x4a8 ZeroCountNotificationRequired : 0y0
   +0x4a8 CycleTimeNotificationRequired : 0y0
   +0x4a8 CycleTimeNotificationPending : 0y0
   +0x4a8 TimersVirtualized : 0y0
   +0x4a8 JobSwapped       : 0y0
   +0x4a8 ViolationDetected : 0y0
   +0x4a8 EmptyJobNotified : 0y0
   +0x4a8 NoSystemCharge   : 0y0
   +0x4a8 DropNoWakeCharges : 0y1
   +0x4a8 NoWakeChargePolicyDecided : 0y1
   +0x4a8 SpareJobFlags    : 0y0000000 (0)
   +0x4ac EffectiveHighEdgeFilters : 0

Niestety brakuje nam symboli, więc znając opis struktury, możemy pokusić się o zrzucenie jej zawartości

kd> dd 0xffffe000`01e08240 L2
ffffe000`01e08240  00000001 00001770

Przy czym 1 określa, że mamy do czynienia z cpu rate, natomiast 1770 tłumaczy się na:

kd> .formats 00001770
Evaluate expression:
  Hex:     00000000`00001770
  Decimal: 6000
  Octal:   0000000000000000013560
  Binary:  00000000 00000000 00000000 00000000 00000000 00000000 00010111 01110000
  Chars:   .......p
  Time:    Thu Jan 01 02:40:00 1970
  Float:   low 8.40779e-042 high 0
  Double:  2.96439e-320

co zgodnie z dokumentacją należy interpretować jako 60 * 100, czyli 60% cpu rate.

Zobaczmy przy okazji jak wyglądał obiekt zadań w Windows 7 x64

kd> dt nt!_ejob
   +0x000 Event            : _KEVENT
   +0x018 JobLinks         : _LIST_ENTRY
   +0x028 ProcessListHead  : _LIST_ENTRY
   +0x038 JobLock          : _ERESOURCE
   +0x0a0 TotalUserTime    : _LARGE_INTEGER
   +0x0a8 TotalKernelTime  : _LARGE_INTEGER
   +0x0b0 ThisPeriodTotalUserTime : _LARGE_INTEGER
   +0x0b8 ThisPeriodTotalKernelTime : _LARGE_INTEGER
   +0x0c0 TotalPageFaultCount : Uint4B
   +0x0c4 TotalProcesses   : Uint4B
   +0x0c8 ActiveProcesses  : Uint4B
   +0x0cc TotalTerminatedProcesses : Uint4B
   +0x0d0 PerProcessUserTimeLimit : _LARGE_INTEGER
   +0x0d8 PerJobUserTimeLimit : _LARGE_INTEGER
   +0x0e0 MinimumWorkingSetSize : Uint8B
   +0x0e8 MaximumWorkingSetSize : Uint8B
   +0x0f0 LimitFlags       : Uint4B
   +0x0f4 ActiveProcessLimit : Uint4B
   +0x0f8 Affinity         : _KAFFINITY_EX
   +0x120 PriorityClass    : UChar
   +0x128 AccessState      : Ptr64 _JOB_ACCESS_STATE
   +0x130 UIRestrictionsClass : Uint4B
   +0x134 EndOfJobTimeAction : Uint4B
   +0x138 CompletionPort   : Ptr64 Void
   +0x140 CompletionKey    : Ptr64 Void
   +0x148 SessionId        : Uint4B
   +0x14c SchedulingClass  : Uint4B
   +0x150 ReadOperationCount : Uint8B
   +0x158 WriteOperationCount : Uint8B
   +0x160 OtherOperationCount : Uint8B
   +0x168 ReadTransferCount : Uint8B
   +0x170 WriteTransferCount : Uint8B
   +0x178 OtherTransferCount : Uint8B
   +0x180 ProcessMemoryLimit : Uint8B
   +0x188 JobMemoryLimit   : Uint8B
   +0x190 PeakProcessMemoryUsed : Uint8B
   +0x198 PeakJobMemoryUsed : Uint8B
   +0x1a0 CurrentJobMemoryUsed : Uint8B
   +0x1a8 MemoryLimitsLock : _EX_PUSH_LOCK
   +0x1b0 JobSetLinks      : _LIST_ENTRY
   +0x1c0 MemberLevel      : Uint4B
   +0x1c4 JobFlags         : Uint4B

Jak widać, zmian jest co niemiara, część ustawień joba została przeniesiona z innych miejsc (np. win32k!tagW32JOB), sporo dodano (child jobs, sibling jobs, etc.). Niestety nie znam żadnego narzędzia, które uwzględniałoby te zmiany - w chwili obecnej ani Process Hacker, ani Sysinternalsowy Process Explorer nie pokazują szczegółów związanych z nowościami i trzeba posiłkować się własnymi narzędziami (z tego powodu nie robiłem zrzutów dla zakładki jobs).

Proponuję rozejrzeć się po systemie w poszukiwaniu różnego rodzaju zadań i powiązanych z nimi procesów - kolorki w PH oraz PE pozwalają szybko namierzyć takie procesy.
Jakiś czas temu Sebastian pisał o wykorzystaniu zadań do ustawiania maksymalnego rozmiaru commited memory (joby umożliwiają także ustawienie max reserved memory) - sprawdzajcie zatem błędy OutOfMemoryException (tak nieco na marginesie wbrew pozorom jest to niezwykle częsty wyjątek).

Mam nadzieję, że w przyszłości wrócę jeszcze do obiektów zadań, bo czuję, że temat nie został jeszcze nawet nadgryziony :)

(*) Linkowany tekst Davida Leblanca (współautora m.in. ‘Writing Secure Code’) pisany był w 2007 r. i część problemów, o których pisał David jest już nieaktualna: od Windows 8 można tworzyć zagnieżdżone zadania, a także przypisywać proces do wielu zadań.

Opublikowane 22 marca 2014 02:14 przez mgrzeg

Powiadamianie o komentarzach

Jeżeli chciałbyś otrzymywać email gdy ta wypowiedź zostanie zaktualizowana, to zarejestruj się tutaj

Subskrybuj komentarze za pomocą RSS

Komentarze:

Brak komentarzy

Co o tym myślisz?

(wymagane) 
(opcjonalne)
(wymagane) 

  
Wprowadź kod: (wymagane)
Wyślij

Subskrypcje

W oparciu o Community Server (Personal Edition), Telligent Systems