KernelSU 内核层实现
本章深入解析 kernel/ 目录的核心实现。当前内核模块已从早期"扁平文件 + Kprobe 逐 syscall hook"彻底重构为"模块化子目录 + syscall tracepoint 分发器 + LSM 槽运行时打补丁"。 模块名仍是 kernelsu.ko。
致旧版读者:如果你读过早期文档,请注意以下已不复存在:
kernel/ksu.c、core_hook.c、allowlist.c(扁平)、supercalls.c、对reboot/cap_task_fix_setuid的 Kprobe 提权、bitmap+数组的白名单缓存、escape_to_root()、默认 domainu:r:su:s0。下文给出的是当前真实实现。
0. 架构总览
0.1 模块化目录结构
kernel/
├── core/ init.c # 模块入口/退出
├── hook/ syscall_hook_manager, tp_marker, syscall_event_bridge,
│ setuid_hook, lsm_hook, patch_memory, <arch>/syscall_hook # Hook 机制
├── feature/ sucompat, kernel_umount, selinux_hide, adb_root, sulog # 可开关特性
├── supercall/ supercall, dispatch, perm # [ksu_driver] IOCTL 接口
├── policy/ allowlist, app_profile, feature # 白名单/Profile/特性框架
├── manager/ throne_tracker, apk_sign, pkg_observer # Manager 识别
├── selinux/ selinux, rules, sepolicy # SELinux 处理
├── sulog/ event, fd # su 审计日志
├── infra/ symbol_resolver, file_wrapper, seccomp_cache, su_mount_ns, event_queue
├── runtime/ boot_event, ksud_integration # 驱动 userspace 启动
├── include/ arch.h, ksu.h, klog.h, util.h
└── tools/ check_symbol.c # 构建期符号校验
kernel/Kconfig 关键项:KSU(tristate,依赖 KPROBES && EXT4_FS)、KSU_DEBUG、KSU_DISABLE_MANAGER(用 root 取代 manager-only 功能)、KSU_DISABLE_POLICY(禁用 per-app profile,提权恒用默认 full root)。
0.2 与旧实现的关键差异
| 维度 | 旧实现 | 当前实现 |
|---|---|---|
| 主 Hook 机制 | Kprobe 逐个 hook syscall | sys_enter tracepoint + ni_syscall 空槽分发器 + 进程标记 |
| 被 hook 的 syscall | reboot / cap_task_fix_setuid / execve... | 经分发器:execve/setresuid/newfstatat/faccessat;直接 patch:read/fstat |
| 提权触发 | cap_task_fix_setuid LSM hook |
hook setresuid syscall(setuid_hook) |
| 提权函数 | escape_to_root() |
escape_with_root_profile() / escape_to_root_for_init() |
| supercall 通道 | reboot + magic 承载所有命令 | [ksu_driver] 匿名 inode 的 IOCTL;reboot kprobe 仅用于装 fd |
| 白名单存储 | bitmap + 数组双缓存 | RCU 哈希表 + kref |
| 默认 SELinux domain | u:r:su:s0 |
u:r:ksu:s0 |
| LSM hook | 标准注册 | 运行时给 LSM 槽 fixmap 打补丁 |
1. 模块入口与初始化(core/init.c)
1.1 全局对象与模块参数
struct cred *ksu_cred:KernelSU 自己的一份特权凭据(带 ksu SELinux domain)。需要以"KSU 身份"做特权文件操作/umount/setns 时用override_creds(ksu_cred)临时切换。prepare_creds()在 init 中创建。bool ksu_late_loaded:是否"后加载"(LKM 在 boot 后由 ksud 加载,而非内置/初期加载)。- 模块参数:
allow_shell(debug 默认 true,否则 false——是否允许 shell/2000 直接获 root)、norc(禁用自定义 rc 注入)。
1.2 两条初始化路径
kernelsu_init() 先做共同初始化(两条路径都执行):
ksu_init_symbol_resolver() // 解析 kallsyms 辅助符号
ksu_syscall_hook_init() // 解析 sys_call_table,找空闲 ni_syscall 槽,安装分发器
ksu_feature_init() // 清空 feature handler 表
ksu_sulog_init() // 注册 sulog feature + 初始化事件队列/fd
ksu_adb_root_init() // 注册 adb_root feature(默认关)
ksu_lsm_hook_init()
ksu_selinux_hide_init() // 注册 selinux_hide feature
ksu_supercalls_init() // dump IOCTL 命令表 + 注册 reboot kprobe(仅装 fd 用)
随后分支(ksu_late_loaded = (current->pid != 1) 仅在 #ifdef MODULE 下判定,pid==1 表示 init 期加载=非 late):
后加载(LKM late-load)路径:
apply_kernelsu_rules() / cache_sid() / setup_ksu_cred() // 立即建立 ksu domain
escape_to_root_for_init() // 把当前 ksud 进程提到 ksu domain root(enforce 前)
ksu_allowlist_init() / ksu_load_allow_list()
ksu_syscall_hook_manager_init()
ksu_throne_tracker_init() / ksu_observer_init() / ksu_file_wrapper_init()
ksu_boot_completed = true; track_throne(false);
if (permissive) setenforce(true); // late-load 强制重新 enforce
注意:后加载路径不调用 ksu_ksud_init()(init.rc 已无需再注入)。
正常加载(内置/早加载)路径:
ksu_syscall_hook_manager_init()
ksu_allowlist_init()
ksu_throne_tracker_init()
ksu_ksud_init() // 安装 init.rc 注入 hook(read/fstat)+ input_event kprobe
ksu_file_wrapper_init()
这条路径下,SELinux 规则、allowlist 加载、manager 扫描延后到 init second_stage execve、post-fs-data、boot-completed 等事件触发(见 §runtime 与 关键流程分析)。
1.3 平台相关处理
- x86_64:编译期若缺
X86_FEATURE_INDIRECT_SAFE→#error;运行期若!boot_cpu_has(X86_FEATURE_INDIRECT_SAFE)打印横幅并return -ENOSYS中止。原因:x86 分发器依赖改写orig_ax重定向 syscall,需要内核有此间接调用加固补丁。 - arm64 LKM stack protector workaround:某些第三方内核工具链与 GKI 不一致,导致 ksu LKM 引用的
__stack_chk_guard无法提供。特定配置下 ksu 自己提供__stack_chk_guard,并用裸函数kernelsu_init_early()(__attribute__((naked)))作为真正的module_init:先bl ksu_setup_stack_chk_guard生成 canary,再b kernelsu_init。 - 自我隐藏:非 debug 的 LKM 在 init 末尾
kobject_del(&THIS_MODULE->mkobj.kobj)把自己从/sys/module摘除。MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver)(用到anon_inode_create_getfile等 VFS 内部 API)。
1.4 退出(kernelsu_exit)
分两阶段:Phase 1 先停所有 hook 防止新回调(ksu_syscall_hook_manager_exit → ksu_supercalls_exit → ksu_ksud_exit → synchronize_rcu() 等 RCU reader);Phase 2 再安全释放数据结构(observer / throne / allowlist / selinux_hide / lsm / adb_root / sulog / feature → put_cred(ksu_cred))。
2. Hook 机制(核心)
这是整个重构中变化最大、也最精巧的部分。
2.1 主机制:sys_enter tracepoint + ni_syscall 槽分发器
KernelSU 不再用 Kprobe 逐个 hook 系统调用入口,而是:
- 安装分发器(
ksu_syscall_hook_init):解析sys_call_table,用ksu_find_ni_syscall_slots找到一个指向__arm64_sys_ni_syscall(x86__x64_sys_ni_syscall)的空闲槽,记其槽号为ksu_dispatcher_nr,把该槽直接打补丁改成指向ksu_syscall_dispatcher。 - 注册 tracepoint:
register_trace_sys_enter(ksu_sys_enter_handler, NULL)。sys_enter只对设置了SYSCALL_TRACEPOINT/TIF_SYSCALL_TRACEPOINT标志的任务触发。 - 重定向:
ksu_sys_enter_handler中,若当前 syscall 号在路由表syscall_hooks[]里被 hook,则把这次调用绕路到分发器槽:- arm64:
PT_REGS_ORIG_SYSCALL(regs) = id; regs->syscallno = ksu_dispatcher_nr; - x86:
regs->ax = id; regs->orig_ax = ksu_dispatcher_nr;(把原号藏进 ax)
- arm64:
- 内核接着按改后的号调用 syscall table → 实际调到
ksu_syscall_dispatcher。 - 分发:
ksu_syscall_dispatcher还原寄存器、取出原 syscall 号、查syscall_hooks[orig_nr]得到 handler 并调用;handler 在需要时调用真实 syscallksu_syscall_table[orig_nr](regs)。
被标记进程进入 execve
│ sys_enter tracepoint 触发
▼
ksu_sys_enter_handler: 把 syscallno 改成 dispatcher_nr(原号存 orig)
│ 内核按 dispatcher_nr 查表
▼
ksu_syscall_dispatcher: 取回原号 → syscall_hooks[execve] → ksu_hook_execve
▼
ksu_hook_execve 处理(必要时调真实 execve)
设计意图:避免直接 patch 高频 syscall 入口(性能/兼容/易被检测)。只 patch 一个废弃槽,再用 tracepoint 选择性地把"目标进程的目标 syscall"绕路。未被标记的普通进程不触发 tracepoint,零开销。
分发器/路由表的两套 API(hook/syscall_hook.h):
- 基于分发器(不改 syscall table,只改路由表):
ksu_register_syscall_hook(nr, fn)/ksu_unregister_syscall_hook(nr)/ksu_has_syscall_hook(nr); - 直接 patch syscall table(fixmap + stop_machine 覆写槽):
ksu_syscall_table_hook(nr, fn, *old)/ksu_syscall_table_unhook(nr),用于 ksud 的 init.rc 注入(hookread/fstat)和分发器槽本身的安装。
2.2 进程标记(hook/tp_marker.c)
因为 sys_enter 只对带标志的任务触发,KernelSU 只给"自己关心的进程"打标志(ksu_mark_running_process_locked 遍历进程,对以下打标,其余清标):
uid==0 && is_task_ksu_domain(ksu root 进程)is_zygote(zygote,要标记以处理其 fork 出的 app)uid==2000(shell)pid==1(init,boot 完成前用于追踪 zygote)ksu_is_allow_uid(uid)(被授权 uid)
与其它 tracepoint 用户共存:tracepoint_reg_count 记录系统对 sys_enter 的使用情况。通过 CONFIG_KRETPROBES 给 syscall_regfunc/syscall_unregfunc 挂 kretprobe:当别人(如 ftrace)也开始用 syscall tracepoint 时(reg_count 即将 >1),改为 ksu_mark_all_process()(全标记),避免破坏别人的 trace;当只剩 KernelSU 时再缩回子集。ksu_clear_task_tracepoint_flag_if_needed 仅在 reg_count<=1 时清标记。
per-task 标记的 get/set 经 IOCTL MANAGE_MARK 暴露给 userspace。
2.3 被 hook 的 syscall 与 syscall_event_bridge
ksu_syscall_hook_manager_init 向分发器路由表注册 4 个 handler,实现在 hook/syscall_event_bridge.c:
| syscall | handler | 作用 |
|---|---|---|
setresuid |
ksu_hook_setresuid |
调真实 setresuid,成功后 ksu_handle_setresuid(old,new)(标记/装fd/umount) |
execve |
ksu_hook_execve |
init second_stage/zygote 探测、su 兼容、adb_root、root execve 审计 |
newfstatat |
ksu_hook_newfstatat |
su_compat 开时 ksu_handle_stat_sucompat(su→ksud) |
faccessat |
ksu_hook_faccessat |
su_compat 开时 ksu_handle_faccessat_sucompat |
ksu_hook_execve 的分支逻辑:用 static key ksud_execve_key 控制是否探测 init/zygote 启动(ksu_execve_hook_ksud);euid==0 时审计 root execve(sulog);若是 init 派生的非 init 进程则 ksu_handle_init_mark_tracker + adb_root 处理;否则若 su_compat 开则 ksu_handle_execve_sucompat。
2.4 setuid_hook:提权/装 fd/umount 触发点(hook/setuid_hook.c)
ksu_handle_setresuid(old_uid, new_uid) 在 setresuid 返回后调用,取代了旧的 cap_task_fix_setuid LSM hook(依赖"zygote 总以相同 uid 调 setresuid(3)"这一事实):
- new_uid 是 manager:给 seccomp filter 放行
__NR_reboot(装 fd 走 reboot),打 tracepoint 标记,然后直接ksu_install_fd()给 manager 装 ksu 匿名 fd; - new_uid 被授权 su:放行
__NR_reboot,打 tracepoint 标记(以便它后续能跑 su hook); - 否则:撤标记;
- 最后调
ksu_handle_umount(old_uid, new_uid)(kernel umount 触发点,见 高级特性与安全)。
真正的"改 cred 提权"不在这里发生——它发生在 GRANT_ROOT IOCTL(
escape_with_root_profile)、sucompat 的 execve、或 init 执行 ksud 时(escape_to_root_for_init)。setuid_hook 只负责标记、装 fd 和触发卸载。
2.5 LSM hook:运行时给 LSM 槽打补丁(hook/lsm_hook.c)
KernelSU 不用 security_add_hooks 正常注册 LSM,而是"运行时改写已有 LSM hook 槽"。ksu_lsm_hook(hook) 有两套实现:
- 6.12+(LSM static call):遍历
lsm_static_call[],找到当前指向target(如selinux_setprocattr)的项,用ksu_patch_text改槽 +__static_call_update改 trampoline; - <6.12(security_hook_heads + hlist):按偏移找对应 hlist,遍历找指向 target 的项,改其函数指针。
当前唯一用到的 LSM hook 是 selinux_setprocattr(供 selinux_hide 功能,见 高级特性与安全)。
2.6 内存改写底座(hook/patch_memory.h + <arch>/patch_memory.c)
ksu_patch_text(dst, src, len, flags) 是所有"改写内核内存"的统一接口:
- 用
stop_machine让所有 CPU 暂停,master CPU 执行改写; phys_from_virt经init_mm页表把内核虚拟地址翻译成物理地址;- 用 fixmap 映射该物理页(arm64
set_fixmap_offset(FIX_TEXT_POKE0, ...))→copy_to_kernel_nofault→clear_fixmap→ 按 flags 刷 d/i cache。
关键设计:刻意不直接改 PTE 让目标页可写,而是用 fixmap poke。原因是直接改 PTE 会触碰 MTK MKP 等 hypervisor 内核保护(导致重启);而 fixmap 会被 MKP 视为"正常"内核行为。这套能力不用于 hook 普通 syscall 入口,只用于:安装/还原分发器槽与 read/fstat 槽、LSM 槽打补丁、selinux_hide 改 selinuxfs 函数指针。
3. 权限管理(policy/)
3.1 App Profile 结构(v4)
struct app_profile 已移到共享头 uapi/app_profile.h(内核经符号链接 kernel/include/uapi 引用),完整定义见 用户空间实现 §2.3。要点:
version(当前KSU_APP_PROFILE_VER=4)、key[256](包名 /"$")、curr_uid、allow_su;- union:
allow_su为真用rp_config(use_default+template_name+root_profile),否则用nrp_config(use_default+non_root_profile{umount_modules}); root_profile含 uid/gid/groups[32]/capabilities(effective/permitted/inheritable)/selinux_domain[64]/namespaces/flags(v4 新增,目前仅FLAG_KSU_NO_NEW_PRIVS)。
3.2 白名单存储:RCU 哈希表(不是 bitmap!)
重要更正:白名单不再是 bitmap + 数组双缓存,全仓已无
BITMAP_UID_MAX。当前是 RCU 保护的哈希表 + 每节点 kref 引用计数(policy/allowlist.c)。
#define ALLOW_LIST_BITS 8
DEFINE_HASHTABLE(allow_list, ALLOW_LIST_BITS); // 256 桶
static u16 allow_list_count; // 上限 U16_MAX
struct perm_data {
struct hlist_node list;
struct rcu_head rcu;
struct kref ref; // 引用计数
struct app_profile profile; // 内嵌完整 profile
};
- 哈希 key =
profile.curr_uid; - 写路径用
allowlist_mutex,读路径用rcu_read_lock+hash_for_each_possible_rcu; - 节点生命周期:
kref_init→ 读取kref_get_unless_zero→put_perm_data()→kfree_rcu;替换用hlist_replace_rcu。
默认 profile(init_default_profiles):default_root_profile uid/gid=0、groups=[0]、capabilities.effective = CAP_FULL_SET、namespaces=继承、selinux_domain = "u:r:ksu:s0";default_non_root_profile.umount_modules = true(默认对非 root app 卸载模块)。
3.3 提权核心:escape_with_root_profile(policy/app_profile.c)
被 GRANT_ROOT IOCTL(do_grant_root)和 sucompat 的 execve 调用:
cred = prepare_creds();若已 root 或线程设了TIF_KSU_DISABLE_ESCAPE_WITH_ROOT(bit 63)→ 放弃;profile = ksu_get_root_profile(uid)(自定义或默认 root profile);- 设 uid/suid/euid/fsuid = profile->uid,gid 四元组 = profile->gid,
securebits = 0; - 刷新 user_struct/ucounts:
alloc_uid(cred->uid)替换cred->user,5.14+ 调set_cred_ucounts(cred)(避免 RLIMIT_NPROC 计数残留在旧 uid——这是相对旧实现的正确性修复); - 设置 capabilities:把
profile->capabilities.effective同时拷给cap_effective/cap_permitted/cap_bset; setup_groups(profile, cred)(特例:仅 root 组时用静态root_groups);setup_selinux(profile->selinux_domain, cred)(切 SELinux domain);commit_creds(cred);disable_seccomp()(清TIF_SECCOMP/SYSCALL_WORK_SECCOMP、seccomp.mode=0、filter=NULL,用一个 fake task 正确释放原 filter 引用);- 若
flags & FLAG_KSU_NO_NEW_PRIVS→set_thread_flag(TIF_KSU_DISABLE_ESCAPE_WITH_ROOT)(提权后禁止再次提权); - 给本进程所有线程打 tracepoint 标记;
setup_mount_ns(profile->namespaces)(按 profile 切 mount namespace,见 §infra)。
另有两个轻量提权:
escape_to_root_for_init():只setup_selinux("u:r:ksu:s0")+commit_creds,不改 uid/caps/seccomp。late-load 时让 ksud 在 enforce 前进入 ksu domain,以及 init 执行 ksud 时用。escape_to_root_for_adb_root()(在 selinux 子系统):adb_root 用。
3.4 判定函数
__ksu_is_allow_uid(uid):forbid_system_uid(uid<2000 且 ≠1000 直接拒);manager 永远允许;allow_shell && uid==2000允许;否则 RCU 查哈希表curr_uid==uid && allow_su。__ksu_is_allow_uid_for_current(uid):uid==0 时返回is_ksu_domain()——只有处于 ksu domain 的 root 进程才算"被允许",防止任意 root 进程冒用。ksu_uid_should_umount(uid):manager / WebView zygote(1053) 不卸载;否则查 profile(无 profile 用默认;allow_su 的不卸载;非 root profile 按其umount_modules)。
3.5 持久化与版本迁移
- 文件
/data/adb/ksu/.allowlist,布局[FILE_MAGIC=0x7f4b5355][version=4][app_profile]...; - 持久化经
task_work_add调度到 init 进程上下文执行,用override_creds(ksu_cred)身份写盘; - 加载
ksu_load_allow_list校验 magic、要求2 <= version <= 4;version<4 时按旧结构 776 字节兼容读,并migrate_profile升级(v2 把u:r:su:s0→u:r:ksu:s0;v3 补FLAG_KSU_NO_NEW_PRIVS),升级后重新以新格式写盘。
4. 通信接口(supercall/)
4.1 [ksu_driver] 匿名 inode + IOCTL
KernelSU 的"设备"是一个进程持有的匿名 inode 文件 [ksu_driver],对它 ioctl 即调用内核功能:
static const struct file_operations anon_ksu_fops = {
.unlocked_ioctl = anon_ksu_ioctl, // → ksu_supercall_handle_ioctl
.compat_ioctl = anon_ksu_ioctl,
.release = ...,
};
int ksu_install_fd(void) {
int fd = get_unused_fd_flags(O_CLOEXEC);
struct file *filp = anon_inode_getfile("[ksu_driver]", &anon_ksu_fops, NULL, O_RDWR|O_CLOEXEC);
fd_install(fd, filp);
return fd;
}
4.2 fd 如何装到进程(两条路)
- Manager:在
setresuidhook 里检测到 new_uid 是 manager 时直接ksu_install_fd(); - 任意请求方(reboot magic 协议):保留一个
reboot的 Kprobe(reboot_kp)。reboot_handler_pre检查reboot(magic1, magic2, cmd, &out_fd),若magic1==0xDEADBEEF && magic2==0xCAFEBABE,则用task_work_add(current, ..., TWA_RESUME)排一个 task_work,在返回用户态前ksu_install_fd()并把 fdcopy_to_user写回。ksud / su 二进制用这条路拿 fd。
与旧实现的关键区别:reboot kprobe 仍在,但仅用于装 fd。所有实际命令(授权、查询、sepolicy、feature、profile…)都改为对
[ksu_driver]fd 的 IOCTL。
4.3 IOCTL 命令表与权限检查
ksu_supercall_handle_ioctl 线性查 ksu_ioctl_handlers[](supercall/dispatch.c),先执行 perm_check()(不过返回 -EPERM),再执行 handler,查不到返回 -ENOTTY。命令幻数 'K',KERNEL_SU_UAPI_VERSION=2。完整命令表见 用户空间实现 §2.2。
权限检查策略(supercall/perm.c):
| 策略 | 含义 | 典型命令 |
|---|---|---|
always_allow |
任何人 | GET_INFO、CHECK_SAFEMODE |
only_root |
uid==0 | REPORT_EVENT、SET_SEPOLICY、GET_SULOG_FD、SET_INIT_PGRP |
only_manager |
仅 Manager | GET/SET_APP_PROFILE |
manager_or_root |
Manager 或 root | feature/allowlist/mark/umount 等 |
allowed_for_su |
Manager 或已授权 uid | GRANT_ROOT |
5. Manager 识别(manager/)
5.1 throne_tracker:扫描与追踪
track_throne(bool prune_only):
- 解析
/data/system/packages.list(每行package uid ...)建立uid_data链表; - 检查当前 manager appid 是否还在列表中:不在则
ksu_invalidate_manager_uid();若 appid 无效则search_manager("/data/app", 2, ...)深度 2 扫描; - 最后
ksu_prune_allowlist(is_uid_exist, ...)清理已卸载应用的条目。
search_manager BFS 遍历目录,遇 base.apk 调 is_manager_apk 验签;命中即 crown_manager(按包名匹配到 uid_data 里的 uid,ksu_set_manager_appid)。用 data_app_magic 做同源校验、用 apk_path_hash_list 缓存已验签路径避免重复。
Manager 身份存的是 appid(
uid % 100000),全局变量ksu_manager_appid(定义在throne_tracker.c),而非完整 uid。is_manager()=ksu_manager_appid == current_uid().val % 100000。
5.2 APK 签名验证:只认纯 V2(apk_sign.c)
is_manager_apk(path) → check_v2_signature(path, EXPECTED_SIZE, EXPECTED_HASH):
- 倒扫找 EOCD → 定位 APK Signing Block(魔数
"APK Sig Block 42"); - 遍历 ID-value 对:
0x7109871a=V2 块(check_block比对证书 SHA-256),0xf05368c0=V3,0x1b93ad61=V3.1; - 必须恰好 1 个 V2 块;
- 若同时存在 V1(JAR) 签名 → 拒绝(防 Janus 降级);
- 若存在任何 V3/V3.1 签名 → 拒绝。
即策略是"只接受纯 V2 签名,排斥 V1 共存与 V3 存在"。证书 SHA-256 与长度(
EXPECTED_HASH/EXPECTED_SIZE,含 PR build 的第二签名*2,及可选KSU_MANAGER_PACKAGE包名白名单)不在源码里,而是由打包脚本在构建时追加到kernel/Makefile末尾。
5.3 pkg_observer:fsnotify 监听包变化
pkg_observer.c(实现 manager_observer.h)用 fsnotify 监听 /data/system 目录:当子文件 packages.list 被创建/移动(包安装/卸载导致重写)时触发 track_throne(false) → 重扫 manager + prune 白名单。即并非直接挂钩包管理器。
6. SELinux 处理(selinux/)
6.1 domain 切换与 SID 缓存(selinux.c)
常量:KERNEL_SU_DOMAIN="ksu"、KERNEL_SU_CONTEXT="u:r:ksu:s0"、KSU_FILE_CONTEXT="u:object_r:ksu_file:s0"。
transive_to_domain(domain, cred, clear_exec_sid) 是核心切换:取 selinux_cred(cred)(6.18+ 是 cred_security_struct,否则 task_security_struct),security_secctx_to_secid 得 sid,写 tsec->sid,清 create/keycreate/sockcreate sid。setup_selinux / setup_ksu_cred 在其上封装。
cache_sid() 在 policy 加载后(post-fs-data / late-load)解析缓存 ksu/zygote/init/ksu_file 四个 context 的 SID,加速 is_ksu_domain/is_zygote/is_init 等判定(缓存失败回退字符串比较)。setenforce/getenforce 仅在 CONFIG_SECURITY_SELINUX_DEVELOP 下可写/可查。
6.2 内置规则 apply_kernelsu_rules(rules.c)
设计:dup 当前 policydb → 在副本上加规则 → RCU 切换 → 销毁旧的 → reset AVC。同时保存一份 backup_sepolicy(供 selinux_hide 回滚使用)。注入的核心规则(ksu 自身 domain = ksu):
ksudomain 设为 permissive,附mlstrustedsubject/netdomain/bluetoothdomain属性;- 创建无约束文件类型
ksu_file(allow domain ksu_file *:*); allow ksu *:*:*(ksu domain 允许一切);allow init ksu *:*(ksud 由 init 触发);- 一系列 Magisk 风格规则:servicemanager/logd 访问、
domain对 ksu 的 fd/fifo/unix_socket/memfd/binder/sigchld 等。
6.3 动态 sepolicy 批处理 handle_sepolicy(rules.c + sepolicy.c)
userspace(ksud/Manager)经 IOCTL SET_SEPOLICY 下发序列化的批量命令(上限 8 MiB,每命令最多 5 个参数)。9 类命令(见 uapi/selinux.h):normal_perm(allow/deny/auditallow/dontaudit)、xperm、type_state(permissive/enforce)、type、type_attr、attr、type_transition、type_change、genfscon。sepolicy.c 在 policydb 副本的 te_avtab 上增删 AV 规则、ioctl xperm、动态新增 type/attribute 等,最后 RCU 热替换整个 policy。
注意:
ksu_genfscon当前是空实现(直接返回 false),即 genfscon 命令实际不生效。
7. feature 系统(概览)
policy/feature.c 提供 feature 注册框架:每个 feature 由 {feature_id, name, get_handler, set_handler} 注册,userspace 经 IOCTL GET/SET_FEATURE 读写。五个 feature(uapi/feature.h):
| ID | feature | 默认 | 实现文件 |
|---|---|---|---|
| 0 | su_compat |
开 | feature/sucompat.c |
| 1 | kernel_umount |
开 | feature/kernel_umount.c |
| 2 | sulog |
关 | feature/sulog.c + sulog/ |
| 3 | adb_root |
关 | feature/adb_root.c |
| 4 | selinux_hide |
关 | feature/selinux_hide.c |
各 feature 的实现细节(su 兼容见 libsu 与 su 兼容层;kernel_umount/selinux_hide/adb_root/sulog 见 高级特性与安全)。
8. 总结
KernelSU 内核层当前实现的要点:
- 模块化重构:13 个子目录各司其职,模块名仍
kernelsu.ko,区分 LKM 后加载与内置加载两条路径。 - tracepoint 分发器 hook:用
sys_enter+ ni_syscall 空槽分发器 + 选择性进程标记,取代逐 syscall Kprobe,对普通进程零开销。 - 提权:
escape_with_root_profile修改 cred(uid/gid/caps)、禁用 seccomp、切u:r:ksu:s0domain、按需禁止再提权;触发点是 hooksetresuid与 sucompat execve。 - RCU 哈希表白名单 + v4 Profile:带引用计数、版本迁移、持久化。
- IOCTL over
[ksu_driver]:reboot kprobe 退居"仅装 fd",所有命令走匿名 inode 的 IOCTL,4 类权限检查。 - Manager 识别:扫描 + 纯 V2 签名校验(期望 hash 由构建注入)+ fsnotify 监听包变化。
- SELinux:动态 dup/RCU 热替换 policydb,注入 ksu domain 规则与动态 sepolicy 批处理。
- 底层能力:fixmap+stop_machine 改写内存(避开 hypervisor 保护)、kallsyms+CFI 符号解析、运行时 LSM 槽打补丁。
下一章节:Android 安全机制与绕过 将从安全角度分析 Android 多层防护及 KernelSU 的绕过实现。
评论
- 还没有评论,来说点什么吧。