
Бэкпорты трёх опубликованных исправлений 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 — конец жизненного цикла. Ни для одного из этих исправлений не существует стабильного backport'а, и его не будет. Именно поэтому этот репозиторий существует: устройство моё, оно работает на ядре, которое никто не поддерживает, и один из этих дефектов сбрасывал его.
Я не обнаруживал эти уязвимости. Все три опубликованы, с апстрим-патчами, написанными другими людьми, указанными ниже. Что сделал я:
Пункт 3 — та часть, которую я попросил бы читателя взвесить. Два из них подтверждены измерениями относительно базовой линии. Третье — нет, и не может быть, на этом устройстве. Это различие сохраняется на протяжении всего текста, а не сглаживается.
| # | Патч | Автор апстрима | Статус на железе |
|---|---|---|---|
| 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 на мусорном слове ticket и крутился с замаскированными IRQ вечно. Локальный таймер останавливается, поток подпитки watchdog'а никогда не пробуждается, и небезопасный watchdog срабатывает примерно через 11 секунд. На практике: телефон делает жёсткий сброс. UsbDeviceManager Android'а перекомпоновывает сам по себе — измерено дважды, примерно за 550 мс — без действий пользователя, так что экзотический триггер не требовался.
Апстрим-исправление: "usb: gadget: f_hid: fix f_hidg lifetime vs cdev", John Keeping, 2022-11-22, Fixes: 71adf1189469. Backport чистый — каждый примитив существует в 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'а: два spinlock'а, две головы очередей ожидания и голову списка, ни одна из которых не появляется в hidg_alloc. init_waitqueue_head сбрасывает голову списка на самоссылающуюся, так что poll/select-ожидающий, зарегистрированный до повторного bind'а, молча осиротевает, и его poll_freewait → remove_wait_queue затем находит prev->next неверным.
Апстрим переносит девять инициализаций из hidg_bind; только пять из них существуют в 4.14.190.
⚠ В нескольких вендорных бюллетенях это названо "повышением привилегий". Вектор — C:N/I:N/A:H — только доступность, т.е. локальный отказ в обслуживании. Я отмечаю это, потому что более пугающая трактовка оправдала бы приоритет, который вектор не поддерживает, и никто не требует от вендорной страницы доказывать себя.
Этот патч также идёт на шаг дальше апстрима. Перенос инициализаций из 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. Авторство апстрим-исправлений указано выше; backporting, добавленная блокировка в патче 2 и работа по проверке — мои.
Здесь не распространяются скомпилированные артефакты — ни образ ядра, ни модули, ни прошиваемый пакет. Этот репозиторий — только исходный код и доказательства.