KernelSU App Profile 深度解析:用 Root Profile 与 Umount 策略实现最小权限 su
发布时间:2026/9/13 23:48:41 锦皓数字建站

KernelSU App Profile 深度解析用 Root Profile 与 Umount 策略实现最小权限 su【免费下载链接】KernelSUA Kernel based root solution for Android项目地址: https://gitcode.com/GitHub_Trending/ke/KernelSUApp Profile 是 KernelSU 提供的按应用定制内核行为的配置机制对被授予 root 的应用它可以约束su之后进程的 UID/GID、Groups、Capabilities 与 SELinux 上下文即 Root Profile对普通应用它可以决定模块挂载内容对该应用是否可见即 Non-root Profile 的 Umount modules。读完本文你将理解 App Profile 每项配置的安全语义、内核侧的实际执行路径escape_with_root_profile、setresuid 钩子与path_umount并能正确配置白名单/黑名单式模块隐藏策略避免“二次 su 提权”等常见配置陷阱。App Profile 是什么对于获得 root 授权可以使用su的应用App Profile 也被称为Root Profile。它允许定制su命令的uid、gid、groups、capabilities与SELinux规则从而限制 root 用户的实际权限。例如只给防火墙类应用网络权限而拒绝文件访问权限或者给冻结类应用 shell 权限而非完整 root 权限——以最小权限原则把“权力”关进笼子。对于没有 root 权限的普通应用App Profile 控制的是内核与模块系统对该应用的行为例如决定模块造成的系统修改是否需要被“隐藏”对应用执行类似 umount 的操作内核和模块系统会据此做出决策。从内核态的数据结构看这一机制的完整定义在 uapi/app_profile.h 中#define KSU_APP_PROFILE_VER 4 #define KSU_MAX_PACKAGE_NAME 256 /* NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups. */ #define KSU_MAX_GROUPS 32 #define KSU_SELINUX_DOMAIN 64 #define FLAG_KSU_NO_NEW_PRIVS (1ULL 0) struct root_profile { __s32 uid; __s32 gid; __u32 groups_count; __s32 groups[KSU_MAX_GROUPS]; /* kernel_cap_t is u32[2] for capabilities v3 */ struct { __u64 effective; __u64 permitted; __u64 inheritable; } capabilities; char selinux_domain[KSU_SELINUX_DOMAIN]; __s32 namespaces; __u64 flags; }; struct non_root_profile { bool umount_modules; }; struct app_profile { __u32 version; char key[KSU_MAX_PACKAGE_NAME]; // 通常是应用包名 __s32 curr_uid; bool allow_su; union { struct { bool use_default; char template_name[KSU_MAX_PACKAGE_NAME]; struct root_profile profile; } rp_config; struct { bool use_default; struct non_root_profile profile; } nrp_config; }; };几个关键约束可以直接从源码读出Groups 上限为 32 个KSU_MAX_GROUPS。内核在 kernel/policy/app_profile.c 的setup_groups()中会检查groups_count KSU_MAX_GROUPS并打印告警后放弃设置因此即使管理器侧允许填写更多超出部分也不会生效。SELinux 域名字符串最长 64 字节KSU_SELINUX_DOMAIN配置超长域名会被截断。struct app_profile用union区分两种 profileallow_su为真的应用使用rp_config可引用模板template_name或完全自定义普通应用使用nrp_config仅含umount_modules一个布尔位。use_default位表示“跟随管理器默认设置”这正对应下文白名单/黑名单两种用法。此外 kernel/policy/allowlist.c 中还定义了KSU_APP_PROFILE_PRESERVE_UID 9999从源码结构看这是一个特殊值表示“保持当前 UID 不变”而非切换为 root。Root Profile约束 su 之后的进程UID、GID 与 GroupsLinux 系统有两个基本概念用户和组。每个用户有一个用户 IDUID一个用户可以属于多个组每个组有自己的组 IDGID。系统用这些 ID 识别用户并决定可访问的资源。UID 为 0 的用户是 root 用户GID 为 0 的组是 root 组通常拥有系统最高权限。在 Android 系统中每个应用都作为一个独立用户运行shared UID 的情况除外拥有唯一 UID。例如0是 root1000是system2000是 ADB shell10000到19999是普通应用。注意这里的 UID 与 Android 多用户/工作资料work profile概念不同。Work profile 实际上是通过划分 UID 区间实现的——10000-19999 是主用户110000-119999 是工作资料其中每个普通应用依然有自己唯一的 UID。每个应用可以有多个组GID 表示主组通常与 UID 相同其余称为附加组supplementary groups。一些权限由组成员关系控制例如网络访问、蓝牙访问。在 ADB shell 中执行id命令输出可能是这样oriole:/ $ id uid2000(shell) gid2000(shell) groups2000(shell),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),1078(ext_data_rw),1079(ext_obb_rw),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats),3009(readproc),3011(uhid),3012(readtracefs) contextu:r:shell:s0这里 UID 是2000GID主组也是2000它还属于若干附加组例如inet允许创建AF_INET/AF_INET6套接字即可访问网络和sdcard_rw可读写 SD 卡。KernelSU 的 Root Profile 允许定制su执行后 root 进程的 UID、GID 与组。例如把某 root 应用的 Root Profile UID 设为2000则它执行su后的实际权限只相当于 ADB shell还可以移除inet组使su无法访问网络。注意App Profile 只控制使用su之后的 root 进程权限不控制应用本身的权限。如果应用已申请了网络权限即使不用su它也能联网把inet组从su移除只影响su进程。Root Profile 由内核强制实施不依赖 root 应用的“自觉”这与应用自己通过su手动切换用户/组有本质区别。su授权完全由用户决定开发者无法绕过。内核侧的执行入口是 kernel/policy/app_profile.c 的escape_with_root_profile()它通过prepare_creds()复制当前凭证然后把uid/suid/euid/fsuid四项统一写入profile-uidgid/fsgid/sgid/egid写入profile-gid并更新cred-user与5.14 的ucounts计数后调用commit_creds()生效——这正是“内核强制”的来源权限变更发生在内核系统调用路径中用户态无法篡改。CapabilitiesCapabilities 是 Linux 的权限分离privilege separation机制。传统 UNIX 为做权限检查只区分两类进程特权进程effective UID 为0即 superuser/root和普通进程。特权进程绕过内核的一切权限检查普通进程则完全按进程凭证effective UID、effective GID、附加组列表检查。自 Linux 2.2 起传统 superuser 的特权被拆分成若干独立单元即 capability可分别启用或禁用。每个 capability 代表一项或多项权限。例如CAP_DAC_READ_SEARCH代表绕过文件读取的权限检查以及目录读/执行权限。如果一个 effective UID 为0的用户不具备CAP_DAC_READ_SEARCH或更高 capability那么即使它是 root 也无法随意读文件。KernelSU 的 Root Profile 允许定制su之后 root 进程的 capabilities从而只授予“部分 root 权限”。与前面的 UID/GID 不同一些 root 应用在su后确实需要 UID0此时限制该 UID0用户的 capabilities 就能约束它能执行的操作。强烈建议Linux capabilities 官方文档capabilities(7)man 页详细解释了每个 capability 代表的权限。如果要自定义 capabilities强烈建议先阅读该文档。内核实现上struct root_profile的capabilities字段保留了effective/permitted/inheritable三个 64 位位图。kernel/policy/app_profile.c 在提权时把用户配置的effective值同时复制到凭证的cap_effective、cap_permitted与cap_bsetbounding set三处——把 bounding set 一并收紧意味着后续 execve 也无法“找回”被剥夺的 capability这是比单纯改 effective set 更彻底的约束。默认配置下kernel/policy/allowlist.cdefault_root_profile使用CAP_FULL_SET满权限即不配置模板时行为等同于完整 root。SELinuxSELinux 是一套强大的强制访问控制MAC机制遵循default deny默认拒绝原则任何未被显式允许的操作都会被拒绝。SELinux 有两种全局模式Permissive宽容模式拒绝行为只记录日志不实际执行拦截Enforcing强制模式拒绝行为既记录日志又实际拦截。警告现代 Android 高度依赖 SELinux 保障系统整体安全。强烈不建议使用处于 Permissive 模式的定制系统因为它相比完全开放的系统几乎没有任何安全优势。完整讲解 SELinux 非常复杂超出本文范围建议通过 Linux 官方文档、Wikipedia 的 Security-Enhanced Linux 条目、Red Hat 与 ArchWiki 的 SELinux 资料等公开资料先建立概念。KernelSU 的 Root Profile 允许定制su之后 root 进程的 SELinux 上下文并可为该上下文设定专门的访问控制规则从而对 root 权限做细粒度控制。典型场景中应用执行su后进程会切换到不受限的 SELinux 域例如u:r:ksu:s0见 kernel/policy/allowlist.c 中的KSU_DEFAULT_SELINUX_DOMAIN。通过 Root Profile可以把该域替换为自定义域如u:r:app1:s0然后为该域定义一组规则type app1 enforce app1 typeattribute app1 mlstrustedsubject allow app1 * * *注意上面的allow app1 * * *仅用于演示。实践中不应广泛使用这条规则因为它与 Permissive 模式几乎没有区别。管理器中配置的这些策略type/enforce/allow等最终由 ksud 通过 supercall 通道下发userspace/ksud/下的 profile.rs 提供set_sepolicy(pkg, policy)、get_sepolicy(pkg)以及模板set_template/get_template/list_templates等命令管理器 App 的界面层如 AppProfileConfigMaterial.kt与之配合完成每应用的策略编辑。提权Escalation与 NO_NEW_PRIVS如果 Root Profile 配置不当可能发生**提权Escalation**场景——App Profile 施加的限制意外失效。举例你给 ADB shell 用户授予了 root很常见的场景又给某个普通应用授予 root但把该应用的 Root Profile 配置为 UID 2000ADB shell 的 UID。那么这个应用通过执行两次su就能拿到完整 root第一次su受 App Profile 约束UID 被改为2000ADB shell而不是0root第二次su时由于 UID 已是2000而配置中 UID2000ADB shell本身被授予了 root于是应用获得完整 root 权限。内核侧可以在 kernel/policy/app_profile.c 看到防护逻辑escape_with_root_profile()会先检查当前euid是否已为 0已是 root 则拒绝再次提权并检查线程标志TIF_KSU_DISABLE_ESCAPE_WITH_ROOT定义于 kernel/policy/app_profile.h。这个标志正是由FLAG_KSU_NO_NEW_PRIVS触发的if (profile-flags FLAG_KSU_NO_NEW_PRIVS) { set_thread_flag(TIF_KSU_DISABLE_ESCAPE_WITH_ROOT); }因此你可以在自定义 App Profile 中启用NO_NEW_PRIVS标志对应 uapi/app_profile.h 中的FLAG_KSU_NO_NEW_PRIVS (1ULL 0)。它会阻止该进程再通过su逃逸并二次提升权限。但是该标志只阻止 KernelSU 为该进程提权进程仍然可能借助其他 Linux 机制逃逸。因此请务必谨慎设置权限。警告上述“两次 su”行为完全符合设计预期不是 bug。官方建议如果确实需要给 ADB 授予 root例如开发者自用配置 Root Profile 时不要把 UID 改成2000改用1000system更安全。Non-root ProfileUmount modules机制与默认值KernelSU 提供无 root 修改系统分区systemless的机制其实现是挂载 OverlayFS。但部分应用对这种挂载行为敏感检测到 /system 上的 overlay 等。此时可以启用 App Profile 中的“Umount modules”选项为特定应用卸载已挂载的模块。内核实现位于 kernel/feature/kernel_umount.c 的ksu_handle_umount()通过try_umount()对mount_list中每个可卸载挂载点调用内核函数path_umount()只处理 6 种 zygote 派生场景普通应用、isolated process、app zygote、webview zygote 等并校验父进程 SELinux 上下文必须是 zygote避免误伤“setuid 到 untrusted_app 但处于全局挂载命名空间”的 root 应用该函数由 setresuid 系统调用钩子 kernel/hook/setuid_hook.c 的ksu_handle_setresuid()调用——zygote fork 出应用后必然调用setresuid(3)这就是“按 UID 精确判定应用”的时机。判定“某个 UID 是否需要卸载模块”的逻辑在 kernel/policy/allowlist.c 的ksu_uid_should_umount()条件结果该 UID 是 KernelSU 管理器自身永不卸载we should not umount on manager!查不到对应 App Profile使用默认值default_non_root_profile.umount_modules查到 Profile 且allow_su为真不卸载已授权 root 的应用不隐藏模块Profile 的use_default为真跟随默认值否则使用该应用自己的umount_modules设置其中默认值在 kernel/policy/allowlist.c 初始化为true即“默认卸载模块”。白名单 / 黑名单两种用法KernelSU Manager 的设置界面提供“Umount modules by default”选项。该选项默认开启含义是除非另有配置KernelSU 或某些模块会为该应用卸载模块。如果你不喜欢这个默认行为或某些应用受到影响有两种做法保持“Umount modules by default”开启只对确实需要加载模块的应用在其 App Profile 中关闭“Umount modules”该选项此时充当白名单默认隐藏、点名放行关闭“Umount modules by default”只对必须隐藏模块的应用在其 App Profile 中开启“Umount modules”此时充当黑名单默认放行、点名隐藏。这与内核中nrp_config.use_default的语义完全对应use_defaulttrue时结果取决于全局开关use_defaultfalse时以应用自身设置为准。内核版本要求5.10 与 path_umount backport说明在 5.10 及以上内核的设备上内核会直接执行模块卸载无需额外动作。而在低于 5.10 的内核上该选项仅是一份“配置”KernelSU 本身不采取任何行动。若要在 5.10 之前的内核上使用“Umount modules”你需要把fs/namespace.c中的path_umount函数 backport 到内核中。更详细的步骤见仓库文档 how-to-integrate-for-non-gki.md其中给出了can_umount/path_umount参考补丁。此外Zygisk 等模块也可能读取该选项来判断是否需要卸载模块。这一点在源码中有直接体现kernel/feature/kernel_umount.c 以extern int path_umount(struct path *path, int flags);声明外部符号——在标准 5.10 GKI 内核中该符号存在编译链接自然通过旧内核没有该符号模块加载路径会失效因此需要手动 backport。同时kernel_umount还注册为可开关的 featurekernel/feature/kernel_umount.c即存在一个总开关可以整体停用内核卸载逻辑。小结配置 App Profile 的实操要点Root 应用优先用模板rp_config.template_name批量管理给非必须 root 的应用把 UID 设为2000/1000并收紧 groups确需 UID0时转而收紧 capabilities需要极致隔离时配置自定义 SELinux 域且避免allow app1 * * *这类近似 Permissive 的规则。ADB 授权场景不要把 Root Profile 的 UID 配成2000否则配合已授权的 ADB 会出现“两次 su 拿满权限”的预期内提权路径必要时在 App Profile 勾选NO_NEW_PRIVS阻断再次逃逸。模块敏感应用按上文白名单/黑名单两种思路配置“Umount modules by default” 应用级开关内核低于 5.10 的设备需先 backportpath_umount否则该选项不产生实际效果。权限边界认知App Profile 约束的是su之后的 root 进程与模块系统对该应用的可见性不改变应用自身的 Android 权限相关实现集中在 kernel/policy/app_profile.c、kernel/policy/allowlist.c、kernel/feature/kernel_umount.cuAPI 定义见 uapi/app_profile.h可作为深入阅读的入口。【免费下载链接】KernelSUA Kernel based root solution for Android项目地址: https://gitcode.com/GitHub_Trending/ke/KernelSU创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。