Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009

.pdf
Скачиваний:
6265
Добавлен:
13.08.2013
Размер:
31.38 Mб
Скачать

142 Часть II. Приступаем к работе

Совет. Утилита Process Explorer от компании Sysinternals (http://www.microsoft.com/technet/sysintemals/ProcessesAndThreads/ProcessEx plorer.mspx) отображает уровень целостности процессов в одноименном столбце, который следует добавить на вкладке Process Image в Select Columns. В ее исходном коде имеется функция GetProcessIntegntyLevel, иллюстрирующая программное получение этих и многих других сведений. Консольная утилита AccessChk того же разработчика (http://www.micmsoft.com/techmt/sysintemah/utilities/accesschk.mspx) выводит уровень це-

лостности, необходимый для доступа к таким ресурсам, как файлы, папки и разделы реестра при вызове с ключами -i и -е. Консольная утилита icacls.exe для Vista поддерживает ключ /setintegntylevel для назначения уровня защиты целостности ресурсам файловой системы. После определения уровней целостности маркера защиты процесса и объекта ядра, к которому он пытается обратиться, система выясняет разрешенные операции, проверяя политики кода (code policy), которые хранятся и в маркере защиты, и в ресурсе. Сначала вызывается функция GetTokenInformation с передачей параметра TokenMandatoryPolicy и маркера защиты процесса. Эта функция возвращает значение DWORD с битовой маской, описывающей действующую политику (см. таблицу 4-11).

Табл. 4-11. Политики кода

Значение Описание

POLICY_NO_WRITE_UP Запрещает коду модификацию ресурсов с более высоким уровнем целостности

POUCY_NEW_PROCESS_MIN Процессы, порожденные кодом с данной политикой, наследуют минимальный уровень (из уровней, заданных для родительского процесса и в манифесте; если манифест отсутствует, считается что им задан уровень Medium)

Также в коде определены две константы для распознавания отсутствия политики (TOKEN_MANDATORY_POLICY_OFF как 0) и наличия битовой маски

(TOKEN_MANDATORY_POLICY_VALID_MASK), служащей для проверки политики (см. ProcessInfo.cpp).

Далее политика ресурса устанавливается по битовой маске Label АСЕ объекта ядра. (Подробнее о том, как это делается, см. в коде функции GetProcessIntegntyLevel в файле ProcessInfo.cpp.) Так получаются две политики доступа к ресурсу (resource policies), которые позволяют определить операции, разрешенные и запрещенные для данного ресурса. По умолчанию действует политика SYSTEM_MANDATORY_LABEL_NO_WRITE_UP, разрешающая процессам с более низким уровнем целостности чтение, но запрещающая запись и удаление ресурса с более высоким уровнем целостности. Политика SYSTEM_MANDATORY_LABEL_NO_READ_UP накладывает еще более строгие ог-

раничения, запрещая процессам с более низким уровнем целостности даже чтение ресурсов с более высоким уровнем целостности.

Глава 4. Процессы.docx 143

Примечание. Процессам с низким уровнем целостности разрешено читать содержимое объектов ядра с более высоким уровнем целостности, даже если установлена политика «No-Read-Up», но только при наличии у процесса разрешения Debug. Поэтому утилита Process Information способна читать командную строку процессов с уровнем целостности System (при работе под администраторской учетной записью, под которой можно выдать себе разрешение Debug).

Помимо защиты доступа к объектам ядра, принадлежащим разным процессам, уровни целостности также используются оконной системой, которая запрещает процессам с уровнем целостности Low модифицировать пользовательские интерфейсы процессов с более высоким уровнем целостности. Этот механизм называется User Interface Privilege Isolation, UIPI (изоляция пользовательского интерфейса по привилегиям). Операционная система блокирует отправленные (асинхронно и синхронно, соответственно функциями PostMessage и SendMessage), а также перехваченные (ловушками Windows) оконные сообщения от процессов с более низким уровнем целостности, чтобы предотвратить несанкционированное чтение и запись данных в окна, принадлежащие процессам с более высоким уровнем целостности. Этот механизм особенно наглядно иллюстрирует утилита WindowDump (http://download.microsoft.com/download/8/3/f/83f69587-47f1-48e2-86a6- aab14f01f1fe/EscapeFromDLLHell.exe). Для получения содержимого списка WindowDump вызывает сначала SendMessage с параметром LB_GETCOUNT, чтобы узнать число элементов списка, а затем ту же функцию с параметром LB_GETTEXT для извлечения текста из этих элементов. Если запустить эту утилиту с уровнем целостности, более низким, чем у процесса, которому принадлежит окно со списком, первый вызов SendMessage закончится успешно, но вернет 0 вместо числа элементов списка. Аналогично, то есть неудачно, заканчивается попытка получения с помощью утилиты Spy++, запущенной с уровнем целостности Medium, сообщений от окна программы, работающей с более высоким уровнем целостности.

Оглавление

 

Г Л А В А 5 Задания................................................................................................................

144

Определение ограничений, налагаемых на процессы в задании ..........................

149

Включение процесса в задание........................................................................................

157

Завершение всех процессов в задании .........................................................................

158

Получение статистической информации о задании...............................................

158

Уведомления заданий..........................................................................................................

162

Программа-пример JobLab.................................................................................................

165

Г Л А В А 5

Задания

Группу процессов зачастую нужно рассматривать как единую сущность. Например, когда вы командуете Visual Studio собрать проект, она порождает процесс Cl.exe, а тот в свою очередь может создать другие процессы (скажем, для дополнительных проходов компилятора). Но, если вы пожелаете прервать сборку, Visual Studio должен каким-то образом завершить Cl.exe и все его дочерние процессы. Решение этой простой (и распространенной) проблемы в Windows было весьма затруднительно, поскольку она не отслеживает родственные связи между процессами. В частности, выполнение дочерних процессов продолжается даже после завершения родительского.

При разработке сервера тоже бывает полезно группировать процессы. Допустим, клиентская программа просит сервер выполнить приложение (которое создает ряд дочерних процессов) и сообщить результаты. Поскольку к серверу может обратиться сразу несколько клиентов, было бы неплохо, если бы он умел как-то ограничивать ресурсы, выделяемые каждому клиенту, и тем самым не давал бы одному клиенту монопольно использовать все серверные ресурсы. Под ограничения могли бы подпадать такие ресурсы, как процессорное время, выделяемое на обработку клиентского запроса, и размеры рабочего набора (working set). Кроме того, у клиентской программы не должно быть возможности завершить работу сервера и т. д.

Windows поддерживает объект ядра под названием «задание» (job). Он позволяет группировать процессы и помещать их в нечто вроде песочницы, которая определенным образом ограничивает их действия. Относитесь к этому объекту как к контейнеру процессов. Кстати, очень полезно создавать задание и с одним процессом — это позволяет налагать на процесс ограничения, которые иначе указать нельзя.

Взгляните на мою функцию StartRestrictedProcess. Она включает процесс в задание, которое ограничивает возможность выполнения определенных операций.

Глава 5. Задания.docx 145

void StartRestrictedProcess() {

//Проверить, не связан ли этот процесс с заданием.

//Если да, переключиться на другое задание невозможно.

BOOL bInJob = FALSE; IsProcessInJob(GetCurrentProcess(), NULL, &bInJob); if (bInJob) {

MessageBox(NULL, TEXT("Process already in a job"), TEXT(""), MB_ICONINFORMATION | MB_OK);

return;

}

//создаем объект ядра "задание"

HANDLE hjob = CreateJobObject(NULL, TEXT("Wintellect_RestrictedProcessJob"));

//вводим ограничения для процессов в задании

//сначала определяем некоторые базовые ограничения

JOBOBJECT_BASIC_LIMIT_INFORMATION jobli = { 0 };

//процесс всегда выполняется с классом приоритета idle jobli.PriorityClass = IDLE_PRIORITY_CLASS;

//задание не может использовать более одной секунды процессорного времени jobli.PerJobUserTimeLimit.QuadPart = 10000; // 1 секунда, выраженная в

//100-наносекундных интервалах

//два ограничения, которые я налагаю на задание (процесс)

jobli.LimitFlags = J0B_0BJECT_LIMIT_PRI0RITY_CLASS J0B_0BJECT_LIMIT_J0B_TIME;

SetInfоrmationJobObject(hjob, JobObjectBasicLimitInfоrmation, &jobli, sizeof(jobli));

//теперь вводим некоторые ограничения по пользовательскому интерфейсу

J0B0BJECT_BASIC_UI_RESTRICTI0NS jobuir; jobuir.UIRestrictionsClass = J0B_0BJECT_UILIMIT_N0NE;

//"замысловатый" нуль

//процесс не имеет права останавливать систему jobuir.UIRestrictionsClass |= J0B_0BJECT_UILIMIT_EXITWIND0WS;

//Процесс не имеет права обращаться к USER-объектам в системе

//(например, к другим окнам).

jobuir.UIRestrictionsClass |= J0B_0BJECT_UILIMIT_HANDLES;

SetInformationJobObject(hjob, JobObjectBasicUIRestrictions, &jobuir, sizeof(jobuir));

146Часть II. Приступаем к работе

//Порождаем процесс, который будет размещен в задании.

//ПРИМЕЧАНИЕ: процесс нужно сначала создать и только потом поместить

//в задание. А это значит, что поток процесса должен быть создан

//и тут же приостановлен, чтобы он не смог выполнить какой-нибудь код

//еще до введения ограничений.

STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi;

TCHAR szCmdLine[8];

_tcscpy_s(szCmdLine, _countof(szCmdLine), TEXT("CMD")); BOOL bResult =

CreateProcess(

NULL, szCmdLine, NULL, NULL, FALSE,

CREATE_SUSPENDED | CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);

//Включаем процесс в задание.

//ПРИМЕЧАНИЕ: дочерние процессы, порождаемые этим процессом,

//автоматически становятся частью того же задания.

AssignProcessToJobObject(hjob, pi.hProcess);

//теперь потоки дочерних процессов могут выполнять код

ResumeThread(pi.hThread);

CloseHandle(pi.hThread);

//Ждем, когда процесс завершится или будет исчерпан.

//Лимит процессорного времени, указанный для задания.

HANDLE h[2];

h[0] = pi.hProcess; h[1] = hjob;

DWORD dw = WaitForMultiple0bjects(2, h, FALSE, INFINITE); switch (dw - WAIT_OBJECT_0) {

case 0:

//процесс завершился…

break; case 1:

// лимит процессорного времени исчерпан… break;

}

FILETIME CreationTime; FILETIME ExitTime; FILETIME KernelTime; FILETIME UserTime; TCHAR szInfo[MAX_PATH];

GetProcessTimes(pi.hProcess, ACreationTime, &ExitTime, &KernelTime, &UserTime);

StringCchPrintf(szInfo, _countof(szInfo), TEXT("Kernel = %u | User = %u\n"), KernelTime.dwLowDateTime / 10000, UserTime.dwLowDateTime / 10000);

MessageBox(GetActiveWindow(), szInfo, TEXT("Restricted Process times"),

Глава 5. Задания.docx 147

MB_ICONINFORMATION | МВ_ОК); // проводим очистку

CloseHandle(pi.hProcess);

CloseHandle(hjob);

}

А теперь я объясню, как работает StartRestrictedProcess. Сначала я создаю новый объект ядра «задание», вызывая:

BOOL IsProcessInJob( HANDLE hProcess, HANDLE hJob, PBOOL pbInJob);

Как и любая функция, создающая объекты ядра, CreateJobObject принимает в первом параметре информацию о защите и сообщает системе, должна ли она вернуть наследуемый описатель. Параметр pszName позволяет присвоить заданию имя, чтобы к нему могли обращаться другие процессы через функцию OpenJobObject

Если процесс уже включен в задание, то ни его, ни порожденные им процессы не удастся вывести из задания. Это защитный механизм, который не дает процессам обойти наложенные на них ограничения.

Внимание! По умолчанию процессы, которые создаются при запуске приложений через Windows Explorer, автоматически включаются в особое задание, имя которого предваряет префикс «РСА». Как сказано в разделе «Уведомления заданий», возможно получение уведомлений о завершении процесса в составе задания. Этот механизм позволяет запустить Program Compatibility Assistant при сбое унаследованного приложения, запущенного че-

рез Windows Explorer.

Если вашему приложению потребуется создать задание, как это делает про- грамма-пример Job Lab, показанная в конце этой главы, у вас ничего не выйдет, поскольку объект-задание, имя которого начинается с «РСА», уже связан с вашими процессами.

Эта функция поддерживается Windows Vista исключительно в целях обеспечения совместимости. Таким образом, если снабдить приложение манифестом (см. главу 4), Проводник Windows не включит его процесс в задание, имя которого начинается префиксом «РСА», предполагая, что разработчики этого приложения уже решили все проблемы с совместимостью.

Однако при отладке приложений возможна следующая ситуация. Если отладчик запущен из-под Windows Explorer, то ваше приложение, даже если у него есть манифест, как и отладчик, будет включено в задание с именем «РСА...». Простое решение этой проблемы - запускать отладчик из командной оболочки, а не из-под Проводника, тогда процесс отладчика не будет включен в это задание.

148 Часть II. Приступаем к работе

Затем я создаю новый объект ядра «задание», вызывая

HANDLE CreateJobObject(

PSECURITY_ATTRIBUTES psa,

PCTSTR pszName);

Как и любая функция, создающая объекты ядра, CreatejobObject принимает в первом параметре информацию о защите и сообщает системе, должна ли она вернуть наследуемый описатель. Параметр pszName позволяет присвоить заданию имя, чтобы к нему могли обращаться другие процессы через функцию OpenJobObject.

HANDLE OpenJobObject(

DWORD dwDesiredAccess,

BOOL bInheritHandle,

PCTSTR pszName);

Закончив работу с объектом-заданием, закройте его описатель, вызвав, как всегда, CloseHandle. Именно так я и делаю в конце своей функции StartRestrictedProcess. Имейте в виду, что закрытие объекта-задания не приводит к автоматическому завершению всех его процессов. На самом деле этот I объект просто помечается как подлежащий разрушению, и система уничтожает его только после завершения всех включенных в него процессов. Заметьте, что после закрытия описателя объект-задание становится недоступным для процессов, даже несмотря на то что объект все еще существует. Этот факт иллюстрирует следующий код:

// создаем именованный объект-задание

HANDLE hJob = CreateJobObject(NULL, TEXT("Jeff"));

//включаем в него наш процесс

AssignProcessToJobObject(hJob, GetCurrentProces());

//закрытие объекта-задания не убивает ни наш процесс, ни само задание,

//но присвоенное ему имя ("Jeff") моментально удаляется

CloseHandle(hJob);

//пробуем открыть существующее задание

hJob = OpenJobObject(JOB_OBJECT_ALL_ACCESS, FALSE, TEXT("Jeff));

//OpenJobObject терпит неудачу и возвращает NULL, поскольку имя ("Jeff")

//Уже не указывает на объект-задание после вызова CloseHandle;

//получить описатель этого объекта больше нельзя

Глава 5. Задания.docx 149

Определение ограничений, налагаемых на процессы в задании

Создав задание, вы обычно строите «песочницу» (набор ограничений) для включаемых в него процессов. Ограничения бывают нескольких видов:

базовые и расширенные базовые ограничения — не дают процессам в задании монопольно захватывать системные ресурсы;

базовые ограничения по пользовательскому интерфейсу (UI) — блокируют возможность его изменения;

ограничения, связанные с защитой, — перекрывают процессам в задании доступ к защищенным ресурсам (файлам, подразделам реестра и т. д.).

Ограничения на задание вводятся вызовом:

BOOL SetInformationJobObject(

HANDLE hJob,

JOBOBJECTINFOCLASS JobObjectInformationClass,

PVOID pJobObjectInformation,

DWORD cbJobObjectlnformationSize);

Первый параметр определяет нужное вам задание, второй параметр (перечислимого типа) — вид ограничений, третий — адрес структуры данных, содержащей подробную информацию о задаваемых ограничениях, а четвертый — размер этой структуры (используется для указания версии). Следующая таблица показывает, как устанавливаются ограничения.

Табл. 5-1. Типы ограничений

Вид ограничений

Значение второго параметра

Структура, указываемая

в третьем параметре

 

 

Базовые ограничения

JobObjectBaskLimitInformation

JOBOBJECT_BASIC_

 

 

LIMIT_INFORMATION

Расширенные базовые

JobObjectExtendedLimttInformation

JOBOBJECT_

ограничения

 

EXTENDED_

 

 

LIMIT_INFORMATION

Базовые ограничения

JobObjectBasicUIRestrictions

JOBOBJECT_BASIC_

по пользовательскому

 

UI_RESTRICTIONS

интерфейсу

 

 

Ограничения, связан-

JobObjectSecurityLimitInformation

JOBOBJECT_

ные с защитой

 

SECURITY_LIMIT_

 

 

INFORMATION

В функции StartRestrictedProcess я устанавливаю для задания лишь несколько базовых ограничений. Для этого я создаю структуру JOB_OBJECT_ BASIC LIMIT_INFORMATION, инициализирую ее и вызываю функцию SetlnformationJobObject Данная структура выглядит так:

Соседние файлы в предмете Программирование на C++