
HUAWEI MatePad Pro 11 GOT-W29에 대한 CVE-2026-43499 (GhostLock) 연구
GOT-W29(HarmonyOS 4.0, kernel 4.19.157-perf+)에서
CVE-2026-43499(rtmutex/futex-PI UAF, "GhostLock") 권한 상승 연구.
핵심 결론:
sysctl_bootid를
재작성, KASLR slide 유도).| 항목 | 값 |
|---|---|
| 모델 | HUAWEI MatePad Pro 11 GOT-W29 |
| SoC | Qualcomm kona (SM8250, Snapdragon 870) |
| 시스템 | HarmonyOS 4.0 (104.0.0.136) |
| 커널 | 4.19.157-perf+ |
| VA | 39-bit, 4K pages, KASLR on |
kernel/locking/rtmutex.c의 remove_waiter()가
rt_mutex_start_proxy_lock() 롤백 경로에서 waiter->task가 아닌 current로 정리하여
댕글링 pi_blocked_on(스택 UAF)을 유발. 2.6.39 ~ 7.1에 영향(본 커널은 범위 내).
업스트림 수정 커밋 3bfdc63936dd.
본 장치 확인: 소스 rtmutex.c:1110-1112, boot.elf 디컴파일, 실기기 트리거 모두 검증.
PI 링을 만들어 FUTEX_CMP_REQUEUE_PI가 -EDEADLK를 반환하게 하고, 롤백이 remove_waiter
버그를 트리거하여 댕글링 pi_blocked_on(waiter 스레드 커널 스택의 rt_waiter를 가리킴)을 남김.
기존 트리거는 waiter가 requeue 대상 futex를 자체 보유(self-own)하게 하여, 본 커널의
task_blocks_on_rt_mutex 조기 owner==task 검사(boot.elf 0x3808-0x3868)에 정확히 걸려
pi_blocked_on 쓰기 전에 반환 → 댕글링 포인터가 절대 생성되지 않음 → overlay 배치는
오진(크래시 없음 + boot_id 불변).
PI 링: owner가 FUTEX_LOCK_PI(target)으로 requeue 대상을 보유; waiter는 chain futex 보유;
owner는 다시 chain에 블록(링: waiter→target→owner→chain→waiter). requeue 시
체인 워크가 rt_mutex_owner(chain)==top_task를 감지 → -EDEADLK → 롤백이 잘못된 대상의
pi_blocked_on을 정리 → waiter의 pi_blocked_on이 댕글링. owner는 우선순위를 낮춰야 함
(nice=10) — boost 후 prio가 owner_waiter->prio와 달라야 하며, 그렇지 않으면
rt_mutex_waiter_equal이 조기 종료.
shell(uid 2000)에서 perf_event_paranoid=-1, perf_event_open(PERF_SAMPLE_IP, exclude_user=1)으로 커널 텍스트 주소 클러스터를 샘플링, 알려진 심볼 오프셋과 정렬하여 slide 도출.
samples=27651 kernel_ips=1685 lo=0xffffff948728176c hi=0xffffff9488ebfc7c
KASLR slide=0x147f200000 (40/40 IP가 커널 텍스트 영역에 매핑됨을 검증)
runtime _stext=0xffffff9487280800
도구: tools/perf_kaslr.c. 실행 전제: shell(Shizuku rish), seccomp 차단 없음.
[M] CMP_REQUEUE_PI ret=-1 errno=35 (EDEADLK!)
[W] WAIT_REQUEUE_PI ret=-1 errno=110 (ETIMEDOUT) ← waiter 반환
[M] waiter_returned=1 ← 댕글링 pi_blocked_on 남김
도구: tools/edeadlk_probe.c(variant 8+2+1 = 11, 또는 27).
rt_mutex_adjust_prio_chain step[7]이 fake waiter에 대해 rb_erase(단일 왼쪽 자식 경로) 수행:
*(tree_left) = tree_pc + __rb_change_child 증분 쓰기. target.h의 모든 오프셋은
boot.elf 디스어셈블리 실측값.
exploit/ghostlock-source/src/target.h 참조. 요점:
자체 작성 KPM(tools/kpm-debug/rtmutex-dbg.c, KernelPatch 0.13.5 inline-hook)이
fake walk 시 overlay(tree/task/lock)를 재구성하고 next_lock 파라미터를
empty_zero_page로 재작성(KernelPatch _transit8가 수정된 fargs로 원래 함수 호출)하여,
rt_mutex_adjust_prio_chain [3] next_lock==waiter->lock 통과, [5] trylock
제로 락 성공, [6] ownerless, [7] rt_mutex_dequeue(rb_erase 단일 왼쪽 자식) 실행 —
sysctl_bootid가 &loggers[0][1]로 재작성됨, slide-kaslr-ok.
REPAIR3 waiter=0xffffff801ecdbc00 lock=0xffffffa7c4950000
slide boot_id_leaked_nfulnl_logger value=ffffffa7c4612320
slide-kaslr-ok base=ffffffa7c1280000 slide=00000027b9200000
스택 지오메트리는 boot.elf 프레임 크기로 확정: __arm64_sys_futex(0x70) + do_futex(0x60+0x1a0)에서
rt_waiter는 sp+0xc0 → 깊이 0x1b0; pselect 경로 stack_fds[0] 깊이
0x210, 차이 0x60 = 12 words. 따라서 word_i는 stack_fds[12+i]에 위치:
words 0-2는 ex[2..4] 입력 영역(직접 제어 가능), words 6-7(task/lock)은
res_in[3..4](in[3..4] + POLLIN-ready fd로 인코딩), words 3-5/8-10은 0 유지.
pselect는 ready fd로 즉시 반환 → waiter는 사용자 공간에서 바쁜 대기(시그널 금지, 제로 syscall)하며
consumer가 완료할 때까지 대기.
overlay의 task/lock words는 res_in[3]/[4](fd ready로 인코딩)에 위치. 실측
res_in[4](lock)는 pselect 반환 경로에서 결정적으로 덮어써짐(rt_sigreturn 프레임 잔여물),
payload의 fake_lock과 일치한 적 없음; res_in[3](task)은 가끔 온전함. 사용자 공간 회피
(FP 연산 + sched_yield)는 do_notify_resume 트리거율을 ~21%로 낮출 수 있지만,
lock 덮어쓰기는 거의 필연적.
관련 사실 두 가지 추가:
rt_mutex_adjust_pi()에 if (!owner) return 0;가 있어 fake_lock에 owner가 없으면 adjust_prio_chain을 호출하지 않음. popsicle(6.12)에는
이 검사가 없음. owner-ful payload는 이식됨(fake_lock owner=fake_task|1) 그러나
lock 워드가 반드시 덮어써져 신뢰할 수 없게 트리거됨.boot.elf 디스어셈블리 __arm64_sys_ppoll / do_sys_poll 확인 후: pollfd는
16바이트 구조(fd 4B + events 4B + revents 4B + pad 4B)로 fake waiter의 64비트
task/lock words를 담을 수 없음 — fd 값은 제한적(실제 fd여야 함), events는 4바이트뿐이고
연속적이지 않으며, revents는 커널이 기록(제어 불가).
smt878u / popsicle은 완전한 권한 상승 가능: 해당 스택 지오메트리는 words가 사용자 제어 가능한 in/out/ex(pselect 3개 fd_set)에 위치하도록 허용. GOT-W29의 waiter 위치(bits+0x60 → task/lock이 res_in[3]/[4])에는 해당 창이 없음. 이는 커널 스택 지오메트리 차이이며, 구현 결함이 아님.
앱 도메인(untrusted_app)에는 기성 KASLR 채널이 없음(perf/kallsyms/pagemap/dmesg 모두
거부됨); CMP_REQUEUE_PI는 1(requeue 성공)을 반환하여 EDEADLK 롤백을 거치지 않음; 비-root
cpuset의 major_only는 체인 워크의 step[6]을 하드 쇼트서킷. QOS 변경의 유일한 진입점
/dev/iaware_qos_ctrl은 SELinux에서 거부됨. 따라서 일반 앱 권한으로는 이 CVE를 트리거할 수 없음.
자체 작성 KernelPatch 모듈(rtmutex-dbg)로 실기기에서 fake waiter와 쓰기 원시어를 관찰, LyraVoid/KernelPatch 0.13.5(FolkPatch 동일 소스) 헤더 기반 컴파일.
cd <KernelPatch>/kpms/rtmutex-dbg
make TARGET_COMPILE=aarch64-linux-android- \
CC=$PREFIX/bin/aarch64-linux-android-clang \
LD=$PREFIX/bin/aarch64-linux-android-ld
산출물 rtmutex_dbg.kpm. 컴파일 플래그에 반드시
-fno-pic -fno-pie -fno-asynchronous-unwind-tables -fno-unwind-tables
포함(Makefile에 내장): clang 기본 PIC는 GOT 재배치 생성, 기본 .eh_frame
(R_AARCH64_PREL32) 생성 — KPM 로더가 모두 미지원 → 로드 실패 -1.
FolkPatch의 superkey는 su(APatch 기본 KernelPatch 아님). sc_kpm_load
도구 사용(소스 sc_kpm_load.c):
adb shell /data/local/tmp/sc_kpm_load su /sdcard/Download/rtmutex_dbg.kpm # 로드
adb shell /data/local/tmp/sc_kpm_load unload rtmutex-dbg su # 언로드
adb shell /data/local/tmp/sc_kpm_load ctl rtmutex-dbg counts su # 카운터
run_rtmdbg_test.sh(장치 측 /data/local/tmp/ghostlock-test/): shell 신분
(GOT_SLIDE_NO_RT=1 실제 경로)으로 GhostLock 실행, 0.5s sync 루프로 로그 유실 방지,
dmesg -w 디스크 기록, 90s 후 자동 수거(SIGSTOP으로 soft-lock 재부팅 방지). 테스트 전:
adb shell 'su -c "sh /sdcard/ghostlock-test/set_debug_no_reboot.sh"' # 재부팅 방지
[RTMDBG])REPAIR3: 쓰기 원시어 검증 시 overlay 재구성 및 next_lock 파라미터 재작성FAKEWALK skip: 덮어쓴 fake walk가 건너뜀(쓰지 않고 크래시 없음)prio_chain[N] / prio_chain_ret: walk 호출 및 반환값(0=완주;
4294967261=-EDEADLK)do_select n=320: 커널 측 res_in[3]/[4]futex op=13/14: CMP_REQUEUE_PI / WAIT_REQUEUE_PIHUAWEI oops/panic 전체 스택은 /data/log/bbox/history.log에 기록.
tools/ 검증 도구(perf KASLR, EDEADLK 프로브, KPM 도구 체인)
target/ 전체 실측 오프셋
exploit/ 이식된 slide.c(EDEADLK 트리거 변경 포함)
| hook | 역할 |
|---|
rt_mutex_adjust_pi | PI 조정 기록; overlay 시 skip(pi_blocked_on 정리) |
rt_mutex_adjust_prio_chain | fake walk 무조건 skip; 전체 waiter 덤프 |
__arm64_sys_pselect6 / __arm64_sys_ppoll | 반환 경로에서 _TIF_WORK_MASK 정리; fd_set 관찰 |
do_select | 커널 측 res_in[3]/[4] 읽기 |
__arm64_sys_futex | WAIT_REQUEUE_PI / CMP_REQUEUE_PI 추적 |
rt_mutex_dequeue | step[7] 쓰기 원시어 실행과 tree 형태 확인 |