溺的文档
KernelSU · 第 2 篇 / 共 8 篇

KernelSU 内核层实现

2026-06-30 · 阅读 5

本章深入解析 kernel/ 目录的核心实现。当前内核模块已从早期"扁平文件 + Kprobe 逐 syscall hook"彻底重构为"模块化子目录 + syscall tracepoint 分发器 + LSM 槽运行时打补丁"。 模块名仍是 kernelsu.ko

致旧版读者:如果你读过早期文档,请注意以下已不复存在:kernel/ksu.ccore_hook.callowlist.c(扁平)、supercalls.c、对 reboot/cap_task_fix_setuid 的 Kprobe 提权、bitmap+数组的白名单缓存、escape_to_root()、默认 domain u: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_DEBUGKSU_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_exitksu_supercalls_exitksu_ksud_exitsynchronize_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 系统调用入口,而是:

  1. 安装分发器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
  2. 注册 tracepointregister_trace_sys_enter(ksu_sys_enter_handler, NULL)sys_enter 只对设置了 SYSCALL_TRACEPOINT/TIF_SYSCALL_TRACEPOINT 标志的任务触发。
  3. 重定向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)
  4. 内核接着按改后的号调用 syscall table → 实际调到 ksu_syscall_dispatcher
  5. 分发ksu_syscall_dispatcher 还原寄存器、取出原 syscall 号、查 syscall_hooks[orig_nr] 得到 handler 并调用;handler 在需要时调用真实 syscall ksu_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 注入(hook read/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_KRETPROBESsyscall_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_virtinit_mm 页表把内核虚拟地址翻译成物理地址;
  • fixmap 映射该物理页(arm64 set_fixmap_offset(FIX_TEXT_POKE0, ...))→ copy_to_kernel_nofaultclear_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_uidallow_su
  • union:allow_su 为真用 rp_configuse_default + template_name + root_profile),否则用 nrp_configuse_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_zeroput_perm_data()kfree_rcu;替换用 hlist_replace_rcu

默认 profileinit_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_profilepolicy/app_profile.c

被 GRANT_ROOT IOCTL(do_grant_root)和 sucompat 的 execve 调用:

  1. cred = prepare_creds();若已 root 或线程设了 TIF_KSU_DISABLE_ESCAPE_WITH_ROOT(bit 63)→ 放弃;
  2. profile = ksu_get_root_profile(uid)(自定义或默认 root profile);
  3. 设 uid/suid/euid/fsuid = profile->uid,gid 四元组 = profile->gid,securebits = 0
  4. 刷新 user_struct/ucountsalloc_uid(cred->uid) 替换 cred->user,5.14+ 调 set_cred_ucounts(cred)(避免 RLIMIT_NPROC 计数残留在旧 uid——这是相对旧实现的正确性修复);
  5. 设置 capabilities:把 profile->capabilities.effective 同时拷给 cap_effective / cap_permitted / cap_bset
  6. setup_groups(profile, cred)(特例:仅 root 组时用静态 root_groups);
  7. setup_selinux(profile->selinux_domain, cred)(切 SELinux domain);
  8. commit_creds(cred)
  9. disable_seccomp()(清 TIF_SECCOMP/SYSCALL_WORK_SECCOMPseccomp.mode=0filter=NULL,用一个 fake task 正确释放原 filter 引用);
  10. flags & FLAG_KSU_NO_NEW_PRIVSset_thread_flag(TIF_KSU_DISABLE_ESCAPE_WITH_ROOT)(提权后禁止再次提权);
  11. 给本进程所有线程打 tracepoint 标记;
  12. 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 <= 4version<4 时按旧结构 776 字节兼容读,并 migrate_profile 升级(v2 把 u:r:su:s0u: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 如何装到进程(两条路)

  1. Manager:在 setresuid hook 里检测到 new_uid 是 manager 时直接 ksu_install_fd()
  2. 任意请求方(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() 并把 fd copy_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)

  1. 解析 /data/system/packages.list(每行 package uid ...)建立 uid_data 链表;
  2. 检查当前 manager appid 是否还在列表中:不在则 ksu_invalidate_manager_uid();若 appid 无效则 search_manager("/data/app", 2, ...) 深度 2 扫描;
  3. 最后 ksu_prune_allowlist(is_uid_exist, ...) 清理已卸载应用的条目。

search_manager BFS 遍历目录,遇 base.apkis_manager_apk 验签;命中即 crown_manager(按包名匹配到 uid_data 里的 uid,ksu_set_manager_appid)。用 data_app_magic 做同源校验、用 apk_path_hash_list 缓存已验签路径避免重复。

Manager 身份存的是 appiduid % 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)

  1. 倒扫找 EOCD → 定位 APK Signing Block(魔数 "APK Sig Block 42");
  2. 遍历 ID-value 对:0x7109871a=V2 块(check_block 比对证书 SHA-256),0xf05368c0=V3,0x1b93ad61=V3.1;
  3. 必须恰好 1 个 V2 块
  4. 若同时存在 V1(JAR) 签名 → 拒绝(防 Janus 降级);
  5. 若存在任何 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_rulesrules.c

设计:dup 当前 policydb → 在副本上加规则 → RCU 切换 → 销毁旧的 → reset AVC。同时保存一份 backup_sepolicy(供 selinux_hide 回滚使用)。注入的核心规则(ksu 自身 domain = ksu):

  • ksu domain 设为 permissive,附 mlstrustedsubject/netdomain/bluetoothdomain 属性;
  • 创建无约束文件类型 ksu_fileallow 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_sepolicyrules.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 内核层当前实现的要点:

  1. 模块化重构:13 个子目录各司其职,模块名仍 kernelsu.ko,区分 LKM 后加载与内置加载两条路径。
  2. tracepoint 分发器 hook:用 sys_enter + ni_syscall 空槽分发器 + 选择性进程标记,取代逐 syscall Kprobe,对普通进程零开销。
  3. 提权escape_with_root_profile 修改 cred(uid/gid/caps)、禁用 seccomp、切 u:r:ksu:s0 domain、按需禁止再提权;触发点是 hook setresuid 与 sucompat execve。
  4. RCU 哈希表白名单 + v4 Profile:带引用计数、版本迁移、持久化。
  5. IOCTL over [ksu_driver]:reboot kprobe 退居"仅装 fd",所有命令走匿名 inode 的 IOCTL,4 类权限检查。
  6. Manager 识别:扫描 + 纯 V2 签名校验(期望 hash 由构建注入)+ fsnotify 监听包变化。
  7. SELinux:动态 dup/RCU 热替换 policydb,注入 ksu domain 规则与动态 sepolicy 批处理。
  8. 底层能力:fixmap+stop_machine 改写内存(避开 hypervisor 保护)、kallsyms+CFI 符号解析、运行时 LSM 槽打补丁。

下一章节Android 安全机制与绕过 将从安全角度分析 Android 多层防护及 KernelSU 的绕过实现。

评论

  • 还没有评论,来说点什么吧。

无需注册或登录,填个昵称即可评论。