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

KernelSU 用户空间实现

2026-06-30 · 阅读 1

本章解析 KernelSU 的用户空间组成。与早期版本不同,当前用户空间的核心已经是一个用 Rust 编写的二进制 ksud,而不再是 Manager 应用内置的 C++ 通信层。Manager(APK)退化为纯 UI + 一条直连内核的 JNI 通路。

重要更正:旧版文档把"用户空间实现"等同于 manager/app/src/main/cpp/{ksu.h, ksu.cc, jni.cc} 是不准确的。真正承担"与内核通信、管理模块、执行 su、修补 boot"职责的是 userspace/ksud(Rust)。JNI 桥依然存在,但只负责 Manager 直接 IOCTL 内核这一条窄通路。


1. 用户空间整体架构

KernelSU 用户空间由三个角色构成:

┌──────────────────────────────────────────────────────────┐
│  Manager App (manager/, Kotlin + Jetpack Compose)         │
│  - 授权列表 / App Profile / 模块管理 / 刷写 boot 等 UI      │
└───────┬───────────────────────────────────┬──────────────┘
        │ 通路 A:JNI 直连内核                │ 通路 B:libsu root shell
        │ Natives.kt → jni.cc → ksu.cc        │ 执行 libksud.so <子命令>
        ▼ ioctl([ksu_driver])                 ▼
┌─────────────────────┐            ┌──────────────────────────────┐
│  内核 supercall      │            │  ksud (userspace/ksud, Rust)  │
│  [ksu_driver] inode  │◄───────────┤  守护进程 + 多功能 CLI         │
└─────────────────────┘  ioctl     │  打包为 libksud.so 进 APK      │
        ▲                           └──────────────┬───────────────┘
        │                                          │ init.rc 各阶段
        │                              ┌───────────▼───────────┐
        │                              │  ksuinit (Rust)        │
        └──────────────────────────────┤  LKM 模式的 init shim  │
          uapi/*.h(三方共享头)         └────────────────────────┘

三个角色:

组件 语言/位置 职责
ksud Rust,userspace/ksud/ 用户空间核心:与内核 IOCTL 通信、模块/metamodule 管理、init 事件处理、su 执行体、boot 修补、sepolicy/profile/feature 管理、resetprop
ksuinit Rust,userspace/ksuinit/ 极小的 init shim(#![no_main]),LKM 模式下在 boot 早期加载 kernelsu.ko,随后移交真正的 init;其 load_module() 库被 ksud 复用
Manager Kotlin/Compose,manager/ 图形界面;通过两条通路与系统交互

两条通信通路(最重要的架构事实):

  • 通路 A(直连内核 IOCTL)Natives.kt(external 方法)→ jni.cc(JNI 实现)→ ksu.ccksuctl() 包装 ioctl())→ [ksu_driver] fd → 内核 supercall handler。用于版本/UAPI 查询、是否 manager/lkm/安全模式、App Profile 读写、feature 开关、授权计数等。
  • 通路 B(经 ksud 命令行)KsuCli.kt/WebViewInterface.kt → libsu root shell → libksud.so <子命令>。用于模块管理、boot 刷写、sepolicy/profile 模板、安装/卸载等。

ksud 被构建后复制为 manager/app/src/main/jniLibs/<abi>/libksud.so 打包进 APK,运行期通过 applicationInfo.nativeLibraryDir + "/libksud.so" 取得路径,用 root shell 执行。Manager 不通过 JNI 调 ksud——ksud 一律走命令行子命令。


2. uapi:三方共享的"单一真相源"

uapi/ 是 KernelSU 新引入的目录,存放用户空间与内核共享的接口定义头文件。它被三方共享:

  • 内核(C)kernel/include/uapi 符号链接到它;
  • ksud(Rust)build.rsbindgen 处理 src/ksu_uapi.h(内容仅 #include "uapi/ksu.h"),生成 ksu_uapi 绑定;
  • Manager JNI(C++)manager/app/src/main/cpp/uapi 是一个 git 符号链接,指向仓库根的 uapi/

因此 IOCTL 命令号、结构体布局、feature ID 在三方完全一致,杜绝了"接口漂移"。

2.1 文件构成

uapi/
├── ksu.h          # 聚合头,include 下列全部
├── supercall.h    # IOCTL 主表、安装魔数、事件常量、各命令结构体
├── app_profile.h  # App Profile 结构(v4)
├── feature.h      # feature ID 枚举
├── selinux.h      # sepolicy 命令/子命令常量
└── sulog.h        # su 日志事件结构

2.2 IOCTL 命令表(uapi/supercall.h

幻数为 'K'KERNEL_SU_UAPI_VERSION = 2(含义:"allowlist v4 root profile flags")。命令号 1–21:

命令 说明
KSU_IOCTL_GRANT_ROOT 1 授予调用进程 root(被授权进程提权入口)
KSU_IOCTL_GET_INFO 2 取 version/flags/features/uapi_version
KSU_IOCTL_GET_INFO_LEGACY 2 旧版回退(无 uapi_version)
KSU_IOCTL_REPORT_EVENT 3 上报 post-fs-data / boot-completed / module-mounted
KSU_IOCTL_SET_SEPOLICY 4 下发序列化的 sepolicy 批处理
KSU_IOCTL_CHECK_SAFEMODE 5 查询内核安全模式
KSU_IOCTL_NEW_GET_ALLOW_LIST 6 取授权列表(count/total_count/uids[])
KSU_IOCTL_NEW_GET_DENY_LIST 7 取拒绝列表
KSU_IOCTL_UID_GRANTED_ROOT 8 查 uid 是否已被授 root
KSU_IOCTL_UID_SHOULD_UMOUNT 9 查 uid 是否应卸载模块
KSU_IOCTL_GET_MANAGER_APPID 10 取 Manager appid
KSU_IOCTL_GET_APP_PROFILE 11 读 App Profile
KSU_IOCTL_SET_APP_PROFILE 12 写 App Profile
KSU_IOCTL_GET_FEATURE 13 读 feature 值
KSU_IOCTL_SET_FEATURE 14 写 feature 值
KSU_IOCTL_GET_WRAPPER_FD 15 取 tty fd 的 ksu 包装 fd(su 用)
KSU_IOCTL_MANAGE_MARK 16 进程标记管理(get/mark/unmark/refresh)
KSU_IOCTL_NUKE_EXT4_SYSFS 17 清理 ext4 sysfs 条目
KSU_IOCTL_ADD_TRY_UMOUNT 18 维护待卸载挂载点列表(wipe/add/del)
KSU_IOCTL_SET_INIT_PGRP 19 把进程组切到 init group
KSU_IOCTL_GET_SULOG_FD 20 取 su 日志事件队列 fd
KSU_IOCTL_DISABLE_ESCAPE_TO_ROOT 21 设置 KSU_NO_NEW_PRIVS,禁止再提权

旧的 KSU_IOCTL_GET_ALLOW_LIST(6)/GET_DENY_LIST(7)/GET_INFO_LEGACY 都标记为 deprecated,新版用 NEW_* 变体。安装魔数 KSU_INSTALL_MAGIC1=0xDEADBEEFKSU_INSTALL_MAGIC2=0xCAFEBABE

2.3 App Profile 结构(uapi/app_profile.h,v4)

#define KSU_APP_PROFILE_VER     4
#define KSU_MAX_PACKAGE_NAME    256
#define KSU_MAX_GROUPS          32
#define KSU_SELINUX_DOMAIN      64
#define FLAG_KSU_NO_NEW_PRIVS   (1ULL << 0)

struct root_profile {
    int32_t  uid;
    int32_t  gid;
    uint32_t groups_count;
    int32_t  groups[KSU_MAX_GROUPS];          // 最多 32 个附加组
    struct {
        uint64_t effective;
        uint64_t permitted;
        uint64_t inheritable;
    } capabilities;
    char     selinux_domain[KSU_SELINUX_DOMAIN];
    int32_t  namespaces;
    uint64_t flags;                            // v4 新增:FLAG_KSU_NO_NEW_PRIVS 等
};

struct non_root_profile {
    bool umount_modules;
};

struct app_profile {
    uint32_t version;                          // = KSU_APP_PROFILE_VER (4)
    char     key[KSU_MAX_PACKAGE_NAME];        // 通常是包名;"$" 表默认 non-root profile
    int32_t  curr_uid;
    bool     allow_su;
    union {
        struct {
            bool use_default;
            char template_name[KSU_MAX_PACKAGE_NAME];
            struct root_profile profile;
        } rp_config;                           // allow_su == true 时使用
        struct {
            bool use_default;
            struct non_root_profile profile;
        } nrp_config;                          // allow_su == false 时使用
    };
};

相比旧版(version=2、无 flags),v4 增加了 flags(首位 FLAG_KSU_NO_NEW_PRIVS),并完善了 capabilities 三集合。内核侧有 migrate_profile 升级链:v2→v3 把默认 SELinux domain 从 u:r:su:s0 迁到 u:r:ksu:s0,v3→v4 补 FLAG_KSU_NO_NEW_PRIVS

2.4 feature ID(uapi/feature.h

enum ksu_feature_id {
    KSU_FEATURE_SU_COMPAT     = 0,   // su 兼容层(默认开)
    KSU_FEATURE_KERNEL_UMOUNT = 1,   // 内核态卸载模块(默认开)
    KSU_FEATURE_SULOG         = 2,   // su 日志(默认关)
    KSU_FEATURE_ADB_ROOT      = 3,   // adb root(默认关)
    KSU_FEATURE_SELINUX_HIDE  = 4,   // 隐藏 SELinux 启用状态(默认关)
    KSU_FEATURE_MAX
};

selinux.hsulog.h 分别定义 sepolicy 批处理命令常量、su 日志事件结构,详见 Android 安全机制与绕过高级特性与安全


3. ksud:用户空间核心(Rust)

源码在 userspace/ksud/src/。它既是开机时由 init 拉起的守护进程,又是供 Manager / shell 调用的多功能命令行工具。

3.1 一个二进制,多重身份

入口 cli::run()userspace/ksud/src/cli.rs)先做 multicall 分发

  1. argv[0] == "su"/system/bin/su → 进入 su::root_shell()(内核 sucompat 把 su 调用重定向到 ksud,并已在内核里提权);
  2. argv[0]resetprop 结尾 → resetprop::resetprop_main()(Magisk 兼容 prop 工具);
  3. 否则按 clap 子命令解析。

依赖(Cargo.toml)中值得注意的有:clap(子命令)、rust-embed(把 busybox、bootctl、ksuinit、各 KMI 的 *_kernelsu.ko 嵌入二进制)、android-bootimg(boot 镜像解析/修补)、prop-rs-android(resetprop 底层)、adb_client(magica 越狱自连 adb)、bindgen(构建期生成 UAPI 绑定)。版本号约定:VERSION_CODE = 30000 + git 提交数

3.2 与内核通信:获取 driver fd + ioctl

实现在 userspace/ksud/src/ksucalls.rs

第一步——拿到 [ksu_driver] fdinit_driver_fd):

  1. 扫描 /proc/self/fd/*readlink 后若目标包含字符串 [ksu_driver] 即复用该 fd(适用于已被内核植入 fd 的进程,如 Manager);
  2. 扫不到则发起 reboot magic syscall 让内核安装一个新 fd:
libc::syscall(libc::SYS_reboot,
    KSU_INSTALL_MAGIC1 /* 0xDEADBEEF */,
    KSU_INSTALL_MAGIC2 /* 0xCAFEBABE */,
    0, &mut fd);

内核 reboot kprobe 识别魔数后,用 task_work 把新 fd 写回 fd

第二步——所有 supercall 都是对该 fd 的 ioctl(通用包装 ksuctl<T>):

let fd = *DRIVER_FD.get_or_init(init_driver_fd);
let ret = libc::ioctl(fd, request as i32, arg);

注意:旧版用 prctl(0xDEADBEEF, ...) 的接口已退居二线,仅在 ksuinit 检测旧版内核时使用。当前主路径是 ioctl over [ksu_driver]

ksucalls.rs 对每个命令做了 Rust 封装:get_info / get_version / is_late_load / is_uapi_version_mismatch / grant_root / report_event / check_kernel_safemode / set_sepolicy / get_feature / set_feature / get_wrapped_fd / get_sulog_fd / mark_* / nuke_ext4_sysfs / umount_list_* / set_init_pgrp / set_ksu_no_new_privs

3.3 子命令体系(cli.rs

子命令 作用
post-fs-data / services / boot-completed 三大 init 事件入口(见 §4.3)
soft-reboot 模拟一次系统重启(不真正重启)
module <install|uninstall|undo-uninstall|enable|disable|action|list|config> 模块管理
install [--libadbroot <path>] 把自身复制到 /data/adb/ksud、释放内嵌资产
uninstall [--package-name] 卸载所有模块、删目录、还原 boot、卸载 Manager、重启(LKM)
unload 卸载 kernelsu 模块(LKM)
sepolicy <patch|apply|check> 运行时 sepolicy 注入/校验
profile <...> App Profile 的 sepolicy 文本与模板的磁盘存取
feature <get|set|list|check|load|save> 内核 feature 开关
boot-patch / boot-restore / boot-info boot 镜像修补/还原/信息查询
late-load [--magica [port]] ... LKM 运行时加载
insmod <ko> [params] 带 kallsyms 重定位加载任意 .ko
kernel <nuke-ext4-sysfs|umount|notify-module-mounted> 直接调内核接口
resetprop / initrc refresh prop 工具 / 重建 preinit rc
debug <...> 开发者命令(suinfomarkset-managerget-signsulogd 等)

PC 端(非 Android)只编译 boot-patch / boot-restore / get-sign / supported-kmis 四个命令,用于离线修补 boot。


4. ksud 模块系统

4.1 模块目录与状态标记

模块装在 /data/adb/modules/<id>/,状态用空文件标记(module.rs):

标记文件 含义
disable 已禁用
remove 标记卸载(真正删除发生在下次 prune_modules
update 刚安装/刚更新
skip_mount 跳过挂载

模块内常见文件:module.propsystem/(要叠加的内容)、sepolicy.rulesystem.propaction.shwebroot/initrc/*.rc,以及各阶段脚本 post-fs-data.sh / service.sh / boot-completed.sh / post-mount.sh / uninstall.sh

安装流程:模块先解压到暂存区 /data/adb/modules_update/<id>,post-fs-data 阶段由 handle_updated_modules 原子搬到 modules/,再由 prune_modules 处理 remove 标记。

4.2 metamodule 与 meta-overlayfs

这是当前模块系统最重要的设计变化:ksud 自己不做 OverlayFS 挂载,而是委托给一个特殊模块 metamodule

  • metamodule 也是装在 /data/adb/modules/<id> 的普通模块,但 module.propmetamodule=1;额外在 /data/adb/metamodule 建一个 symlink 指向它。
  • 全系统只能有一个活动 metamodule(安装第二个会被拒绝)。
  • metamodule 提供脚本:metamount.sh模块挂载/overlayfs 入口)、metainstall.sh(普通模块安装 hook)、metauninstall.sh(卸载 hook)、各 stage 脚本。
  • 安装普通模块时若活动 metamodule 处于不稳定态(带 update/remove/disable 标记),check_install_safety阻断安装并提示先重启。

相关实现:userspace/ksud/src/metamodule.rs。这种解耦让"如何把模块叠加到系统分区"成为可插拔策略(即 userspace/meta-overlayfs)。

4.3 init 事件流程(init_event.rs

ksud 由内核注入的 init.rc 在各阶段以 ksud post-fs-data / services / boot-completed 拉起。三个入口都先用 is_uapi_version_mismatch() 检查内核/ksud 版本是否匹配,不匹配则跳过。

on_post_data_fs()(最重的阶段)顺序

  1. report_post_fs_data()(通知内核 EVENT_POST_FS_DATA);
  2. clear_all_temp_configs()、后台抓启动日志;
  3. 检测到 Magisk → 让位并返回;安全模式 → disable_all_modules() 并返回;
  4. handle_updated_modules()(搬运 modules_update → modules);
  5. prune_modules()(处理 remove 标记);
  6. regenerate_preinit_rc()(刷新 preinit rc,见 §4.4);
  7. restorecon()load_sepolicy_rule()(各模块 sepolicy.rule)、profile::apply_sepolies()(root profile 的 SELinux 规则);
  8. feature::init_features()(应用 feature 配置);
  9. metamodule 的 post-fs-data.sh 先行,再跑普通模块 post-fs-data.sh
  10. load_system_prop()(各模块 system.prop → resetprop);
  11. metamodule 的 mount 脚本 metamount.sh(执行 overlayfs 挂载);
  12. post-mount 阶段脚本。

统一规则:每个阶段都按「公共 *.d 脚本 → metamodule 脚本(优先) → 普通模块脚本」执行。servicesboot-completed 是非阻塞阶段。

4.4 preinit modules.rc

regenerate_preinit_rc() 把每个启用模块的 initrc/*.rc 拼接成 /metadata[/watchdog]/ksu/modules.rc,并打上 u:object_r:metadata_file:s0 标签。内核的 init.rc 注入 hook 会在下次开机把这个文件拼进 init.rc(见 内核层实现 runtime 部分)。post-fs-data 也会重生成一次作为安全网。


5. Manager 的 JNI 直连通路

Manager 需要"实时、低延迟"地读写内核状态(版本、Profile、feature、授权计数),这条路由 JNI 直接 IOCTL 内核,不经 ksud。

5.1 native 库与文件(manager/app/src/main/cpp/

cpp/
├── ksu.h / ksu.cc     # IOCTL 包装与业务函数 → 编译进 libkernelsu.so
├── jni.cc             # JNI 入口 Java_me_weishu_kernelsu_Natives_* + magica 入口
├── adbroot.cc         # 编译为 libadbroot.so(adb root 的 LD_PRELOAD hook)
├── logging.h
├── CMakeLists.txt
└── uapi -> ../../../../../uapi   # 符号链接到仓库根 uapi/

System.loadLibrary("kernelsu") 加载 libkernelsu.so(由 jni.cc + ksu.cc 构建)。注意 libksud.so 不是 CMake 产物,而是 ksud 的 Rust 构建产物复制进 jniLibs。

5.2 fd 扫描(ksu.cc

与 ksud 同理,Manager 不主动 open 任何设备节点——内核在 Manager 进程 setresuid 时(或经 reboot kprobe)已把 [ksu_driver] fd 植入其 fd 表,Manager 只是去 /proc/self/fd 把它"捡"回来:

static int fd = -1;
static inline int scan_driver_fd();   // 遍历 /proc/self/fd,basename 含 "[ksu_driver]" 即命中
template<typename... Args>
static int ksuctl(unsigned long op, Args&&... args) {
    if (fd < 0) fd = scan_driver_fd();
    return ioctl(fd, op, std::forward<Args>(args)...);   // 至多 1 个额外参数
}

5.3 版本/状态/feature

get_info()KSU_IOCTL_GET_INFO(失败回退 GET_INFO_LEGACY),返回 {version, flags, features, uapi_version}。flags 位:LKM(1<<0) / MANAGER(1<<1) / LATE_LOAD(1<<2) / PR_BUILD(1<<3),对应 is_lkm_mode / is_manager / is_late_load_mode / is_pr_build。当 version <= 0 时回退到 legacy prctl(0xDEADBEEF, ...)

feature 开关统一经 get_feature(id, &value, &supported) / set_feature(id, value),对外暴露 is_su_enabled/set_su_enabled(id=0)、is_kernel_umount_enabled/set_…(id=1)、is_selinux_hide_enabled/set_…(id=4)。

5.4 Profile 双向映射(jni.cc

getAppProfile(pkg, uid) / setAppProfile(Profile) 通过 JNI 反射在内核 app_profile 结构与 Kotlin Natives$Profile 对象之间转换:

  • name ← keycurrentUid ← curr_uid
  • get_app_profile 失败(无 profile)→ allowSu=false, nonRootUseDefault=true(即默认非 root profile);
  • allow_su:填 rootUseDefault / rootTemplate / uid / gid / groups[] / capabilities(按 effective 位枚举 0..CAP_LAST_CAP)/ context(selinux_domain)/ namespace / flags
  • 否则:填 nonRootUseDefault / umountModules

写入方向,capabilities 经 capListToBits()1<<capcap_valid 校验)转回 effective 位掩码;groups 超过 32 直接返回 false。

注意Profile.rules(sepolicy 文本)字段不经 JNI(源码注释明确 "this field is save in ksud")。它由 ksud 的 profile get-sepolicy/set-sepolicy 子命令持久化。这就是 AppProfile 保存时的"双通路":profile 结构体走 JNI,sepolicy 文本走 ksud。

5.5 Kotlin 接口(Natives.kt

关键常量:MINIMAL_SUPPORTED_KERNEL = 32513KERNEL_SU_DOMAIN = "u:r:ksu:s0"(root profile 默认 SELinux context)、FLAG_KSU_NO_NEW_PRIVS = 1L

Natives.Profile 数据模型字段(与内核 v4 对应):

字段 类型 默认 对应
name String key(包名 / "$"
currentUid Int 0 curr_uid
allowSu Boolean false allow_su
rootUseDefault Boolean true rp_config.use_default
rootTemplate String? null rp_config.template_name
uid / gid Int 0 rp_config.profile.uid/gid
groups List<Int> [] groups[](≤32)
capabilities List<Int> [] capabilities.effective
context String u:r:ksu:s0 selinux_domain
namespace Int INHERITED(0) namespaces(INHERITED/GLOBAL/INDIVIDUAL)
nonRootUseDefault Boolean true nrp_config.use_default
umountModules Boolean true nrp_config.profile.umount_modules
rules String "" 不在内核结构,存于 ksud
flags Long NO_NEW_PRIVS(1) rp_config.profile.flags

辅助:checkUAPIMismatch()kernelUAPIVersion != managerUAPIVersion)、requireNewKernel()(版本过低或 UAPI 不匹配)、setDefaultUmountModules()(用特殊 key "$" + uid 9999 读写全局默认非 root profile)。


6. ksuinit 与 boot/LKM 集成(概要)

userspace/ksuinit 是一个 #![no_main] 的极小 init 程序,用于 GKI 模式boot-patch 把它和 kernelsu.ko 塞进 boot 镜像的 ramdisk,开机时它先挂 /proc/sys,加载 /kernelsu.ko(参数读自 /ksu_config),然后 execve 真正的 init(init.real/system/bin/init)。

它的库 lib.rs::load_module() 实现了"带 kallsyms 重定位加载 .ko"——解析 ELF 未定义符号,逐行读 /proc/kallsyms 把符号地址回填,再 init_module。这个库被 ksud 的 insmod / late-load / magica 复用。

两种安装形态、late-loadmagica(adb root 越狱注入)的完整流程见 高级特性与安全


7. 总结

  • 用户空间核心是 Rust 的 ksud,集守护进程与多功能 CLI 于一身;Manager 退化为 UI,ksud 以 libksud.so 形态打包进 APK。
  • 两条通路:Manager 直连内核 IOCTL(JNI,实时状态);以及经 libsu 执行 ksud 子命令(模块/boot/sepolicy 等重操作)。
  • uapi/ 是单一真相源,内核/ksud/Manager 通过符号链接或 bindgen 共享同一套定义,IOCTL 走 [ksu_driver] 匿名 inode。
  • 模块系统引入 metamodule + meta-overlayfs:ksud 不直接挂载,挂载策略由唯一活动的 metamodule 承担;模块状态用空文件标记,安装走 modules_update/ 暂存。
  • init 事件三阶段(post-fs-data / services / boot-completed)统一遵循"公共脚本 → metamodule → 普通模块"的顺序。

下一章节关键流程分析 将把内核与用户空间串起来,详细分析从开机到授权、卸载模块的完整流程。

评论

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

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