
CVE-2026-98260(fs/exec.c POSIX CPU timer UAF)XRING O1 / Android 17 / 6.18.21 研究记录与 PC 一键流程(授权设备安全研究)
CVE-2026-98260(Linux fs/exec.c POSIX CPU timer UAF)在
XRING O1 / Android 17 / 6.18.21-android17-5-g1d099fcb35e0-abogki538445360-4k
上的独立实现与研究记录。
仅用于自有设备的授权安全研究。这里的代码会触发内核 UAF 并修改 SELinux 状态与进程凭据,可能导致 panic、重启或数据损坏,请勿在非授权设备上运行。
仓库只保留电脑端流程。电脑通过 ADB 推送 pc-kit
中的运行时文件,完成临时 root、KernelSU、probeshadow、管理器启动和 su 验收,
流程默认复用当前开机状态,只有显式传入 -Reboot(bash 为 --reboot)才会重启设备。
新版 real-cred 收尾后 xring_plant(fake cred holder 以及全部 locker helper)
会自行退出,只保留真 cred 的 pc98260-rootd;收尾门禁要求 pidof xring_plant
为空且 pc98260-rootd 在线,然后关闭无认证的 5556;门禁不齐全则保留 5556 供恢复。
成功后会写入内核 wakelock xring_root_holder,锁屏时阻止进入 deep suspend,避免 suspend 阶段挂死后被 watchdog 复位。该 wakelock 会持续到重启,并带来额外耗电。
# 仅检查 ADB、设备与 pc-kit
.\run.ps1 -CheckOnly
# 完整的一键流程(默认不重启,沿用当前开机状态)
.\run.ps1
# 明确需要冷启动时才加 -Reboot
.\run.ps1 -Serial <SERIAL> -Reboot
late-load 之后,流程还会用 5556(ksu 域 shell)再 plant 一次,并把当前开机
动态测得的 ksu 域 SELinux sid 写进 fake cred,让新的 rootd 运行在
u:r:ksu:s0 —— Enforcing 下不受 shell 域权限边界限制(shell 域不允许
connect shell_data_file 的 unix socket)。第一次 plant 的 fake blob 保持
sid=0 以维持稳定;security blob 的实测布局(复合 LSM blob、osid/sid 偏移)
与 sid 运行时分配的细节见 docs/98260-适配记录.md §15。
KSU_SID=0 可跳过第二次 plant。
要求:
me.weishu.kernelsu 已安装;pc-kit 中运行时文件齐全;日志写入 build\run.log。
execve() 时,de_thread() 把旧 TID 的归属换给 leader,
pid_task(timer->it.cpu.pid, PIDTYPE_PID) 返回 NULL,于是
timer_lock_sighand() 认为 timer node 已经出队;bprm->point_of_no_return 在 de_thread() 之前就已置位,若随后的
unshare_files() / set_mm_exe_file() / exec_mmap() /
exec_task_namespaces() 失败,任务在 begin_new_exec() 里的
posix_cpu_timers_exit() 之前就 do_exit();exit_itimers() 释放仍挂在 posix_cputimers rbtree 上的 k_itimer,
之后 release_task() → posix_cpu_timers_exit() →
timerqueue_del() → rb_erase() 对已释放节点操作,形成 UAF 写;posixtimer_exec() 并在 de_thread() 后立即调用。src/xr98260p.h 常量、IPC 结构、跨模块接口
src/xr98260p_trace.c trace/kprobe 控制、KASLR/task/k_itimer 泄漏、pfn 判据
src/xr98260p_race.c VMA 压力、locker/killer helper、非 leader exec 竞态
src/xr98260p_plant.c order-1 页复用载体、假 rb_node / 假 cred 载荷
src/xr98260p_main.c 阶段编排(task / selinux / cred)
src/xr98260-dropuid.c 从 root adbd 降到 uid 2000 的小启动器
src/exploit-android-* 早期单文件变体(保留作对照,非当前主链)
src/pc98260_rootd.c 驻留 root daemon
src/pc98260_su.c 客户端
docs/98260-适配记录.md 目标常量、BTF 核对、阻塞与证据边界
run.ps1 电脑端一键运行
pc-kit/ 电脑流程运行时依赖
build.sh 主机侧 Android NDK AArch64 构建
run.sh 设备侧阶段执行(每轮结束自动 reboot)
posix_timers_cache 是 8 KiB(order-1)slab,
用 pipe/AIO/perf 这类 order-0 分配很难命中原页。这里用
AF_UNIX SOCK_STREAM 写 8192 字节,写入的正是新分配的 order-1 页,
可直接落在被释放的 slab 上。victim + cpu_timer.node 所在的 4 KiB 半页
pfn,再用 & ~1 得到 order-1 base pfn;不是 slab 起始页。rb_erase 直接写零,而是把
node[0] 设为 node_kva + 24,让 rb_erase 把假 cred 指针
写进 task->cred;假 cred 就摆在 node+24 的同一页内。2026-11-11 起内核常量改为运行时 profile(
src/xr98260_profiles.c):同一份二进制 按uname -r自动选择 old/new 两套值(XR98260_KERNEL=old|new可强制覆盖)。 下表是旧内核g1d099fcb35e0-abogki538445360的值;新内核g284297031c53-abogki553682993(OS4.0.4.0)的完整对照、call site 实测与真机验证 见docs/98260-适配记录.md§17。
| 项 | 值 |
|---|---|
selinux_state | 0xffffffc082958630(enforcing 在 +0) |
__arm64_sys_getpid | 0xffffffc08023da88 |
sizeof(struct k_itimer) | 0x180 |
k_itimer.it.cpu_timer.node | +0xe0 |
posix_timers_cache | 21 objects / order-1 8 KiB slab |
task_struct.cred | +0x908 |
struct cred | 184 字节;caps 0x30..0x50,security=0x80、user=0x88、user_ns=0x90、ucounts=0x98、group_info=0xa0 |
| 静态对象 | root_user / user_ns / groups / ucounts 见 xr98260p.h |
ANDROID_NDK_HOME=/path/to/android-ndk ./build.sh
产物(Android AArch64 PIE):
xr98260-plant 主链(模块化)
xr98260-perf-chain 早期单文件变体
xr98260-key-ptr 完整 capabilities 阶段
xr98260-su / xr98260-dropuid
./run.sh <serial> plant-task # 只验证 task_struct 泄漏
./run.sh <serial> plant-selinux # 零 selinux_state.enforcing
./run.sh <serial> plant-cred # 植入假 cred,验证 uid=0
每个阶段都有主机侧超时,结束或超时后自动 adb reboot:
UAF 失败时故意不回收 zombie(fail-closed),必须靠重启清理。
run.sh 不包含机型门禁,只用于本地授权测试;请自行确认目标设备。
de_thread/SIGKILL)、目标 RCU 回调、
精确 free_slab、PFN 级页复用命中(旧偏移下);cpu_timer.node 由 +0x110 改为 BTF 实测的 +0xe0;
命中判据由 slab base 改为 node 所在半页;__rb_erase_augmented() 语义,这更像 stale node 未进入写路径;xr98260-plant 新链尚未在真机上验证,因此本仓库不宣称
已获得 SELinux Permissive 或 uid 0。现象:run.ps1 / run.sh 在“装载 KernelSU”后等待 5556 root shell 时整机重启(每次必现,十几秒内;此前 run.sh 旧流程也有类似现象)。
根因:pc-kit/kernelsu.ko 的 vermagic 是 6.18.32-4k-g47cc22554442-dirty,
与设备内核 6.18.21-android17-5-g1d099fcb35e0-abogki538445360-4k 不是同一构建。
ksudx insmod 会改写模块 vermagic 后强行 init_module(日志里的
“Loaded kernel module” 只代表装载调用返回,不代表 ABI 匹配),装载后内核因
结构体/符号不匹配而崩溃并硬复位;平台未启用 pstore/last_kmsg,所以重启后
看不到 panic 日志,ro.boot.bootreason 仍是 AP_S_COLDBOOT,NA。
修复:装载 KernelSU 改用 ksudx late-load --allow-shell,使用 ksudx 内嵌的
匹配 KMI 模块(android17-6.18),不再推送/加载 pc-kit/kernelsu.ko。
脚本在 late-load 后轮询 /proc/modules 等待 kernelsu 出现(异步装载,约 1~3 秒)。
KernelSU 管理器改由 adb shell(shell 域)启动;在 5556 的 ksu 域里调用 am 会
返回 Failed transaction。
验证(真机,XRING O1 / 6.18.21-android17):
./run.sh 完整通过:plant 临时 root(7s) → late-load 后 kernelsu 在线 →
5556 响应(uid=0)→ probeshadow 在线 → 管理器启动成功 →
su 验收 uid=0 / u:r:ksu:s0 → 清理;全程 boot_id 不变、设备未重启。
其它注意:
pc-kit/kernelsu.ko 与设备不匹配,仅作历史保留,禁止再用它 insmod。usage=2。holder 的 task->cred 被写入 fake 页后,holder
会 fork 孙子进程续期;孙子确认真 cred 生效后,holder 在 fake 页仍被 socket
引用时立刻退出(put_cred 只把 usage 从 2 减到 1,不会把 socket 页当
cred_jar 对象释放),随后 plant 主进程才退出并释放 socket 页。
旧版那种“holder 常驻到重启”的收尾方式已经废弃;对旧版常驻 holder 直接
kill -9 仍可能触发 kernel BUG at kernel/cred.c,只能重启清场。src/xring_plant_realcred.c(构建产物 build/xring_plant-realcred,并作为
kit 里的 xring_plant)是默认流程:续期成功后 holder 退出,孙子进程
execve("/proc/self/exe", {"pc98260-rootd","--rootd"}) 成为独立的真 cred
rootd(重建 mm/cmdline,不再带 plant 的大 VMA)。重复一键 root 时,
新 rootd 会先收掉旧的 pc98260-rootd 实例,避免 rootd 累积。pc-kit 中 violin_plant / violin_slide_probe 已统一更名为
xring_plant / xring_slide_probe。run.sh 现在是和 run.ps1 等价的一键入口(macOS/Linux bash),
旧的设备侧 CVE 测试模式保留为子命令:
./run.sh # 完整一键流程
./run.sh --reboot # 先冷启动
./run.sh --serial <SERIAL>
./run.sh --check
./run.sh --keep-5556 # 只跳过关闭 5556;xring_plant 仍会退出,pc98260-rootd 保留
./run.sh plant-task|plant-selinux|plant-cred|selinux|cred|...
提交前可用 PowerShell 官方解析器校验 run.ps1(PowerShell 7+):
pwsh -NoProfile -Command '$t=$null;$e=$null; [System.Management.Automation.Language.Parser]::ParseFile((Resolve-Path "run.ps1").Path,[ref]$t,[ref]$e) | Out-Null; if ($e.Count -eq 0) { "SYNTAX_OK" } else { $e | ForEach-Object { "line $($_.Extent.StartLineNumber): $($_.Message)" } }'
run.sh 用 bash -n run.sh 校验。本机已把可移植 PowerShell 解压到 build/pwsh/pwsh
(build/ 不提交,仅作本地校验用)。
本项目以 MIT License 开源,详见 LICENSE。 仅供自有设备上的授权安全研究使用;请勿用于未授权设备。
run.sh 需要向转发到设备的 5556 root shell 发送命令,使用
tools/send5556.c 现场编译出的小二进制(优先 cc,其次 clang/gcc,
产物在 build/send5556)。运行时不再依赖 python 或 nc;宿主机需要有
C 编译器之一。run.ps1 使用 .NET 内建 TcpClient,无此要求。