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
相关产品推荐
相关产品推荐

