Skip to content

bug(dkapture): fs_watch() 指定路径时错误调用了 trace_file_init 而非 mountsnoop_init #57

Description

@zhoubucai

问题描述

so/dkapture.cppdkapture::fs_watch() 函数存在复制粘贴错误:当 path 参数非空时,else 分支(第 388 行)调用了 trace_file_init(),而不是 mountsnoop_init(),导致启动了一个完全无关的 BPF 程序(文件事件追踪),而非预期的挂载事件监控。

问题位置

  • 文件: so/dkapture.cpp
  • 函数: dkapture::fs_watch()
  • 行号: 第 388 行

问题代码

// so/dkapture.cpp 第 370-390 行
int dkapture::fs_watch(const char *path, DKCallback cb, void *ctx)
{
    if (!cb)
    {
        return mountsnoop_deinit();              // ✅ 正确:使用 mountsnoop
    }
    if (path == nullptr || path[0] == 0)
    {
        char *arg0 = (char *)"dkapture";
        char *args[] = {arg0, 0};
        return mountsnoop_init(1, args, cb, ctx); // ✅ 正确:使用 mountsnoop
    }
    else
    {
        char *arg0 = (char *)"dkapture";
        char *arg1 = (char *)"-p";
        char *arg2 = (char *)path;
        char *args[] = {arg0, arg1, arg2, 0};
        return trace_file_init(3, args, cb, ctx); // ❌ 错误:应为 mountsnoop_init
    }
}

分析过程

1. API 定义确认

bpf/export/dkapture.h 第 303-319 行对 fs_watch 的定义:

@brief 文件系统事件监控
@param path 挂载点路径,或文件系统所在分区设备路径
      如果 mount /dev/sda1 /mnt,则 path 可以是 /mnt 或 /dev/sda1。
      指定路径为 null,则监控所有文件系统挂/卸载事件。

该函数的职责明确是监控 mount/umount 事件,应始终使用 mountsnoop_* 系列函数。

2. 三个分支一致性分析

分支 条件 实际调用 预期调用 是否正确
deinit cb == nullptr mountsnoop_deinit() mountsnoop_deinit()
无路径 path 为空 mountsnoop_init(1, ...) mountsnoop_init(1, ...)
有路径 path 非空 trace_file_init(3, ...) mountsnoop_init(3, ...)

三个分支中两个正确使用 mountsnoop_*,唯独 else 分支使用了 trace_file_init,明显不一致。

3. mountsnoop_init 对 -p 参数的支持确认

observe/mountsnoop.cpp-p 参数的处理逻辑:

// 第 82-87 行:解析 -p 参数,存入 filter_path
case 'p':
    memset(filter_path, 0, PATH_MAX);
    strncpy(filter_path, arg, PATH_MAX);
    break;

// 第 532-533 行:将 filter_path 写入 BPF map,用于内核侧过滤
DEBUG(0, "filter path: %s", filter_path);
bpf_map_update_elem(filter_map_fd, &key, filter_path, BPF_ANY);

mountsnoop_init 完全支持通过 -p <path> 进行路径过滤,且函数签名与当前调用方式兼容。

4. 复制粘贴来源确认

对比上方的 file_watch() 函数(第 323-344 行):

int dkapture::file_watch(const char *path, DKCallback cb, void *ctx)
{
    if (cb == nullptr) { return trace_file_deinit(); }
    if (path == nullptr || path[0] == 0)
    {
        // ...
        return trace_file_init(1, args, cb, ctx);
    }
    else
    {
        // ...
        return trace_file_init(3, args, cb, ctx);  // ← else 分支被复制到 fs_watch
    }
}

fs_watch() 的 else 分支与 file_watch() 的 else 分支结构完全相同,是从 file_watch() 复制后忘记将 trace_file_init 改为 mountsnoop_init

触发条件

调用 fs_watch() 时传入非空的 path 参数,例如:

dk->fs_watch("/mnt", my_callback, my_ctx);
//
dk->fs_watch("/dev/sda1", my_callback, my_ctx);

pathnullptr 或空字符串时不会触发此问题。

预期行为

fs_watch("/mnt", cb, ctx) 应调用 mountsnoop_init(3, args, cb, ctx),启动 mountsnoop BPF 程序,监控挂载点 /mnt 相关的 mount/umount 系统调用事件,并通过回调 cb 返回 FsLog 类型数据。

实际行为

fs_watch("/mnt", cb, ctx) 错误调用了 trace_file_init(3, args, cb, ctx),导致:

  1. 功能完全错误:启动的是文件事件追踪(trace-file)BPF 程序,而非挂载事件监控(mountsnoop),不会捕获任何 mount/umount 事件。
  2. 资源清理错乱:后续调用 fs_watch(nullptr, nullptr, nullptr) 停止监控时,执行的是 mountsnoop_deinit(),无法正确清理 trace_file 实例,造成资源泄漏。
  3. 回调数据类型不匹配trace_file 返回的是 FileLog 类型数据,而 fs_watch 的用户期望的是 FsLog 类型数据。

建议修复

--- a/so/dkapture.cpp
+++ b/so/dkapture.cpp
@@ -385,7 +385,7 @@
 		char *arg1 = (char *)"-p";
 		char *arg2 = (char *)path;
 		char *args[] = {arg0, arg1, arg2, 0};
-		return trace_file_init(3, args, cb, ctx);
+		return mountsnoop_init(3, args, cb, ctx);
 	}
 }

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions