В iOS 18.4 обнаружен баг с PAC и dlsym — приложения вылетают при вызове strcmp из-за двойной подписи указателя и сбоя в dyld
Разработчики заметили странный баг в iOS 18.4 — некоторые приложения падают при использовании стандартных функций вроде strcmp
.
Причина — критическая ошибка в механизме динамической загрузки символов и работе с Pointer Authentication (PAC) на архитектуре arm64e. А ведь речь идёт о типовом коде с dlopen()
и dlsym()
— ничего нестандартного.
Вызываешь strcmp()
через dlsym
, вроде бы получаешь валидный указатель, но при вызове — краш. В системных логах фигурирует KERN_PROTECTION_FAILURE
, указывающий на проблему с PAC-подписью указателя.
🔥 Как ФБР взломало Android-смартфон стрелка в Трампа за 40 минутtproger.ru
Ошибка не единичная — повторный запуск воспроизводит падения, а адреса strcmp
каждый раз разные, как будто подпись сломана или отсутствует вовсе.
PAC и двойная подпись: что пошло не так
На новых чипах Apple (в частности, A15) используется расширенный механизм PAC из архитектуры Armv8.6-A. В этой версии подпись не заменяет старшие биты указателя, а XOR’ится с ними — что делает поведение более тонким и сложным.
Выяснилось, что в dyld
(динамическом загрузчике macOS/iOS) отсутствует важная инструкция XPACI
, которая должна была очищать старую подпись перед повторной подписью указателя. В результате символ, полученный через dlsym
, оказывается подписан дважды — и система воспринимает его как kernel-указатель. Это приводит к сбою безопасности на уровне ядра и аварийному завершению приложения.
Особо интересный момент: strcmp — это не прямая функция, а лениво резолвящийся stub (EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER
), который изначально ссылается на функцию __platform_strcmp_ptr
в другой библиотеке. Именно на этапе резолвинга и возникает ошибка: dyld получает один раз подписанный указатель и подписывает его снова, не очистив первую подпись.
Почему падает не всё подряд?
Вопрос, который возникает у многих: почему strcpy
, загружаемый тем же способом, работает, а strcmp
— нет? Ответ — в деталях реализации. У strcpy
нет флага ленивого резолвинга, значит вызов не проходит через тот же багованный путь в dyld
.
Apple назвала дату презентации iPhone 16 — 9 сентября. Какие новинки покажут на мероприятии?tproger.ru
Ещё один важный момент: стандартный загрузчик обычно использует безопасный режим (Loader::skipResolver
), который не активирует резолвер. А вот ручной вызов dlsym()
в коде напрямую запускает багованный сценарий.
И да, это настоящая проблема
Проблема воспроизводится стабильно, но только при специфических условиях: arm64e, PAC, использование dlsym
, вызов функции с ленивым резолвером. Однако таких случаев может быть гораздо больше, особенно в нативных iOS-приложениях, использующих сторонние библиотеки или плагины.
Пока Apple не выпустила исправление, разработчикам остаётся избегать ленивой загрузки таких символов или вручную пересписывать указатели. Костыльно, но безопаснее.