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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 18:46:04