вторник, 17 февраля 2015 г.

Анализ бага win32k (DOS)

На форуме rsdn.ru проскользнула любопытная тема http://rsdn.ru/forum/winapi/5956603,  в которой kero открыл баг в ядре графической подсистемы windows - win32k.sys.

Мне стало любопытно, а в чем собственно состоит баг? Данная заметка - мой небольшой анализ.

Прежде всего, как вызвать BSOD? kero, помимо exe, приложил еще и исходники, из них следует, что если создать определенное количество окон и установить каждому предыдущему окну владельцом текущее окно через SetWindowLong( GWL_HWNDPARENT ) и затем удалить самое последнее окно, то это вызовет удаление всех окон в цепочке и BSOD в итоге.

Из-за чего происходит BSOD? Из-за переполнения стека, которую вызвала рекурсия.

Рекурсия вызывается из-за:

xxxDestroyWindow()
{
   если у окна нет свойства WS_CHILD, вызывается xxxDW_DestroyOwnedWindows
}

xxxDW_DestroyOwnedWindows
{
   для всех окон, имеющих владельца родителя вызывается xxxDestroyWindow
}

Собственно, данная рекурсия не отличается от любой другой - если нет защиты от рекурсии, она рано или поздно исчерпает весь стек. Также происходит и тут, локальные переменные данных двух функций выжирают весь ядерный стек, при очередном push'е стека не хватает, происходит исключение, которое опять таки не может быть обработано, ибо также использует стековые переменные, система переключается на panic stack и выбрасывает исключение DOUBLE_FAULT.

Количество окон, которые система может создать без BSOD'a зависит от путей исполнения программы, а пути зависят от входных данных, к примеру добавив новый стиль в CreateWindow, или убрав любой из примера - получим другое число созданных окон без падения.

Почему нет функции, которая бы проверяла и предотвращала подобные вещи?

На самом деле она есть:

SetWindowLong( GWL_HWNDPARENT ) => ValidateOwnerDepth

BOOL ValidateOwnerDepth()
{
    UINT cDepth = 1;
    while (pwndOwner != NULL )
   {
       if ( pwndOwner == pwnd )
           return FALSE;
 
      pwndOwner = pwndOwner->spwndOwner; // всегда 0
      cDepth++;
    }
    return (cDepth <= NESTED_WINDOW_LIMIT);
}

spwndOwner всегда будет == 0, т.к. при создании окна, если отсутствует  свойство WS_CHILD и hWndParent == NULL, spwndOwner устанавливается в NULL.

Поэтому SetWindowLong будет успешно устанавливать владельца, т.к. функция валидатор всегда будет давать добро. А при удалении цепочки окон всегда будет BSOD если создано слишком много окон.

Баг воспроизводится на всех последних ОС любых разрядностей, не проверял лишь win10.
Спасибо kero за интересный кейс!