Hook Open系统调用后调用user_path_at触发内核崩溃,求正确路径获取方法
内核崩溃原因与解决方案
一、内核崩溃的核心原因
你的代码触发崩溃主要有两个关键问题:
- 原子上下文调用可睡眠函数:
kprobe的处理函数运行在原子上下文(比如sys_open执行时可能处于中断上下文,或持有自旋锁的场景),而user_path_at、strnlen_user、strncpy_from_user、kzalloc(GFP_KERNEL)这些函数都可能触发睡眠(比如用户空间内存不在物理页时需要调页,GFP_KERNEL允许内存分配时睡眠)。原子上下文不允许睡眠,直接调用会导致内核死锁或崩溃。 - 用户空间地址访问不安全:
直接将path_addr强转为char*传递给user_path_at,没有确保当前进程的内存空间(mm)是发起syscall的进程的内存空间,在kprobe上下文可能存在mm切换风险,导致非法内存访问。
二、正确的路径获取方法
要获取路径遍历后的真实路径,正确的函数仍然是user_path_at(结合d_path生成字符串),但必须在进程上下文(允许睡眠的环境)中调用。可以通过task_work_add将路径解析工作委托到当前进程的上下文执行,以下是可行代码示例:
#include <linux/string.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/kprobes.h> #include <linux/types.h> #include <linux/namei.h> #include <linux/workqueue.h> #include <linux/sched.h> #include <linux/slab.h> // 存储需要传递给进程上下文的参数 struct open_work_data { struct callback_head work; const char __user *user_path; }; // 在进程上下文执行的路径处理函数 static void process_open_path(struct callback_head *work) { struct open_work_data *data = container_of(work, struct open_work_data, work); struct path tmp_path; char *real_path; int ret; // 安全调用user_path_at(进程上下文允许睡眠) ret = user_path_at(AT_FDCWD, data->user_path, LOOKUP_FOLLOW, &tmp_path); if (ret < 0) { goto free_data; } // 分配内存存储真实路径(PAGE_SIZE足够容纳大部分路径) real_path = (char *)__get_free_page(GFP_KERNEL); if (!real_path) { path_put(&tmp_path); goto free_data; } // 生成真实路径字符串 d_path(&tmp_path, real_path, PAGE_SIZE); printk(KERN_INFO "Resolved real path: %s\n", real_path); // 释放资源 free_page((unsigned long)real_path); path_put(&tmp_path); free_data: kfree(data); } // kprobe处理函数(仅做参数收集,不执行可睡眠操作) int open_condition(u64 path_addr, u64 flags, u64 mode, u64 __, u64 ___, u64 ____) { const char __user *userpath = (const char __user *)path_addr; struct open_work_data *data; // 原子上下文用GFP_ATOMIC分配内存 data = kzalloc(sizeof(*data), GFP_ATOMIC); if (!data) { return 1; } data->user_path = userpath; // 将工作添加到当前进程的待处理队列,进程调度时自动执行 task_work_add(current, &data->work, TWA_SIGNAL); return 1; }
三、代码改进建议
- 严格区分原子/进程上下文:kprobe handler属于原子上下文,禁止调用任何可能睡眠的函数(包括内存分配、用户空间访问、VFS操作),所有耗时操作必须委托到进程上下文。
- 用户空间指针规范:用户空间地址必须用
__user修饰,编译器会提供安全检查,避免非法内存访问。 - 错误处理不可省略:用户空间内存访问函数(
strnlen_user、strncpy_from_user)的返回值必须检查,比如strnlen_user返回ULONG_MAX表示访问出错。 - VFS资源释放:调用
user_path_at获取struct path后,必须调用path_put释放引用,否则会导致VFS对象泄漏。 - 更可靠的路径获取方式:可以考虑使用
kretprobe劫持sys_open的返回值,通过返回的文件描述符获取struct file,再从file->f_path获取已解析的路径,这种方式无需重复解析路径,更高效可靠。
内容的提问来源于stack exchange,提问作者BlockMagiX97
相关产品推荐
相关产品推荐

