嵌入KernelSU和SuSFS修补 - JackA1ltman/NonGKI_Kernel_Build_2nd GitHub Wiki

嵌入 KernelSU & SuSFS 修补

在将内核推送到 Action 自动化编译之前,我们往往需要先在本地验证 KernelSU 与 SuSFS 的功能是否正常。

SuSFS 是由 simonpunk(GitLab)开发的内核模块,提供挂载信息隐藏、AVC 审计信息隐藏、内核版本信息伪装等多项隐藏功能。由于其以内核补丁的形式存在,因此需要手动执行补丁脚本进行修补。

本教程以 ReSukiSU小米 Mix2s 内核(evox_mix2s_a15) 为例进行演示。


一、嵌入 KernelSU

步骤 1:检查并清理已有的 KernelSU

在嵌入前,先确认内核源码中是否已存在 KernelSU:

  • 若源码根目录下存在名为 KernelSU 的文件夹
  • fs/exec.c 中存在 CONFIG_KSU 相关代码

则说明已嵌入,需要先执行清理:

cp NonGKI_Kernel_Build_2nd/bin/clean_hook.sh Kernel/evox_mix2s_a15/
cd Kernel/evox_mix2s_a15/
bash clean_hook.sh

步骤 2:嵌入 KernelSU

访问 ReSukiSU 仓库,进入 kernel/ 文件夹,找到 setup.sh,点击右上角 Raw 按钮并复制链接。

cd Kernel/evox_mix2s_a15/
curl -LSs "<setup.sh 的 Raw 链接>" | bash -s <分支名称>

也可以手动下载 setup.sh 并复制到内核源码目录后执行:

bash setup.sh <分支名称>

步骤 3:修改 defconfig

打开 defconfig 文件(本例为 arch/arm64/configs/vendor/mi845_defconfig),在末尾添加:

CONFIG_KSU=y
CONFIG_KSU_MANUAL_HOOK=y

ReSukiSU 支持自动修补,可自行研究,此处不再赘述。

步骤 4:执行手动 Hook 修补

在开始前,需要了解两个修补脚本的区别:

脚本 适用场景
syscall_hook_patches.sh 仅嵌入 KernelSU,不嵌入 SuSFS 时使用
susfs_inline_hook_patches.sh 同时嵌入 KernelSU 与 SuSFS 时使用

本节仅嵌入 KernelSU,使用 syscall_hook_patches.sh。若需要 SuSFS,请改用 susfs_inline_hook_patches.sh(详见下一章节)。

cp NonGKI_Kernel_Build_2nd/Patches/syscall_hook_patches.sh Kernel/evox_mix2s_a15/
cp NonGKI_Kernel_Build_2nd/Patches/backport_patches.sh Kernel/evox_mix2s_a15/
cd Kernel/evox_mix2s_a15/
bash syscall_hook_patches.sh
bash backport_patches.sh

两个脚本执行不分先后:syscall_hook_patches.sh 负责嵌入 Hook,backport_patches.sh 负责向后移植部分函数。

注意:脚本内含检测函数,请务必确保先完成 KernelSU 嵌入(步骤 2)再执行脚本

步骤 5:编译

按照 编译内核 章节中相同的步骤执行编译即可。


二、执行 SuSFS 修补

由于 Non-GKI 内核的高度碎片化,无法保证所有设备在嵌入 SuSFS 后都能按预期工作。此处提供的兼容补丁基于 simonpunk 的原始代码制作,无法覆盖所有情况。

步骤 1:应用 SuSFS 补丁

首先确认自己的内核版本。若不确定,可查看内核源码根目录下的 Makefile 前两行。

VERSION = 4
PATCHLEVEL = 9

组合规则为:VERSION.PATCHLEVEL,这里就是4.9

确认后,我们执行如下命令

cp NonGKI_Kernel_Build_2nd/Patches/Patch/susfs_patch_to_<版本号>.patch Kernel/evox_mix2s_a15/
cd Kernel/evox_mix2s_a15/
patch -p1 < susfs_patch_to_4.9.patch

若补丁完全应用成功,不会产生任何 .rej 文件,可直接跳至执行编译

若出现类似如下错误,则需要进行手动修补:

X out of X hunk FAILED -- saving rejects to file XXX/XXX.c.rej

步骤 2:手动修补 .rej 文件

在开始前,先备份内核源码目录:

cp -r Kernel/evox_mix2s_a15 Kernel/evox_mix2s_a15_bak
cd Kernel/evox_mix2s_a15

以错误 1 out of 1 hunk FAILED -- saving rejects to file kernel/sys.c.rej 为例,我们需要同时处理 kernel/sys.ckernel/sys.c.rej 两个文件。

推荐使用以下编辑器同时打开两个文件:

  • KDE 用户:Kate
  • 通用:Zed、VSCode、Fresh Editor 等
kate kernel/sys.c.rej kernel/sys.c

三、手动修补详解

读懂 .rej 文件

.rej 文件是标准的 Patch 格式,记录了自动修补失败的代码段,通常是因为目标文件的上下文与补丁预期的不一致。

kernel/sys.c.rej 为例:

--- kernel/sys.c
+++ kernel/sys.c
@@ -1240,12 +1240,18 @@ static int override_release(char __user *release, size_t len)
 	return ret;
 }
 
+#ifdef CONFIG_KSU_SUSFS_SPOOF_UNAME
+extern void susfs_spoof_uname(struct new_utsname* tmp);
+#endif
 SYSCALL_DEFINE1(newuname, struct new_utsname __user *, name)
 {
 	struct new_utsname tmp;
 
 	down_read(&uts_sem);
 	memcpy(&tmp, utsname(), sizeof(tmp));
+#ifdef CONFIG_KSU_SUSFS_SPOOF_UNAME
+	susfs_spoof_uname(&tmp);
+#endif
 	up_read(&uts_sem);

+ 开头的行是需要我们手动插入的新代码,其余行是用于定位插入位置的上下文。

定位插入位置的原则

  • 优先搜索功能函数名,如 SYSCALL_DEFINE1(newuname, ...) 通常全文唯一
  • 若存在多个同名函数,参数列表也是区分依据,可完整复制搜索
  • 若仍难以定位,可搜索较为独特的代码片段,如 if (copy_to_user(name, &tmp, sizeof(tmp)))
  • 灵活变通,不必强求上下文完全一致

实际修补示例

打开 kernel/sys.c,找到对应函数(可能长这样):

SYSCALL_DEFINE1(newuname, struct new_utsname __user *, name)
{
	struct new_utsname tmp;

	down_read(&uts_sem);
	memcpy(&tmp, utsname(), sizeof(tmp));
	if (current_uid().val == 0 &&
		(!strncmp(current->comm, "bpfloader", 9) ||
	    !strncmp(current->comm, "netbpfload", 10) ||
	    !strncmp(current->comm, "netd", 4) ||
	    !strncmp(current->comm, "uprobestats", 11))) {
		strcpy(tmp.release, "5.10.240");
		pr_debug("fake uname: %s release=%s\n",
			 current->comm, tmp.release);
	}
	up_read(&uts_sem);
	...
}

本例来自 4.19 内核,多出了一段 BPF 伪装代码,干扰了自动修补流程。

第一处:在函数定义前添加外部声明(直接插入,位置明确):

#ifdef CONFIG_KSU_SUSFS_SPOOF_UNAME
extern void susfs_spoof_uname(struct new_utsname* tmp);
#endif
SYSCALL_DEFINE1(newuname, struct new_utsname __user *, name)
{
	...
}

第二处:在 memcpy 之后插入调用(忽略后续多出的 BPF 代码,找到最近的锚点即可):

修补后结果:

SYSCALL_DEFINE1(newuname, struct new_utsname __user *, name)
{
	struct new_utsname tmp;

	down_read(&uts_sem);
	memcpy(&tmp, utsname(), sizeof(tmp));
#ifdef CONFIG_KSU_SUSFS_SPOOF_UNAME
	susfs_spoof_uname(&tmp);
#endif
	if (current_uid().val == 0 &&
		(!strncmp(current->comm, "bpfloader", 9) ||
	    !strncmp(current->comm, "netbpfload", 10) ||
	    !strncmp(current->comm, "netd", 4) ||
	    !strncmp(current->comm, "uprobestats", 11))) {
		strcpy(tmp.release, "5.10.240");
		pr_debug("fake uname: %s release=%s\n",
			 current->comm, tmp.release);
	}
	up_read(&uts_sem);
	...
}

对其余 .rej 文件遵循相同原则逐一处理即可。


四、制作二次修补补丁

若需要为自动化流程制作可复用的二次修补补丁,执行以下命令生成 diff:

git diff -u Kernel/evox_mix2s_a15_bak Kernel/evox_mix2s_a15 > susfs_fixed.patch

生成后打开 susfs_fixed.patch,可能会看到类似如下内容:

diff --git a/evox_mix2s_a15_bak/.git/index b/evox_mix2s_a15/.git/index
index 48cdcb2..bf7b3e7 100644
Binary files a/evox_mix2s_a15_bak/.git/index and b/evox_mix2s_a15/.git/index differ
diff --git a/evox_mix2s_a15_bak/kernel/sys.c b/evox_mix2s_a15/kernel/sys.c
index cdb8b16..3b26985 100644
--- a/evox_mix2s_a15_bak/kernel/sys.c
+++ b/evox_mix2s_a15/kernel/sys.c
@@ -1240,12 +1240,18 @@ ...

需要做两处处理:

第一步:删除 .git/index 相关的前三行(二进制文件差异,对修补无用):

diff --git a/evox_mix2s_a15_bak/kernel/sys.c b/evox_mix2s_a15/kernel/sys.c
index cdb8b16..3b26985 100644
--- a/evox_mix2s_a15_bak/kernel/sys.c
+++ b/evox_mix2s_a15/kernel/sys.c
@@ -1240,12 +1240,18 @@ ...

第二步:将所有路径中的 /evox_mix2s_a15_bak/evox_mix2s_a15 目录前缀全部删除,使路径还原为相对于内核源码根目录的形式:

diff --git a/kernel/sys.c b/kernel/sys.c
index cdb8b16..3b26985 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -1240,12 +1240,18 @@ ...

至此,susfs_fixed.patch 制作完毕,可用于 Action 自动化流程中的二次修补。

最后清理备份目录:

rm -rf Kernel/evox_mix2s_a15_bak

五、执行编译

在编译前,向 defconfig 文件末尾注入以下 SuSFS 相关配置项:

CONFIG_KSU_SUSFS=y
CONFIG_KSU_SUSFS_SUS_PATH=y
CONFIG_KSU_SUSFS_SUS_MOUNT=y
CONFIG_KSU_SUSFS_SUS_KSTAT=y
CONFIG_KSU_SUSFS_SPOOF_UNAME=y
CONFIG_KSU_SUSFS_ENABLE_LOG=y
CONFIG_KSU_SUSFS_HIDE_KSU_SUSFS_SYMBOLS=y
CONFIG_KSU_SUSFS_SPOOF_CMDLINE_OR_BOOTCONFIG=y
CONFIG_KSU_SUSFS_OPEN_REDIRECT=y
CONFIG_KSU_SUSFS_SUS_MAP=y

随后按照 编译内核 章节中的步骤执行编译即可。


六、争议 SuSFS 修补代码

部分代码需要灵活处理,不能死板地照搬 Patch 文件的写法。本节记录已知存在版本差异的修补点。

security/selinux/avc.c

定位到 rc = security_sid_to_context(...) 调用处,根据参数形式判断所属内核版本并选择对应修补方案。


形式一:(tsid, &scontext, &scontext_len)

常见于:4.9 内核

缺少 state 参数,因此无需额外声明 sad 变量,直接在对应位置插入以下代码:

	rc = security_sid_to_context(tsid, &scontext, &scontext_len);

#ifdef CONFIG_KSU_SUSFS
	if (static_branch_likely(&susfs_avc_log_spoofing_key_true)) {
		if (unlikely(tsid == susfs_ksu_sid)) {
			if (rc)
				audit_log_format(ab, " tsid=%d", susfs_priv_app_sid);
			else
				audit_log_format(ab, " tcontext=%s", "u:r:priv_app:s0:c512,c768");
			goto bypass_orig_flow;
		}
	}
#endif

形式二:(state, tsid, &scontext, &scontext_len)

常见于:4.14 — 4.19 内核

引入了不完整的 state 参数,需要额外声明 sad 变量。

第一步:在 u32 scontext_len; 声明的下方添加:

	u32 scontext_len;
#ifdef CONFIG_KSU_SUSFS
	struct selinux_audit_data sad;
#endif

第二步:在对应位置插入:

	rc = security_sid_to_context(state, tsid, &scontext, &scontext_len);

#ifdef CONFIG_KSU_SUSFS
	if (static_branch_likely(&susfs_avc_log_spoofing_key_true)) {
		if (unlikely(sad.tsid == susfs_ksu_sid)) {
			if (rc)
				audit_log_format(ab, " tsid=%d", susfs_priv_app_sid);
			else
				audit_log_format(ab, " tcontext=%s", "u:r:priv_app:s0:c512,c768");
			goto bypass_orig_flow;
		}
	}
#endif

形式三:(sad->state, sad->tsid, &scontext, &scontext_len)

常见于:5.4 及更新内核

为完整的 state 定义,直接合并上游修改即可:

	rc = security_sid_to_context(sad->state, sad->tsid, &scontext,
				     &scontext_len);

#ifdef CONFIG_KSU_SUSFS
	if (static_branch_likely(&susfs_avc_log_spoofing_key_true)) {
		if (unlikely(sad->tsid == susfs_ksu_sid)) {
			if (rc)
				audit_log_format(ab, " tsid=%d", susfs_priv_app_sid);
			else
				audit_log_format(ab, " tcontext=%s", "u:r:priv_app:s0:c512,c768");
			goto bypass_orig_flow;
		}
	}
#endif

fs/namei.c

根据上游代码,此处修补涉及 vfs_readlink 函数、res = readlink_copy(buffer, buflen, link); 语句以及 if (unlikely(!(inode->i_opflags & IOP_DEFAULT_READLINK))) {...} 判断。

但实际情况中,古老内核的函数命名与上游存在差异,甚至存在函数缺失的情况,需根据当前内核的实际定义选择修补方案。


形式一:仅存在 int generic_readlink(...)

常见于:4.9 内核

generic_readlink 实为 vfs_readlink 的部分代码形式。由于只存在 res = readlink_copy(buffer, buflen, link);,在其前方插入以下代码:

#ifdef CONFIG_KSU_SUSFS_OPEN_REDIRECT
	if (SUSFS_IS_INODE_OPEN_REDIRECT(inode)) {
		res = susfs_open_redirect_spoof_vfs_readlink(inode, buffer, buflen);
		if (!res) {
			do_delayed_call(&done);
			return res;
		}
	}
#endif
	res = readlink_copy(buffer, buflen, link);

形式二:int generic_readlink(...)int vfs_readlink(...) 共存

常见于:4.14 内核

此形式的 vfs_readlink 内部存在 return generic_readlink(dentry, buffer, buflen);,仍依赖 generic_readlink。在完成形式一的修补后,还需对 vfs_readlink 进行补充修补,替换为以下完整实现:

int vfs_readlink(struct dentry *dentry, char __user *buffer, int buflen)
{
	struct inode *inode = d_inode(dentry);
#ifdef CONFIG_KSU_SUSFS_OPEN_REDIRECT
	int res;
#endif
 
	if (unlikely(!(inode->i_opflags & IOP_DEFAULT_READLINK))) {
		if (unlikely(inode->i_op->readlink))
#ifdef CONFIG_KSU_SUSFS_OPEN_REDIRECT
		{
			if (SUSFS_IS_INODE_OPEN_REDIRECT(inode)) {
				res = susfs_open_redirect_spoof_vfs_readlink(inode, buffer, buflen);
				if (!res)
					return res;
			}
			return inode->i_op->readlink(dentry, buffer, buflen);
		}
#else
			return inode->i_op->readlink(dentry, buffer, buflen);
#endif
 
		if (!d_is_symlink(dentry))
			return -EINVAL;
 
		spin_lock(&inode->i_lock);
		inode->i_opflags |= IOP_DEFAULT_READLINK;
		spin_unlock(&inode->i_lock);
	}
 
	return generic_readlink(dentry, buffer, buflen);
}
EXPORT_SYMBOL(vfs_readlink);

形式三:仅存在 int vfs_readlink(...)

常见于:4.19 及更新内核

具有完整的 vfs_readlink 定义,直接采用上游 SuSFS 方案。在函数定义前添加外部声明,并替换函数体为以下完整实现:

#ifdef CONFIG_KSU_SUSFS_OPEN_REDIRECT
extern int susfs_open_redirect_spoof_vfs_readlink(struct inode *inode, char __user *buffer, int buflen);
#endif
 
int vfs_readlink(struct dentry *dentry, char __user *buffer, int buflen)
{
	struct inode *inode = d_inode(dentry);
	DEFINE_DELAYED_CALL(done);
	const char *link;
	int res;
 
	if (unlikely(!(inode->i_opflags & IOP_DEFAULT_READLINK))) {
		if (unlikely(inode->i_op->readlink))
#ifdef CONFIG_KSU_SUSFS_OPEN_REDIRECT
		{
			if (SUSFS_IS_INODE_OPEN_REDIRECT(inode)) {
				res = susfs_open_redirect_spoof_vfs_readlink(inode, buffer, buflen);
				if (!res)
					return res;
			}
			return inode->i_op->readlink(dentry, buffer, buflen);
		}
#else
			return inode->i_op->readlink(dentry, buffer, buflen);
#endif
 
		if (!d_is_symlink(dentry))
			return -EINVAL;
 
		spin_lock(&inode->i_lock);
		inode->i_opflags |= IOP_DEFAULT_READLINK;
		spin_unlock(&inode->i_lock);
	}
 
	link = READ_ONCE(inode->i_link);
	if (!link) {
		link = inode->i_op->get_link(dentry, inode, &done);
		if (IS_ERR(link))
			return PTR_ERR(link);
	}
#ifdef CONFIG_KSU_SUSFS_OPEN_REDIRECT
	if (SUSFS_IS_INODE_OPEN_REDIRECT(inode)) {
		res = susfs_open_redirect_spoof_vfs_readlink(inode, buffer, buflen);
		if (!res) {
			do_delayed_call(&done);
			return res;
		}
	}
#endif
	res = readlink_copy(buffer, buflen, link);
	do_delayed_call(&done);
	return res;
}
EXPORT_SYMBOL(vfs_readlink);

fs/namespace.c

此文件存在两组函数名差异,且两组差异通常同时出现:

旧版函数(4.9 — 4.14) 新版函数(4.19+)
ida_remove ida_free
ida_pre_get + ida_get_new_above ida_alloc_min

注意:若内核不支持 ida_alloc_min,须将 SuSFS 相关代码中所有 ida_alloc_min 调用替换为 ida_simple_get。 例如将 res = ida_alloc_min(&susfs_mnt_id_ida, DEFAULT_KSU_MNT_ID, GFP_KERNEL); 替换为 res = ida_simple_get(&susfs_mnt_id_ida, DEFAULT_KSU_MNT_ID, 0, GFP_KERNEL);


static void mnt_free_id(struct mount *mnt)

若使用 ida_remove(4.9 — 4.14 内核):

static void mnt_free_id(struct mount *mnt)
{
	int id = mnt->mnt_id;
 
#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
	if (id >= DEFAULT_KSU_MNT_ID) {
		spin_lock(&mnt_id_lock);
		ida_remove(&susfs_mnt_id_ida, id);
		if (mnt_id_start > id)
			mnt_id_start = id;
		spin_unlock(&mnt_id_lock);
		return;
	}
 
	if (mnt->mnt.mnt_flags & VFSMOUNT_MNT_FLAGS_KSU_UNSHARED_MNT) {
		return;
	}
 
#endif // #ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
 
	spin_lock(&mnt_id_lock);
	ida_remove(&mnt_id_ida, id);
	if (mnt_id_start > id)
		mnt_id_start = id;
	spin_unlock(&mnt_id_lock);
}

若使用 ida_free(4.19+ 内核):

static void mnt_free_id(struct mount *mnt)
{
#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
	if (mnt->mnt_id >= DEFAULT_KSU_MNT_ID) {
		ida_free(&susfs_mnt_id_ida, mnt->mnt_id);
		return;
	}
 
	if (mnt->mnt.mnt_flags & VFSMOUNT_MNT_FLAGS_KSU_UNSHARED_MNT) {
		return;
	}
 
#endif // #ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
 
	ida_free(&mnt_id_ida, mnt->mnt_id);
}

static int mnt_alloc_group_id(struct mount *mnt)

若使用 ida_pre_get(4.9 — 4.14 内核):

static int mnt_alloc_group_id(struct mount *mnt)
{
	int res;
 
#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
	if (susfs_is_current_ksu_domain()) {
		if (!ida_pre_get(&susfs_mnt_group_ida, GFP_KERNEL))
			return -ENOMEM;
		res = ida_get_new_above(&susfs_mnt_group_ida,
					DEFAULT_KSU_MNT_GROUP_ID,
					&mnt->mnt_group_id);
		goto bypass_orig_flow;
	}
 
	if (!ida_pre_get(&mnt_group_ida, GFP_KERNEL))
		return -ENOMEM;
	res = ida_get_new_above(&mnt_group_ida,
				mnt_group_start,
				&mnt->mnt_group_id);
bypass_orig_flow:
#else
	if (!ida_pre_get(&mnt_group_ida, GFP_KERNEL))
		return -ENOMEM;
 
	res = ida_get_new_above(&mnt_group_ida,
				mnt_group_start,
				&mnt->mnt_group_id);
#endif
	if (!res)
		mnt_group_start = mnt->mnt_group_id + 1;
 
	return res;
}

若使用 ida_alloc_min(4.19+ 内核):

static int mnt_alloc_group_id(struct mount *mnt)
{
#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
	int res;
 
	/* - mnt_alloc_group_id will unlikely get called after screen is unlocked on reboot,
	 *   so here we can persistently check if current is ksu domain, and assign a sus
	 *   mnt_group_id if so.
	 * - Also we can re-use the original mnt_group_ida so there is no need to use
	 *   another ida nor hook the mnt_release_group_id() function.
	 */
	if (susfs_is_current_ksu_domain()) {
		res = ida_alloc_min(&susfs_mnt_group_ida, DEFAULT_KSU_MNT_GROUP_ID, GFP_KERNEL);
		goto bypass_orig_flow;
	}
	res = ida_alloc_min(&mnt_group_ida, 1, GFP_KERNEL);
bypass_orig_flow:
#else
	int res = ida_alloc_min(&mnt_group_ida, 1, GFP_KERNEL);
#endif
 
	if (res < 0)
		return res;
	mnt->mnt_group_id = res;
	return 0;
}

void mnt_release_group_id(struct mount *mnt)

若使用 ida_remove(4.9 — 4.14 内核):

void mnt_release_group_id(struct mount *mnt)
{
	int id = mnt->mnt_group_id;
 
#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
	if (id >= DEFAULT_KSU_MNT_GROUP_ID) {
		ida_remove(&susfs_mnt_group_ida, id);
		if (mnt_group_start > id)
			mnt_group_start = id;
		mnt->mnt_group_id = 0;
		return;
	}
#endif // #ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
 
	ida_remove(&mnt_group_ida, id);
	if (mnt_group_start > id)
		mnt_group_start = id;
	mnt->mnt_group_id = 0;
}

若使用 ida_free(4.19+ 内核):

void mnt_release_group_id(struct mount *mnt)
{
#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
	if (mnt->mnt_group_id >= DEFAULT_KSU_MNT_GROUP_ID) {
		ida_free(&susfs_mnt_group_ida, mnt->mnt_group_id);
		mnt->mnt_group_id = 0;
		return;
	}
#endif // #ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
 
	ida_free(&mnt_group_ida, mnt->mnt_group_id);
	mnt->mnt_group_id = 0;
}

alloc_vfsmnt(...)

alloc_vfsmnt(...); 中的函数为name

struct vfsmount *
vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
{
	struct mount *mnt;
	struct dentry *root;

	if (!type)
		return ERR_PTR(-ENODEV);

#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
	// - We will just stop checking for ksu process if /sdcard/Android is accessible,
	//   for the sake of performance
	if (static_branch_unlikely(&susfs_set_sdcard_android_data_decrypted_key_false)) {
		if (susfs_is_current_ksu_domain()) {
			mnt = susfs_alloc_non_unshare_ksu_vfsmnt(name ?:"none");
			goto bypass_orig_flow;
		}
	}
#endif

	mnt = alloc_vfsmnt(name);

#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
bypass_orig_flow:
#endif
	if (!mnt)
		return ERR_PTR(-ENOMEM);

alloc_vfsmnt(...); 中的函数为fc->source ?: "none"

struct vfsmount *vfs_create_mount(struct fs_context *fc)
{
	struct mount *mnt;
	struct super_block *sb;

	if (!fc->root)
		return ERR_PTR(-EINVAL);
	sb = fc->root->d_sb;

#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
	// - We will just stop checking for ksu process if /sdcard/Android is accessible,
	//   for the sake of performance
	if (static_branch_unlikely(&susfs_set_sdcard_android_data_decrypted_key_false)) {
		if (susfs_is_current_ksu_domain()) {
			mnt = susfs_alloc_non_unshare_ksu_vfsmnt(fc->source ?: "none");
			goto bypass_orig_flow;
		}
	}
#endif // #ifdef CONFIG_KSU_SUSFS_SUS_MOUNT

	mnt = alloc_vfsmnt(fc->source ?: "none");
#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
bypass_orig_flow:
#endif // #ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
	if (!mnt)
		return ERR_PTR(-ENOMEM);

fs/proc/task_mmu.c

  • 若文件中存在 seq_put_hex_ll,按 4.19 — 5.4 补丁方案修补即可
  • 若不存在,则参考 4.9 — 4.14 补丁方案修补

fs/notify/fdinfo.c

部分较古老的内核缺少 inotify_mark_user_mask 函数。

修补的核心争议在于 seq_printf 的输出格式:需确保 SuSFS 注入块与原始流程中的两处 seq_printf 所调用的函数完全一致,但 path.dentry 部分应当保留(这是 SuSFS 用于路径伪装的关键)。

若你的内核存在 inotify_mark_user_mask,直接应用如下补丁:

#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
static void inotify_fdinfo(struct seq_file *m, struct fsnotify_mark *mark, struct file *file)
#else
static void inotify_fdinfo(struct seq_file *m, struct fsnotify_mark *mark)
#endif
{
	struct inotify_inode_mark *inode_mark;
	struct inode *inode;
#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
	struct mount *mnt = NULL;
#endif

	if (!(mark->flags & FSNOTIFY_MARK_FLAG_ALIVE) ||
	    !(mark->flags & FSNOTIFY_MARK_FLAG_INODE))
		return;

	inode_mark = container_of(mark, struct inotify_inode_mark, fsn_mark);
	inode = igrab(mark->inode);
	if (inode) {
#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
		mnt = real_mount(file->f_path.mnt);
		if (mnt->mnt_id >= DEFAULT_KSU_MNT_ID &&
			likely(susfs_is_current_proc_umounted()))
		{
			struct path path;
			char *pathname = kmalloc(PAGE_SIZE, GFP_KERNEL);
			char *dpath;
			if (!pathname) {
				goto orig_flow;
			}
			dpath = d_path(&file->f_path, pathname, PAGE_SIZE);
			if (!dpath) {
				goto out_kfree;
			}
			if (kern_path(dpath, 0, &path)) {
				goto out_kfree;
			}
			if (!path.dentry->d_inode) {
				goto out_path_put;
			}
			seq_printf(m, "inotify wd:%x ino:%lx sdev:%x mask:%x ignored_mask:0 ",
					inode_mark->wd, path.dentry->d_inode->i_ino, path.dentry->d_inode->i_sb->s_dev,
					inotify_mark_user_mask(mark));
			show_mark_fhandle(m, path.dentry->d_inode);
			seq_putc(m, '\n');
			path_put(&path);
			kfree(pathname);
			iput(inode);
			return;
out_path_put:
			path_put(&path);
out_kfree:
			kfree(pathname);
		}
orig_flow:
#endif // #ifdef CONFIG_KSU_SUSFS_SUS_MOUNT

		seq_printf(m, "inotify wd:%x ino:%lx sdev:%x mask:%x ignored_mask:0 ",
			   inode_mark->wd, inode->i_ino, inode->i_sb->s_dev,
			   inotify_mark_user_mask(mark));
		show_mark_fhandle(m, inode);
		seq_putc(m, '\n');
		iput(inode);
	}
}

若内核缺少 inotify_mark_user_mask,则需将补丁中所有调用该函数的地方替换为内核中实际存在的等效表达,同时保持两处 seq_printf 的格式字符串与参数一致,path.dentry 相关代码保持不变。

#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
static void inotify_fdinfo(struct seq_file *m, struct fsnotify_mark *mark, struct file *file)
#else
static void inotify_fdinfo(struct seq_file *m, struct fsnotify_mark *mark)
#endif
{
	struct inotify_inode_mark *inode_mark;
	struct inode *inode;
#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
	struct mount *mnt = NULL;
#endif

	if (!(mark->flags & FSNOTIFY_MARK_FLAG_ALIVE) ||
	    !(mark->flags & FSNOTIFY_MARK_FLAG_INODE))
		return;

	inode_mark = container_of(mark, struct inotify_inode_mark, fsn_mark);
	inode = igrab(mark->inode);
	if (inode) {
		/*
		 * IN_ALL_EVENTS represents all of the mask bits
		 * that we expose to userspace.  There is at
		 * least one bit (FS_EVENT_ON_CHILD) which is
		 * used only internally to the kernel.
		 */
		u32 mask = mark->mask & IN_ALL_EVENTS;
#ifdef CONFIG_KSU_SUSFS_SUS_MOUNT
		mnt = real_mount(file->f_path.mnt);
		if (mnt->mnt_id >= DEFAULT_KSU_MNT_ID &&
			likely(susfs_is_current_proc_umounted()))
		{
			struct path path;
			char *pathname = kmalloc(PAGE_SIZE, GFP_KERNEL);
			char *dpath;
			if (!pathname) {
				goto orig_flow;
			}
			dpath = d_path(&file->f_path, pathname, PAGE_SIZE);
			if (!dpath) {
				goto out_kfree;
			}
			if (kern_path(dpath, 0, &path)) {
				goto out_kfree;
			}
			if (!path.dentry->d_inode) {
				goto out_path_put;
			}
			seq_printf(m, "inotify wd:%x ino:%lx sdev:%x mask:%x ignored_mask:%x ",
					inode_mark->wd, path.dentry->d_inode->i_ino, path.dentry->d_inode->i_sb->s_dev,
					mask, mark->ignored_mask);
			show_mark_fhandle(m, path.dentry->d_inode);
			seq_putc(m, '\n');
			path_put(&path);
			kfree(pathname);
			iput(inode);
			return;
out_path_put:
			path_put(&path);
out_kfree:
			kfree(pathname);
		}
orig_flow:
#endif // #ifdef CONFIG_KSU_SUSFS_SUS_MOUNT

		seq_printf(m, "inotify wd:%x ino:%lx sdev:%x mask:%x ignored_mask:%x ",
			   inode_mark->wd, inode->i_ino, inode->i_sb->s_dev,
			   mask, mark->ignored_mask);
		show_mark_fhandle(m, inode);
		seq_putc(m, '\n');
		iput(inode);
	}
}
⚠️ **GitHub.com Fallback** ⚠️