KernelSU 用户空间实现
本章解析 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.cc(ksuctl()包装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.rs用bindgen处理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=0xDEADBEEF、KSU_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.h 与 sulog.h 分别定义 sepolicy 批处理命令常量、su 日志事件结构,详见 Android 安全机制与绕过、高级特性与安全。
3. ksud:用户空间核心(Rust)
源码在 userspace/ksud/src/。它既是开机时由 init 拉起的守护进程,又是供 Manager / shell 调用的多功能命令行工具。
3.1 一个二进制,多重身份
入口 cli::run()(userspace/ksud/src/cli.rs)先做 multicall 分发:
argv[0] == "su"或/system/bin/su→ 进入su::root_shell()(内核 sucompat 把 su 调用重定向到 ksud,并已在内核里提权);argv[0]以resetprop结尾 →resetprop::resetprop_main()(Magisk 兼容 prop 工具);- 否则按
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] fd(init_driver_fd):
- 先 扫描
/proc/self/fd/*,readlink后若目标包含字符串[ksu_driver]即复用该 fd(适用于已被内核植入 fd 的进程,如 Manager); - 扫不到则发起 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 <...> |
开发者命令(su、info、mark、set-manager、get-sign、sulogd 等) |
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.prop、system/(要叠加的内容)、sepolicy.rule、system.prop、action.sh、webroot/、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.prop里metamodule=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()(最重的阶段)顺序:
report_post_fs_data()(通知内核EVENT_POST_FS_DATA);clear_all_temp_configs()、后台抓启动日志;- 检测到 Magisk → 让位并返回;安全模式 →
disable_all_modules()并返回; handle_updated_modules()(搬运 modules_update → modules);prune_modules()(处理 remove 标记);regenerate_preinit_rc()(刷新 preinit rc,见 §4.4);restorecon()、load_sepolicy_rule()(各模块sepolicy.rule)、profile::apply_sepolies()(root profile 的 SELinux 规则);feature::init_features()(应用 feature 配置);- metamodule 的
post-fs-data.sh先行,再跑普通模块post-fs-data.sh; load_system_prop()(各模块system.prop→ resetprop);- metamodule 的 mount 脚本
metamount.sh(执行 overlayfs 挂载); post-mount阶段脚本。
统一规则:每个阶段都按「公共 *.d 脚本 → metamodule 脚本(优先) → 普通模块脚本」执行。services 与 boot-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 ← key、currentUid ← 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<<cap,cap_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 = 32513、KERNEL_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-load、magica(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 → 普通模块"的顺序。
下一章节:关键流程分析 将把内核与用户空间串起来,详细分析从开机到授权、卸载模块的完整流程。
评论
- 还没有评论,来说点什么吧。