=# KP 0.13.9 SELinux 隐藏功能漏洞技术分析
——/sys/fs/selinux/status mmap ABI 违规分析(检测验证示例见附录)
分析对象:KP(KernelPatch)0.13.9 内置 selinux_hide 功能,kernel/patch/common/selinux_hide.c。
影响范围:当前所有AP系管理器root方案(APatch、FolkPatch、Aster...)
结论:selinux_hide 在替换 sel_mmap_handle_status 时未保留上游全部四项 ABI 检查,普通权限 app 通过发送"stock 内核必然拒绝、hook 实现必然放行"的请求组合即可判定该功能是否存在。判定依据为上游内核源码的规格约束(对参考值测量无依赖),因此在任何设备、任何内核版本上都不会对干净内核产生误报。此外,其中一项违规伴随内核内存可写性问题,属于应按安全缺陷处理的问题。
KP 0.13.9 的 SELinux 隐藏功能(selinux_hide.c,约 980 行)目标是使 uid >= 10000 的 app 在 SELinux 层面观察到修改前的系统状态,掩盖 APatch/KernelSU 类方案留下的痕迹。核心机制四条:
启用路径:/data/adb/ap/selinux_hide 控制文件存在 → post-fs-data before 快照策略、after 安装 hook。仅支持 kernel >= 4.19。
hook 安装方式分两档:符号解析成功时走 inline hook;失败时对 write_op[] 表和 sel_handle_status_ops fops 结构做数据指针替换(不修改内核 text,规避 text 完整性校验)。本文的分析范围限于替换后的 handler 是否保留了上游 ABI,问题集中在该环节。
/sys/fs/selinux/status 是 libselinux 做 avc 无锁 fast-path 查询的节点。本地核对的 security/selinux/selinuxfs.c 4.19 与 5.10 源码中 sel_mmap_handle_status() 逐字一致(4.19 :246-266,5.10 :246-266):
四项检查(6.x 内核该函数的检查块与此相同):
同文件 sel_write_access()(5.10 :813-862)的应答格式:
security_compute_av_user() 从 avd_init() 出发(allowed=0, auditallow=0, auditdeny=0xffffffff),tclass==0 时直接返回。因此对探测串 u:r:untrusted_app:s0 u:r:untrusted_app:s0 0,真内核的 6 字段应答必然满足 field[1]==ffffffff 且 field[3]==ffffffff。该推导构成 /access 查询面的检测依据(见附录 A Oracle [2])。
KP 的替换实现(selinux_hide.c :570-625)核心逻辑:
实现只检查 vm_start 页对齐和假页物理地址可解析,然后直接 remap_pfn_range 一页假 status 到 vma 上:
对照上游,四项检查全部缺失。能 open 该节点的进程发送任一非法请求,stock 内核返回 -EIO/-EPERM、KP 返回成功,应答差异构成检测依据。
以基线映射(mmap(0, 4096, PROT_READ, MAP_PRIVATE),libselinux 自身使用的参数)为参照,以下四个探测请求在 stock 与 KP 下行为不同:
探测设计需遵守的判定规则(避免误报):
该判定为对上游内核源码行为的规格断言,跨设备可移植,对干净内核不产生误报。附件检测工程实现了上述探测组合,验证结果与本节一致。
KP 下,app 对 status fd 执行 read(buf, n) 且 pos=4096 时返回 EOF(假页有效内容 20 字节),而 mmap(fd, ..., offset=4096) 却成功并展示完整 status 头部。"offset 4096 读为空、mmap offset 4096 却有内容"这一组合没有任何真内核会产生——即使不做 3.2 的四项探测,单对比 read/mmap 语义也能判定替换实现存在。这是 3.1 中"不检查 vm_pgoff"的直接推论。
上游清除 VM_MAYWRITE 是安全边界的一部分。KP 的映射由 remap_pfn_range 建立(VM_PFNMAP,页背后无 struct page),且 KP 将读取自 vma 的 vm_page_prot 原样传给该函数。两条路径的行为不同:
综合:探测 c 使 uid >= 10000 的普通 app 可能获得对内核 vmalloc 内存的写映射。当前可写范围限于假 status 页本身(20 字节有效内容),破坏面有限,但这破坏了"status 节点对用户态只读"的上游保证,应按内核安全缺陷处理。
对照 bmax121/KernelPatch master 分支(最新提交 f36f3d1377004871f8c964f7cd3276712eeba943,2026-10-10 "refine selinux_hide",已拉取该提交 patch 及 master 全文核对):
本文 3.1 的问题未修复。 master 的 my_sel_mmap_handle_status(master 版 selinux_hide.c :711-766)与本地 0.13.9 版本一致——仍只检查 vm_start 页对齐和假页物理地址可解析,vm_pgoff、vm_end - vm_start、VM_WRITE 三项检查缺失,VM_MAYWRITE 未清除。3.2 的四项探测(a/b/c/d)在 master 上仍然全部命中,fill_fake_status_bytes(master :188-208)的硬编码常量也未修改。
master 近期两次 selinux_hide 相关提交修复的是其他类别的可观测差异:
总结:上游在收敛语义/时序类可观测差异,但 status mmap 的 ABI 级问题未处理,3.2 的探测组合对当前 master 仍然有效。
KP 0.13.9 selinux_hide 的策略快照/seqno 补丁链路设计合格——应答形状保持内核一致。但 status mmap 路径的替换实现丢弃了上游 handler 的全部输入校验与 vma flag 副作用:四项 ABI 违规均可被无 root 探测,其中 mprotect 项单项即可判定;且探测 c 的路径使普通 app 可能获得对内核 vmalloc 内存的直接写映射,已超出"可检测"范畴构成安全缺陷。
核心结论:替换内核 handler 时,原 handler 对用户态输入的全部校验与 vma flag 修改均需逐项保留;返回值与安全边界同属被替换行为的组成部分,二者都须维持与上游一致。
附件工程 KP139_SElinux-hide-check 为本文漏洞的验证示例,包含 libkpsc.so(native 探测,native/check.c)与 App 载体两部分。判定逻辑概要:
849K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6x3N6h3&6Q4x3X3c8a6f1#2)9J5c8V1c8G2N6$3&6D9L8$3q4V1i4K6u0r3M7X3g2D9k6h3q4K6k6i4y4Q4x3V1k6@1j5h3N6Q4x3V1k6$3K9i4m8Q4x3X3g2D9N6h3&6G2M7#2)9J5k6h3E0H3M7$3g2D9K9h3&6#2P5r3S2A6k6r3g2Q4x3X3g2U0K9r3g2U0K9#2)9J5k6h3c8W2L8h3)9I4
对基线映射执行探测 a/b/c/d,任一 GRANTED 即判定 KP 0.13.9 的 selinux_hide 存在。判定规则与 3.2 节一致:拒绝视为通过、基线失败报 UNTESTABLE(值 2),三态结果为 0=clean / 1=detected / 2=untestable。
老版 SELinux_hook KPM 1.1.7 对 /access 查询固定应答 "0 0 0 0 1 0":field[1](decided)= 0、field[3](auditdeny)= 0。按第 2 节推导,真内核对 tclass=0 探测只能给出 ffffffff/ffffffff,存活节点返回该形状即表明 hook 存在且为老版本。
防误报与防交叉干扰设计:
static int sel_mmap_handle_status(struct file *filp,
struct vm_area_struct *vma)
{
struct page *status = filp->private_data;
unsigned long size = vma->vm_end - vma->vm_start;
BUG_ON(!status);
/* only allows one page from the head */
if (vma->vm_pgoff > 0 || size != PAGE_SIZE)
return -EIO;
/* disallow writable mapping */
if (vma->vm_flags & VM_WRITE)
return -EPERM;
/* disallow mprotect() turns it into writable */
vma->vm_flags &= ~VM_MAYWRITE;
return remap_pfn_range(vma, vma->vm_start,
page_to_pfn(status),
size, vma->vm_page_prot);
}
length = scnprintf(buf, SIMPLE_TRANSACTION_LIMIT,
"%x %x %x %x %u %x",
avd.allowed, 0xffffffff,
avd.auditallow, avd.auditdeny,
avd.seqno, avd.flags);
start = vm[KP_VMA_VM_START_OFF / sizeof(unsigned long)]; /* 按偏移读取 vma 字段 */
prot = vm[kp_vma_page_prot_off() / sizeof(unsigned long)];
ret = kp_remap_pfn_range(vma, start, pa >> page_shift, KP_PAGE_SIZE, prot);
分析对象:KP(KernelPatch)0.13.9 内置 selinux_hide 功能,kernel/patch/common/selinux_hide.c。
影响范围:当前所有AP系管理器root方案(APatch、FolkPatch、Aster...)
结论:selinux_hide 在替换 sel_mmap_handle_status 时未保留上游全部四项 ABI 检查,普通权限 app 通过发送"stock 内核必然拒绝、hook 实现必然放行"的请求组合即可判定该功能是否存在。判定依据为上游内核源码的规格约束(对参考值测量无依赖),因此在任何设备、任何内核版本上都不会对干净内核产生误报。此外,其中一项违规伴随内核内存可写性问题,属于应按安全缺陷处理的问题。
| 机制 |
挂载点 |
效果 |
| 干净策略快照 |
post-fs-data before 深拷贝 boot policy |
context/access/setprocattr 对 app 按快照作答,Magisk/apd 之后的 policy reload 不可见 |
| 假 status 页 |
hook sel_read_handle_status + sel_mmap_handle_status |
app 读/mmap /sys/fs/selinux/status 观察到 enforcing=1、干净 seqno |
| seqno 补丁 |
hook sel_write_access |
DirtySepolicy 探针(untrusted_app → untrusted_app, tclass=0)的应答 seqno 被改写为 1,其余字段原样保留 |
| setprocattr 净化 |
hook selinux_setprocattr |
app 切 context 时按干净快照校验 |