对 drivers/usb/gadget/function/f_hid.c 的三项修复,手工移植到 4.14.190 Android 厂商内核,并在实际运行的设备上验证通过。
4.14 已停止维护。 这些修复均无稳定的反向移植版本,也不会有。这正是本仓库存在的全部原因:设备是我的,运行着一个无人维护的内核,而其中一个缺陷曾导致设备重启。
我没有发现这些漏洞。这三项均已公开,上游补丁由他人编写,署名见下文。我所做的是:
第 3 点是我希望读者重点权衡的部分。其中两项已通过与基线的测量对比得到确认。第三项在此设备上无法确认,也不可能确认。这一区别贯穿全文,而非一笔带过。
| # | 补丁 | 上游作者 | 硬件状态 |
|---|---|---|---|
| 1 | f_hidg 生命周期与 cdev(释放后使用) | John Keeping,2022-11-22 | ✅ 效果已实测 |
| 2 | CVE-2026-31721 — 对象生命周期状态在 hidg_bind 中被重新初始化 | Michael Zimmermann,2026-03-31 | ✅ 效果已实测 |
| 3 | CVE-2026-31606 — 对使用中的 cdev 调用 cdev_init | 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 释放了该对象。没有任何机制针对打开的文件对其进行引用计数——对整个文件执行 refcount|kref|atomic_|open_count 的 grep 搜索,仅找到 opts->lock。
因此,在 Android 重新组合 gadget 后仍存活的 /dev/hidg0 上的 select()/poll() 循环,会在已释放的 slab 上调用 remove_wait_queue(),对垃圾票据字执行 spin_lock_irqsave,并在 IRQ 被屏蔽的情况下永远自旋。本地定时器停止,看门狗喂狗线程永远不会被唤醒,非安全看门狗约 11 秒后触发。实际表现:手机硬重启。Android 的 UsbDeviceManager 会自行重新组合——实测两次约 550 毫秒——无需用户操作,因此这不需要任何特殊触发条件。
上游修复:"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 重新初始化了属于对象生命周期而非绑定生命周期的状态:两个自旋锁、两个等待队列头和一个链表头,这些均未出现在 hidg_alloc 中。init_waitqueue_head 将链表头重置为自指向,因此在重新绑定前注册的 poll/select 等待者会被静默孤立,其 poll_freewait → remove_wait_queue 随后会发现 prev->next 错误。
上游将九项初始化移出 hidg_bind;其中只有五项存在于 4.14.190 中。
⚠ 多家厂商公告将此漏洞定性为"权限提升"。但向量为 C:N/I:N/A:H——仅影响可用性,即本地拒绝服务。 我特别指出这一点,因为更吓人的解读本会赋予一个向量并不支持的优先级,而且没有人会要求厂商页面自证其说。
此补丁还比上游更进一步。将初始化移出 hidg_bind 后,留下了两处对锁保护字段的无锁写入,目前仅因锁在其正上方被立即重新初始化而被掩盖。一旦锁变为持久存在,竞态便真实存在:一个阻塞在 f_hidg_write 等待循环中的写入者,持有一个在解除绑定时幸存的 fd,可以获取锁、通过检查并在绑定于锁外写入 NULL 时读取 hidg->req——重新检查发生在解引用之后。因此,补丁在这两处写入周围加上了 write_spinlock。
一般形式值得阐明:将初始化移出 bind 可能会产生一个原本由重新初始化掩盖的竞态。 请思考被移除的初始化究竟在无意中保护了什么。
CVSS 5.5,相同向量。struct cdev cdev 嵌入在 struct f_hidg 中,hidg_bind 在 cdev_device_add 之前立即对其调用 cdev_init()——即 memset(cdev, 0, sizeof *cdev) 加上 kobject_init。一个已打开的 fd 通过 chrdev_open → cdev_get → kobject_get_unless_zero 持有同一 kobject 上的引用。在 fd 打开的情况下执行 unbind→rebind 时,bind 会清除一个被引用对象上的活动引用状态。
修复是结构体变更而非防护措施:嵌入的 struct cdev 变为 struct cdev *,每次绑定时执行 cdev_alloc()。这样 cdev_init 在文件中出现零次——不再存在使用中的对象可供重新初始化。
⛔ 此设备无法检测此缺陷,无论修复前后。 参见 verification/03-cve-2026-31606.md。这是关于设备检测能力的肯定性陈述,而非含糊其辞。
方法及各缺陷记录见 verification/。简要说明:修复 1 和 2 通过与紧邻前一构建的单变量回归对比得到确认,并设有阳性对照;修复 2 还从另一个日志来源进行了二次确认。修复 3 仅通过源码阅读作为证据。
补丁为针对 4.14.190 Samsung 厂商树的 git format-patch 输出,按顺序应用:
git am patches/0001-*.patch patches/0002-*.patch patches/0003-*.patch
补丁是累积性的,需按顺序应用——补丁 3 假定结构体保持补丁 1 处理后的状态。
GPL-2.0,作为 Linux 内核的衍生作品。参见 LICENSE。原始修复的上游作者署名见上文;反向移植、补丁 2 中新增的锁以及验证工作均为本人完成。
此处不发布任何编译产物——无内核镜像、无模块、无可刷写包。本仓库仅包含源码和证据。