
CVE-2025-38352 эксплойт для ядра LG webOS Smart TVs (ARM64). Обеспечивает постоянный root-доступ на реальном потребительском оборудовании с использованием новых техник эксплуатации. Ответственно раскрыт LG.
Эксплойт ядра для CVE-2025-38352 (состояние гонки таймера CPU POSIX / use-after-free), обеспечивающий постоянный root на LG webOS Smart TV с ядром 5.4.268 на ARM64. Проверено на 4 моделях телевизоров с различными версиями прошивки. Ответственно раскрыто в Программе исследователей безопасности LG (февраль 2026).
Создано на основе Chronomaly от farazsth98. Логика гонки UAF на этапе 1 и инфраструктура кросс-кэша портированы и адаптированы для ARM64; этапы 2–5 перепроектированы с использованием новых методов эксплуатации и решений, учитывающих ограничения реального оборудования, отсутствующие в эмулированных средах. Разработано с помощью Claude Opus 4.6.
См. VULNERABILITY_REPORT.md для полного анализа уязвимости, описания цепочки эксплойта и рекомендуемых мер защиты.
prisoner (uid=5038)Данный эксплойт был разработан в рамках ответственного исследования безопасности и сообщен в Программу исследователей безопасности LG 7 февраля 2026 года. Он публикуется исключительно в образовательных целях. Используйте ответственно и только на принадлежащих вам устройствах. Авторы не несут ответственности за любой ущерб, повреждение устройства ('bricking'), потерю данных или аннулирование гарантии в результате использования данного программного обеспечения. Программное обеспечение предоставляется 'как есть' без каких-либо гарантий.
Оригинальное произвольное уменьшение медленное и шумное: оно распыляет 1000 объектов struct cred через форкнутые процессы, затем уменьшает поле EUID целевого cred N раз. Каждое уменьшение — отдельная операция. Это приемлемо в QEMU, где тайминги терпимы, но ненадежно на реальном оборудовании, где управляемая прерываниями сборка страниц может украсть страницы буфера канала между операциями.
Это было заменено одной произвольной записью через list_del_init(). Эксплойт перезаписывает указатели list_head.next и list_head.prev UAF'нутого sigqueue через буфер канала. Когда ядро извлекает ожидающий сигнал (collect_signal() → list_del_init()), оно выполняет prev->next = next (записывает адрес поддельного cred в task_struct->cred) и next->prev = prev (контролируемая побочная запись). Одна запись заменяет указатель cred процесса на указатель на поддельную структуру cred, содержащую все нулевые поля uid/gid. Никакого распыления cred, никаких форкнутых процессов, детерминированно.
peek_pipe() через tee() для неразрушающего чтенияОригинал использует разрушающие вызовы read() для буферов каналов на протяжении всего эксплойта. В QEMU это работает нормально, так как страницы не крадутся между операциями. На реальном оборудовании с 4 физическими ядрами per-CPU список страниц (pcplist) ядра агрессивно перерабатывает освобожденные страницы. Разрушающее чтение освобождает резервную страницу буфера канала, которая может быть немедленно украдена аппаратным прерыванием до того, как эксплойт сможет её перераспределить.
Решение — неразрушающий примитив чтения канала с использованием tee(). Системный вызов tee() дублирует данные канала между двумя каналами без их потребления, удерживая резервную страницу исходного буфера канала закрепленной. Это позволяет эксплойту многократно читать данные ядра из кросс-кэшированного буфера канала без риска потери страницы. Это было критически важно для надежности на реальном оборудовании.
Оригинал распыляет объекты cred и надеется, что один из них окажется в предсказуемом месте. Эта версия создает поддельную структуру cred по известному адресу путем выполнения второго кросс-кэша: выделяет новый sigqueue (через tkill(SIGRTMIN+1)), узнает его адрес из утечки кучи первого буфера канала, а затем кросс-кэширует страницу slab этого sigqueue во второй буфер канала. Поддельные cred записываются во второй буфер канала по точному смещению страницы утекшего адреса sigqueue. В результате получаются поддельные cred по детерминированному виртуальному адресу ядра без угадывания.
Оригинал извлекает SIGUSR2 на раннем этапе 2, чтобы узнать адрес UAF sigqueue. Это потребляет сигнал, поэтому оригиналу нужен другой механизм для финальной записи. Эта версия никогда не требует собственного адреса UAF sigqueue (утечка кучи получается из соседних указателей sigqueue в буфере канала). SIGUSR2 остается ожидающим на всех пяти этапах, и его извлечение используется как финальный триггер произвольной записи. Сигнал, создавший UAF, — это тот же сигнал, чье извлечение его эксплуатирует.
modprobe_path + socket(44)Поддельная структура cred содержит NULL-указатели user_ns, user и group_info (поскольку буфер канала обнуляется за пределами полей uid/gid). Вызов setresuid(), fork() или exec() приведет к разыменованию этих NULL-указателей и панике ядра. Оригинал избегает этого, потому что его распыление cred использует реальные объекты cred с валидными указателями.
Решение: перезаписать /proc/sys/kernel/modprobe, указав на скрипт полезной нагрузки (/tmp/pwn), затем вызвать call_usermodehelper через socket(44, SOCK_STREAM, 0) (запрос несуществующего семейства протоколов). Ядро выполняет хелпер modprobe с init_cred (собственные корневые учетные данные ядра, полностью валидные), полностью обходя поврежденные cred. Полезная нагрузка выполняется с полными правами root и может выполнять произвольные операции.
Критическое окно на этапе 4 (запись вредоносных указателей в буфер канала, затем извлечение сигнала) уязвимо для аппаратных прерываний, которые могут украсть страницу буфера канала из per-CPU списка страниц. В QEMU этого не происходит. На реальном оборудовании это окно защищается приоритетом SCHED_FIFO (когда доступен) и sched_yield() для завершения ожидающих работ на ЦП перед входом в критическую секцию, а также предварительно подготовленным содержимым буфера для минимизации времени между записью и триггером. Эксплойт также корректно переключается в резервный режим, когда SCHED_FIFO недоступен (как на webOS, где пользователь prisoner не имеет CAP_SYS_NICE).
task_struct для ARM64Произвольная запись нацелена на task_struct->cred, что требует знания байтового смещения от task_struct->pending (чей адрес утекает из буфера канала) до task_struct->cred. Это смещение зависит от конфигурации ядра. Смещение 0x80 (128 байт) было вручную вычислено из исходного кода ядра LG webOS с учетом CONFIG_KEYS=y, CONFIG_SYSVIPC=y и специфичных для ARM64 структуры и выравнивания. Смещение x86_64 в оригинале отличается из-за другой упаковки структур и параметров конфигурации.