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

Linux内核空间分配用户空间内存及Hook open系统调用问询

问题1:如何在内核空间中分配用户空间内存?

首先得明确:内核本身不能直接"分配"用户空间内存——用户空间内存属于进程的虚拟地址空间,内核必须依托目标进程的上下文来操作。常见的实现方式有这几种:

  • 内核分配内存 + 拷贝到用户空间:
    先在内核用kmalloc()或vmalloc()分配一块内核内存,处理完数据后,用copy_to_user()把数据拷贝到用户空间预先准备好的缓冲区里。反过来,如果要从用户空间读数据到内核,用copy_from_user()。这是最常用的方式,适合大多数场景。
  • 映射用户空间页面到内核:
    用get_user_pages()函数可以将用户空间的指定页面映射到内核地址空间,这样内核就能直接读写这些页面。不过要注意检查页面的有效性和权限,操作完成后必须调用put_page()释放映射。
  • 直接分配用户空间页面:
    使用alloc_user_pages()可以分配物理页面,并自动映射到当前进程的用户地址空间,返回对应的内核虚拟地址。用完后要记得释放这些页面。
  • 无进程上下文的特殊情况:
    如果是在中断、软中断这类没有进程上下文的环境下,需要先通过switch_mm()切换到目标进程的地址空间,再进行上述操作——但这种操作风险很高,要格外小心死锁和上下文泄漏问题。

问题2:Hook open系统调用后打印文件名的正确姿势

你提到调用getcwd来获取绝对路径,这里首先要踩个坑:内核里没有getcwd这个系统调用的实现,getcwd是用户空间的C库函数,直接在内核调用会报错。另外,你的memndup_from_user函数还有几个需要修正的地方,结合正确的实现思路给你梳理下:

1. 先修正你的memndup_from_user函数

原函数漏了返回值,而且可以加一些边界检查,修正后如下:

void *memndup_from_user(const void __user *src, long len) { 
    void *kbuf = NULL; 
    // 边界检查:防止len过大导致内核内存耗尽
    if (src == NULL || len <= 0 || len > PATH_MAX) { 
        return NULL; 
    } 
    kbuf = kmalloc(len + 1, GFP_KERNEL); 
    if(kbuf != NULL) { 
        if (copy_from_user(kbuf, src, len)) { 
            printk(KERN_ALERT "copy_from_user failed.\n"); 
            kfree(kbuf); 
            kbuf = NULL; 
        } else { 
            ((char *)kbuf)[len] = '\0'; 
        } 
    } else { 
        printk(KERN_ALERT "kmalloc failed to allocate memory.\n"); 
    } 
    // 补上返回值
    return kbuf; 
}

其实内核已经提供了现成的strndup_from_user()函数,功能和你写的这个几乎一致,你可以直接用它来简化代码。

2. 内核中获取当前进程的工作目录

要拼接绝对路径,内核里可以通过get_fs_pwd()获取当前进程的工作目录,再用d_path()转换成字符串:

#include <linux/fs.h>
#include <linux/sched.h>

void print_open_filename(const char __user *user_filename) {
    char *kernel_filename = NULL;
    struct path pwd_path;
    char *pwd_str = NULL;
    char *full_path = NULL;

    // 把用户空间的文件名拷贝到内核
    kernel_filename = strndup_from_user(user_filename, PATH_MAX);
    if (!kernel_filename) {
        printk(KERN_ALERT "Failed to copy filename from user space.\n");
        goto cleanup;
    }

    // 获取当前进程的工作目录
    if (get_fs_pwd(current->fs, &pwd_path)) {
        printk(KERN_ALERT "Failed to get current working directory.\n");
        goto cleanup;
    }

    // 把路径转换成字符串
    pwd_str = d_path(&pwd_path, kmalloc(PATH_MAX, GFP_KERNEL), PATH_MAX);
    if (!pwd_str) {
        printk(KERN_ALERT "Failed to convert path to string.\n");
        path_put(&pwd_path);
        goto cleanup;
    }

    // 拼接工作目录和文件名(处理相对路径)
    full_path = kmalloc(PATH_MAX, GFP_KERNEL);
    if (full_path) {
        if (kernel_filename[0] == '/') {
            // 如果是绝对路径,直接用
            snprintf(full_path, PATH_MAX, "%s", kernel_filename);
        } else {
            // 相对路径则拼接工作目录
            snprintf(full_path, PATH_MAX, "%s/%s", pwd_str, kernel_filename);
        }
        printk(KERN_INFO "Opened file: %s\n", full_path);
    }

cleanup:
    // 释放所有分配的资源
    if (kernel_filename) kfree(kernel_filename);
    if (pwd_str) kfree(pwd_str);
    if (full_path) kfree(full_path);
    path_put(&pwd_path);
}

3. Hook open时的注意事项

  • 确保你Hook的是正确的系统调用入口(比如x86和x86_64的系统调用表结构不同);
  • 操作用户空间指针时必须用copy_from_user()/strndup_from_user()这类安全函数,绝对不能直接解引用用户空间指针,否则会触发页错误甚至崩溃;
  • 内核中打印日志尽量用KERN_INFO这类级别,KERN_ALERT是紧急级别,不要滥用;
  • 所有分配的内核内存必须记得释放,防止内存泄漏。

内容的提问来源于stack exchange,提问作者lidawei

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:18:45