Любите ли вы отзывчивые программы так, как люблю их я? Любовь эта привела меня к Колибри ОС - невероятно шустрой операционной системе, которая запускает программу до того, как вы осознаете, что кликнули по ней. И недавно у неё нашли уязвимость: ping of death. Так получилось, что моя первая работа была связана с симуляцией компьютерных систем – от серверов до мобильных устройств. И там мы использовали симулятор Simics. Этой системой пользуются крупные производители железа для опережающей разработки драйверов. Если бы только можно было использовать Simics для отладки любительской ОС... Баг описал пользователь turbocat на форуме: достаточно большой ICMP echo-запрос может убить один из процессов операционной системы или полностью вывести её из строя. Для воспроизведения проблемы нужно пингануть машину с запущенной системой Колибри. А для этого потребуется два компьютера, подключенных к одной локальной сети. Если бы только Simics мог симулировать компьютерные сети… Симуляция в Simics настраивается с использованием simics-скриптов, которые создают и настраивают устройства. Для воспроизведения можно создать свой скрипт: «targets/linux-kolibri.simics». Для начала нужна машина с Linux на борту. Это самая простая часть, такая машина включена в базовый дистрибутив Simics, и её можно создать, используя скрипт из пакета «simics-qsp-x86». Он даже сеть автоматически может настроить! Но здесь это не нужно: Этот скрипт уже можно запустить командой simics linux-kolibri.simics и введя continue в командной строке Simics (изначально симуляция находится в состоянии паузы). После этого видим графику и консоль Linux: Ещё нужен ПК с установленной Колибри. Его можно создать тем же скриптом, нужно только указать свой образ жесткого диска. А для упрощения работы можно настроить виртуальный USB-планшет - он позволит с комфортом использовать мышь внутри Колибри: Теперь скрипт запускает две системы: Но у Колибри проблема: она не распознаёт сетевую карточку. Не беда! В стандартную поставку Simics входит вагон устройств на любой вкус: от USB флешек до видеокарт. Есть и сетевые карточки. Нужно найти ту, которая поддерживается в Колибри ОС: Среди устройств Simics есть i82543 – должен заработать. Нужно только загрузить одноименный модуль, создать устройство и подключить его к свободному PCIe слоту на северном мосту машины. Главное – не забыть инициализировать новые устройства командой «instantiate-components»: Вуаля: новая сетевая карта определилась как «I8254x»: Теперь нужно как-то соединить эти две машины в одну сеть. Для этого достаточно создать коммутатор и подключить к нему сетевые карты машин: Чтобы не настраивать IP руками, можно поставить локальный DHCP сервер, который присвоит IP адреса автоматически. Для этого существует объект «Service node». Service node - это вообще универсальная штука: и DNS он тебе предоставит, и DHCP, и даже доступ к интернету. Нужно только его настроить: создать на нём шлюз по умолчанию, подключить его к коммутатору и указать пул выдаваемых адресов:   Ну и, конечно же, снова инициализировать все компоненты:   Если симулируемая машина ничем не нагружена, симуляция может идти очень быстро, что может приводить к таймаутам и разрывам соединений. Поэтому для ручного тестирования лучше включить режим реального времени:   Можно запустить этот скрипт и посмотреть IP адреса в Колибри:   И в Linux: И пингануть Колибри из Линукса:   Система настроена! Теперь можно воспроизводить проблему. Но для начала лучше сохранить текущее состояние симуляции, чтобы не загружать обе системы заново, если что-то пойдет не так: остановить симуляцию командой stop, и выполнить команду: Текущая конфигурация сохранится в папку «after_boot.ckpt». Её можно будет запустить просто передав Simics название этой папки вместо скрипта: simics after_boot.ckpt. У нашего Ping of Death два симптома: либо он убивает процесс сетевого драйвера, либо заставляет виснуть всю систему целиком. Возможно, если разобраться с первым случаем, будет исправлен и второй. В Колибри есть доска отладки, в которую выводятся логи приложений и ядра. Если открыть её в режиме ядра, можно запечатлеть падение процесса драйвера. Но чтобы понять, на каком размере эхо-запроса начинаются проблемы, придется несколько раз запускать ping из консоли Линукса, и если зависание таки произойдёт - перезапускать симуляцию и пинговать заново. Если бы только Simics позволял автоматизировать работу с ОС… Simics скрипты позволяют полностью автоматизировать взаимодействие с симулируемыми системами. Можно написать скрипт «ping-of-death.simics», который самостоятельно загрузит чекпойнт и пинганёт Колибри из Линукса. Скрипт с одним параметром – размер отправляемого echo-запроса. Это надуманный повод познакомить читателя с очередным понятием Это поможет быстрее найти значение, которое роняет драйвер. Для загрузки чекпойнта в Simics скриптах есть отдельная команда: А дальше нужно как-то ввести в консоль линукса команду «ping», причём сделать это нужно при запущенной симуляции. Но если написать в скрипте «continue», то следующие команды скрипта не выполнятся, пока пользователь сам не остановит симуляцию, как же быть? На помощь приходят Script Branches. Они позволяют выполнять команды параллельно с запущенной симуляцией. Можно создать такой, который введёт команду в консоль линукса: Осталось сделать так, чтобы можно было настроить размер пинга. У каждого скрипта могут быть собственные параметры, которые задаются при запуске. Обычно такие создаются в начале скрипта: Их можно использовать как обычные переменные:   А чтобы не приходилось вводить «continue» в консоль Simics можно ввести его уже в скрипте, в самом его конце: С таким скриптом можно спокойно экспериментировать с размерами пингов: Экспериментальным путём было выявлено, что размер пинга, при котором он перестаёт возвращаться: 1469, но драйвер падает на другом значении: 1473. И, судя по доске отладки, падение вызвано исключением Page Fault: Если бы только Simics мог показать, где возникло это исключение… В Simics предусмотрена тонна инструментов для отладки оборудования и драйверов. Например, точки останова: ставим такую на выполнение какого-то действия – и программа останавливается при её достижении. Можно поставить точку останова на вызов нужного исключения процессора: Тогда симуляция остановится на первом же возникшем Page Fault. Если сделать это в скрипте, то в консоли Simics это не будет никак отражено, он просто поставит симуляцию на паузу и разрешит ввод (simics>). Чтобы посмотреть, на какой инструкции возникло исключение, можно воспользоваться вторым инструментом: отладкой процессора. Начать отладку можно так: После чего появится контекст отладки «dbg», у которого есть такие полезные методы, как показ ассемблерного кода: В simics можно загрузить информацию о символах ОС, но, к сожалению, ассемблер, на котором написана Колибри, не умеет генерировать информацию нужного формата. Но к счастью – это ассемблер! А значит, можно выявить строку кода с ошибкой по инструкциям, окружающим этот код. Вот она: Внезапно, ECHO-запрос вызывает блок кода «dump» (очевидно, имелось в виду «drop» – отбросить пакет). И если вывести содержимое регистра edi, становится понятно, что оно совсем не такое, какое ожидается: В edi должен был бы быть индекс сетевого девайса, а лежит какой-то адрес. Но почему? И что привело систему в этот блок? Если бы только Simics умел выполнять код в обратном направлении… Прежде, чем воспользоваться функцией обратного выполнения в Simics, нужно включить её командой «enable-reverse-execution». Её можно выполнить после загрузки чекпойнта. Начиная с этого момента, можно будет выполнять код в обратном направлении. Для упрощения жизни можно тут же начать отладку: Запускается тест и срабатывает точка останова. Теперь можно пройтись на одну инструкцию назад: Если пройтись пару шагов назад, становится понятно, что пакет был сброшен при проверке размера собранного фрагментированного IP пакета. Дело в том, что отправить такой большой запрос одним IP пакетом нельзя, поскольку размер стандартного ethernet фрейма ограничен полутора килобайтами. Поэтому ping использовал IP фрагментацию и разделил запрос на два пакета. Но при объединении этих пакетов в один Колибри не смогла определить размер конечного пакета. Сумма размеров всех фрагментов равна одному: А если сложить размер последнего фрагмента и его смещение, получается другое: И это заставляет код дропать пакет. А если пройтись чуть дальше, можно заметить, что размер заголовка IP равен нулю, а размер пакета ненормально большой. Что же содержится в таком странном IP пакете? Для просмотра памяти можно использовать команду «x» процессора, который эту память видит: На первый взгляд, всё в порядке. И IP заголовок на месте, и размеры у него валидные. Но, почему-то, код берёт размеры не оттуда, где они находятся. К примеру, размер заголовка читается со смещения 0x0e, а в реальности он лежит по смещению 0x26. Очевидно два варианта: либо этот код не оттуда читает, либо кто-то не туда записывает. Судя по коду, этот буфер должен состоять из двух частей: сначала идёт структура «IPv4_FRAGMENT_entry», а затем сам IP пакет. Но внимание на себя обращает подозрительный комментарий к скрытому полю этой структуры: То есть подразумевалось, что эта структура лежит на месте ethernet-заголовка. Но что же тогда лежит в самом начале буфера? Если бы только Simics мог показать, кто писал туда до нашего кода… Для того, чтобы поставить точку останова на запись по виртуальному адресу можно воспользоваться командой «break», но перед этим нужно выбрать нужный процессор в качестве текущего:   Теперь можно развернуть симуляцию вспять и подождать, когда сработает точка останова: Первым попался код инициализации полей структуры «IPv4_FRAGMENT_entry», наш старый знакомый. Реверсируем дальше: Внезапно, теперь мы в ethernet.inc: По видимости, то, что мы видим в самом начале буфера, должно было быть структурой «NET_BUFF». А уже после него должна лежать наша «IPv4_FRAGMENT_entry». Дело не хитрое: мазок, другой - и в прод. Ну, а дальше идёт исправление остаточных проблем при помощи всё тех же инструментов. Сначала костылим восстановление значения edi перед обработкой ICMP запроса, чтобы драйвер не падал при проверке IP адреса. Главное не забыть потом привести это в нормальный вид. Затем разбираемся с несовпадением чексуммы исправляя алгоритм сборки IP фрагментов. Ну и, напоследок, исправляем стек внутри кода сборки пакета, поскольку этот код был написан ещё в те времена, когда обработчики сетевых протоколов принимали размер пакета как аргумент. Результат: Колибри живёт! Правда, есть нюанс: хоть IP пакет и собирается без ошибок, обработать его Колибри пока не может. Но это - уже совсем другая история… Simics имеет достаточное количество инструментов для разработки и отладки операционных систем, а система скриптов позволяет автоматизировать тестирование и воспроизведение проблем. Сильные стороны системы: Возможность обратного выполнения кода. Обширный инструментарий для отладки. Богатый скриптовый язык с интегрированным Python. Большое количество эмулируемых девайсов и возможность написания своих. Слабые стороны: Медлительность ввиду избыточного функционала. Проприетарность. Лично я предпочитаю использовать её при отладке багов в ядре и для тестирования драйверов. В прочих тестах использую Qemu.