
CVE-2026-46215のエクスプロイトで、LinuxカーネルDRM GEMのuse-after-freeによるローカル権限昇格です。レース、スラブスプレー、Dirty Pipeスタイルのファイル上書きを使用して、権限のないレンダーノードユーザーをパスワードなしのrootに変えます。
change_handle の Use-After-Free (非特権 LPE)DRM コアの ioctl DRM_IOCTL_GEM_CHANGE_HANDLE (drm_gem_change_handle_ioctl, ioctl nr 0xD2) における Use-After-Free を介したローカル特権昇格。レンダーノード (/dev/dri/renderD*、主要なデスクトップディストリビューションでは systemd-logind によってアクティブセッションに許可される) にアクセスできるすべてのユーザーが到達可能です。このリポジトリのチェーンは、非特権ユーザーからパスワードなしの root への UAF を実現します。
AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)53096728b891 ("drm: Add DRM prime interface to reassign GEM handle", David Francis / AMD), AMD の CRIU 作業のために追加5e28b7b94408)。この ioctl は、この脆弱性および関連する競合のため、上流の 7.1 で無効化される予定。このバグは、最初に Puttimet Thammasaeng によって報告され、上流では修正の Reported-by クレジットは彼にあります。私は独自に発見し、2026-04-12 に [email protected] に報告しました。私の報告は認識され、メンテナーに転送されましたが、上流でクレジットされたのは先の報告です。このリポジトリには、私自身の分析とエクスプロイトが含まれています。
Writeup: https://cyberstan.co.uk
修正はリリース済みの安定版カーネル (6.18.32 以降および 7.0.9 以降) に含まれています。このリポジトリは、修正が広く利用可能になった後に公開されました。
drm_gem_change_handle_ioctl() は GEM オブジェクトをあるハンドルから別のハンドルに移動しますが、obj->handle_count を一切調整しません。また、drm_vma_node_allow/revoke やドライバの open/close コールバックもスキップします。handle_count が 1 のままであるため、古いハンドルに対する同時の GEM_CLOSE によってカウントが 0 に減少し、オブジェクトが解放される一方で、新しいハンドルは依然として IDR 内でそのオブジェクトを参照しています。そのダングリングハンドルが Use-After-Free であり、後で drm_gem_object_release_handle() で逆参照されます。
GEM_CHANGE_HANDLE と GEM_CLOSE を競合させ、ダングリングハンドルを取得する。pipe_buffer 配列を使って解放されたオブジェクトのスラブスロットを再利用する(msg_msg feng shui で kmalloc-512 を調整し、その後 splice で満たされたパイプ)。pipe_buf_ops をリークする: obj->size (オフセット 216) が pipe_buf[5].ops と重なり、カーネルポインタと KASLR ベースを提供する。obj->name (オフセット 224) が pipe_buf[5].flags に重なり、PIPE_BUF_FLAG_CAN_MERGE (name = 16 = 0x10) を設定する。/etc/passwd、root がパスワードなしになる。オフセットは pahole で検証されており、6.18 から 7.0 のレイアウトに固有です。他のカーネルでは GEM_* / PIPEBUF_* の定義を上書きしてください。
poc.c - エクスプロイト。-lpthread で静的にビルド。run_exploit.sh - PoC と最小限の initramfs をビルドし、QEMU で起動。qemu-system-x86_64、gcc、busybox (静的)、fakeroot、cpio、gzip。KVM (/dev/kvm) を推奨します。これにより競合は非常に信頼性が高まります。
エクスプロイトには、特定のオプションでビルドされた脆弱なターゲットが必要です:
CONFIG_KASAN は OFF でなければなりません。KASAN は解放されたスラブを隔離し、パイプスプレーによる再利用をブロックするため、KASAN ビルドではエクスプロイトは機能しません。virtio_gpu (デモで使用) または nouveau。CONFIG_DRM=y、CONFIG_DRM_VIRTIO_GPU=y、CONFIG_DEVTMPFS=y、CONFIG_BLK_DEV_INITRD=y。手順:
CONFIG_KASAN が設定されていないことを確認する。make -j"$(nproc)" bzImage を実行し、arch/x86/boot/bzImage を生成する。デモは nokaslr で起動するため、リークされたポインタは決定的です。リークは単独で KASLR を無効化するため、KASLR オンでも動作しますが、アドレスは起動ごとに異なります。
drivers/gpu/drm/drm_gem.c の関数 drm_gem_change_handle_ioctl() を確認します。
簡単な確認:
awk '/^int drm_gem_change_handle_ioctl/,/^}/' \
drivers/gpu/drm/drm_gem.c | grep -c handle_count
0 は VULNERABLE (参照カウント処理なし) を意味します。構造による判定:
file_priv->prime.lock を取得し、単一の idr_alloc(&file_priv->object_idr, obj, ...) を実行し、その後古いハンドルを idr_remove() する。drm_gem_object_handle_get はなし。idr_alloc してから idr_replace(NULL) で古いハンドルを切り離す)。実際のオブジェクトはプライム操作が成功した後にのみ入れ替えられる。./run_exploit.sh /path/to/bzImage
これにより poc.c がビルドされ、initramfs にラップされ、-device virtio-gpu-pci と nokaslr を指定して QEMU が起動します。PoC は uid 1000 (非特権) で実行され、その後スクリプトが /etc/passwd を前後で表示し、シェルを起動します。VM からは poweroff -f または Ctrl-A X で終了します。
[!] Race won (iter 977): handle=132049
[!] KASLR: pipe_buf_ops = 0xffffffff82428400
[!] EXPLOIT SUCCESSFUL
[!] FLINK: 16 = 0x10
[*] /etc/passwd:
root::0:0:pwned:/root:/bin/sh
[!] LPE CONFIRMED
[!] root account is now passwordless
root が所有する読み取り専用 (chmod 444) の /etc/passwd が非特権プロセスによって上書きされます。root: 行からパスワードフィールドが削除されます。
テストでは起動ごとに約99% (100回の新規起動中99回成功、さらに以前の実行で53/53)。PoC は内部で再試行します: リークが失敗した場合、死んだスロットを再スプレーするのではなく、新しいダングリングオブジェクトのために再競合します。最大200ラウンド、各ラウンドは数十ミリ秒。ほとんどの起動で最初の競合で成功し、最悪でも200ラウンド中11ラウンド使用しました。
稀な (約1%) 障害モード: 競合の負けによるインターリーブがカーネル状態を破壊し、VM がフリーズする (サイレントハング、判定が出力されない)。これはカーネル競合 UAF に固有のものです。実行開始から約30秒以内に判定が出力されない場合は、電源を入れ直して再実行してください。
パッチ適用済みツリー (6.18.32, 7.0.9, 7.1-rc3 以降、または上記のパッチ確認に合格する任意のツリー) で再ビルドし、再度実行します:
./run_exploit.sh /path/to/patched-bzImage
パッチ適用済みカーネルに対して、エクスプロイトは決して "EXPLOIT SUCCESSFUL" に到達しません。競合によって新しいハンドルの下からオブジェクトが解放されることがなくなり、リークが有効なカーネルポインタを返さなくなるためです。
上流の修正が出荷された後、研究および防御目的で公開されています。現状のまま提供されます。制御下にある VM でのみ実行してください。