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

KernelSU libsu 与 su 兼容层

2026-06-30 · 阅读 3

本文档解读 Manager 应用如何使用 topjohnwu 的 libsu 库调用 ksud,以及"传统 su 体验"在内核与 ksud 两侧的实现。它对应 用户空间实现 中的"通路 B(经 ksud 命令行)"。

阅读前提:当前 ksud 是 Rust 二进制,被打包为 libksud.so 随 APK 分发;Manager 通过 libsu 创建的 root shell 以 libksud.so <子命令> 形式调用它。请先了解 用户空间实现 的整体架构。


1. libsu 库简介

libsu 是 Magisk 作者 topjohnwu 开发的 Android root 应用开发库,提供:

  • 便捷的 root shell 创建与命令执行;
  • 进程间服务(IPC);
  • root 文件 I/O。

GitHubhttps://github.com/topjohnwu/libsu

1.1 版本与依赖

KernelSU 当前使用 libsu 6.0.0manager/gradle/libs.versions.tomllibsu = "6.0.0"),引入三个 artifact:

// manager/app/build.gradle.kts
implementation(libs.com.github.topjohnwu.libsu.core)     // Shell 执行、任务管理
implementation(libs.com.github.topjohnwu.libsu.service)  // 跨进程服务
implementation(libs.com.github.topjohnwu.libsu.io)       // root 文件 I/O

KernelSU 主要使用 core(执行 ksud 命令、模块管理)。

1.2 ksud 作为 libksud.so 的集成

ksud(Rust)构建后被复制为 manager/app/src/main/jniLibs/<abi>/libksud.so 打包进 APK。运行期取路径:

// KsuCli.kt
private fun getKsuDaemonPath(): String =
    ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libksud.so"

为什么用 .so 形式打包?Android 只保证 nativeLibraryDir 下的文件在安装后保留可执行权限并随 APK 更新,把可执行的 ksud 命名为 libksud.so 即可被 root shell 直接执行。


2. Shell 管理(KsuCli.kt

文件:manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt

2.1 Shell 三级回退

fun createRootShell(globalMnt: Boolean = false): Shell {
    Shell.enableVerboseLogging = BuildConfig.DEBUG
    val builder = Shell.Builder.create()
    return try {
        // 第一级:用 ksud 自带的 su 入口
        if (globalMnt) builder.build(getKsuDaemonPath(), "debug", "su", "-g")
        else           builder.build(getKsuDaemonPath(), "debug", "su")
    } catch (e: Throwable) {
        try {
            // 第二级:系统 su(globalMnt 时 -mm 全局挂载)
            if (globalMnt) builder.build("su", "-mm") else builder.build("su")
        } catch (e: Throwable) {
            // 第三级:普通 sh(无 root)
            builder.build("sh")
        }
    }
}
ksud debug su [-g]   ←  KernelSU 自身的 su
      ↓ 失败
su [-mm]             ←  系统已有的 su(兼容其它方案)
      ↓ 失败
sh                   ←  无 root,降级运行
  • 第一级用 libksud.so debug su-g 表示全局 mount namespace;
  • 这条命令进内核 → escape_with_root_profile 提权 → ksud exec shell(见 §5)。

2.2 全局 Shell 与临时 Shell

object KsuCli {
    val SHELL: Shell = createRootShell()              // 应用私有 ns
    val GLOBAL_MNT_SHELL: Shell = createRootShell(true)  // 全局 ns
}
fun getRootShell(globalMnt: Boolean = false): Shell =
    if (globalMnt) KsuCli.GLOBAL_MNT_SHELL else KsuCli.SHELL

inline fun <T> withNewRootShell(globalMnt: Boolean = false, block: Shell.() -> T): T =
    createRootShell(globalMnt).use(block)   // 用完自动关闭
Shell mount namespace 用途
SHELL 应用私有 普通命令
GLOBAL_MNT_SHELL 全局(init) 模块操作、系统级修改、WebUI
withNewRootShell{} 每次新建 长耗时/隔离操作(如刷写)

3. execKsud 与通过 ksud 实现的功能

3.1 execKsud 封装

fun execKsud(args: String, newShell: Boolean = false): Boolean =
    if (newShell) withNewRootShell { ShellUtils.fastCmdResult(this, "${getKsuDaemonPath()} $args") }
    else ShellUtils.fastCmdResult(getRootShell(), "${getKsuDaemonPath()} $args")

即在 root shell 里跑 libksud.so <args> 并返回布尔成功。绝大多数"重操作"都经它落到 ksud。

3.2 KsuCli 通过 ksud 实现的功能一览

功能 ksud 子命令
安装 ksud 自身 install --libadbroot <libadbroot.so>
列出模块(JSON) module list
启用/禁用模块 module enable/disable <id>
卸载/撤销卸载 module uninstall/undo-uninstall <id>
刷写模块(流式输出) module install <zip>
运行模块动作 module action <id>(全局 ns)
刷写 boot boot-patch [-f | -b boot.img] [--allow-shell] [--enable-adbd] [-u] [-m lkm.ko | --kmi x] [-o downloads] [--partition p]
还原 boot boot-restore -f
永久卸载 uninstall --package-name <pkg>
KMI 信息 boot-info <current-kmi | supported-kmis | is-ab-device | ...>
feature 持久值 feature check <f> / feature get --config <f>
sepolicy 校验/读写 sepolicy check '<rules>' / profile get-sepolicy <pkg> / profile set-sepolicy <pkg> '<rules>'
profile 模板 profile list-templates / get-template / set-template / delete-template
软重启 soft-reboot

注意:boot-patch 不再依赖外部 libmagiskboot.so——boot 镜像解析/修补由 ksud 内嵌的 android-bootimg 完成(见 用户空间实现 §3.1、高级特性与安全)。少数查询如 getSuperuserCount() 走 JNI(Natives),不走 ksud。

3.3 流式输出(刷写进度)

模块/boot 刷写用 libsu 的 CallbackList 把 stdout/stderr 实时回调给 UI:

private fun flashWithIO(cmd: String, onStdout: (String)->Unit, onStderr: (String)->Unit): Shell.Result {
    val stdout = object : CallbackList<String?>() { override fun onAddElement(s: String?) { onStdout(s ?: "") } }
    val stderr = object : CallbackList<String?>() { override fun onAddElement(s: String?) { onStderr(s ?: "") } }
    return withNewRootShell { newJob().add(cmd).to(stdout, stderr).exec() }
}

4. su 兼容层(内核侧)

"调用 /system/bin/su 就能拿 root"是传统 root 应用的预期。KernelSU 用内核 feature KSU_FEATURE_SU_COMPAT(默认开启)实现这一兼容,代码在 kernel/feature/sucompat.c

4.1 Hook 机制(已不是 Kprobe)

重要更正:旧版用 Kprobe 分别 hook execve/faccessat/newfstatat/pts_unix98_lookup。当前实现改为 sys_enter tracepoint + ni_syscall 空槽分发器(详见 内核层实现)。对被标记进程的目标 syscall,分发器查路由表 syscall_hooks[] 调用对应 handler。

sucompat 关注的 syscall:execvefaccessatnewfstatatsetresuidsetuid_hook 处理,见 内核层实现关键流程分析)。只有在 allowlist 中(或 Manager)的 UID 才会触发改写,避免干扰普通应用。

4.2 三类处理

① 存在性/状态查询改写(faccessat / newfstatat)

应用常用 access("/system/bin/su")stat("/system/bin/su") 探测 su 是否存在。sucompat 把这两类调用里指向 /system/bin/su 的路径改写为 /system/bin/sh,让探测"看到一个存在且可访问的目标":

应用: access("/system/bin/su", F_OK)
  → sucompat 改写 path: /system/bin/su → /system/bin/sh
  → 探测成功(sh 一定存在)

② 执行重定向 + 提权(execve)

当授权 UID 执行 /system/bin/su 时,sucompat 把执行目标重定向到 ksud,并在 exec 前调用 escape_with_root_profile() 把进程提权为对应 root profile:

应用: execve("/system/bin/su", argv, envp)
  → 校验 UID 在 allowlist
  → 重定向到 KSUD_PATH (/data/adb/ksud),argv[0] 设为 "su"
  → escape_with_root_profile()  提权(改 cred / 禁 seccomp / 切 SELinux 到 u:r:ksu:s0)
  → 实际执行 ksud(进入 su::root_shell,见 §5)

③ 终端(tty)SELinux 修复 —— 由 file_wrapper 取代旧 devpts hook

root shell 的 stdin/stdout/stderr 通常是应用域下的 pts,直接交给 u:r:ksu:s0 进程会触发 SELinux 拒绝。旧版用 Kprobe hook pts_unix98_lookup 改 devpts 的 SELinux sid;当前改用 infra/file_wrapper:内核构造 [ksu_fdwrapper] 匿名 inode 把目标 fd 包一层,并把该 inode 的 SELinux sid 设为 ksu_file_sid。ksud 在 su::root_shell 里通过 IOCTL KSU_IOCTL_GET_WRAPPER_FD(即 get_wrapped_fd)取回包装 fd 替换 tty(见 §5)。

4.3 feature 开关

su 兼容可被开关,对应 KSU_FEATURE_SU_COMPAT(id=0):

Natives.isSuEnabled()       // 查询(JNI → IOCTL GET_FEATURE)
Natives.setSuEnabled(true)  // 开关(JNI → IOCTL SET_FEATURE)

内核侧 set handler 会按需注册/注销 sucompat 的 syscall hook,并更新全局开关 ksu_su_compat_enabled


5. ksud 的 su 实现(用户侧,su.rs

文件:userspace/ksud/src/su.rs。当内核把 su 调用重定向到 ksud 后,cli::run 检测到 argv[0] == "su" 即进入 root_shell()此时进程已在内核里被提权为 root

5.1 root_shell:Magisk/标准 su 兼容

root_shell()getopts 解析与 Magisk/标准 su 一致的参数:

参数 说明
-c <cmd> 执行命令后退出
-l 登录 shell
-p 保留环境
-s <shell> 指定 shell
-g <gid> / -G <group> 主组 / 附加组
-M / --mount-master-mm 全局 mount namespace
-W / --no-wrapper 不做 tty fd 包装
--ksu-no-new-privs 设置后禁止再次提权
-v / -V 版本信息

pre_exec(exec 前在子进程上下文执行)依次做:

  1. umask(022)switch_cgroups()(把进程挪进合适 cgroup);
  2. -Mswitch_mnt_ns(1) 切到 init 的全局 mount ns;
  3. tty fd 包装:对 isatty 的 0/1/2,用 get_wrapped_fd 换成 [ksu_fdwrapper] 包装 fd(解决 §4.2③ 的 SELinux 问题);除非传了 -W
  4. set_identity(uid, gid, groups) 设置目标身份;
  5. 设置 HOME/USER/LOGNAME/SHELLPATH 追加 /data/adb/ksu/bin;存在 .ksurc 则作为 ENV

--ksu-no-new-privs 会调 set_ksu_no_new_privs()(IOCTL KSU_IOCTL_DISABLE_ESCAPE_TO_ROOT),之后该进程不能再经 sucompat/supercall 提权。

5.2 grant_root(ksud debug su

createRootShell 第一级用的就是它(su.rs::grant_root):

pub fn grant_root(global_mnt: bool) -> Result<()> {
    ksucalls::grant_root()?;          // IOCTL KSU_IOCTL_GRANT_ROOT → 内核提权
    // 可选切全局 mount ns,PATH 追加 /data/adb/ksu/bin,然后 exec sh
}

5.3 完整调用链

Manager (KsuCli.createRootShell)
  → libsu: build("libksud.so", "debug", "su")
  → ksud: cli::run → debug::su → su::grant_root
  → ksucalls::grant_root  ──IOCTL KSU_IOCTL_GRANT_ROOT──▶ 内核
  → 内核 escape_with_root_profile:改 cred / 禁 seccomp / 切 u:r:ksu:s0
  → ksud exec sh(已是 root)
  → libsu 拿到 root shell
  → Manager 在该 shell 上跑命令

6. WebUI:模块网页界面的 root 桥接

模块可附带 webroot/,由 Manager 的 WebView 加载并提供 root 能力。这套机制由两部分组成:

6.1 Java↔JS 桥(WebViewInterface.kt

manager/app/src/main/java/me/weishu/kernelsu/ui/webui/WebViewInterface.kt@JavascriptInterface 向页面注入全局对象 ksu

  • exec(cmd[, options][, callback]):用 withNewRootShell(true)(全局 ns)执行命令,结果经 webView.loadUrl("javascript:...") 回调;processOptions 处理 cwd(cd)、env(export);
  • spawn(command, args, options, callback):用 CallbackList 把 stdout/stderr/exit/error 流式回调;
  • toast / fullScreen / enableEdgeToEdge / moduleInfo / listPackages / getPackagesInfo / exit

6.2 JS 库(js/,npm 包 kernelsu

供模块作者在 WebUI 里使用的前端库,版本 3.0.2,纯 ES module 无依赖。它封装注入的 ksu.* 对象,导出:

import { exec, spawn, toast, moduleInfo, listPackages } from "kernelsu";

const { errno, stdout } = await exec("id");        // root 执行
const child = spawn("logcat", ["-v", "time"]);     // 流式:child.stdout.on('data', ...)
toast("done");

约定:ksu://icon/{packageName} 取应用图标;引入 internal/insets.css 自动开启 edge-to-edge。


7. libsu 高级用法

7.1 Job 链式调用

getRootShell().newJob()
    .add("cd /data/adb/ksu")
    .add("ls -l")
    .exec()

7.2 输出捕获 / 实时回调

val out = ArrayList<String>(); val err = ArrayList<String>()
getRootShell().newJob().add("ls /system").to(out, err).exec()

val cb = object : CallbackList<String?>() {
    override fun onAddElement(s: String?) { /* 处理每一行 */ }
}
getRootShell().newJob().add("long_cmd").to(cb, null).exec()

8. 最佳实践与调试

8.1 复用 vs 隔离

  • 短命令复用全局 getRootShell(),减少进程开销;
  • 长耗时/危险操作(刷写、卸载)用 withNewRootShell{} 隔离并自动关闭。

8.2 命令注入防护

ksud 命令多以字符串拼接构造,传入用户可控内容时务必转义/校验:

val escaped = filename.replace("\"", "\\\"")
getRootShell().newJob().add("rm \"$escaped\"").exec()

实测发现 KsuCli.setAppProfileTemplate 的引号转义存在拼接瑕疵(拼成 "$escapedTemplate'"),属可改进点。

8.3 调试

Shell.enableVerboseLogging = BuildConfig.DEBUG       // 打印 SHELL_IN/OUT
val r = getRootShell().newJob().add("id").exec()      // 期望 uid=0(root)
Log.d(TAG, "root=${getRootShell().isRoot}, code=${r.code}, out=${r.out}")

8.4 拿不到 root 的排查

  1. 内核是否正常?Natives.version / Natives.isManager
  2. UAPI 是否匹配?Natives.checkUAPIMismatch()
  3. su 兼容是否开启?Natives.isSuEnabled()
  4. 该应用是否授权?App Profile 的 allowSu

9. 总结

  • KernelSU 通过 libsu 6.0.0 创建 root shell,以三级回退 ksud su → 系统 su → sh 保证可用性;
  • "传统 su 体验"是 内核 + ksud 协同:内核 sucompat(tracepoint 分发器,非 Kprobe)改写 su 探测、重定向 su 执行到 ksud 并提权;ksud su.rs 负责 Magisk 兼容参数解析、tty fd 包装、身份设置;
  • tty 的 SELinux 问题由 file_wrapper([ksu_fdwrapper] + get_wrapped_fd 解决,取代旧的 devpts Kprobe hook;
  • 模块/boot/sepolicy 等重操作经 execKsud 落到 libksud.so 子命令;WebUI 通过 WebViewInterface 注入的 ksu 对象 + js 库 kernelsu@3.0.2 获得 root 能力。

相关文档

评论

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

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