
CVE-2022-22706 の概念実証エクスプロイト: Mali GPU カーネルドライバーのページキャッシュ書き込みの欠陥を悪用して、/etc/passwd をメモリ上で改変し、root シェルを取得します。
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内で実行してください。
インポートされたページに誰が書き込めるかについて、2つの判断が矛盾しています。
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")によって修正されました。これは、書き込みアクセスを 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 はアドレスを記録するだけであり、ピン留めは後で 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 を返し、/etc/shadow を読み取るのはフィールドが正確に x の場合だけです。
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 は再度パスワードを要求します。ディスク上のバイトは一切変更されていません。