You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Hook Open系统调用后调用user_path_at触发内核崩溃,求正确路径获取方法

内核崩溃原因与解决方案

一、内核崩溃的核心原因

你的代码触发崩溃主要有两个关键问题:

  1. 原子上下文调用可睡眠函数:
    kprobe的处理函数运行在原子上下文(比如sys_open执行时可能处于中断上下文,或持有自旋锁的场景),而user_path_at、strnlen_user、strncpy_from_user、kzalloc(GFP_KERNEL)这些函数都可能触发睡眠(比如用户空间内存不在物理页时需要调页,GFP_KERNEL允许内存分配时睡眠)。原子上下文不允许睡眠,直接调用会导致内核死锁或崩溃。
  2. 用户空间地址访问不安全:
    直接将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 21:47:04