binder_free_transaction() 进程生命周期释放后使用
本仓库包含针对 Linux 内核 Binder 释放后使用漏洞的内容,该漏洞已由上游提交
f223d27a546c1e1f48d38fd67760e78f068fe8c4 修复:
binder_chain_64468.c — 一个无特权的概念验证程序,可触发该漏洞并让内核自行证明,附带 KASAN 实验室和易受攻击/已修复的差分测试(lab/、run.sh、verify.sh)。exploit.c — 一个针对 x86_64 的自包含本地权限提升程序。使用 gcc -O2 -pthread -o exploit exploit.c 编译,以普通用户身份运行,最终获得 root shell。demo/ — 一个实验室,在未打补丁的内核上启动真实的 Debian 13 用户空间,因此该漏洞利用程序可由目标自身的 gcc 编译,并在其随后接管的那台机器上运行。所有内容均以普通用户身份(uid/gid 1000,无能力,无命名空间)针对未打任何补丁的纯上游内核运行。
警告
此代码有意竞态内核对象生命周期,然后劫持内核控制流。竞态失败会破坏内核堆状态,并可能导致机器崩溃或挂起。请仅在您拥有的隔离、一次性虚拟机中运行。切勿在宿主机上运行。
binder_free_transaction() 在 t->lock 下从事务中读取目标进程,释放该锁,然后获取目标进程的内部锁:```c
spin_lock(&t->lock);
target_proc = t->to_proc;
spin_unlock(&t->lock);
if (target_proc) {
binder_inner_proc_lock(target_proc); /* use after free */
没有任何东西能让 `target_proc` 在该间隙中保持存活。一个正在被并行销毁的进程可能在此期间到达 `binder_proc_dec_tmpref() -> kfree()`,因此锁会在已释放的内存上被获取。上游修复方案在仍持有 `t->lock` 的同时固定 `t->to_thread`,这会使所属进程保持存活,直到内部锁被使用并释放。
该漏洞由 **Alice Ryhl** 报告,并由 **Carlos Llamas** 修复,两人均来自 Google。[原始报告](https://lore.kernel.org/all/[email protected]/) 附带了相关的 KASAN 跟踪信息。
### 到达易受攻击的访问点
唯一能够到达*外部* `to_proc` 的调用者是 `binder_send_failed_reply()`,并且它仅在 `t->from` 为 `NULL` 时才遍历到 `t->from_parent`:```c
target_thread = binder_get_txn_from_and_acq_inner(t);
if (target_thread) { ...; binder_free_transaction(t); return; }
next = t->from_parent;
binder_free_transaction(t);
t = next;
from_parent 恰好在一处被赋值,而该赋值在几行之前受到 binder 的坏事务栈检查的保护:一个线程只有在栈顶是它正在接收的事务时,才能发送同步事务。因此,对于该链的每一环,子进程的发送方和父进程的接收方都是同一个线程。
这带来一个尖锐的后果。binder_thread_release() 在持有 proc->inner_lock 的情况下遍历正在消亡线程的栈,并写入以下两者:```
iteration j : [holds child->lock ] child->from = NULL ; unlock child->lock
spin_lock(parent->lock)
iteration j+1 : [holds parent->lock] parent->to_proc = NULL
in **同一遍历的相邻迭代中**,每次迭代都持有各自的
`t->lock`。遍历者只有在释放了 `child->lock` 之后,才能得知 `child->from == NULL`,并且需要 `parent->lock` 来获取自身的快照。因此,整个机会窗口就位于该次遍历的 `spin_unlock(&child->lock)` 与
`spin_lock(&parent->lock)` 之间——仅相隔几条指令。释放遍历持有自旋锁,在该处无法被抢占;只有中断才能延迟它。
这正是该竞态条件极为狭窄的原因,也是概念验证和利用程序都依赖概率的原因。
### 概念验证构建了什么
`binder_chain_64468.c` 构建了能够触达易受攻击访问的最短链,从而使遍历者在该窗口内的工作量尽可能小——仅两次 `t->lock` 获取和一次 `kfree()`,不涉及外部 `inner_proc_lock`、无 `wake_up`,也无回复投递:```
B thread i --e2 (sync, code 0x4442414b)--> P thread Y_i
P thread Y_i --e1 (sync, code 0x54414c4c)--> B thread i (nested target)
B thread i stack: [ e2 outgoing , e1 incoming (top) ]
P thread Y_i stack: [ e2 incoming , e1 outgoing (top) ]
P 随后关闭其 binder fd,因此 binder_deferred_release() 会释放
Y_1..Y_K 并最终释放 binder_proc,而每个 B 线程
同时发出 BINDER_THREAD_EXIT:```
binder_thread_release(B_i) nulls e1->to_proc (own proc) and e2->from
binder_send_failed_reply(e1) e1->from == NULL once Y_i was released
-> binder_free_transaction(e1) target_proc already NULL, kfree(e1)
-> binder_free_transaction(e2) target_proc == P <-- vulnerable access
第三个进程是活页夹上下文管理器,仅用于分发另外两个进程所需的句柄。`exploit.c` 完全复用了这一构造。
### 两个独立条件
一份 KASAN 报告需要同时满足以下两点:
* **易受攻击的访问** —— 遍历者必须在上方所述的狭窄窗口内获取 `parent->lock`,从而快照到一个仍然存活的 `to_proc`;以及
* **窗口内的释放落地** —— 遍历者随后必须在释放 `t->lock` 与获取受害者内部锁之间失去其 CPU,并保持离线状态,直到延迟释放完成并释放 `binder_proc`。
第一个条件有其自身的判定依据,无需依赖 KASAN:活页夹会打印```
binder: binder_free_proc: Unexpected outstanding_txns -1
每当这种情况发生时,因为遍历器和 binder_thread_release() 随后都会对同一事务的同一个计数器进行递减。请注意,这本身并非漏洞/修复之间的差异——修复阻止的是释放,而不是第二次递减——因此它在两个内核上都会出现。这里使用它只是为了展示易受攻击的代码路径被执行。
概念验证止步于对已释放内存的 4 字节递减。要将其转化为 uid 0 需要四样东西,而它们都不来自漏洞本身:该漏洞不会泄露任何信息。
struct binder_proc 为 648 字节,通过普通的 GFP_KERNEL kzalloc 分配——而非 __GFP_ACCOUNT。因此它会落入 kmalloc-1k,与该大小的所有其他未记账分配放在一起,并且不会被隔离在 kmalloc-cg-* 之后。仅此一点就使得该对象可以被回收。
kfree() 发生的时刻无法从用户空间观察到,遍历器再次触碰该对象的时刻同样无法观察到,因此没有任何可供计时对齐的时机。因此喷射以泵的形式运行:它持续分配并释放 kmalloc-1k 对象,在运行延迟释放的那个 CPU 上执行,只要遍历器在运行就持续进行。
为此使用 System V 消息。alloc_msg() 是一个普通的未记账 kmalloc,包含 48 字节头部加负载,因此一条 976 字节的消息就是一次 1024 字节的分配;配额是按队列而非按 uid 计算的;并且 msgrcv() 会同步释放。实测吞吐量:每秒约 198,000 次分配,零失败。
首先尝试了 add_key/user_key_payload,但它是个陷阱。其负载受按 uid 的字节配额(kernel.keys.maxbytes,默认 20000)限制,该配额只有在密钥垃圾收集器销毁密钥时才会释放,因此紧凑的分配/释放循环会在毫秒内耗尽它:实测 27,151 次成功分配,对应 2,121,009 次失败——98.7% 的喷射在静默地做无用功,看起来就像一场永远赢不到槽位的喷射。KEYCTL_INVALIDATE 使情况更糟(4,775 次成功),因为它会排队等待 GC 工作。
遍历器会触碰已释放 binder_proc 的四个字段(偏移量通过 pahole 在目标构建上测得):
当 outstanding_txns == 1 且 is_frozen == 1 时,递减达到零,遍历器调用 wake_up_interruptible_all(&proc->freeze_wait)。随后 __wake_up_common 计算 curr = head.next - 24 并调用 *(head.next - 8):一个从 head.next 所指向位置读取的函数指针,而该值由被回收的对象提供。
该指针必须到达攻击者控制的内存,且位于内核地址空间,而该漏洞不泄露任何信息。这两个地址都来自预取计时——与 KASLD(Brendan Coles,MIT)相同的信道,本实现即源自该项目:
内核文本。 对已映射内核地址的预取会在页表遍历中解析并退出,其速度可测量地快于对未映射地址的预取,即使该访问在架构上永远不会可见。扫描文本范围的 2 MiB 槽位会显示镜像为一段快速槽位;其第一个槽位即 _text。
直接映射。 启用 CONFIG_RANDOMIZE_MEMORY 后,直接映射以 1 GiB 为单位随机化,因此也必须定位它。与文本不同,它覆盖全部 RAM,因此它是映射槽位中最长的连续段。需要两项改进才能使其可用:
运行可靠起始的位置并非 page_offset_base 本身,而是内核可能以 1 GiB 页映射的第一个槽位——即覆盖物理 4 GiB 的那个。在其之下,PCI 空洞和固件保留区强制使用 2 MiB 页,其更长的遍历在此处无法与未映射区分。该槽位正是喷射所需的,因此使用它。
随后约 60% 的物理内存被填充为同一精心构造的 4 KiB 页的副本,因此无论布局最终如何,从该锚点起的固定偏移都由该构造页支撑。
freeze_wait.head.next -> entry1 (in the sprayed page)
entry1.func = mov 0x28(%rdi),%rdi ; mov 0x18(%rdi),%rax ; jmp *0x58(%rax) RDI <- entry1+0x28 = &cred RAX <- cred+0x18 jmp *(RAX+0x58) = commit_creds -> commit_creds(cred)
entry2.func = mov $-1,%rax ; ret __wake_up_common breaks out on ret < 0
没有栈迁移,也没有 `iretq`:被劫持的线程正常地从它的 `ioctl()` 返回,然后只是以 root 身份回到用户空间。
### 伪造的 cred,以及那个本会浪费它的 bug
分发器 gadget 从 `cred+0x18` 获取它的 `RAX`,而 `cred+0x18` 是
`euid`/`egid`,所以链执行后 `euid` 立即变成内核指针的低半部分。
这只是外观问题。真正的问题在于 `prepare_creds()` ——
**每个后续的 `fork()` 和 `execve()` 都会调用它** —— 会解引用三个字段,且没有 NULL 检查:```c
get_group_info(new->group_info); /* refcount_inc(&gi->usage) */
get_uid(new->user); /* refcount_inc(&u->__count) */
new->ucounts = get_ucounts(new->ucounts);
伪造的凭据若将其置为 NULL,会得到 uid 0,然后在第一次 execve 时使机器崩溃——也就是说,该漏洞利用会报告成功,却立刻摧毁目标系统。因此,利用链将 user、ucounts 和 group_info 指向真实的内核全局变量 root_user、init_ucounts 和 init_groups,这些变量的地址来自同一个 _text 基址。设置好这些之后,提权线程在 init_user_ns 中持有 CAP_SETUID,于是它调用 setresuid(0,0,0),内核便会用一份干净、由内核分配的 root 凭据覆盖伪造的凭据。只有在这之后,才会执行其他任何操作。
权限通过一份 setuid-root 的漏洞利用二进制副本传递给父进程,该副本在 exec 启动 shell 之前会自行删除,因此 root shell 在主进程中于干净的终端上运行,且不会留下任何 setuid 残留。
仅凭 uname -r 并不足够。厂商内核通常会移植 binder 修复,而不会将版本号改为对应的主线版本;请检查源码或软件包变更日志。
该修复带有 Cc: stable 标签,因此 stable 分支和厂商分支都会收到移植补丁。
这是两个不同的问题,而第二个问题才是决定影响范围的关键。
只要设置了 CONFIG_ANDROID_BINDER_IPC,易受攻击的代码就会被编译进去。这包括通用发行版——但在所有被调查的发行版中,该驱动都是默认不加载的模块,即使加载了,init_binder_device() 在注册 misc 设备时也未设置 miscdev.mode,因此 devtmpfs 会以 0600 root:root 权限创建 /dev/binder。在 Android 上,是 ueventd 将其权限改为 0666,这正是该漏洞在 Android 上至关重要、而在桌面系统上大多无关紧要的原因。
以下配置读取自各发行版自带的已发布内核软件包:
因此,桌面发行版上的实际暴露面是间接的:任何加载 binder 并将其开放给用户的组件——Waydroid、Anbox、Android 模拟器或容器运行时——都会在仍存在该漏洞的内核上重新引入与 Android 完全相同的可触达性。
这些选项不影响漏洞本身;它们影响的是这个漏洞利用。
两个内核均为标准上游树。未打任何补丁。
第一个实验室中的 CONFIG_KASAN_GENERIC 是检测器,而非使能器:没有它,竞态条件完全相同。CONFIG_PREEMPT 是真正的先决条件,也是 Android 所启用的配置。
架构对漏洞而言并非影响因素——它是架构无关 C 代码中的生命周期错误。但对漏洞利用而言,架构影响极大:gadget、预取信道和直接映射布局都是 x86_64 特有的。
要求:clang、lld、make、cpio、gzip、qemu-system-x86_64、docker(仅用于组装 Debian rootfs)、Linux git 树的本地克隆,以及 gcc。
gcc -O2 -pthread -o exploit exploit.c ./exploit
对于 `demo/` 中以外的内核,请先提取其偏移量:```sh
./mkoffsets.sh /path/to/vmlinux > offsets.h
gcc -O2 -pthread -DEXPLOIT_OFFSETS='"offsets.h"' -o exploit exploit.c
可调参数,全部为可选,均从环境变量中读取:
CVE64468_SECONDS、CVE64468_THREADS、CVE64468_SPRAY_PERCENT、
CVE64468_CALL_OFFSET_MB、CVE64468_KASLR_ATTEMPTS、CVE64468_DELAY_MAX_US、
CVE64468_DELAY_STEP_US、CVE64468_STAGGER_US、CVE64468_VERBOSE、
CVE64468_SHELL。
LINUX_GIT=/path/to/linux ./lab/build.sh # x86_64 LINUX_GIT=/path/to/linux TARGET_ARCH=arm64 ./lab/build.sh # arm64 ./run.sh vulnerable ./verify.sh 600 16
该脚本会在上述提交处创建两个分离的工作树,如果任一工作树存在未提交的更改则拒绝运行,验证存在漏洞的树缺少修复而修复后的树包含该修复,构建两个内核,并将概念验证打包进initramfs。
### 漏洞利用实验室```sh
./demo/build-kernel.sh # vulnerable kernel, KASAN off, hardening on
./demo/build-rootfs.sh # Debian 13 userland + gcc, as an initramfs
./demo/run-demo.sh # boot it; this is what the recording shows
./demo/verify.sh logs/ # unattended reliability run, one guest per trial
demo/run-demo.sh 启动客户机并将控制台交给 uid 1000,该用户使用客户机自带的 gcc 编译 exploit.c 并运行它。
内核镜像、工作树、根文件系统树和 initramfs 均为实验室产物,不纳入版本管理。
docs/example-output.txt 是真实存在漏洞内核的完整输出记录,包含 KASAN 报告;docs/patched-negative-output.txt 是修复后内核的对照组;docs/e2e-results.json 是机器可读的结果。
报告中的调用链与上游报告完全一致:``` BUG: KASAN: slab-use-after-free in queued_spin_lock_slowpath+0x62/0x6a0 Read of size 4 at addr ffff888100282270 by task cve64468-B/91 CPU: 6 UID: 1000 PID: 91 Comm: cve64468-B Not tainted 7.2.0-rc1+ #2 PREEMPT _raw_spin_lock+0x55/0x60 binder_free_transaction+0x80/0x1f0 binder_send_failed_reply+0x98/0x340 binder_thread_release+0x528/0x5e0 binder_ioctl+0x1ca/0xeb0 __x64_sys_ioctl+0x8c9/0xcf0
Allocated by task 93: binder_open+0xb5/0x7b0
Freed by task 9: kfree+0x113/0x310 binder_deferred_func+0x104b/0x1180 process_scheduled_works+0x69f/0xbb0
`UID: 1000` 是概念验证自身无特权身份:受害者
`binder_proc` 由 `binder_open()` 分配,由 binder 延迟
工作队列释放,并在释放后被遍历器读取。
记录运行,2026-08-16,`./verify.sh 1200 16 3` — 每个变体三个并发 QEMU/KVM
客户机,各 10 个 vCPU,16 线程,每个变体 1200 秒,**两侧均无内核补丁**:
| | 易受攻击的 `114a116aaa5f` | 已修复的 `f223d27a546c` |
| --- | --- | --- |
| 尝试次数 | 158,384 | 158,471 |
| 遍历次数 | 2,534,144 | 2,535,536 |
| 设置失败 | 0 | 0 |
| 易受攻击的访问(`Unexpected outstanding_txns -1`) | 1,127 | 934 |
| **KASAN `slab-use-after-free`** | **8** | **0** |
这就是差异所在:相同的工作负载,尝试次数相差在
0.06% 以内,而释放后使用仅出现在未打补丁的易受攻击内核上。
易受攻击的访问出现在*两个*内核上,这是预期的 — 该修复
防止进程在窗口内被释放,而不是防止 `outstanding_txns` 的第二次
递减。这就是为什么该行仅用作廉价
预言机,而从不作为差异依据。
### 权限提升

`docs/lpe-output.txt` 是来自演示客户机的真实记录,而
`docs/lpe-demo.cast` 是上述动画渲染所依据的完整、未经编辑的 Asciinema 录制(`asciinema play docs/lpe-demo.cast` 可完整重放)。动画省略了该录制中漫长的竞争中间部分 — 客户机的串行控制台在整个约 24 分钟的竞争期间持续输出 binder 调试信息,若完整渲染将产生数十兆字节的滚动日志 — 保留了 Debian 前言和 root shell;exploit 自身的 `hit after 26661 attempts ... in 1437s` 行精确说明了被省略的内容。两者均为单次运行:未能在预算内获胜的客户机会关机,录制只是简单重复,而非编辑成一次胜利。
有关实测成功率,请参阅下方*可靠性*部分。
## 可靠性
该竞争在两方面都是概率性的,因此失败运行有时是预期行为,而非 exploit 故障。
### 内存安全
基于上述运行,在易受攻击内核上得出的比率:
| 数量 | 值 |
| --- | --- |
| 尝试速率 | 每个客户机约 44 次尝试/秒,三个合计约 132 次/秒 |
| 易受攻击的访问 | 每次尝试 7.1e-3 |
| 给定该访问后,释放落入窗口的概率 | 7.1e-3 |
| KASAN 报告 | 约每 20,000 次尝试 1 次,即按此速率大约每 2.5 分钟一次 |
### 权限提升
每个客户机都是一次独立试验:其自身的 KASLR、其自身的直接映射
随机化,而获胜的客户机会停止竞争。因此该数字是
**每次启动的成功率**,而非每次尝试的成功率。
记录活动,2026-08-16 23:32 UTC,`GUESTS=6 CPUS=8 MEMORY_MB=9216 ./demo/verify.sh`
— 六个并发 QEMU/KVM 客户机,未打补丁的 `114a116aaa5f`,Debian 13 用户空间,
每个预算 60 分钟,exploit 在客户机内编译,以 uid 1000 启动:
| | |
| --- | --- |
| 达到 uid 0 的客户机 | **6 个中的 2 个** |
| 到达 root 的时间 | 623 秒和 1,344 秒 |
| 获胜竞争中的尝试次数 | 13,839 和 31,125 |
| 每个未获胜客户机的尝试次数 | 整个 3,600 秒内约 95,000 次 |
| 整个活动中的总尝试次数 | 427,676(6.8 百万次栈遍历) |
| 观察到的易受攻击的访问(`Unexpected outstanding_txns -1`) | 256 |
| **内核崩溃、oops 或 panic** | **0**,在 9 个客户机小时内 |
该表中有两点值得解读。
**基本上每次获胜的竞争都变成了 root。** KASAN 实验室测得
给定易受攻击的访问后,释放落入窗口的概率为
7.1e-3。将其应用于此处观察到的 256 次访问,可预测整个活动中约有
~1.8 次释放后使用 — 而实际获得了 2 次 root。回收、
地址发现和链并非瓶颈;竞争才是。
**没有任何崩溃。** 在九个客户机小时内,没有客户机出现 oops,包括
四个从未获胜的客户机。要么链针对正确定位、喷洒的
页面触发,要么根本不触发 — 这正是阶段 2 中多数投票拒绝
的用途。
该拒绝在实践中确实会触发。在已负载的主机上同时启动六个客户机,
产生了一个在阶段 2 放弃的客户机,并显示```
[*] direct map not found; refusing to fire at an unverified address
并退出竞态。这正是预期行为:当时间通道无法达成共识时,浪费一次启动是正确的结局,远比在从未确认过的地址上触发链式利用要好得多。
docs/lpe-results.json 携带机器可读形式,包括所用确切内核和 initramfs 的 SHA-256。
并发运行客户机不仅仅是为了并行性。在争用激烈的宿主机上,KVM 会取消调度客户机 vCPU,而这正是第二个条件所需的延迟:walker 必须在释放 t->lock 与获取受害者内部锁之间失去其 CPU。在空闲宿主机上的单个客户机,其易受攻击访问率经测量约为三个并发客户机的二十分之一。
不过存在上限。在 32 线程宿主机上运行 8 个各含 8 个 vCPU 的客户机,每个客户机喷洒约 5 GiB 的直接映射内存时,宿主机进入交换状态,8 个客户机中有 3 个完全没有任何进展。6 个是随附的默认值。
可选的客户机内抢占辅助线程经测量会使尝试率增加约三倍,但并未提高命中率,因此未使用。
有一个环境因素被证明比预期更重要:binder 自身的调试输出。当 binder.debug_mask 处于默认值时,驱动在竞态期间会发出大量限速的 pr_info 流量,而由此产生的 printk 和 console-lock 压力会延长第二个条件所需的精确抢占窗口。使用 binder.debug_mask=0 将其静音以获得更整洁的控制台——这是录制时显而易见该做的事——在测试中可测量地降低了命中率:客户机运行远超两个获胜者的尝试次数却未命中。因此 demo/run-demo.sh 将 binder 调试保留在默认值,而安静的控制台是显式选择加入的。这是实验室的属性,而非利用本身的属性——但它很好地说明了此竞态在多大程度上依赖于系统范围的时序抖动,而非利用本身能控制的任何东西。
/dev/binder、发送 binder 事务并退出 binder 线程。它不安装任何内容,也不留下任何痕迹。panic=1 oops=panic 启动,因此运行会终止而非在损坏状态下继续。务必始终从干净启动重新开始。<[email protected]>(Twitter:@aramosf)于 2026-08-16 使用 SearchSploit(本地 Exploit-DB 副本)及针对 CVE-2026-64468、binder_free_transaction 和 f223d27a546c 的网络搜索进行了检查。未找到针对此 CVE 的公开利用或概念验证;SearchSploit 仅返回无关的、较旧的 Android binder 条目。这是时间点检查,而非永久保证。
| 状态 | 声明 |
|---|
| 已确认 | 该漏洞真实存在,可从无特权进程触达,且上游修复已将其消除。 |
| 已演示 | 对濒临消亡的 binder_proc 的易受攻击解引用在未打补丁的内核上自然且反复发生,而在已修复内核上从未发生。 |
| 已演示 | 完整的释放后使用,由 KASAN 在未打补丁的内核上报告。 |
| 已演示 | 使用攻击者控制的字节回收已释放的 binder_proc、内核控制流劫持,以及在 x86_64 上从无特权用户提权至 uid 0。 |
| 未声明 | 针对特定厂商或 Android 设备的任何结果。仅测试了下列上游内核,均在 x86_64 上。 |
| 未声明 | 随附的漏洞利用程序无需修改即可针对发行版内核运行。请参阅受影响系统:它需要针对每个内核的偏移量,而且在所调查的每个通用发行版上,binder 设备首先就无法被无特权用户触达。 |
| 偏移量 | 字段 | 遍历器执行的操作 |
|---|
| 108 | int outstanding_txns | 对其递减 |
| 113 | bool is_frozen | 读取它 |
| 120 | wait_queue_head_t freeze_wait | 若 outstanding_txns == 0 && is_frozen 则遍历它 |
| 624 | spinlock_t inner_lock | 获取并释放它 |
| 状态 | 提交 | 备注 |
|---|
| 引入谱系 | a370003cc301 | 由上游 Fixes: 标签命名 |
| 已验证存在漏洞 | 114a116aaa5f | 修复的直接父提交;携带相邻的 CVE-2026-64469 修复,因此该组合可单独隔离 CVE-2026-64468 |
| 已修正的主线版本 | f223d27a546c | 正在测试的修复 |
| 发行版 | 内核 | ANDROID_BINDER_IPC | 设备 | SLAB_BUCKETS | RANDOM_KMALLOC_CACHES | 非特权用户可触达? |
|---|
| Debian 13 (trixie) | 6.12.101 | m | ANDROID_BINDER_DEVICES="binder",BINDERFS 关闭 | y | 关闭 | 否——模块未加载;/dev/binder 权限为 0600 |
| Debian 12 (bookworm) | 6.1.0 | m | ANDROID_BINDER_DEVICES="binder",BINDERFS 关闭 | 不适用(6.11 之前) | 不适用(6.6 之前) | 否——同上 |
| Ubuntu 24.04 LTS | 6.8.0 | m | BINDERFS=m,ANDROID_BINDER_DEVICES="" | 不适用(6.11 之前) | y | 否——需要 root 权限执行 mount -t binder |
| Ubuntu 22.04 LTS | 5.15.0 | m | BINDERFS=m,ANDROID_BINDER_DEVICES="" | 不适用 | 不适用 | 否——同上 |
| Android(AOSP / 厂商) | 6.1、6.6、6.12 GKI | y | /dev/binder、/dev/hwbinder、/dev/vndbinder | — | — | 是——该驱动是平台 IPC,且对全局可访问 |
| 选项 | 此处的影响 |
|---|
CONFIG_SLAB_BUCKETS(6.11+) | 对该内存回收利用是致命的。 它将 msg_msg 隔离到独立的 kmalloc 桶中,因此泵送分配永远无法落入 binder_proc 的槽位。Debian 13 启用了它。需要另找一种未被计入的 1 KiB 分配。内核 6.6(即相关 Android 设备所运行的版本)完全早于该选项。 |
CONFIG_RANDOM_KMALLOC_CACHES(6.6+) | 按调用点将 kmalloc-1k 拆分为多个缓存,因此泵送分配必须命中同一个缓存;这对内存回收利用是一笔 1/16 的额外开销,而非一堵墙。Ubuntu 启用了它,Debian 未启用。 |
页表隔离(未使用 nopti) | 对地址发现是致命的。 在 PTI 激活时,预取无法看到内核文本,漏洞利用会检测到这一点并停止。上述每个发行版都编译了 PTI,但由 CPU 决定其是否激活:在不受 Meltdown 影响的硬件上它是关闭的,而这些结果正是在此类硬件上测得的。 |
CONFIG_SLAB_FREELIST_RANDOM、..._HARDENED | 在实验室中已启用,与发行版发布时一致。无可测量的影响:泵送分配并不预测空闲链表顺序,它只是分配大量对象。 |
KASLR(RANDOMIZE_BASE、RANDOMIZE_MEMORY) | 已启用。被预取阶段所击败;未使用 nokaslr。 |
| 每内核偏移量 | 利用链需要 commit_creds、三个与凭据相关的全局变量以及两个 gadget,均以 _text 为基准的偏移量。mkoffsets.sh 从目标 vmlinux 中提取它们;缺少这些偏移量,漏洞利用就会在错误的地址上触发。这是任何内核漏洞利用的按构建属性,而非一种防御手段。 |
内存安全实验室(lab/) | 漏洞利用实验室(demo/) |
|---|
| 内核版本 | 7.2.0-rc1+ | 7.2.0-rc1+ |
| 存在漏洞的提交 | 114a116aaa5f0295376cdf12da743c5bce3b20ce | 相同 |
| 已修复的提交 | f223d27a546c1e1f48d38fd67760e78f068fe8c4 | —(漏洞利用仅在存在漏洞的内核上测量) |
| 架构 | x86_64(KVM)和 arm64(TCG) | x86_64(KVM) |
| 编译器 | Ubuntu clang 21.1.8 / LLD 21.1.8 | 相同 |
| KASAN | 开启——它是检测器 | 关闭——它会改变 slab 布局,并使任何内存回收利用失去代表性 |
| Slab 加固 | — | SLAB_FREELIST_RANDOM、SLAB_FREELIST_HARDENED 开启;SLAB_BUCKETS、RANDOM_KMALLOC_CACHES 关闭 |
| KASLR | — | RANDOMIZE_BASE、RANDOMIZE_MEMORY 开启 |
| 用户空间 | 最小 initramfs | Debian GNU/Linux 13 (trixie),使用发行版自带的 gcc |
| 起始身份 | uid 1000、gid 1000、无能力、无命名空间 | 相同 |
| 启动命令行 | console=ttyS0 rdinit=/init panic=1 oops=panic kasan_multi_shot=1 | console=ttyS0 loglevel=4 rdinit=/init——无 nopti、无 nokaslr、无 mitigations=off |