
CVE-2022-22706에 대한 개념 증명 익스플로잇: Mali GPU 커널 드라이버의 페이지 캐시 쓰기 결함을 악용하여 메모리에서 /etc/passwd를 수정하고 루트 셸을 획득합니다.
Arm Mali GPU 드라이버는 읽기 전용으로 고정한 페이지들에 대해 사용자 공간에 CPU 쓰기 가능 매핑을 제공합니다. 따라서 권한이 없는 프로세스는 O_RDONLY로만 열 수 있는 파일의 페이지 캐시에 대한 쓰기 가능한 별칭을 얻게 됩니다.
exploit.c는 /etc/passwd 페이지 캐시에서 root의 비밀번호 필드를 비우고 su root를 실행합니다. 디스크의 파일은 절대 수정되지 않습니다.
이 저장소의 드라이버 트리와 QEMU 타깃을 위한 PoC입니다 (
mali_kbaser35p0-01eac0,CONFIG_MALI_NO_MALI=y, x86_64 GKI 5.15). VM에서 실행하세요.
임포트된 페이지에 누가 쓸 수 있는지에 대해 두 결정이 서로 일치하지 않습니다.
CPU 매핑의 쓰기 가능성은 KBASE_REG_CPU_WR에서 비롯되지만, pin은 오직 KBASE_REG_GPU_WR만을 기준으로 쓰기 액세스를 요청합니다:
/* mali_kbase_mem.c */
pinned_pages = pin_user_pages_remote(
mm, address, alloc->imported.user_buf.nr_pages,
reg->flags & KBASE_REG_GPU_WR ? FOLL_WRITE : 0, pages, NULL, NULL);
CPU_WR가 설정되고 GPU_WR가 해제된 상태로 임포트하면 두 가지를 동시에 얻습니다: 임포트에 대한 쓰기 가능한 CPU 매핑과 FOLL_WRITE 없이 수행된 get_user_pages pin입니다. FOLL_WRITE가 없으면 get_user_pages는 읽기 전용 파일 매핑에서 COW를 절대 깨지 않으며 페이지 캐시 페이지 자체를 반환합니다. 그러면 드라이버는 그 정확한 페이지들을 쓰기 가능한 상태로 사용자 공간에 다시 매핑합니다.
5381ff7 ("GPUCORE-32592 Fix userbuf imports to respect RO memory")에 의해 수정되었습니다. 이 수정은 쓰기 액세스를 GPU_WR 단독이 아닌 KBASE_REG_CPU_WR | KBASE_REG_GPU_WR에서 파생합니다.
sequenceDiagram
participant U as unprivileged process
participant K as mali_kbase
participant PC as page cache
U->>U: mmap /etc/passwd O_RDONLY, PROT_READ
U->>K: MEM_IMPORT(anon page, CPU_RD|CPU_WR|GPU_RD)
Note over K: address recorded, nothing pinned yet
U->>U: munmap(anon) + mremap file mapping onto that VA
U->>K: mmap(import cookie) → writable CPU mapping
U->>K: JOB_SUBMIT(EXTERNAL_RESOURCES)
K->>PC: pin_user_pages_remote() without FOLL_WRITE
U->>PC: memcpy() through the writable mapping
U->>U: execl("/bin/su", "su", "root")MEM_IMPORT는 주소만 기록합니다. pin은 나중에 JOB_SUBMIT에서 수행됩니다. 바로 이 간격 덕분에 그 사이에 익명 페이지가 파일 매핑으로 교체될 수 있습니다.
편집은 길이를 보존하므로 root의 줄 뒤에 있는 어떤 것도 이동하지 않습니다:
root:x:0:0:root:/root:/bin/sh ← before
root::0:0:rootx:/root:/bin/sh ← after (empty password)
busybox su는 비밀번호 필드가 비어 있으면 프롬프트를 표시하기 전에 CHECKPASS_PW_HAS_EMPTY_PASSWORD를 반환하며, 정확히 x일 때만 /etc/shadow를 읽습니다.
gcc -static -o exploit exploit.c
VM에 복사한 후 권한이 없는 사용자로 실행하세요:
$ ./exploit

QEMU 타깃에 user(uid 1000)로 SSH 접속하여 녹화했습니다. ./exploit는 /etc/passwd 페이지 캐시에서 root의 줄을 패치하고 su root를 실행하며, 프롬프트 없이 곧바로 root 셸로 떨어집니다.
그 셸을 나와서 su를 다시 실행해도 여전히 root가 됩니다: 페이지가 캐시에 남아 있으므로 이후의 모든 /etc/passwd에 대한 open()/read()는 패치된 바이트를 보게 됩니다.
echo 1 > /proc/sys/vm/drop_caches가 페이지를 축출하고 파일이 저장소에서 다시 읽힌 후에는 su가 다시 비밀번호를 요구합니다. 디스크의 바이트는 결코 건드려지지 않았습니다.