
Эксплойт для уязвимости прошивки AMD SEV-SNP CVE-2024-21978, позволяющий расшифровать произвольную память гостевой системы путем повреждения памяти контекстных страниц.
Этот репозиторий содержит эксплойт для уязвимости в прошивке SEV. Эксплойт позволяет расшифровывать произвольную память работающего гостя SEV-SNP.
Протестировано на версии 1.55.16 (последней на момент написания).
Поле nv_paddr команды SEV_INIT_EX можно использовать для передачи фрагмента памяти прошивке, чтобы она могла использовать его вместо постоянной flash. Если SEV-SNP включен, эта память должна находиться в состоянии FIRMWARE. Прошивка проверяет это один раз во время выполнения команды SEV_INIT_EX. После этого прошивка предполагает, что данная память находится в состоянии FIRMWARE, и записывает в неё без каких-либо дополнительных проверок.
Предположение о том, что память всё ещё находится в состоянии FIRMWARE, не всегда верно — ничто не мешает хосту изменить состояние обратно на HYPERVISOR с помощью команды SNP_PAGE_RECLAIM. Как только страницы оказываются в состоянии HYPERVISOR, их можно перевести в другие состояния, например, CONTEXT. Даже если страницы больше не находятся в состоянии FIRMWARE, прошивка будет в них записывать, нарушая целостность, требуемую некоторыми состояниями страниц.
Мы можем использовать это повреждение памяти, нацелившись на страницы CONTEXT. Страницы CONTEXT — мощная цель, но есть некоторые проблемы:
CONTEXT шифруются другим ключом, чем остальная память. В результате сложно управлять открытым текстом, даже если мы можем управлять шифротекстом.Повреждение памяти фактически заполняет страницу CONTEXT случайными данными, поэтому нелегко повредить страницы CONTEXT таким образом, чтобы это было полезно для атакующего. Чтобы обойти это, мы можем многократно вызывать ошибку, вызывая повреждение, и использовать команду SNP_GUEST_STATUS для чтения соответствующих полей повреждённой страницы CONTEXT, пока не увидим полезные значения.
Команда SNP_DBG_DECRYPT может использоваться для расшифровки памяти гостя SEV-SNP с включённой политикой DEBUG. Если мы сможем сформировать CONTEXT так, чтобы в нём был установлен флаг DEBUG и содержался ASID другого гостя, мы сможем использовать его для расшифровки памяти другого гостя, даже если у него не установлена политика DEBUG.
Оказывается, SNP_DBG_DECRYPT игнорирует большинство полей на странице CONTEXT; она проверяет только gctx->guest.asid, gctx->guest.policy_snp и gctx->guest.guest_flags. Вероятность того, что эти поля окажутся корректными после повреждения памяти, невысока, но не за пределами возможного. Хорошая новость также в том, что мы можем прочитать все эти поля с помощью команды GUEST_STATUS.
Итак, мы можем использовать ошибку, выполнив следующие шаги:
nv_paddr в состояние FIRMWARE с помощью инструкции rmpupdate.SEV_INIT_EX.nv_paddr обратно в состояние HYPERVISOR с помощью команды SNP_RECLAIM_PAGE.CONTEXT по адресу nv_paddr.nv_paddr с помощью команды SEV_PDH_GEN. Это повреждает страницы CONTEXT.GUEST_STATUS для проверки, будет ли SNP_DBG_DECRYPT успешным; если нет, вернуться к шагу 5. Основное узкое место здесь в том, что ASID хранятся в 32-битном int, но допустимых ASID гораздо меньше (509 или 1006 в зависимости от CPU), поэтому потребуется довольно много попыток, чтобы добиться правильного результата.CONTEXT. Отслеживать страницу секретов. Это возможно, потому что прошивка SEV отслеживает активные ASID внутренне и не проверяет активные страницы CONTEXT на наличие дубликатов.CONTEXT для выполнения SNP_DBG_DECRYPT на странице секретов гостя-жертвы.Эксплойт проводит большую часть времени на шагах 5 и 6. Шансы на выполнение всех правильных условий составляют примерно 1/20 000 000 на EPYC Milan, и мы можем делать около 100 попыток в секунду, поэтому ожидаем, что правильные условия будут достигнуты примерно раз в два дня (предупреждение: расчёты приблизительны, и я мог что-то напутать, но анекдотически оценка «раз в два дня» кажется верной). Мы можем ускорить это, атакуя не одну страницу CONTEXT за раз, а три страницы CONTEXT по адресам nv_paddr, nv_paddr+4096 и nv_paddr+8192 (SEV_PDG_GEN повредит три страницы). Удобно, что эти шаги можно выполнить до запуска гостя-жертвы, и их нужно выполнить успешно только один раз, чтобы атаковать произвольное количество гостей (однако обратите внимание, что PoC в настоящее время атакует только одного гостя).
Хотя я ещё не смог это проверить, я считаю, что после того, как атакующий использует эту уязвимость для получения ключей связи гостевой платформы виртуальной машины, он сможет отправлять гостевые сообщения прошивке от имени гостя и использовать это для запроса аттестационных отчётов. Это нарушает ключевой принцип SEV-SNP, согласно которому только гость может запрашивать аттестационные отчёты.
Несколько команд (например, SNP_RECLAIM_PAGE, SNP_GCTX_CREATE, RING_BUFFER, возможно, ещё, а может быть, для безопасности все?), которые принимают страницу FIRMWARE, должны проверять, перекрывается ли она с nv_paddr, и завершаться ошибкой в случае перекрытия.
Есть ещё одна проблема, которая меня беспокоит, и я не уверен, обоснована ли она; буду рад услышать ваше мнение: если я правильно понимаю, прошивку SEV можно обновлять без прерывания работы запущенных гостей. Это означает, что можно перенести повреждённую страницу CONTEXT из старой уязвимой версии прошивки в новую исправленную версию. Можно ли начать на старой уязвимой версии, выполнить описанный эксплойт, обновить и зафиксировать новую исправленную прошивку, запустить гостя на новой прошивке (чтобы старая версия прошивки не отображалась в аттестационном отчёте), а затем использовать повреждённую страницу CONTEXT, созданную с помощью старой прошивки, для атаки на гостя, созданного с помощью новой версии? Сможет ли потребитель аттестационных отчётов, созданных новым гостем, определить, что старая версия прошивки работала в какой-то момент до запуска нового гостя? Если нет, требуются ли дополнительные меры смягчения для предотвращения этого?