
Бэкпорты трёх опубликованных исправлений f_hid (включая CVE-2026-31721, CVE-2026-31606) для устаревшего вендорного ядра Android на базе Linux 4.14.190, с записями проверки на устройстве.
Три исправления в drivers/usb/gadget/function/f_hid.c, вручную перенесённые в вендорное ядро Android 4.14.190 и проверенные на устройстве, на котором они работают.
4.14 — это конец жизненного цикла. Ни для одного из этих исправлений не существует стабильного бэкпорта, и он не появится. Именно в этом причина существования данного репозитория: устройство моё, и один из этих дефектов вызывал его перезагрузку.
Чтобы быть точным в вопросе о том, кто что поддерживает: дерево NetHunter, из которого это собрано
(AzeAstro/samsung_kernel_sm7125, ветка
OneUI-6), действительно активно поддерживается, и эта работа здесь не ставится под сомнение. Но это поддержка устройства и NetHunter — база остаётся закреплённой на вендорском срезе Samsung 4.14.190. Никто не переносит в него стабильные исправления безопасности 4.14.y, и Samsung их тоже не поставляет — сам 4.14.y завершился на 4.14.336 10 января 2024 года, и эта серия больше не числится среди поддерживаемых релизов kernel.org. Именно в этом пробеле и находятся эти три патча.
Я не обнаружил эти уязвимости. Все три опубликованы, с патчами в upstream, написанными другими людьми, с указанием авторства ниже. Что я сделал:
Пункт 3 — это то, что я попросил бы читателя взвесить. Два из них подтверждены измерением относительно базовой линии. Третий — нет, и не может быть, на этом устройстве. Это различие сохраняется на протяжении всего текста, а не сглаживается.
| # | Патч | Автор upstream | Статус на оборудовании |
|---|---|---|---|
| 1 | Время жизни f_hidg против cdev (use-after-free) | John Keeping, 2022-11-22 | ✅ эффективность измерена |
| 2 | CVE-2026-31721 — состояние времени жизни объекта повторно инициализируется в hidg_bind | Michael Zimmermann, 2026-03-31 | ✅ эффективность измерена |
| 3 | CVE-2026-31606 — cdev_init на используемом cdev | Michael Zimmermann (81ebd43cc0d6d), 2026-03-27; доработка -ENOMEM от Ethan Tidmore, 2026-04-02 | ⛔ не наблюдаемо на этом устройстве |
f_hidg против cdevf_hidg_open сохранял f_hidg * и не брал ссылку. f_hidg_poll регистрировал ожидающих poll на
очередях ожидания, встроенных в kzalloc'нутый f_hidg. hidg_unbind вызывал cdev_del(), который не
инвалидирует открытые fd, а hidg_free затем kfree'ил объект. Ничто не подсчитывало ссылки на него против открытых
файлов — grep по всему файлу на refcount|kref|atomic_|open_count нашёл только opts->lock.
Таким образом, цикл select()/poll() на /dev/hidg0, переживший перекомпоновку гаджета Android, вызывал бы
remove_wait_queue() на освобождённой slab-памяти, брал бы spin_lock_irqsave на мусорном слове тикета и крутился бы
с замаскированными IRQ вечно. Локальный таймер останавливается, поток, дёргающий watchdog, никогда не просыпается, и
non-secure watchdog срабатывает примерно через 11 с. На практике: аппарат жёстко перезагружается. Android'овский
UsbDeviceManager перекомпоновывает сам по себе — измерено дважды с интервалом ~550 мс — без действий пользователя, так что это
не требовало экзотического триггера.
Исправление в upstream: "usb: gadget: f_hid: fix f_hidg lifetime vs cdev", John Keeping, 2022-11-22,
Fixes: 71adf1189469. Бэкпорт чистый — каждый примитив существует в 4.14.190 — за исключением
kfree(hidg->set_report_buf) из патча, поля, которого в этой версии нет, и оно отброшено.
CVSS 3.1 5.5, вектор AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H. hidg_bind повторно инициализировал состояние,
принадлежащее времени жизни объекта, а не bind'у: два спинлока, две головы очередей ожидания и голову списка,
ни одна из которых не появляется в hidg_alloc. init_waitqueue_head сбрасывает голову списка в
самоуказывающую, поэтому ожидающий poll/select, зарегистрированный до перепривязки, молча осиротевает, и его
poll_freewait → remove_wait_queue затем обнаруживает неверный prev->next.
Upstream выносит девять инициализаций из hidg_bind; только пять из них существуют в 4.14.190.
⚠ Несколько вендорских рекомендаций озаглавливают это как "повышение привилегий". Вектор — C:N/I:N/A:H —
только доступность, то есть локальный отказ в обслуживании. Я отмечаю это, потому что более пугающее прочтение
оправдало бы приоритет, который вектор не поддерживает, а никто не просит страницу вендора доказывать себя.
Этот патч также идёт на шаг дальше upstream. Вынос инициализаций из hidg_bind оставляет два
незаблокированных сохранения в поля, защищённые блокировкой, замаскированных сегодня только тем, что блокировка
переинициализировалась непосредственно над ними. Как только блокировка становится постоянной, гонка реальна: писатель, заблокированный
в цикле ожидания f_hidg_write, на fd, пережившем unbind, может захватить блокировку, пройти проверку
и прочитать hidg->req, пока bind сохраняет NULL вне неё — повторная проверка происходит после разыменования.
Поэтому патч берёт write_spinlock вокруг обоих сохранений.
Общую форму стоит сформулировать: вынос инициализации из bind может создать гонку, которую скрывала переинициализация. Спросите, что удалённая инициализация случайно защищала.
CVSS 5.5, тот же вектор. struct cdev cdev встроена в struct f_hidg, и hidg_bind вызывал
cdev_init() на ней — что есть memset(cdev, 0, sizeof *cdev) плюс kobject_init — непосредственно
перед cdev_device_add. Открытый fd держит ссылку на тот же kobject через chrdev_open →
cdev_get → kobject_get_unless_zero. При unbind→rebind с открытым fd bind стирает живое состояние ссылки
на объекте, на который есть ссылка.
Исправление — изменение структуры, а не защитная проверка: встроенная struct cdev становится struct cdev *
с cdev_alloc() на каждый bind. cdev_init тогда встречается в файле ноль раз — не остаётся используемого
объекта для переинициализации.
⛔ Это устройство не может обнаружить этот дефект, ни до, ни после исправления. См.
verification/03-cve-2026-31606.md. Это утвердительное заявление об инструментации устройства, а не
оговорка.
Метод и записи по каждому дефекту в verification/. Краткая версия: исправления 1 и 2
были закрыты однопеременной регрессией относительно непосредственно предшествующей сборки, с положительными
контролями, и исправление 2 было подтверждено второй раз из другого источника логов. Исправление 3 подтверждается
только чтением исходного кода.
Патчи — это вывод git format-patch относительно вендорского дерева Samsung 4.14.190, по порядку:
git am patches/0001-*.patch patches/0002-*.patch patches/0003-*.patch
Они кумулятивны и предполагают применение по порядку — патч 3 исходит из структуры, оставленной патчем 1.
GPL-2.0, как производные работы ядра Linux. См. LICENSE. Авторство исходных исправлений указано выше; портирование, добавленная блокировка в патче 2 и работа по проверке — мои.
Скомпилированные артефакты здесь не распространяются — ни образа ядра, ни модулей, ни прошиваемого пакета. Этот репозиторий содержит только исходный код и доказательства.