
CVE-2018-4185: iOS 11.2-11.2.6 раскрытие указателей ядра, введённое в результате мер Apple по смягчению последствий Meltdown.
iOS 11.2 представила утечку информации о ядре, которую можно было использовать для определения сдвига kASLR. Проблема возникла из-за новой функции __ARM_KERNEL_PROTECT__, которая непреднамеренно приводила к тому, что адрес функции ядра Lel0_synchronous_vector_64_long появлялся в регистре x18 при получении значений регистров потока с помощью thread_get_state. Проблема была обнаружена, когда указатели ядра начали появляться в журналах сбоев iOS-приложений.
В iOS 11.2 Apple представила функцию на arm64 под названием __ARM_KERNEL_PROTECT__. Согласно комментарию в osfmk/arm64/proc_reg.h:
`__ARM_KERNEL_PROTECT__` — это функция, предназначенная для защиты от потенциальных архитектурных или микроархитектурных уязвимостей, которые могут позволить ядрам читать/получать доступ к отображениям, доступным только на уровне EL1, находясь в режиме EL0. Это достигается удалением как можно большего количества отображений при переходе ядра из режима EL1 в режим EL0 и восстановлением этих отображений при переходе ядра из режима EL0 в режим EL1.
То есть при переходе из EL1 (режим ядра) в EL0 (режим пользователя) будет удалено как можно больше отображений ядра. Это должно ограничить возможную поверхность атаки на отображения памяти ядра при эксплуатации микроархитектурных уязвимостей, таких как Spectre или Meltdown.
Если вы посмотрите на различия между версиями XNU 4570.20.62 и 4570.31.3, вы найдете несколько новых ссылок на регистр x18 в файле osfmk/arm64/locore.s, связанных с __ARM_KERNEL_PROTECT__. В частности, вы увидите, что вектор исключения Lel0_synchronous_vector_64, который вызывается при системном вызове (инструкция svc #0), теперь выглядит так:
.text
.align 7
Lel0_synchronous_vector_64:
MAP_KERNEL
BRANCH_TO_KVA_VECTOR Lel0_synchronous_vector_64_long, 8
Макрос BRANCH_TO_KVA_VECTOR определяется как:
.macro BRANCH_TO_KVA_VECTOR
#if __ARM_KERNEL_PROTECT__
/*
* Find the kernelcache table for the exception vectors by accessing
* the per-CPU data.
*/
mrs x18, TPIDR_EL1
ldr x18, [x18, ACT_CPUDATAP]
ldr x18, [x18, CPU_EXC_VECTORS]
/*
* Get the handler for this exception and jump to it.
*/
ldr x18, [x18, #($1 << 3)]
br x18
#else
b $0
#endif /* __ARM_KERNEL_PROTECT__ */
.endmacro
Этот макрос выполняет косвенный переход к настоящей реализации вектора исключения Lel0_synchronous_vector_64_long, загружая указатель на эту функцию в регистр x18. Однако обратите внимание, что эта перезапись x18 происходит до того, как регистры пользовательского пространства сохраняются функцией fleh_dispatch64, которая вызывается Lel0_synchronous_vector_64_long. Это означает, что при сохранении пользовательских регистров x18 будет на самом деле указателем на Lel0_synchronous_vector_64_long, а не оригинальным значением из пользовательского пространства.
Хотя x18 очищается при возврате из исключения, сохранение указателя ядра в состоянии пользовательских регистров проблематично, потому что thread_get_state может использоваться для копирования сохраненного состояния пользовательских регистров обратно в пользовательское пространство, включая значение регистра x18. Все, что нужно сделать потоку для получения адреса функции Lel0_synchronous_vector_64_long, — это вызвать thread_get_state для самого себя и посмотреть на сообщаемое значение x18. Это делает тривиальным определение сдвига kASLR путем вычитания статического адреса Lel0_synchronous_vector_64_long из полученного таким образом значения x18.
Как упоминалось выше, эксплуатация тривиальна: просто вызовите функцию thread_get_state, посмотрите на значение регистра x18 и вычтите из него статический адрес функции ядра Lel0_synchronous_vector_64_long.
Я обнаружил эту проблему 26 февраля 2018 года, заметив указатель ядра в регистре x18 журнала сбоя iOS-приложения. Быстрая проверка показала, что то же самое значение появлялось в регистре x18 каждого журнала сбоя на устройстве, что указывало на серьезную утечку информации.
Затем я попытался выяснить, что именно происходит с регистром x18, путем экспериментов. Я установил точку остановки в пустом iOS-приложении и использовал lldb для чтения значения регистра x18, подтвердив, что утечка не ограничивается только сбойными приложениями. Затем я попытался прочитать значение x18 с помощью встроенного ассемблера и обнаружил, что полученное значение не совпадает со значением, показываемым отладчиком при использовании команды типа reg read x18. Это наводило на мысль, что, возможно, утечка на самом деле была в thread_get_state, и что регистр x18 на самом деле не содержал указателя ядра, пока ЦП выполнял код в пользовательском пространстве. Быстрая проверка концепции, считывающая значение x18 с помощью thread_get_state, подтвердила, что эта функция действительно была источником утечки.
Я сообщил о проблеме в Apple 26 февраля 2018 года, в тот же день, когда обнаружил её.
Автор: Brandon Azad