Назад к блогу

Файловые уведомления в ядре Linux и root-эксплойты на Android: как устроена механика

Файловые уведомления в ядре Linux и root-эксплойты на Android: как устроена механика

Механизмы файловых уведомлений в ядре Linux — inotify, fanotify и dnotify — не просто инструмент для отслеживания изменений, но и потенциальная поверхность атаки, через которую злоумышленник может наблюдать за поведением других процессов. В статье разбирается внутреннее устройство этих трёх подсистем, их архитектурные различия и то, как особенности реализации приводят к реальным root-эксплойтам на Android.

Файловые уведомления — это интерфейс, через который процесс узнаёт об изменениях в файловой системе, не опрашивая её в цикле. Именно эта возможность превращает уведомления в поверхность атаки: тот, кто видит события, видит и поведение других процессов. Разберём, как устроены три механизма ядра — inotify, fanotify и dnotify, — где проходят границы их архитектур и как на этой почве возникают реальные атаки.

Три механизма и общая точка входа

В исходниках рассматриваются три механизма: inotify, fanotify и dnotify. inotify и dnotify «были в значительной степени переписаны, чтобы использовать инфраструктуру fsnotify» — это прямо указано в комментариях к обоим файлам. Общая точка входа для всех трёх — функция fsnotify(), которая вызывает все зарегистрированные группы и передаёт событие каждой из них.

Принципиальная разница между ними — в способе доставки. dnotify доставляет уведомления через сигналы: процессу посылается сигнал, а не пишется событие в файловый дескриптор. inotify, напротив, использует файловый дескриптор. В документации прямо сказано, что «сигналы — ужасный, ужасный интерфейс для файловых уведомлений», а идеальное решение — интерфейс на основе файлового дескриптора, позволяющий базовый файловый ввод-вывод и poll/select.

fanotify отличается наличием группы разрешений и сторожевого таймера, который срабатывает по истечении настраиваемого таймаута. У inotify и dnotify такого механизма нет.

Архитектура inotify: группы, метки и idr

Соответствие между watch descriptor и inode хранится в структуре idr внутри группы inotify: ключом служит wd, а значением — метка. Такой выбор объясняется тем, что wd — это целое число, которое ядро выдаёт пользователю, и его нужно быстро сопоставлять с меткой; idr даёт именно такое отображение «число → указатель» и позволяет циклически переиспользовать освободившиеся номера.

Добавление записи выделяет новый wd в диапазоне от 1, записывает его в метку и берёт дополнительную ссылку на неё, удерживая спин-блокировку. Поиск по wd требует, чтобы блокировка уже была захвачена, и при нахождении берёт ещё одну ссылку, проверяя, что счётчик ссылок не меньше двух — одна за нахождение в idr, одна только что взятая.

Удаление записи под блокировкой проверяет, что wd не равен -1, находит метку, убеждается, что найденная совпадает с удаляемой, проверяет счётчик ссылок не меньше двух, затем удаляет запись из idr и отпускает ссылку за нахождение в idr. В конце wd сбрасывается в -1 и отпускается ссылка, взятая при поиске.

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

Лимиты на группы и watches

Группа inotify создаётся функцией, которая выделяет структуру группы и отдельный overflow-эвент, инициализирует idr и увеличивает счётчик экземпляров для текущего пользователя. Если счётчик вернул NULL, группа уничтожается и возвращается ошибка -EMFILE. Инициализация проверяет допустимость флагов — только IN_CLOEXEC и IN_NONBLOCK, иначе -EINVAL.

Лимит на число групп на пользователя задаётся значением 128. Лимит на watches вычисляется по формуле:

watches_max = (((si.totalram - si.totalhigh) / 100) << PAGE_SHIFT) /
		INOTIFY_WATCH_COST;
watches_max = clamp(watches_max, 8192UL, 1048576UL);

То есть от 8192 до 1048576, в зависимости от объёма адресуемой памяти. Оба лимита применяются через механизм ucounts: счётчик групп инкрементируется при создании, а счётчик watches уменьшается при неудачном добавлении метки. Значения доступны через sysctl как max_user_instances и max_user_watches.

Жизненный цикл watch

При обновлении наблюдения сначала предпринимается попытка обновить уже существующую метку; если та возвращает -ENOENT (метки для этого inode в группе нет), создаётся новая. Существующая метка ищется по inode; если она найдена и задан IN_MASK_CREATE, возвращается -EEXIST. Иначе маска обновляется на месте: при replace маска сбрасывается в 0 и снимаются флаги, после чего к маске добавляются биты из аргумента, а к флагам — соответствующие флаги.

После изменения маски вызывается пересчёт агрегированной маски объекта: под блокировкой коннектора по списку меток накапливается новая маска через побитовое ИЛИ, и результат записывается в маску объекта через WRITE_ONCE. Новая метка инициализируется, получает маску и флаги из аргумента, добавляется в idr и затем на inode; пересчёт маски здесь напрямую не вызывается, а выполняется внутри добавления метки на inode.

При удалении watch сначала ставится в очередь событие игнорирования, затем метка удаляется из idr, после чего уменьшается счётчик watches. Снятие метки с inode обнуляет маску inode и отвязывает коннектор через rcu_assign_pointer. Уже поставленные в очередь события при удалении watch не удаляются: при формировании события читается wd, и если он равен -1, событие не создаётся. Для события с маской FS_IN_IGNORED объединение с предыдущим всегда запрещено. Освобождение метки откладывается: снимается флаг ALIVE и вызывается callback группы, который и запускает удаление из idr.

Доставка событий: от VFS до пользовательского буфера

Цепочка начинается с проверки, есть ли вообще метки на объектах; если нет — сразу возвращается 0. Затем под блокировкой SRCU инициализируются метки для каждого типа объекта, беря первую метку из списка. Далее в цикле выбирается группа с максимальным приоритетом среди голов очередей: приоритет группы; сравнение идёт через функцию сравнения групп, и для выбранной группы устанавливается маска отчёта. PARENT-метка пропускается, если она не следит за детьми — в её маске и маске игнорирования нет флага FS_EVENT_ON_CHILD.

После этого проверяется, интересуется ли группа событием: собираются маски всех меток группы, и если пересечение тестовой маски с маской меток за вычетом маски игнорирования пусто, возвращается 0. Если группа заинтересована, вызывается её обработчик, который для inotify создаёт событие и добавляет его в очередь группы. После обработки метки текущей группы продвигаются к следующей, и цикл повторяется.

Объединение событий

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

Чтение событий пользователем

Длина имени округляется вверх до кратного размеру структуры события: если длина имени нулевая, возвращается 0, иначе — округление длины имени плюс один байт до кратного размеру структуры. Это делается, чтобы имя вместе с завершающим нулевым байтом занимало целое число структур события.

При чтении берётся первый элемент очереди, к размеру структуры прибавляется округлённая длина имени, и если полученная длина больше переданного пользователем размера буфера, возвращается ошибка -EINVAL, при этом событие из очереди не удаляется. Если буфер достаточен, событие удаляется из очереди и возвращается вызывающему. Копирование заполняет структуру события полями длины, маски, wd и cookie, копирует её в буфер, затем копирует само имя и добивает остаток до округлённой длины нулями.

Чтение в цикле вызывает получение одного события с текущим размером буфера; при ошибке цикл прерывается, иначе событие копируется, уничтожается, буфер сдвигается на скопированное число байт, а размер уменьшается. Опрос дескриптора возвращает признак читаемости, когда очередь событий не пуста.

fanotify: разрешения, watchdog и очереди

Таймаут сторожевого таймера задаётся через sysctl и хранится в переменной; при нулевом значении watchdog не запускается. Группа добавляется в список наблюдения функцией, которая при пустом таймауте сразу возвращает управление, а иначе под блокировкой добавляет группу в хвост списка и, если список был пуст, планирует отложенную работу. Само планирование читает таймаут и, если он не ноль, вызывает отложенную работу на число секунд, равное таймауту.

При срабатывании watchdog под блокировкой перебирает группы из списка, пропускает те, у которых пуст список ожидания ответа, и для каждого события в этом списке увеличивает счётчик: с 0 до 1, затем с 1 до 2, и только при переходе в 2 один раз печатает предупреждение о PID, который не ответил на очередь fanotify дольше таймаута секунд. Удаление группы из-под наблюдения выполняет функцию, которая под блокировкой делает удаление из списка, после чего watchdog больше не сканирует эту группу. Процессы, ожидающие ответа на permission-событие, при срабатывании watchdog не разблокируются: код лишь помечает события счётчиком и печатает предупреждение.

Приоритет группы — это уровень, определяющий, в каком порядке группы получают события: группа с более высоким приоритетом обрабатывает событие раньше остальных. Приоритет pre-content — один из таких уровней, предназначенный для групп, которые работают с содержимым файла до его чтения или изменения; в отличие от остальных уровней, только для групп с этим приоритетом допускается ненулевой errno в ответе FAN_DENY на permission-событие.

Обработка permission-событий

При чтении события, если оно является permission-событием, оно не уничтожается сразу, а при условии, что чтение прошло успешно и файловый дескриптор события неотрицателен, добавляется в список ожидания ответа, и запоминается PID читающего процесса. Если же чтение не удалось или дескриптор события отрицателен, событие немедленно завершается как запрещённое.

Ответ пользователя принимается при записи, которая извлекает структуру ответа и передаёт её на обработку. Там сначала проверяется, что в ответе не установлены биты вне допустимой маски. Затем в зависимости от разрешения или запрета проверяется код ошибки: для разрешения код ошибки должен быть нулевым; для запрета ненулевой код ошибки разрешён только для групп с приоритетом pre-content. Если установлен флаг информации, проверяется, что длина информации равна размеру структуры, копируется структура из пользовательского пространства, и проверяется соответствие полей заголовка ожидаемым значениям. При любом несоответствии возвращается отрицательный код ошибки, и ответ не применяется: событие остаётся в списке ожидания до таймаута или до корректного ответа. Если ответ корректен, событие удаляется из списка и переводится в состояние «отвечено» (или уничтожается, если оно уже было отменено), сохраняя при необходимости правило аудита.

Хеш слияния и копирование info-записей

хеш слияния создаётся при инициализации группы: выделяется массив и инициализируется; при неудаче инициализация завершается с -ENOMEM. Удаление события из этого хеша выполняется под блокировкой: проверяется, не удалено ли событие уже, и при необходимости делается удаление из хеша. Вызов происходит при извлечении события из очереди, если событие является хешируемым.

Копирование info-записей идёт в фиксированном порядке: сначала fid каталога и имя (тип DFID_NAME при наличии имени, иначе DFID; для переименования — OLD_DFID_NAME), затем при наличии dir2 — NEW_DFID_NAME, затем child fid, затем pidfd (если задан режим pidfd), затем error info (если событие является ошибкой), затем range (если событие имеет диапазон доступа).

Для групп без FAN_REPORT_FID действуют особые правила: при режиме FAN_REPORT_FID или уже заданном типе информации второй записи присваивается FID; при FAN_REPORT_NAME и FAN_ONDIR без записанного имени отдаётся «.» с типом DFID_NAME; при FAN_REPORT_DIR_FID для dirent-событий и событий на каталоге — DFID; иначе — FID. При инициализации действуют ограничения: если режим fid задан, класс должен быть FAN_CLASS_NOTIF, иначе -EINVAL; FAN_REPORT_NAME требует FAN_REPORT_DIR_FID; FAN_REPORT_TARGET_FID требует и FAN_REPORT_NAME, и FAN_REPORT_FID.

Проверки перед добавлением метки

Перед добавлением метки проверяется, что dentry не принадлежит файловой системе с нулевым fsid (например, fuse): если оба слова fsid нулевые, возвращается -ENODEV, причём для метки inode ошибка ослабляется до weak fsid. Затем проверяется, что dentry не является подтомом (например, btrfs), использующим fsid, отличный от корня суперблока: если fsid не равен fsid корня, возвращается -EXDEV, для inode-метки — weak fsid.

Далее проверяется, что файловая система умеет кодировать уникальный fid: если не умеет, возвращается -EOPNOTSUPP; для sb/mount-меток дополнительно требуется умение декодировать file handle. Для sb/mount-меток поддержка файловой системой нужна потому, что такие метки не несут путь для фильтрации, и события должны содержать fid, который можно сопоставить с объектом файловой системы; поэтому требуется и кодирование, и декодирование file handles.

Проверка поддержки событий проверяет поддержку pre-content событий (нужен флаг SB_I_ALLOW_HSM и только регулярные файлы или каталоги), запрет permission-событий для файловых систем с FS_DISALLOW_NOTIFY_PERM, запрет sb/mount-меток на внутренних псевдо-ФС с SB_NOUSER, а также строгую проверку dirent-событий для не-каталогов при новых API.

dnotify: legacy-механизм

Установка dnotify-наблюдения выделяет и метку для fsnotify, и структуру dnotify, которая к этой метке прикрепляется. Сначала проверяется, что наблюдение разрешено, что маска не нулевая (нулевая маска означает явное снятие наблюдения), и что объект — каталог, иначе возвращается -ENOTDIR. Затем маска пользовательских флагов преобразуется во внутренние через функцию преобразования, после чего выполняется проверка безопасности.

Добавление новой метки обходит список и, если для того же владельца и того же файла уже есть структура, просто дополняет её маску и возвращает -EEXIST; иначе заполняет поля новой структуры и вставляет её в голову списка. Преобразование флагов выполняется только при создании наблюдения и переводит каждый флаг: DN_MULTISHOT в FS_DN_MULTISHOT, DN_DELETE в FS_DELETE | FS_MOVED_FROM, DN_MODIFY в FS_MODIFY, DN_ACCESS в FS_ACCESS, DN_ATTRIB в FS_ATTRIB, DN_RENAME в FS_RENAME, DN_CREATE в FS_CREATE | FS_MOVED_TO, а базовым всегда добавляется FS_EVENT_ON_CHILD.

Доставка в dnotify отличается от очереди inotify тем, что событие отправляется сигналом: для каждой подходящей структуры берётся владелец файла и вызывается отправка сигнала; при этом если структура не помечена FS_DN_MULTISHOT, она удаляется из списка и освобождается, а маска inode пересчитывается.

Общая инфраструктура fsnotify: метки, коннекторы, refcount

Атомарность привязки коннектора к объекту обеспечивается тем, что сначала полностью инициализируется структура коннектора, а затем предпринимается попытка установить её в указатель через cmpxchg; если там уже не NULL, значит кто-то другой успел создать коннектор, и наш коннектор отцепляется и освобождается. Комментарий в коде прямо говорит, что cmpxchg служит барьером, чтобы читатели указателя видели только инициализированную структуру.

Захват коннектора берёт указатель через srcu_dereference под srcu_read_lock, затем под спинлоком проверяет, не помечен ли коннектор как отсоединённый, и если да — захват не удаётся, иначе коннектор удерживается вместе со спинлоком. Добавление метки в список вызывает захват коннектора; если коннектора нет, вызывается привязка коннектора к объекту и происходит переход на повтор. После успешного добавления метки в список записывается коннектор в метку через WRITE_ONCE, и комментарий поясняет, что благодаря cmpxchg при привязке коннектора к объекту инициализация коннектора гарантированно видна всем, кто увидит установленный коннектор.

Пересчёт маски и счётчики watchers

При добавлении или изменении метки берётся спинлок коннектора, проверяется, следил ли объект за детьми до пересчёта, вызывается пересчёт маски, затем снова проверяется слежение за детьми и, если родитель начал следить, устанавливаются флаги PARENT_WATCHED у детей. Пересчёт маски обнуляет новую маску, проходит по всем присоединённым меткам, объединяет их маски и записывает результат в маску объекта через WRITE_ONCE; для inode-коннектора он также возвращает признак необходимости удержания ссылки на inode, если хотя бы одна присоединённая метка не имеет флага NO_IREF.

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

Обновление счётчиков на суперблоке определяет highest_prio по первой метке в списке, увеличивает счётчики для приоритетов от текущего плюс один до highest_prio, уменьшает для приоритетов от текущего до highest_prio плюс один, сохраняет новый приоритет и при появлении первой метки ставит флаг IS_WATCHED и увеличивает общий счётчик, а при исчезновении последней метки снимает флаг и уменьшает общий счётчик.

Гарантии времени жизни метки

Метку нельзя освободить, пока кто-то ещё держит ссылку. При lockless-обходе списка меток система сначала пытается увеличить счётчик ссылок только если он не ноль, а затем под блокировкой метки проверяет флаг ATTACHED: если метка ещё прикреплена, счётчик ожиданий у группы растёт и метка считается удержанной, иначе ссылка откатывается и метка пропускается.

Подготовка к ожиданию пользователя удерживает каждую метку итерации счётчиком ссылок и только после этого снимает SRCU-блокировку. Завершение ожидания заново берёт SRCU-блокировку и для каждой метки уменьшает счётчик ссылок; когда счётчик ожиданий доходит до нуля и группа завершается, ожидающие на очереди уведомлений пробуждаются.

Уничтожение меток под блокировкой коннектора берёт ссылку на каждую метку, отпускает блокировку, отсоединяет и освобождает метку и затем отпускает ссылку, поэтому метка не освобождается, пока идёт обход. Освобождение метки лишь помечает её как неживую, снимая флаг ALIVE, и вызывает callback группы; фактическое освобождение откладывается до падения последней ссылки, а ожидание уничтожения меток дожидается выполнения отложенной работы. Рабочий поток перед фактическим освобождением меток ждёт завершения SRCU-периода.

Пограничные случаи и гонки

В обработчике события inotify явно обрабатывается гонка с отсоединением метки: перед созданием события читается wd, и если он равен -1, событие не создаётся. При освобождении метки вызывается удаление из idr, а callback для случая, когда idr освобождается, но в нём ещё остались метки, предназначен именно для этой ситуации. Лениво снимается флаг DCACHE_FSNOTIFY_PARENT_WATCHED у dentry, если родительский inode больше не следит за детьми. Проверка наличия подписчиков на маску проверяет, есть ли вообще inode, mount или sb, подписанные на данный mask.

Поведение при удалении inode и суперблока

При вытеснении inode из ядра очищаются все метки на этом inode. При удалении суперблока сначала проверяется, добавлялись ли вообще метки на объекты этого суперблока, и если нет — работа сразу завершается; иначе выполняется размонтирование inode, затем очистка меток по суперблоку, после чего система дожидается исчезновения внешних ссылок на отслеживаемые объекты и проверяет отсутствие приоритетных наблюдателей.

Размонтирование inode идёт в цикле: пока среди коннекторов inode суперблока есть inode, не находящийся в состоянии освобождения, такой inode получает событие FS_UNMOUNT, его метки очищаются, а счётчик ссылок уменьшается. Когда подходящих inode больше не остаётся, цикл заканчивается.

Атаки через файловые уведомления

В исследовании описаны две атаки на файловые уведомления Linux. Первая — inter-keystroke timing attack: атакующий использует inotifywatch для наблюдения за каталогом и по времени между событиями определяет нажатия клавиш, причём даже без права чтения файлов внутри каталога. Вторая — UI-redress attack (clickjacking) на KDE 5 и KDE 6: атакующий следит за /usr/bin/pkexec, чтобы определить момент, когда Polkit выводит окно аутентификации, и рисует поверх настоящего окна поддельное окно ввода пароля для сбора учётных данных.

Обе уязвимости сохраняются, хотя ядро частично смягчило проблему исправлением, вошедшим в версии 5.10.248, 5.15.198, 6.1.160, 6.6.120, 6.12.65 и 6.18.3, выпущенные в январе.

Ограничения и предупреждения inotify

Дескрипторы inotify можно отслеживать через select, poll и epoll: при наличии события дескриптор становится доступным для чтения. С Linux 2.6.25 доступна сигнальная уведомляемость ввода-вывода для дескрипторов inotify, при этом в структуре siginfo_t устанавливаются номер дескриптора, номер сигнала, код POLL_IN и POLLIN в поле band.

Последовательные идентичные события (совпадают wd, mask, cookie и name) объединяются в одно, если более старое ещё не прочитано, что снижает расход памяти, но не позволяет надёжно подсчитывать события файлов. Очередь событий может переполняться, и тогда события теряются; надёжные приложения должны корректно обрабатывать потерю событий, например пересоздавая дескриптор inotify и кэш.

До Linux 3.19 fallocate не создавал событий inotify, а начиная с Linux 3.19 вызовы fallocate генерируют события IN_MODIFY. До Linux 2.6.16 флаг маски IN_ONESHOT не работал, а с Linux 2.6.36 при срабатывании IN_ONESHOT генерируется событие IN_IGNORED. До Linux 2.6.25 код объединения событий ошибочно проверял возможность объединения самого нового события с самым старым непрочитанным, а не с двумя последними.

При удалении watch-дескриптора (или из-за удаления файла или размонтирования) непрочитанные события для него остаются доступными для чтения; при последующем выделении дескрипторов ядро циклически перебирает диапазон от 1 до INT_MAX и не проверяет наличие непрочитанных событий для номера, что может привести к переиспользованию номера с оставшимися событиями.

Root-эксплойты на Android: кейс OnePlus

Уязвимости OnePlus позволяют установленному приложению получить root без разрешений: исследователь Rasmus Moorats связал две ошибки в собственном ПО OnePlus. Первая — в сервисе AtlasService, который собирает отладочные данные, работает от root и принимает вызовы от любого приложения без проверки вызывающего; специально сформированный вызов попадает в отладочный инструмент, который берёт текст приложения и без проверки вставляет его в системную команду, давая root только внутри ограниченной зоны dumpstate. Вторая — в аппаратном помощнике olc2, у которого есть команда, выполняющая любую полученную shell-инструкцию, а единственная защита — требование, чтобы вызывающий уже был root, что обеспечивает первая ошибка; тогда команда выполняется в зоне со всеми низкоуровневыми привилегиями Linux, включая загрузку кода ядра.

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

Кто попадает под воздействие

Под воздействие попадают пользователи, у которых на телефоне установлено и запущено вредоносное приложение. Атака не требует разрешений и не показывает пользователю запросов, и сработала на немодифицированном телефоне. Затронуты OnePlus 12 Pro и, по ожиданиям исследователя, OxygenOS 16 в целом, а также устройства OPPO из-за общей программной основы. На момент раскрытия не было ни CVE, ни исправления, ни advisory. Единственная практическая защита до выпуска исправления — устанавливать приложения только из доверенных источников.

Что из этого следует на практике

Файловые уведомления — это канал утечки информации о поведении системы, а не только инструмент мониторинга. inotify позволяет наблюдать за каталогом без права чтения файлов внутри него, и именно на этом строятся обе описанные атаки: timing-атака по нажатиям клавиш и подмена окна аутентификации. Исправление в ядре лишь частично смягчило проблему, но не устранило её.

Лимиты inotify — 128 групп на пользователя и от 8192 до 1048576 watches — задают границы, в которых приложение может одновременно наблюдать за объектами. Переполнение очереди событий означает потерю событий, поэтому надёжные приложения должны уметь восстанавливать состояние, а не полагаться на полноту очереди. Объединение идентичных событий снижает расход памяти, но делает inotify непригодным для точного подсчёта событий.

Гарантии времени жизни метки в fsnotify построены на комбинации счётчика ссылок, SRCU-блокировок и отложенного освобождения: метка не освобождается, пока с ней работает кто-то ещё, а фактическое освобождение дожидается завершения SRCU-периода. Гонка с отсоединением метки обрабатывается чтением wd: если он равен -1, событие не создаётся.

Для fanotify ключевая особенность — группы разрешений и сторожевой таймер: процесс, ожидающий ответа на permission-событие, не разблокируется при срабатывании watchdog, код лишь помечает событие счётчиком и печатает предупреждение. Невалидный ответ не применяется, и событие остаётся в списке ожидания до таймаута или до корректного ответа.

Для Android-устройств OnePlus описанная цепочка уязвимостей показывает, как сервис, работающий от root и принимающий вызовы без проверки вызывающего, в сочетании с инструментом, выполняющим произвольные shell-инструкции, даёт полный контроль над устройством. Защита до выпуска исправления — установка приложений только из доверенных источников.

Где смотреть в коде

Источники

Похожее