
Технический анализ и эксплойты для проверки концепции для трех уязвимостей в прошивке контроллера SM2259 твердотельного накопителя Crucial MX500, включая переполнение буфера и целочисленное переполнение (underflow) в обработчике ATA DOWNLOAD-MICROCODE, что может привести к выполнению кода.
Рассматриваемое устройство — любой SSD серии MX500. Этими SSD управляет микроконтроллер Silicon-Motion SM2259 (в более старых партиях использовался более старый контроллер, Silicon-Motion SM2258, но основное внимание в этом документе уделяется новому).
SM2259 — это 4-канальный SATA 6 Гбит/с микроконтроллер с 32-битным little-endian процессором на архитектуре ARC.
При анализе последней прошивки, актуальной на дату написания документа, а именно M3CR046, было выявлено и подтверждено как статически, так и динамически несколько проблем.
Все проблемы были найдены в механизме обновления прошивки контроллера, который соответствует обработчику микроконтроллера команды ATA PIO DOWNLOAD-MICROCODE (0x92), а именно в логике загрузки прошивки методом смещений (офсетов) для подкоманд 0x03 и 0x0E.
Все ошибки, описанные в данном документе, были проверены на Crucial MX500 500GB SSD (CT500MX500SSD1), контроллере SM2259H-AC с прошивкой M3CR046 и чипами флэш-памяти NY112 на ПК с процессором x86_64.
Код прошивки отображается на базовый адрес 0x80020000, а уязвимый обработчик ATA находится по адресу 0x80024A9C. Декомпилированная версия функции для удобства приведена в файле resources/download_microcode_handler.c.
Для тех, кто не хочет вникать в технические детали, а предпочитает понять суть, обратитесь к разделу FAQ внизу.
Поскольку M3CR046 содержит несколько образов прошивки, из которых механизм обновления выбирает подходящий (возможно, в зависимости от используемых чипов флэш-памяти или других аппаратных характеристик), в этом документе будут описаны особенности первого варианта прошивки (так как именно эта прошивка поддерживается на нашем конкретном диске, и мы могли проверить только её). Однако представленные в документе ошибки, по-видимому, применимы ко всем вариантам прошивки, хотя при их воспроизведении могут быть некоторые различия в деталях.
Эта проблема относится к случаям, когда первый отправленный чанк имеет размер более 0x200 секторов. Если заглянуть внутрь обработчика ATA-команды, а именно в логику, выполняемую, когда размер чанка превышает размер сектора и чанк является первым:

Здесь устанавливаются некоторые переменные на основе следующего смещения (которое в нашем случае, поскольку мы отправили только один чанк, равно длине чанка в секторах) и переменной с именем lower_bound_fw_offset — смещения блока (т.е. смещения в единицах секторов) внутри загружаемого образа, где, как ожидается, будет найдена наша прошивка. Это жёстко заданное значение для каждого варианта прошивки; в нашем случае (первый вариант) оно равно 0.
В этом случае при вычислении разности для some_index возникает переполнение (underflow), в результате чего some_index становится равным 0xFFFF. Это неожиданное поведение, поскольку, судя по логике перемещения данных в буфер загрузки:

Мы видим, что исходный адрес, с которого копируются данные, может быть недействительным из-за неожиданного значения some_index.
При динамическом тестировании, когда был отправлен запрос на обновление прошивки с первым чанком размером более 0x200 секторов, контроллер зависает и даже не отправляет ответ на исходный запрос. Это стабильно воспроизводится.
Вероятно, это происходит из-за некорректной ссылки на вычисленный исходный адрес, что вызывает исключение, приводящее к зависанию контроллера. Это не доказано, а лишь гипотеза, объясняющая зависание.
Загружаемый образ (для M3CR046) имеет размер 0x242400 байт, и внутри этого образа находятся 3 внутренних образа прошивки, из которых только один в итоге записывается во флэш-память после процесса обновления; каждый такой образ имеет размер 0xC0C00 байт (или 0x606 секторов).
Это означает, что когда механизм обновления прошивки извлекает правильную копию прошивки из загружаемого образа, он должен убедиться, что её размер не превышает 0xC0C00 байт.
Контроллер действительно пытается это сделать, но есть некоторые краевые случаи, которые могут привести к неожиданному поведению. Взглянем на следующий фрагмент (который частично совпадает с кодом предыдущей ошибки):

Если текущий чанк имеет размер более 0x200 секторов и не является первым в последовательности, то за один раз копируется 0x200 секторов (0x40000 байт). Затем выполняется проверка, цель которой — обрезать лишние байты из количества копируемых байт, если общий размер образа прошивки превышает higher_bound_fw_offset (который в нашем случае равен 0x606 секторам, поскольку размер прошивки должен быть именно таким).
В целом эта логика имеет смысл, но есть недостаток: если последний отправленный чанк приводит к тому, что следующее смещение становится слишком большим, так что количество лишних байт превышает 0x200 секторов (или 0x40000 байт), то curr_bytes_to_copy получает «отрицательное» значение, которое переполняется (underflow) до примерно ~4 ГБ (~0xFFFFFFFF). Как мы видели ранее, эта переменная используется для определения количества байт, передаваемых в буфер загрузки.
Если заглянуть внутрь r_maybe_some_efficient_data_transfer, мы видим следующий фрагмент кода:

Это означает, что размер копирования урезается до 32 МБ (с исходного ~4 ГБ), но это всё ещё большое число, которое также может вызвать неопределённое поведение, если диапазон памяти, начинающийся с 0x40000000, имеет размер менее 32 МБ.
При динамическом тестировании, когда отправлялись ATA-чанки для достижения смещения 0x600, а затем отправлялся большой чанк размером 0x207 секторов для вызова переполнения (underflow), контроллер снова зависает, вероятно, из-за недопустимого обращения к памяти во время копирования.
Эта ошибка интереснее предыдущей, потому что, хотя у нас нет контролируемой перезаписи (а есть большая перезапись, которая, возможно, вызывает исключение, вешающее контроллер), если функция, перемещающая данные в буфер загрузки, всё же успеет передать такое количество данных до сбоя (перезаписав диапазон памяти, расположенный сразу после буфера загрузки в основной памяти), то поведение обработчика исключений может измениться на основе перезаписанных данных. Это может произойти, например, если обработчик исключений читает указатель из перезаписанной области и затем переходит по нему (данный конкретный случай маловероятен, но при дальнейших исследованиях может быть обнаружено нечто подобное).
Как уже говорилось, размер загружаемого образа составляет 0x242400 (или 0x1212 секторов). Прошивка проверяет, что общий размер переданного образа не превышает этот размер, проверяя, что следующее смещение не превышает 0x1212 секторов. Эта проверка имеет смысл, но вычисление следующего смещения содержит ошибку:

Если текущее смещение равно 0x600 секторов, а следующая обрабатываемая ATA-команда имеет достаточно большой размер (скажем, 0xFC00 секторов, что допускается стандартом ATA), то следующее смещение переполняется (wraps around), и вышеупомянутая проверка работает некорректно:

Иными словами, в обычном случае механизм обновления прошивки сбросил бы свой конечный автомат и вернул ошибку, но если мы отправили очень большой чанк, обработка продолжится. Следующий фрагмент кода показывает, как выполняется передача:

Напомним, что если количество передаваемых секторов больше 0x200 и текущий чанк не первый, то за один раз в буфер загрузки копируется 0x200 секторов. Это очень интересно, потому что это означает, что мы можем скопировать около 0x200 секторов (или 0x40000 байт) за пределы буфера загрузки, перезаписывая данные в основной памяти. Например, если текущее смещение равно 0x605 секторов, а размер чанка равен 0xF9FB секторов, то __next_offset получает значение 0 из-за переполнения. Индекс источника, с которого начинается копирование, равен 0, а curr_bytes_to_copy получает значение 0x40000. Поскольку мы сейчас находимся на смещении 0x605 секторов, g_blocks_copied получает значение 0x605. Так как текущее смещение действительно (и следующее тоже), запускается операция копирования в буфер загрузки, что приводит к массовой перезаписи чуть менее байт за концом буфера загрузки.
Это сильный примитив, который обеспечивает гораздо более масштабное переполнение буфера контроллера (которое не приводит к немедленному зависанию, как в предыдущих случаях), и может привести к выполнению кода с гораздо большей вероятностью, чем предыдущая ошибка (однако для определения характеристик эксплуатации необходимо дополнительное исследование того, что именно находится после буфера загрузки в основной памяти).
Все эти ошибки были проверены на машине с Ubuntu 22.04 64-bit с использованием стандартного драйвера SCSI Linux через интерфейс SG_IO. Следует отметить, что для воспроизведения Ошибки №3 с этим конкретным драйвером необходимо включить большие страницы (huge pages) и выделить одну страницу размером 1 ГБ для большого запроса. Причина в том, что этот драйвер, по-видимому, требует, чтобы весь ATA-запрос находился в непрерывных блоках физической памяти. Так как размер запроса составляет около ~30 МБ, страниц по 2 МБ недостаточно, поэтому страницы по 1 ГБ являются следующим (и последним) доступным размером в нашей тестовой системе.
Однако также следует отметить, что это не означает, что данный шаг является обязательным для инициирования ошибки, поскольку, возможно, существуют другие обходные пути, позволяющие отправлять большие ATA-запросы, которые мы ещё не рассмотрели. Включение больших страниц было просто самым быстрым способом подтвердить эту ошибку. Кроме того, единственным необходимым условием для инициирования всех этих ошибок является наличие соответствующих разрешений для отправки ATA-пакетов (как правило, root-доступ к ПК, взаимодействующему с контроллером).
Исходный код, воспроизводящий все вышеупомянутые ошибки, предоставлен в составе этого репозитория. Для Ошибки №1 и Ошибки №2 ожидаемое поведение — зависание диска до следующего цикла питания. Для Ошибки №3 предоставленный исходный код не обязательно приводит к сбою контроллера, но выполняет большую перезапись за пределами буфера загрузки.
Как уже говорилось, поскольку ошибки были проверены на машине с Ubuntu 22.04 64-bit, процесс компиляции должен выполняться на аналогичной машине. Гарантий для других дистрибутивов или операционных систем нет.
Для сборки выполните в корневом каталоге проекта:
cmake -B build && make
В результате сборки создаются 3 бинарных файла, которые будут доступны в каталоге build с именами CVE_MX500_BUG_1, CVE_MX500_BUG_2 и CVE_MX500_BUG_3, соответствующие исходным файлам, инициирующим Ошибку №1, Ошибку №2 и Ошибку №3 соответственно.
Каждый бинарный файл ожидает путь к устройству MX500 SSD и должен запускаться с правами root. Например:
sudo ./build/CVE_MX500_BUG1 /dev/sda
Это зависит от конечной цели потенциального злоумышленника. Если ему нужен только полный доступ на чтение/запись к хранилищу вашего диска, то нахождение внутри вашего ПК уже достаточно. Но что, если этот злоумышленник хочет пойти дальше? Если прошивка диска имеет цифровую подпись, то Ошибка №3 позволяет злоумышленнику обойти проверку подписи прошивки, дав возможность внедрить вредоносную нагрузку в прошивку диска. Попав внутрь, такая нагрузка хорошо скрыта, переживает форматирование диска и даже может пережить обновления прошивки контроллера. Что именно может делать такая нагрузка, выходит за рамки данного документа, поэтому обсуждаться не будет.
Скорее всего, ответ — категорическое НЕТ. Объём НИОКР, необходимый для реального выполнения такой атаки, очень велик и (ВЕСЬМА вероятно) доступен только очень серьёзным угрозам. Если вас не разыскивают правительства, крайне маловероятно, что это как-то вас затронет.
Вендор не отвечал на многочисленные письма по этим вопросам в течение нескольких месяцев. Чтобы CVE был фактически опубликован, необходимо предоставить публичную ссылку назначающему CNA. К сожалению, отправка им информации в частном порядке — это не то, как это работает.
Ошибки, упомянутые в этом документе, были первоначально обнаружены в мае 2024 года. Micron неоднократно связывались с тех пор (по их официальному адресу безопасности), и ответа не последовало. MITRE было уведомлено в июле 2024 года, а CVE был назначен в августе 2024 года. В конце августа 2024 года этот репозиторий был сделан публичным (через несколько дней после того, как CVE был одобрен MITRE).
Поскольку прошивки M3CR04X старше M3CR046 больше не доступны для загрузки, неясно, затронуты ли они, но если бы мне пришлось гадать, я бы сказал, что да. Что касается ещё более старых версий, например M3CR033, то, основываясь на статическом анализе, похоже, что там существуют очень похожие ошибки.
Рассматриваемый контроллер, SM2259, также встроен в SSD других вендоров. Возможно, вендоры модифицируют некоторую часть кода прошивки, но я бы также сказал, что определённо возможно, что эти ошибки (или очень похожие) присутствуют и в SSD других вендоров.
Этот CVE был опубликован MITRE. Он также был проанализирован NVD с оценкой CVSS 3.0, равной 6.7 (средняя).
Если вы обнаружили неточности или ошибки в описании или у вас возникли проблемы с воспроизведением этих ошибок, свяжитесь со мной по адресу [email protected].
0x40000