Бессистемное программирование

Включая компьютеры и запуская Windows, пользователи не подозревают, какой шаткой и ненадежной системе доверяют свои данные. Часто об этом не задумываются и прикладные программисты, создающие коммерческое программное обеспечение, оставляя эту "головную боль" на откуп системщикам или хакерам. При построении операционных систем должен соблюдаться здоровый баланс в соотношении надежность/производительность. Повышенные требования к надежности и соответственные "накладки" на код резко снижают общую производительность программного обеспечения. Продукты от Microsoft для обеспечения сиюминутной коммерческой выгоды практически всегда делают крен в сторону повышения производительности, намеренно не ставя вопрос о надежности вообще. В статье приведены простейшие примеры ошибок, часто намеренных, допущенных системными программистами Microsoft при реализации Windows-систем.

Все примеры тестировались на Windows 95 OSR2 (rus) с включенным Norton CrashGuard 4.0. Для компиляции использовались Borland C++ 5.02 и TASM 5.0 (4.1).

Часть примеров взята с сайта "Вторая сторона", автор Сергей Забарянский, часть - из других источников.

1. Пример: некорректная обработка расширений.

Пример описан Дмитрием Леоновым с "Хакерской Зоны России", я встречал упоминание о нем и в других источниках с описаниями "багов" операционных систем. Ошибка очень проста и может быть смоделирована даже средне подготовленным пользователем, не говоря уже о программистах. В исполняемых файлах можно изменить расширение на любое или убрать совсем и, тем не менее, Windows NT запустит его как исполнимый. Можно переименовать calc.exe в file.diz и запустить на исполнение командой start file.diz и Windows тем не менее отработает его как исполнимый файл. Чтобы подобное смоделировать на Windows 95, переименованный файл необходимо запустить через разделы системного реестра Run, RunOnce, но до отработки разделов RunService, RunServiceOnce, запускающих "сервисы", после которых начинается корректная обработка расширений. Подобный "баг" оставляет хорошую лазейку для маскировки РПС (разрушающих программных средств).

2. Пример: некорректный размер файла System.ini.

Если создать файл system.ini размером больше 64 Kb (например, командой copy system.ini+some_big_text_file.txt system.ini), тогда система при загрузке или повиснет, или перезагрузится. Дело в том, что для совместимости с 16-битными Windows приложениями созданы API-функции вроде "GetProfileSection" или "GetPrivateProfileSection". Последняя функция восстанавливает все строковые или целочисленные значения заданной секции. Ниже приведен заголовок функции, взятый из справочного файла по Win32 API:

DWORD GetPrivateProfileSection(
LPCTSTRlpAppName,// указатель на строку, содержащую имя секции
LPTSTRlpReturnedString,// указатель на буфер,принимающий секцию
DWORDnSize,// размер, в символах,принимающего буфера
LPCTSTRlpFileName // указатель на строкус именем ini-файла
);

В Windows 95 размер буфера должен быть не больше 32,767 байт, в отличие от Windows NT, где размер принимающего буфера не ограничен. Но дело не только в "ущербной" реализации Windows API, кодировщик Microsoft считывает System.ini файл в сегмент c размером 6720h байт. Обойти это можно, загрузив Windows в "Safe Mode" или "Safe Mode Command Prompt Only", когда система обходит файлы конфигурации при загрузке, и восстановив поврежденный ini-шник.

3. Пример: бесконечный цикл 16-битного Windows приложения.

#include <windows.h>
#pragma hdrstop
int PASCAL WinMain
(HINSTANCE hCurInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
while(TRUE);
}

Подобная маленькая программка, откомпилированная как 16-битовое приложение, приводит к зависанию windows-системы. Из коллапса выводится система по Ctrl-Alt-Del и снятию задачи. В 32-битном варианте этот код не эффективен, система продолжает выполнять другие приложения.

4. Пример: сбрасывание флага прерывания.

Для демонстрации некорректной обработки "сброса" флага прерывания IF Windows-системами Джефом Чапеллом был предложен этот код. "Очистка" действительного флага прерывания процессами - плохая практика для операционных систем, работающих в защищенном режиме, потому что это запрещает прерывания и блокирует прерывания для любого windows или dos-приложения. Intel предоставляет возможность перехватывать инструкции, которые могут изменить состояние флага прерывания (PUSHF, POPF, INT x, IRET, CLI, STI). Для этого предлагается механизм "виртуальных" флагов, другими словами - для каждого процесса создается свой виртуальный, а состояние действительного флага не затрагивается. Ранние версии спецификации DPMI 1.0 указывали, что виртуализованные команды STI и CLI требуют для исполнения 300 тактов, тогда как обычные 3-5, что сильно снижает производительность операционной системы в целом. Для быстрой работы системы Microsoft не виртуализует флаг в реализации VMM (Virtual Machine Manager) для Windows 95. Подробно эта намеренная недоработка Microsoft описана в книге Эндрю Шульмана "Неофициальная Windows 95".

#include "windows.h"
int PASCAL WinMain
(HINSTANCE hCurInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
for(;;)
_asm cli
return 0;
}

или аналог для сеанса MS DOS

codeSEGMENT byte public 'CODE'
ASSUME cs:code, ds:code
ORG 100h
begin:cli
jmpbegin
codeENDS
ENDbegin

В итоге получается всего 3 байта, действующие на систему уничтожающе - она "зависает" и вывести можно только "холодным" рестартом - нажатием "Reset". Для Windows NT этот код не фатален, там реализован механизм виртуальных флагов прерывания.

5. Пример: работа с приоритетами.

В системе запускается программа с приоритетом "реального времени" (Real Time Priority). Приоритет реального времени - это наивысший возможный приоритет в системе. Такой процесс вытесняет все процессы в системе, даже функционирующие системные процессы критичные для работы операционной системы в целом. Подобный процесс, задействованный на продолжительное время, может нарушить работу дискового кэша или "повесить" мышь, которая перестанет отвечать на команды.

Нажатие клавиш Ctrl-Alt-Del - единственный способ снять приведенный ниже процесс реального времени, который из-за отсутствия цикла обработки сообщений, как "вампир", захватывает процессор.

#include <windows.h>
#pragma hdrstop
int WINAPI
WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szLine, int Cmd)
{
SetPriorityClass(GetCurrentProcess(),REALTI-ME_PRIORITY_CLASS);
while(TRUE);
}

Эта процедура не пройдет в многопроцессорных системах, в Windows NT, где программа должна быть запущена с правами Increase Scheduling Priority.

6. Пример: работа с потоками.

Этот пример является усложненным вариантом предыдущего. Здесь используется высший приоритет (High Priority). Высший приоритет присваивается обычно задачам, критичным ко времени исполнения, которые должны быть выполнены немедленно для их корректной работы. Пример системного приложения, имеющего такой приоритет, - "Список Задач" Windows, вызываемый по Ctrl-Alt-Del, который также должен незамедлительно реагировать на вызов пользователем. Потоки, создаваемые процессом такой приоритетности, выгружают потоки процессов с "нормальными" (Normal Priority) или "простаивающими" (Idle Priority) приоритетами и могут захватить почти все доступные циклы процессора.

На запуск процесса с таким приоритетом не надо особенных прав. Планировщик, реализованный в Windows, не контролирует собственно процессы, а распределяет процессорное время между потоками. Если у какой-то задачи запущенных потоков будет на порядок больше, чем у других, то процессы, принадлежащие остальным задачам, остановятся, не получив процессорного времени. В этом примере запускается максимально возможное количество потоков, которое позволяют ресурсы системы.

#include <windows.h>
#pragma hdrstop
DWORD ThreadRoutine(LPDWORD lpdw_dummy_param)
{
while(TRUE);
return 0;
}
int WINAPI
WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szLine, int Cmd)
{
DWORD dw_dummy_param;
SetPriorityClass(GetCurrentProcess(),HIGH_PRIORITY_CLASS);
SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_HIGHEST);
while(TRUE)
{
CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadRoutine,
NULL,0,&dw_dummy_param);
}
return 0;
}

В результате запуска этого программного "вампира" Windows уходит в глубокий коллапс, прерываемый только нажатием кнопки "Reset".

P.S. Может сменить "операционку"? На Linux, например? Там свои "баги"...

Werewolf


Компьютерная газета. Статья была опубликована в номере 30 за 1999 год в рубрике программирование :: разное

©1997-2024 Компьютерная газета