Лев Казаркин, Удивительные приключения регистров SSE или в поисках одного бага
1. Click to edit Master title style
Об удивительных
приключениях
регистров SSE и
поиске одного
бага
Лев Казаркин
Лаборатория Касперского
Группа шифрования данных
2. Click to edit Master title style
Стр. 2
Full Disk Encryption от Касперского
Сокращенно: FDE
Похоже на: TrueCrypt, Bitlocker, только лучше ;)
Шифруем мы весь диск целиком, а не только отдельные тома.
Предоставляем предзагрузочную аутентификацию – по
логину/паролю (а сейчас еще и по смарт-картам).
Компьютер нельзя загрузить (с системного диска) в обход этого
агента, да и смысла нет – все же зашифровано.
Решение централизованно управляется с единого сервера
(Kaspersky Security Center).
Шифруем безопасно, быстро, недорого, без SMS... ;)
3. Click to edit Master title style
Стр. 3
Исторический экскурс
• Основано на реальных событиях.
• Время действия: Ноябрь 2013.
• Второй релиз технологии в продукте Kaspersky Endpoint Security (KES 10).
• Стадия высокой готовности к релизу - идет внутреннее развертывание
продукта (IRO).
• Internal Rollout или dogfooding (как говорят в Microsoft) – "ешь еду своей
собаки".
4. Click to edit Master title style
Стр. 4
Дальше по плану
1. Матчасть - как устроено FDE от Касперского.
2. Завязка истории. Затем, ее пошаговое развитие. Интерактив!
3. Разоблачение.
Не забываем, что у нас детектив, проявляем активность
5. Click to edit Master title style
Стр. 5
Архитектура FDE v.1 (endpoint)
6. Click to edit Master title style
Стр. 6
Агент предзагрузочной аутентификации выглядел так
8. Click to edit Master title style
Стр. 8
Завязка
2013/11/1 от саппорта приходит внутренний баг с IRO.
Пользователь из далекой Великобритании пришел утром на работу и
обнаружил, что его рабочий комп превратился в кирпич.
FDE радостно мигает текстовым курсором на черном экране и сообщает, что
кто-то испортил метаданные FDE.
9. Click to edit Master title style
Стр. 9
Завязка
2013/11/1 от саппорта приходит внутренний баг с IRO.
Пользователь из далекой Великобритании пришел утром на работу и
обнаружил, что его рабочий комп превратился в кирпич.
FDE радостно мигает текстовым курсором на черном экране и сообщает, что
кто-то испортил метаданные FDE.
10. Click to edit Master title style
Стр. 10
"Развал" метаданных
Метаданные здорового человека:
Поврежденная копия:
11. Click to edit Master title style
Стр. 11
Первое впечатление
•Н у да, у нас есть какой-то неприятный баг. Может быть, в пребуте.
• Не страшно. Подумаешь, баг у одного пользователя!
• В целом качество продукта не вызывает сомнения.
• Продукт же прошел отдел тестирования, преодолел нагрузочное
тестиорование на стендах, да и баг проявился только у одного пользователя.
• Багу был назначен невысокий, обычный приоритет.
НО!
Не понятно как его воспроизводить. Что является триггером?
Ни на виртуалках, ни на реальном железе не воспроизводится.
Как исправлять то, что не воспроизводится?
12. Click to edit Master title style
Стр. 12
Гипотеза 1
Включаем воображение!
Что может вызвать порчу данных?
•Аппаратный сбой – не интересно.
•Конфликт клиентов при одновременном доступе! Вот это уже что-то.
13. Click to edit Master title style
Стр. 13
Гипотеза 1
Включаем воображение!
Что может вызвать порчу данных?
•Аппаратный сбой – не интересно.
•Конфликт клиентов при одновременном доступе! Вот это уже что-то.
14. Click to edit Master title style
Стр. 14
Проверка
• Организовали тестирование.
• Два клиента в одном Продукте.
• Запуск двух экзампляров Продукта одновременно.
• ...
15. Click to edit Master title style
Стр. 15
Проверка
• Организовали тестирование.
• Два клиента в одном Продукте.
• Запуск двух экзампляров Продукта одновременно.
• ...
Синхронизация в user mode стойко вынесла все нападки.
Конфликта 2х продуктовых клиентов быть не может!
16. Click to edit Master title style
Стр. 16
Гипотеза 2. Конфликт Продукта с гибернацией?
17. Click to edit Master title style
Стр. 17
Гипотеза 2. Конфликт Продукта с гибернацией?
Теоретическая возможность этого обсуждалась уже давно.
Но вероятность этих событий в реальной жизни казалась (и была) очень
малой.
Правильное решение было дорогим.
Воспроизвели с первого раза.
Ага, кажется, это то, что нужно.
Фикс получился очень сложный. Пришлось разработать новый механизм
синхронизации доступа к метаданным.
Изменить архитектуру, и это во время релиза :)
Синхронизация была сделана на основе драйвера
Пока шел доступ, гибернация в системе была заблокирована.
18. Click to edit Master title style
Стр. 18
2013/11/14, вечер.
• Исправления были закоммичены после тщательной проверки и ревью.
• Проблема исправлена.
• Команда торжествовала.
• Конфликт проверяли ручным тестом на разных машинах.
• Процесс 1: модифицирует метаданные в цикле.
• Процесс 2: вызывает гибернацию, циклически.
19. Click to edit Master title style
Стр. 19
2013/11/14, вечер.
Был оставлен на ночь Процесс 1, на всякий случай.
20. Click to edit Master title style
Стр. 20
2013/11/15, 11 часов дня. Пятница.
Что увидели разработчики?
21. Click to edit Master title style
Стр. 21
2013/11/15, 11 часов дня. Пятница.
ASSERTION FAILED
Метаданные изменены после или во время записи на диск.
Насчитанная контрольная сумма не совпадает с записанной.
22. Click to edit Master title style
Стр. 22
2013/11/15, 11 часов дня. Пятница.
ASSERTION FAILED
Метаданные изменены после или во время записи на диск.
Насчитанная контрольная сумма не совпадает с записанной.
Та-да-да-даам.
• Один клиент
• Никакой гибернации
• Машина надежная (виртуальная)
23. Click to edit Master title style
Стр. 23
Пожар
• Замечательно исправили проблему, но не ту!
• Проблема никуда не делась!
• У нас есть воспроизведение!
• Совершенно не понятно что происходит.
24. Click to edit Master title style
Стр. 24
Пожар и сверхурочные
Команда мобилизована на воспроизведение.
• Проблема вопроизводится, воспроизвести сумели почти все, особенно дело
хорошо шло на Windows XP.
• Есть условия, сильно улучшающие воспроизведение. Например, если диск
активно шифруется, оно ускоряется в разы.
• Еще быстрее воспроизведение наступает, если создать сильную нагрузку на
диск.
• Чемпионом стала однопроцессорная виртуалка с XP. Воспроизведение
через минуту!
Да, в те годы мы еще поддерживали XP ;)
25. Click to edit Master title style
Стр. 25
Пожар и сверхурочные
Команда мобилизована на воспроизведение.
• Проблема вопроизводится, воспроизвести сумели почти все, особенно дело
хорошо шло на Windows XP.
• Есть условия, сильно улучшающие воспроизведение. Например, если диск
активно шифруется, оно ускоряется в разы.
• Еще быстрее воспроизведение наступает, если создать сильную нагрузку на
диск.
• Чемпионом стала однопроцессорная виртуалка с XP. Воспроизведение
через минуту!
Да, в те годы мы еще поддерживали XP ;)
Что происходит? Как это возможно?
26. Click to edit Master title style
Стр. 26
На самом деле
• Для воспроизведения проблемы модифицировать метаданные не нужно
вовсе.
• Достаточно их просто читать.
• Чтение метаданных у нас идет через драйвер, через особый IOCTL.
Почему?
• Т.к. весь диск зашифрован, и все что читает/пишет Windows, шифруется...
• Да-да, даже PAGEFILE, HIBERFIL, и даже "синие дампы" - вообще все.
• …Доступ к метаданным должен идти другим, нелегальным каналом. То
есть, через драйвер.
27. Click to edit Master title style
Стр. 27
Тест на чтение метаданных через драйвер
Был сделан максимально простой тест.
Тест читал с диска через драйвер (тем самым волшебным IOCTL-ом).
Ничего лишнего - выделяем память, и читаем в нее с известного места на
диске известные данные и сравниваем с образцом.
На волшебной виртуалке с XP – воспроизводилось хорошо. У других
разработчиков показания разнятся.
• Буфер на стеке – нет проблемы.
• Холодный буфер через VirtualAlloc() – как из пушки.
28. Click to edit Master title style
Стр. 28
Тест на чтение метаданных через драйвер
Был сделан максимально простой тест.
Тест читал с диска через драйвер (тем самым волшебным IOCTL-ом).
Ничего лишнего - выделяем память, и читаем в нее с известного места на
диске известные данные и сравниваем с образцом.
На волшебной виртуалке с XP – воспроизводилось хорошо. У других
разработчиков показания разнятся.
• Буфер на стеке – нет проблемы.
• Холодный буфер через VirtualAlloc() – как из пушки.
Варианты:
• виноват драйвер, который как-то странно работает с памятью буфера, тут
как-то замешан PAGING,
• кто-то стреляет по памяти, внутри процесса, ну или из драйвера как вариант.
29. Click to edit Master title style
Стр. 29
Стресс-тест на драйвер с агрессивным пейджингом
• Система была еле жива под
тяжестью теста.
• Но жива.
• Для чистоты эксперимента
взяты свежие виртуалки, с
современными OS.
• Windows 7 64,
• Windows 8 64,
• новейшая Windows 8.1 64,
• разные конфигурации,
контроллеры диска...
30. Click to edit Master title style
Стр. 30
Стресс-тест на драйвер
• Система была еле жива под
тяжестью теста.
• Но жива.
• Для чистоты эксперимента
взяты свежие виртуалки, с
современными OS...
НИЧЕГО
31. Click to edit Master title style
Стр. 31
Тем временем другие разработчики...
Проверялись самые безумные теории.
Проверку читаемого с диска буфера довели до полной паранойи.
32. Click to edit Master title style
Стр. 32
What?
• Внезапно.
• Все происходит в юзер моде, без участия драйвера.
• Мы просто копируем данные в памяти.
В псевдокоде
copy(A, B);
ASSERT(B == ETALON);
ASSERT(A == ETALON); // <--- FAIL!
33. Click to edit Master title style
Стр. 33
What?
• Внезапно.
• Все происходит в юзер моде, без участия драйвера.
• Мы просто копируем данные в памяти.
В псевдокоде
copy(A, B);
ASSERT(B == ETALON);
ASSERT(A == ETALON); // <--- FAIL!
35. Click to edit Master title style
Стр. 35
На самом деле, ч.2
Единственной архитектурой, на которой воспроизводилась проблема, на
самом деле была Intel IA32 (x86).
Все усилия поиска проблемы со стороны драйвериста были направлены на
эту архитектуру AMD64 (тем более что IA32 уже занимаются другие люди ;))
Между архитектурами IA32 и AMD64 есть существенная разница в том, как
они работают с плавающей точкой и регистрами SSE.
Windows реализует ленивое сохранение контекста FPU (SSE).
Экономия
36. Click to edit Master title style
Стр. 36
Ленивое сохранение FP контекста
37. Click to edit Master title style
Стр. 37
Ленивое сохранение FP контекста
38. Click to edit Master title style
Стр. 38
Ленивое сохранение FP контекста
39. Click to edit Master title style
Стр. 39
На самом деле, ч.2
Между архитектурами IA32 и AMD64 есть существенная разница в том, как
они работают с плавающей точкой и регистрами SSE.
Windows реализует ленивое сохранение контекста FPU (SSE).
Экономия
Так вот...
• В ядре ОС на IA32 всякое сохранение контекста FPU/SSE должен явно
делать программист.
• На AMD64 FPU/SSE входит в стандартный набор регистров,
сохраняемый в KTRAP_FRAME, то есть, при любом переключении
потоков, прерывании и т.д.
40. Click to edit Master title style
Стр. 40
Догадка, которая напрашивалась, была чудовищна
- Лев, похоже криптомодуль ядра портит конекст SSE.
- Угу, но ты представляешь, что это значит? Это значит, что проблеме подвержены все потоки в
системе. ВСЕ.
Действительно, разве может система еще шевелиться, как ни в чем ни бывало? Это же ходячий
труп.
- ВСЕЕЕ. Да, это совершенно невероятно. Поэтому я не думаю, что моя гипотеза верна.
41. Click to edit Master title style
Стр. 41
2013/11/17, вечер.
• Деваться некуда.
• Но как конкретно драйвер портит регистры в чужих процессах?
• Почему усиление дисковой активности усиливает эффект.
• И при чем тут PAGING, почему проблема воспроизводится только при
обращении к холодной памяти?
• Все входы и выходы в ядерный криптомодуль перекрыты.
• Вход в криптопримитив - сохранение (KeSaveFloatingPointState()).
• Выход из криптопримитива - восстановление (KeRestoreFloatingPointState()).
• Все это завернуто в такой плюсовый автоматический scoped guard, мышь не
проползет.
• Однако, факты говорили другое.
42. Click to edit Master title style
Стр. 42
2013/11/17, вечер.
Было сделано 2 вещи:
• был собран криптомодуль без инструкций SSE/AES NI,
• и в драйвер-фильтр, перед каждым обращением к крипте, была добавлена
собственная пара сохранение - восстановление контекста.
Оба подхода показали, что проблема уходит. Действительно уходит.
Печальный тимлид настраивал поставку криптомодуля без SSE/AES NI
оптимизации.
Это был наш план Б.
А без SSE2 и AES NI шифрование работает в 4-5 раз медленнее.
43. Click to edit Master title style
Стр. 43
IDA Pro
Метод скрупулезного и тщательного вглядывания в ядерный криптомодуль
принес нам это:
44. Click to edit Master title style
Стр. 44
IDA Pro
Это тело функции SymmetricContext::OSSL_Transform(), той самой, которая
выполняет симметричное шифрование/расшифровывание блочным
шифром.
45. Click to edit Master title style
Стр. 45
SymmetricContext::OSSL_Transform()
Вот так она выглядит в коде
46. Click to edit Master title style
Стр. 46
SymmetricContext::OSSL_Transform()
А ExecutionContext это структурка, которая как раз в среде ядра призвана
сохранять FPU context.
47. Click to edit Master title style
Стр. 47
IDA Pro
Итак
Что это? SSE инструкции в коде конструктора?
Зачем и откуда они там?
48. Click to edit Master title style
Стр. 48
Разоблачение
• Спасибо всем, кто помогал команде FDE найти баг ;)
• Кто-то за 2 месяца до этого включил SSE2 оптимизацию для всего проекта
cm_km.sys, ядерного криптомодуля.
• Оптимизация благоприятно сказалась на скорости работы крипты :)
• Хотя алгоритмы блочного шифрования на SSE2 и AES-NI написаны на
асме, и в оптимизации такого рода не нуждались.
• Оказался соптимизирован весь код, даже тот, который находится снаружи
оберток KeSaveFloatingPointState()/KeRestoreFloatingPointState().
• Хотя его вроде бы и нет. Или есть?
• Зануление полей объекта в одном конструкторе, через memset(),
заиспользовало только один раз регистр xmm0.
• И все
49. Click to edit Master title style
Стр. 49
Схема происшествия
50. Click to edit Master title style
Стр. 50
Happy end
Решение было простым - код крипты вынесли из драйвера в отдельную
статическую библиотеку, и наступило щастье.
2013/11/19. Наш исправленный оптимизированный криптомодуль заработал.
2013/11/22. После тщательнейшей проверки, мы сделали поставку и
отчитались об исправлении бага.
Разработчики получили большие премии за сверхурочную работу.
А радости менеджмента не было конца.
Вывод?
51. Click to edit Master title style
Стр. 51
Вывод, наша версия
Единственное, что
вас спасет - это
способность вашей
команды быстро
реагировать, когда
это случится.
52. Click to edit Master title style
Стр. 52
Конец
Спасибо за внимание!
lev.kazarkin@kaspersky.com
lev.kazarkin@gmail.com
Notes de l'éditeur
Здравствуйте, друзья!
Если вы подумали, что это портрет Евгения Касперского, то и продолжайте так думать ;)
Я Лев Казаркин. Работаю в Лаборатории Касперского.
И сегодня я расскажу вам историю из жизни команды полнодискового шифрования.
Вы знаете, сейчас стало модно все шифровать.
И вот, мы в Лаборатории Касперского тоже шифруем.
Наверное, многие слышали о таких технологиях, как TrueCrypt, Bitlocker?
Вот, мы создали и продвигаем похожую технологию, правда шифруем мы весь диск целиком, а не только отдельные тома.
По-английски Full Disk Encryption.
Правильное сокращение - FDE (Эф-Ди-И).
Но за 5 с лишним лет мы так привыкли говорить Эф-Дэ-Е, что нас уже не переделать.
Поэтому, когда я буду говорить "Эф-Дэ-Е", прошу отнестись с пониманием :)
И да, шифруем мы безопасно, быстро, недорого, без SMS... ;)
История, о которой вы сегодня услышите, произошла на самом деле, около 3х лет назад.
Но участники тех собыий помнят все, как будто это случилось вчера. Настолько сильны были пережитые эмоции.
В те дни готовился второй релиз нашей технологии в составе продукта Kaspersky Endpoint Security.
Это такой комбайн по информационной безопасности для корпоративного применения.
В нем много всего, начиная от знаменитого антивируса, защиты от сетевых атак, и вот, FDE.
И вот, дело зашло так далеко, что шло внутреннее развертывание продукта (IRO).
У нас, как и у многих, принята практика, обозначаемая метафорой "сам ешь еду своей собаки".
То есть, мы испытываем наши изделия сначала на себе, и только потом уже мучаем наших клиентов.
К сожалению, время не позволяет рассказать все смачные подробности.
В истории, которая держала команду из 6 разработчиков в напряжении, а руководство в панике порядка 2х недель, более 40 эпизодов.
Я постарался выбрать самое интересное, что укладывается в ограниченное время нашей встречи.
Я должен заранее извиниться перед теми слушателями, кто пришел послушать историю об идеоматическом C++, в котором наш герой при помощи лямбда-выражений, корутин и шаблонной магии отматывает "столько веревки, чтобы выстрелить себе в ногу".
Сегодняшняя история из мира системного программирования.
Наш герой тоже стреляет, но не из ружъя, а из пушки.
И не в ногу, а в голову.
И не в себя, а в соседа. Но и в себя тоже попадает.
Так что скорее, наоборот, я покажу как с легкостью плывут и рушатся все прекрасные воздушные замки идиом программирования, с одного неосторожного движения.
Если вам все еще интересно, оставайтесь с нами. Будет весело.
Я не просто рассказываю историю. У нас здесь детектив.
Это предполагает активную вовлеченность в процесс зрителей. То есть, вас.
Я построил свой рассказ так:
0. Самопрезентация. Ее вы уже прослушали, спасибо ;)
1. Матчасть. Кратко введу вас в основы матчасти - как устроено FDE от Касперского.
2. Завязка истории. Затем, ее пошаговое развитие. Интерактив. Заинтригую вас и дам некоторую отправную точку для начала рассуждений. Помните, это детектив, и наша конечная цель - вычислить убийцу. Когда (если) ваш поток идей будет иссякать, я буду делать следующий шаг в рассказывании истории, показывая еще несколько слайдов. В принципе, вы можете поднимать руку и высказывать свою теорию в любой момент на этой фазе.
3. Ну и наконец, полное разоблачение. Независимо от того, насколько точно вами будет угадан источник проблемы, я раскрываю карты.
Поехали!
А еще у диска есть метаданные. Вот так в упрощенном виде можно их представить.
2013/11/1 от саппорта приходит внутренний баг с IRO.
Пользователь из далекой Великобритании пришел утром на работу и обнаружил, что его рабочий комп превратился в кирпич.
FDE радостно мигает текстовым курсором на черном экране и сообщает, что кто-то испортил метаданные FDE.
Испортил так, что верить им нельзя и надо звать какого-то системного администратора.
Видимо, обычно администратор приходит и молча все поправляет ;).
Это был развал метаданных.
Вот вам для сравнения – метаданные здорового человека.
И поврежденная копия.
В принципе, все то же самое, только вот с хедером ему как-то не повезло
Вы же помните, что наше решение надежно и безопасно. Так вот, метаданные хранятся на диске в 3х независимых копиях.
Практика показала это решение весьма эффективным, дающим колоссальную живучесть FDE от Касперского даже в случае попыток переразбить диск, при появлении бэд-блоков и так далее.
И все 3 экземпляра были испорчены совершенно одинаковым образом.
Начало заголовка (1ый сектор экземпляра метаданных) было испорчено. Контрольная сумма соответствовала содержимому метаданных с целым заголовком.
То есть, кто-то или что-то испортило данные либо на диске, либо в процессе записи после того как был насчитан хэш.
Пришлось крепко взяться за репку и включить воображение.
После тщательного всматривания в код, работающий с метаданными, тот, который обеспечивает ввод-вывод с диска, возникла единственная логичная гипотеза.
Нам казалось, единственное, что могло с какой-то вероятностью привести к подобной проблеме (кроме аппаратного сбоя) - это конфликт 2х одновременных клиентов, пытающихся обратиться к метаданным.
Например, так (рис 1).
Пришлось крепко взяться за репку и включить воображение.
После тщательного всматривания в код, работающий с метаданными, тот, который обеспечивает ввод-вывод с диска, возникла единственная логичная гипотеза.
Нам казалось, единственное, что могло с какой-то вероятностью привести к подобной проблеме (кроме аппаратного сбоя) - это конфликт 2х одновременных клиентов, пытающихся обратиться к метаданным.
Например, так.
Синхронизация в юзер моде оказалась достаточно крепкой и с честью выдержала все эти нападки.
Мне кажется, коллеги, здесь самое время начать строить догадки. Если у кото-то есть мысли, я прошу высказываться.
Второй, более сложный вариант конфликта 2х клиентов, который мы рассматривали, это конфликт с гибернацией.
Что происходит на этом страшном черно-белом слайде?
Это может произойти, например, тогда, когда Продукт идет в фасад FDE, чтобы порулить политикой.Открывает метаданные, что-то делает.В это время машина уходит в гибернацию (или в гибридный сон), вместе с Продуктом.Потом, при пробуждении, стартует пребутовый агент аутентификации.Видит незакрытую транзакцию на метаданных, очень удивляется, откатывает ее и как ни в чем не бывало, загружает систему.И тут Продукт просыпается.Ах да, на чем я остановился! Ага, все, теперь коммит!И поверх уже измененного метафайла накатывается коммит старого варианта данных.На диске фарш, машина после следующей перезагрузки - кирпич.
Оценили коварство сценария?Теоретическая возможность этого обсуждалась уже давно.Но вероятность этих событий в реальной жизни казалась столь малой, а правильное решение - таким дорогим, что закрытие этой дыры всегда откладывалось.Воспроизвели с первого раза. Ага, кажется, это то, что нужно.Фикс получился очень сложный. Пришлось разработать новый механизм синхронизации доступа к метаданным.Изменить архитектуру, и это во время релиза :)Синхронизация была сделана на основе драйвера, который давал доступ к метаданным диска только одному клиенту.В том числе и самому себе, он давал доступ на общих основаниях.И это важно, пока шел доступ, гибернация в системе была заблокирована.
2013/11/14, вечер. Исправления были закоммичены после тщательной проверки и ревью.Проблема была исправлена. Команда торжествовала.Чтобы убедиться, что новый сложный механизм работает надежно, был слеплен грязный и быстрый ручной тест.Который включал 2 процесса 1 в бесконечном цикле долбит метаданные - сначала открывает, затем что-то меняет, ну и делает коммит. И так далее.
2ой в бесконечном цикле кладет систему в гибернацию. Потом небольшой таймаут, и снова.
Это потестировали руками на виртуалках и физических машинах.Как вы понимаете, чтобы пробуждать машину из гибернации, нужен человек.А значит, гонять такой тест в автоматическом режиме было затруднительно.
На ночь мы поставили только 1ый процесс без 2го на виртуалке и физической машине.Примерно с таким кодом.
На всякий случай. Чтобы быть еще больше уверенными, что у нас все хорошо.И, довольные собой, разошлись по домам.
2013/11/15, 11 часов дня. Пятница.Отдохнувшие после вчерашнего кошмара разработчики вышли на работу.Каково же было их удивление, когда они между делом поинтересовались, как там наш тест.Подойдя к машине, они увидели...
2013/11/15, 11 часов дня. Пятница.Отдохнувшие после вчерашнего кошмара разработчики вышли на работу.Каково же было их удивление, когда они между делом поинтересовались, как там наш тест.Подойдя к машине, они увидели...
А вот и нет. Они увидели, что сработал ASSERT.Тест работал в отладочной конфигурации.Он проехал порядка 8 часов и остановился.И вот, в одной из операций коммита в метаданные, после сотен тысяч итераций, контрольная сумма на метаданные, насчитанная после коммита не сошлась с той, что записана на диск. Да, да, в дебажном коде делалась, и делается по сей день эта параноидальная проверка.
Та-да-да-даам.
Никакой гибернации. Никакого конфликта при доступе, один клиент.Произошло то, чего никто не ожидал.Был подключен отладчик, и мы увидели картину порчи заголовка метаданных.Первые 16 байт затерты нулями. Все как у пользователя из далекой Британии.
Стало понятно, что мы замечательно исправили проблему, но не ту.Стало понятно, что проблема никуда не делась.Но! У нас появилось живое воспроизведение!И было совершенно непонятно что происходит.Об всем этом было немедленно доложено руководству.И с этого момента началась паника и пожар. Паника и пожар, как вы понимаете, с той минуты не прекращались вплоть до самого хэпи энда.Ну а нам с вами до этого хэппи-энда еще очень далеко.
Все, кого было возможно мобилизовать, были мобилизоаны на работу в выходные.Был организован непрекращающийся мозговой штурм.
Все, и стар и млад, пытались воспроизводить.
На разных конфигурациях аппаратуры, с разной версией OS, на разных стадиях шифрования диска.Довольно скоро стали появляться факты:
Проблема вопроизводится, воспроизвести сумели почти все, особенно дело хорошо шло на Windows XP.
Есть условия, сильно улучшающие воспроизведение. Например, если диск активно шифруется, оно ускоряется в разы.
Еще быстрее воспроизведение наступает, если создать сильную нагрузку на диск.
Но чемпионом стала однопроцессорная виртуалка с XP. Воспроизведение там (под нагрузкой) наступало через минуту. Да, в те годы мы еще поддерживали XP ;)
Команда была в шоке. Что происходит? Как это возможно?Строили мы строили, и наконец построили (C).Мы же тестировали - успокаивали мы свою совесть :)Мы же все сделали - автоматический запуск юнит-тестов на конвейере, и нагрузочные тесты были.И тестлаб провел все тесты по тестпланам. Были задействованы опытные тестировщики.Еще больше удивлялось руководство направления FDE.И уже совсем сильно недоумевало руководство Продукта.
Команда была в шоке. Что происходит? Как это возможно?Строили мы строили, и наконец построили (C).Мы же тестировали - успокаивали мы свою совесть :)Мы же все сделали - автоматический запуск юнит-тестов на конвейере, и нагрузочные тесты были.И тестлаб провел все тесты по тестпланам. Были задействованы опытные тестировщики.Еще больше удивлялось руководство направления FDE.И уже совсем сильно недоумевало руководство Продукта.
На другой день стало понятно, что для воспроизведения проблемы модифицировать метаданные не нужно вовсе.Достаточно их просто читать. Много, под хорошей нагрузкой на диск.Чтение метаданных у нас идет через драйвер, через особый IOCTL.Почему? Да потому, что при обычном обращении к диску, напрмер, через файловый API, данные подвергаются шифрованию.Вы же помните, мы - полнодисковое шифрование.Все, что пишет или читает Windows, мы шифруем (расшифровываем). Включая PAGEFILE, файл гибернации и даже "синие дампы" - вообще все.А метаданные не подлежат шифрованию, по очевидным причинам.Так что если надо поработать с ними, надо провезти данные как-то нелегально.
Был сделан максимально простой тест. Тест читал с диска через драйвер (тем самым волшебным IOCTL-ом).Ничего лишнего - выделяем память, и читаем в нее с известного места на диске известные данные и сравниваем с образцом.На той самой волшебной виртуалке с XP новый тест также воспроизводил проблему на ура.И вот тут сюрпризы продолжились.Оказывается, если выделить небольшой буфер на стеке, и читать в него, проблема перестает воспроизводиться.А вот если каждый раз вызывать VirtualAlloc(), и читать в холодную память, то как из пушки.Первые 16 байт - нули.Никому ничего не напоминает? У кого есть идеи, что тут происходит?
Правильно, любой опытный разработчик скажет вам, что тут 2 варианта:
* виноват драйвер, который как-то странно работает с памятью буфера, тут как-то замешан PAGING, к бабке не ходи,
* кто-то стреляет по памяти, внутри процесса, ну или из драйвера как вариант.
Драйвер FDE - довольно сложная штука. В нем теоретически много чего может быть, разные проблемы.Некоторые проблемы приходилось вылавливать несколько дней, такие как гонка между запросами на ввод-вывод.Могла быть и гонка с memory manager-ом при пейджинге страниц.Короче, нужен был стресс-тест на ввод-вывод через драйвер в холодную память.
Правильно, любой опытный разработчик скажет вам, что тут 2 варианта:
* виноват драйвер, который как-то странно работает с памятью буфера, тут как-то замешан PAGING, к бабке не ходи,
* кто-то стреляет по памяти, внутри процесса, ну или из драйвера как вариант.
Драйвер FDE - довольно сложная штука. В нем теоретически много чего может быть, разные проблемы.Некоторые проблемы приходилось вылавливать несколько дней, такие как гонка между запросами на ввод-вывод.Могла быть и гонка с memory manager-ом при пейджинге страниц.Короче, нужен был стресс-тест на ввод-вывод через драйвер в холодную память.
Внимательное изучение кода драйвера, написание жесточайших стресс тестов, специально сталкивающих пейджинг и чтение в ту же память через драйвер, никак не помогло.Под этими тестами система кряхела, ползла, замирала, дергалась.Система была еле жива.Ее кобасило, но она не умирала, и не обнаруживала проблемы коррапта данных.Были взяты специально другие, свежие виртуалки, для чистоты эксперимента, так сказать.С современными актуальными операционными системами.Кто его знает, может быть с виртуалкой, дававшей самое стабильное воспроизведение, что-то не чисто?Проблема настолько мутная, что факты требовали проверки и перепроверки.Баг не хотел проявляться. Упорно, несмотря на то, что я тестировал на множестве конфигураций: на Windows 7 64,
на Windows 8 64,
на новейшей Windows 8.1 64,
с разным числом процессоров, количеством памяти, разным типом дискового контроллера.
Результат?
Ничего. Не воспроизводится. Вообще.
Параллельно другие разработчики продолжали изучать то, что давало какие-то результаты.Проверялись самые безумные теории.Проверку читаемого с диска буфера довели до полной паранойи.Буфер читался с диска, затем копировался в другой буфер, и потом оба сравнивались между собой и с эталоном.Примерно вот так.
Никто уже не мог толком объяснить зачем и почему мы проверяем то, что казалось бы, проверено и очевидно.Команда, понимая, что мы провалились в бочку со сметаной, что называется, "взбивала сметану в масло".
И вот, один из таких тестов выявил вещь, которая просто взорвала мозг.При некоторых копированиях прочитанных данных в другой буфер, а все происходит в юзер моде, без участия драйвера.Так вот, в начале одной из страниц возникали те же 16 байтов нулей.Все, что было задействовано в промежутке от хорошего буфера - к плохому, это только функция memcpy().Еще раз, медленно, в псевдокоде
copy(A, B);ASSERT(B == ETALON);ASSERT(A == ETALON); // <--- FAIL!
Это воспроизвелось сначала один раз, потом другой.Разработчики отказывались верить глазам своим.
Вот такой была моя реакция.
Слишком уж волшебной была эта виртуалка, до подозрительного подозрительная.Но мы были просто обязаны найти объяснение - это же происходило с нашим кодом, черт возьми!Поскольку в проекте была включена SSE2-оптимизация, это был векторизованный вариант memcpy().Выглядел он примерно так.
Высказывайтесь, коллеги. Может быть, у кого-то возникли вопросы?
Внимательный зритель, наверняка уже заподозрил неладное. Некоторую нестыковку в рассказе.Чуть выше я говорю про то, что жесточайшие тесты на драйвер не выявили проблемы.И тут же сравнительно простой тест у другого разработчика, на его виртуалке, находит проблему легко.Дело в том, что мы упустили из виду важный нюанс.Единственной архитектурой, на которой воспроизводилась проблема, на самом деле была Intel IA32 (x86).А с самого начала кто-то, уже невозможно установить кто, пустил следствие по ложному следу, сообщив, что проблема воспроизводится на архитектуре AMD64 (Intel EM64T) тоже.Неудивительно, что, раз эта архитектура более сложная, более важная на практике и основные усилия поиска проблемы со стороны драйвериста были направлены на эту архитектуру.И ничего не находилось.
Многие опытные разработчики знают это, но я на всякий случай напомню.Дело в том, что между архитектурами IA32 и AMD64 есть существенная разница в том, как они работают с плавающей точкой и регистрами SSE.На IA32, еще со времен процессора 286, сопроцессор архитектурно отделен от набора регистров общего назначения.И Windows (да и другие операционные системы) исповедуют ленивое сохранение контекста. Они экономят на лишних операциях выгрузки/загрузки регистров FPU/SSE исходя из соображения, что далеко не все программы используют эти регистры, и поэтому на оттягивании момента сохранения этих регистров можно сэкономить.
Работу механизма сохранения FPU контекста можно проиллюстрировать таким примером.
Это когда-то давала хорошое сокращение задержки на переключении контекста потоков.В наше время повальной оптимизации всего под SSE/SSE2 и так далее, это уже скорее лишнее усложнение, которое мало что экономит.И тем более в ядре ОС всякое сохранение контекста FPU/SSE должен явно делать программист.Вот такой закат солнца вручную.В AMD64 разработчики отбросили многое из старого балласта и включили регистры SSE в стандартный набор регистров, сохраняемый в KTRAP_FRAME, то есть, при любом переключении потоков, прерывании и т.д.Но если мы захотим использовать совсем новые регистры, типа из расширения AVX, история повторяется. Нам опять придется заботиться об их сохранении в ядре.
- Лев, похоже криптомодуль ядра портит конекст SSE, - осторожно сказал мне коллега.- Угу, - ответил я, - но ты представляешь, что это значит? Это значит, что проблеме подвержены все потоки в системе. ВСЕ.
Действительно, разве может система еще шевелиться, как ни в чем ни бывало? Это же ходячий труп.- ВСЕЕЕ. Да, - соглашался коллега Евгений, - это совершенно невероятно. Поэтому я не думаю, что моя гипотеза верна.
Это дословная цитата из диалога разработчиков, которая сохранилась в чате.И ярчайший пример того, как люди до конца отрицают уже практически очевидные, но невероятные, и пугающие их страшные факты :)
Деваться некуда. К концу того дня мы были готовы принять уже эту гипотезу.Но как конкретно драйвер портит регистры в чужих процессах?Почему усиление дисковой активности усиливает эффект.И при чем тут PAGING, почему проблема воспроизводится только при обращении к холодной памяти?Мы, конечно же, знали как работает сохранение контекста FPU в ядре.Как говорится, "не первый год замужем".И все входы и выходы в ядерный криптомодуль были перекрыты.Вход в криптопримитив - сохранение (KeSaveFloatingPointState()).Выход из криптопримитива - восстановление (KeRestoreFloatingPointState()).Все это завернуто в такой плюсовый автоматический scope guard, мышь не проползет.Однако, факты говорили другое.
Было сделано 2 вещи: был собран криптомодуль без инструкций SSE/AES NI,
и в драйвер-фильтр, перед каждым обращением к крипте, была добавлена собственная пара сохранение - восстановление контекста.
Оба подхода показали, что проблема уходит. Действительно уходит.И в принципе причина понятна, но вот что конкретно мы делаем не так? ;)Печальный тимлид сидел обхватив голову руками.Он настраивал поставку криптомодуля без SSE/AES NI оптимизации.Это был наш план Б на худший случай.А без AES NI шифрование работает в 4-5 раз медленнее...
Друзья, у кого есть идеи? Как помочь команде FDE?
Я не уверен, что всем хорошо видно, поэтому я увеличу начало.
Откуда они там?
Занавес.
Спасибо всем, друзья, кро проявил активность в обсуждении.У вас были классные гипотезы... ;)Все оказалось просто. Кто-то за 2 месяца до этого включил SSE2 оптимизацию для всего проекта cm_km.sys, ядерного криптомодуля.Спору нет, оптимизация благоприятно сказалась на скорости работы крипты :)Хотя самое главное, алгоритмы блочного шифрования на SSE2 и AES-NI написаны на асме, и в оптимизации такого рода не нуждались.Но оказался соптимизирован весь код, даже тот, который находится снаружи оберток KeSaveFloatingPointState()/KeRestoreFloatingPointState().Хотя его совсем чуть. Вот, зануление полей объекта в одном конструкторе, через memset(), заиспользовало только один раз регистр xmm0.И все, погиб криптомодуль и вся система с ним.
Собственно, вот типичный сценарий порчи данных.
Вы спросите, а как это прошло мимо тестирования? Очень просто.Проблема проявлялась только под большим стрессом, и только на старых 32-битных системах.Поэтому она и не была обнаружена командой тестирования и стресс-тестами - основной упор там делался на современные 64-битные системы.Позже незадачливый автор коммита признался, что хотелось иметь общую кодовую базу для ядра и юзер-мода.И обертки KeSaveFloatingPointState()/KeRestoreFloatingPointState() были вставлены в криптомодуль, под #ifdef.А о том, что может быть соптимизирован другой код (которого даже не видно, но он есть!), было забыто.
Решение было простым - код крипты вынесли из драйвера в отдельную статическую библиотеку, и наступило щастье.2013/11/19. Наш исправленный оптимизированный криптомодуль заработал.2013/11/22. После тщательнейшей проверки, мы сделали поставку и отчитались об исправлении бага.Несомненно, это был баг года 2013.Разработчики получили большие премии за сверхурочную работу.А радости менеджмента не было конца.И в завершение мне хочется спросить вас, коллеги.Как вы думаете, может ли эта история нас чему-то научить? А вас? ;)Какой бы вы сделали вывод? Предлагайте.
Вы знаете, я сам долго думал, и у меня есть свой вариант вывода.Казалось бы история настолько неоднозначна, что не дает никаких волшебных рецептов, никаких серебряных пуль.Команды можно винить в произошедшем, но способа надежно защищиться, избежать таких ошибок в будущем тоже не демонстрируется.Ну какая тут мораль? Зачем вообще все это рассказано?
Однако, кое-что все-таки есть. То, что я понял, я формулирую так:Любой проект может столкнуться проблемой любого уровня сложности на стадии релиза, или уже после него.Предвидеть появление такой проблемы невозможно, от нее не спасут ни хорошее тестовое покрытие, ни прекрасная архитектура, ни отлично спланированное исполнение проекта. Все эти меры существенно снижают риск. Существенно! Но никогда не устраняют его до конца.Оказывается, на этот случай есть наставление в знаменитом сборнике принципов руководителей проектов NASA.Оно гласит:Единственное, что вас спасет - это способность вашей команды быстро реагировать, когда это случится.Вот и ответ не поставленные выше вопросы. И прямое руководство к действию :)