
세 가지 공개된 f_hid 수정 사항(CVE-2026-31721, CVE-2026-31606 포함)을 지원 종료(EOL)된 Linux 4.14.190 Android 벤더 커널에 백포트한 것으로, 기기 내 검증 기록을 포함합니다.
drivers/usb/gadget/function/f_hid.c에 대한 세 가지 수정 사항으로, 4.14.190 Android 벤더 커널로 직접 이식하여 해당 커널이 구동되는 기기에서 검증한 것입니다.
4.14는 수명 종료(EOL) 상태입니다. 이 중 어느 것도 안정적인 백포트가 존재하지 않으며, 앞으로도 나오지 않을 것입니다. 이것이 바로 이 저장소가 존재하는 이유입니다. 기기는 제 것이며, 이 결함 중 하나가 기기를 리셋시키고 있었습니다.
누가 무엇을 유지보수하는지 정확히 말하자면: 이 작업의 기반이 되는 NetHunter 트리(AzeAstro/samsung_kernel_sm7125, OneUI-6 브랜치)는 실제로 활발히 유지보수되고 있으며, 그 작업은 여기서 문제 삼는 바가 아닙니다. 하지만 그것은 기기 및 NetHunter 활성화 작업입니다. 기반은 삼성의 4.14.190 벤더 드롭에 고정된 채로 남아 있습니다. 아무도 4.14.y 안정 보안 수정 사항을 그 안으로 내려받지 않고 있으며, 삼성도 이를 제공하지 않습니다. 4.14.y 자체가 2024년 1월 10일 4.14.336을 끝으로 종료되었고, 해당 시리즈는 더 이상 kernel.org의 지원 릴리스 목록에 올라 있지 않습니다. 이 세 가지 패치가 바로 그 공백에 자리하고 있습니다.
저는 이 취약점들을 발견하지 않았습니다. 세 가지 모두 공개되어 있으며, 다른 사람들이 작성한 업스트림 패치가 있고, 아래에 출처를 밝혔습니다. 제가 한 일은 다음과 같습니다:
3번 항목이 독자께서 저울질해 보시길 청하는 부분입니다. 이 중 두 가지는 기준선 대비 측정으로 확인되었습니다. 세 번째는 그렇지 않으며, 이 기기에서는 그럴 수도 없습니다. 그 구분은 매끄럽게 덮지 않고 문서 전체에 걸쳐 유지했습니다.
| # | 패치 | 업스트림 작성자 | 하드웨어에서의 상태 |
|---|---|---|---|
| 1 | f_hidg 수명 vs 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에 대한 cdev_init | Michael Zimmermann (81ebd43cc0d6d), 2026-03-27; -ENOMEM 후속 수정 by Ethan Tidmore, 2026-04-02 | ⛔ 이 기기에서는 관찰 불가 |
f_hidg 수명 vs cdevf_hidg_open은 f_hidg *를 저장했지만 참조를 잡지 않았습니다. f_hidg_poll은 kzalloc으로 할당된 f_hidg에 내장된 대기 큐에 poll 대기자를 등록했습니다. 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가 마스킹된 채로 영원히 스핀하게 됩니다. 로컬 타이머가 멈추고, watchdog pet 스레드는 결코 깨어나지 않으며, 비보안 watchdog이 약 11초 후에 짖습니다. 실제로는: 핸드셋이 하드 리셋됩니다. Android의 UsbDeviceManager는 사용자 조작 없이 스스로 재구성하며 — 약 550ms에서 두 번 측정됨 — 따라서 이는 별다른 트리거가 필요하지 않았습니다.
업스트림 수정: "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를 잘못된 것으로 찾게 됩니다.
Upstream는 hidg_bind에서 아홉 개의 초기화를 밖으로 옮기지만, 4.14.190에는 그중 다섯 개만 존재합니다.
⚠ 여러 벤더 권고문이 이 문제를 "권한 상승"이라고 제목을 붙입니다. 벡터는 C:N/I:N/A:H —
가용성에만 해당하며, 즉 로컬 서비스 거부입니다. 제가 이 점을 지적하는 이유는, 더 무서운 해석이
벡터가 뒷받침하지 않는 우선순위를 정당화했을 것이며, 아무도 벤더 페이지에 스스로를 증명하라고
요구하지 않기 때문입니다.
이 패치는 또한 upstream보다 한 단계 더 나아갑니다. 초기화를 hidg_bind 밖으로 옮기면 잠금으로
보호되는 필드에 대한 잠금 없는 저장 두 개가 남는데, 이는 오늘날 오직 그 바로 위에서 잠금이
재초기화되고 있었기 때문에만 가려져 있습니다. 잠금이 지속되면 경쟁 조건은 실제가 됩니다:
f_hidg_write의 대기 루프에서 블록된 작성자가, unbind에서 살아남은 fd 상에서, 잠금을 획득하고
검사를 통과한 뒤 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는 chrdev_open → cdev_get →
kobject_get_unless_zero를 통해 같은 kobject에 대한 참조를 보유합니다. fd가 열린 상태에서
unbind→rebind가 일어나면, bind는 참조 중인 객체의 살아있는 참조 상태를 지워버립니다.
수정은 가드가 아니라 구조체 변경입니다: 내장된 struct cdev가 struct cdev *가 되고 bind마다
cdev_alloc()을 사용합니다. 그러면 cdev_init은 파일 내에서 0번 등장합니다 — 재초기화할
사용 중인 객체가 남지 않습니다.
⛔ 이 장치는 수정 전이든 후든 이 결함을 탐지할 수 없습니다. verification/03-cve-2026-31606.md를
참조하십시오. 이는 장치의 계측에 대한 단정적 주장이며, 얼버무림이 아닙니다.
방법과 결함별 기록은 verification/에 있습니다. 요약하면: 수정 1과 2는 양성
대조군과 함께 직전 빌드에 대한 단일 변수 회귀를 통해 종료되었고, 수정 2는 다른 로그 소스에서
두 번째로 확인되었습니다. 수정 3은 소스 읽기만으로 입증됩니다.
패치는 4.14.190 삼성 벤더 트리에 대한 git format-patch 출력이며, 순서대로 다음과 같습니다:
git am patches/0001-*.patch patches/0002-*.patch patches/0003-*.patch
이 패치들은 누적 적용을 전제로 하며 순서대로 적용해야 합니다 — 3번 패치는 1번 패치가 남긴 구조체를 전제로 합니다.
Linux 커널의 2차적 저작물로서 GPL-2.0을 따릅니다. LICENSE를 참조하세요. 원본 수정 사항의 업스트림 저작자는 위에 명시되어 있으며, 백포팅, 2번 패치에서 추가된 잠금, 그리고 검증 작업은 제가 수행했습니다.
여기에는 컴파일된 산출물이 배포되지 않습니다 — 커널 이미지도, 모듈도, 플래시 가능한 패키지도 없습니다. 이 저장소는 소스와 증거 자료만 포함합니다.