Linux模块中使用kprobe检测文件打开路径失败的问题排查
问题:Linux内核模块Hook文件打开系统调用失败,无法捕获/home/user下的文件操作
我尝试在Linux中Hook文件打开行为,编写了一个内核模块记录相关系统调用。用kprobe挂钩openat2和open系统调用,通过printk打印参数,但打开/home/user/file.txt时无法捕获该操作。
环境信息
- 内核版本:6.5.0-41-generic
- 系统:Ubuntu 22.04.4
内核模块代码
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/syscalls.h> #include <linux/file.h> #include <linux/fs.h> #include <linux/string.h> #include <linux/mm.h> #include <linux/sched.h> #include <linux/unistd.h> #include <asm/pgtable.h> #include <asm/uaccess.h> #include <asm/ptrace.h> #include <linux/kprobes.h> /* ** module macros */ MODULE_LICENSE("GPL"); MODULE_AUTHOR("sena"); MODULE_DESCRIPTION("hook openat"); static int sys_read_kprobe_pre_handler(struct kprobe *p, struct pt_regs *regs) { char filename[256]; struct pt_regs *user_regs; user_regs = task_pt_regs(current); if (user_regs->si) { if (strncpy_from_user(filename, (char __user *)user_regs->si, sizeof(filename)) > 0) { filename[sizeof(filename) - 1] = '\0'; if(strstr(filename, "home/sena/testdir")){ pr_info("openat syscall: filename: %s\n", filename); } } } return 0; } struct kprobe syscall_kprobe = { .symbol_name = "__x64_sys_openat", .pre_handler = sys_read_kprobe_pre_handler, }; static int __init audit_init(void) { int err; err = register_kprobe(&syscall_kprobe); if (err) { pr_err("register_kprobe() failed: %d\n", err); return err; } else{ pr_info("register_kprobe() init: %d\n", err); } return 0; } static void __exit audit_exit(void) { unregister_kprobe(&syscall_kprobe); pr_info("kprobe unregistered\n"); } module_init(audit_init); module_exit(audit_exit);
当前运行结果
[ 5336.037341] openat syscall: filename: /run/log/journal/3dfe2f456e1c418781ce6b6d5361db7d/system.journal [ 5336.037362] openat syscall: filename: /run/log/journal/3dfe2f456e1c418781ce6b6d5361db7d/system.journal [ 5336.037383] openat syscall: filename: /run/log/journal/3dfe2f456e1c418781ce6b6d5361db7d/system.journal [ 5336.037403] openat syscall: filename: /run/log/journal/3dfe2f456e1c418781ce6b6d5361db7d/system.journal
打开/home/user下的文件后,用grep "home"过滤内核日志无匹配结果。尝试过挂钩__x64_sys_open、__x64_sys_openat、__x64_sys_openat2、do_sys_open作为symbol_name,均无对应日志输出。也试过挂钩sys_call_table,但发现该表已不再用于系统调用,仅作追踪用途。
测试代码
#include <fcntl.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> int main() { int fd; fd = open("./file", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); // 当前目录存在该文件 close(fd); return 0; }
问题分析与修复方案
1. 系统调用参数获取冗余
kprobe的pre_handler已经传入struct pt_regs *regs,直接使用该参数即可,无需额外调用task_pt_regs(current)获取寄存器,后者属于冗余操作且可能引入不必要的上下文问题。
2. 文件名匹配逻辑不匹配测试场景
- 测试代码打开的是相对路径
./file,而你的匹配条件是strstr(filename, "home/sena/testdir"),两者完全不匹配,自然不会触发日志。 - 即使打开绝对路径
/home/user/file.txt,匹配字符串home/sena/testdir也与目标路径home/user不符。
3. 字符串终止处理不严谨
strncpy_from_user返回成功读取的字节数,直接固定设置filename[sizeof(filename)-1] = '\0'可能截断有效字符串,正确做法是用返回值设置终止符。
修复后的内核模块代码
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/syscalls.h> #include <linux/file.h> #include <linux/fs.h> #include <linux/string.h> #include <linux/mm.h> #include <linux/sched.h> #include <linux/unistd.h> #include <asm/pgtable.h> #include <asm/uaccess.h> #include <asm/ptrace.h> #include <linux/kprobes.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("sena"); MODULE_DESCRIPTION("hook openat"); static int sys_openat_kprobe_pre_handler(struct kprobe *p, struct pt_regs *regs) { char filename[256]; // __x64_sys_openat的第2个参数是文件名,对应rsi寄存器 const char __user *user_path = (const char __user *)regs->si; if (!user_path) return 0; ssize_t read_len = strncpy_from_user(filename, user_path, sizeof(filename)-1); if (read_len <= 0) return 0; filename[read_len] = '\0'; // 用实际读取长度设置终止符 // 匹配/home开头的绝对路径,或测试用的相对路径./file if (strstr(filename, "/home") || strcmp(filename, "./file") == 0) { pr_info("[PID:%d] openat syscall: filename: %s\n", current->pid, filename); } return 0; } struct kprobe syscall_kprobe = { .symbol_name = "__x64_sys_openat", .pre_handler = sys_openat_kprobe_pre_handler, }; static int __init audit_init(void) { int err = register_kprobe(&syscall_kprobe); if (err) { pr_err("register_kprobe() failed: %d\n", err); return err; } pr_info("kprobe registered successfully\n"); return 0; } static void __exit audit_exit(void) { unregister_kprobe(&syscall_kprobe); pr_info("kprobe unregistered\n"); } module_init(audit_init); module_exit(audit_exit);
额外注意事项
- 编译模块时确保Makefile正确引用当前内核头文件
- 加载模块后用
dmesg -w实时查看内核日志 - 现代glibc中
open()会默认调用openat(AT_FDCWD, ...),所以挂钩__x64_sys_openat即可覆盖大部分文件打开场景 - 内核6.x版本中sys_call_table确实不再用于系统调用分发,kprobe是更可靠的Hook方式
内容的提问来源于stack exchange,提问作者sena
相关产品推荐
相关产品推荐

