WEBKT

使用eBPF追踪进程文件打开操作实战

17 0 0 0

eBPF 简介

核心思路:追踪 open 系统调用

eBPF 程序示例 (简化版)

进阶:处理 openat 系统调用

安全性和性能注意事项

总结

想知道某个进程偷偷摸摸打开了哪些文件?或者需要排查某个服务的文件访问行为?eBPF (extended Berkeley Packet Filter) 给你提供了一个强大的武器,可以在内核态进行安全高效的观测和分析,而无需修改内核代码或加载内核模块。

eBPF 简介

eBPF 最初是为网络数据包过滤设计的,但现在已经发展成为一个通用的内核态虚拟机,允许用户在内核中运行自定义程序,从而实现各种各样的功能,例如性能分析、安全监控、网络优化等等。eBPF 程序运行在受限的环境中,需要经过严格的验证,确保程序的安全性,例如防止程序崩溃、死循环等。

核心思路:追踪 open 系统调用

我们的目标是追踪特定进程打开文件的操作,因此需要找到合适的切入点。在 Linux 系统中,openopenat 系统调用负责打开文件。我们可以利用 eBPF 的 kprobe 或 tracepoint 功能,在这两个系统调用上挂载我们的 eBPF 程序,从而拦截文件打开操作。

  • kprobe: 允许你动态地在内核函数的入口和出口处插入探测点。这非常灵活,但依赖于内核函数的具体实现,可能会因为内核版本升级而失效。
  • tracepoint: 是内核中预先定义好的静态探测点。相比 kprobe,tracepoint 更加稳定,不容易受到内核版本变化的影响。

考虑到稳定性,我们这里选择使用 tracepointopen 相关的 tracepoint 一般是 syscalls:sys_enter_opensyscalls:sys_exit_open,分别在 open 系统调用进入和退出时触发。

eBPF 程序示例 (简化版)

下面是一个简化的 eBPF 程序示例,用于追踪指定 PID 的进程打开文件的操作。为了便于理解,这里使用 bcc (BPF Compiler Collection) 框架来简化 eBPF 程序的编写和部署。

from bcc import BPF
import os
# 定义 eBPF 程序
program = '''
#include <linux/sched.h>
// 定义输出数据结构
struct data_t {
u32 pid;
char filename[256];
};
// 定义 BPF 映射 (BPF map),用于将数据从内核态传递到用户态
BPF_PERF_OUTPUT(events);
// tracepoint 处理函数
tracepoint(syscalls, sys_enter_open) {
// 获取当前进程的 PID
u32 pid = bpf_get_current_pid_tgid();
// 过滤指定的 PID
if (pid != TARGET_PID) {
return 0; // 忽略其他进程
}
// 获取文件名
struct data_t data = {};
data.pid = pid;
bpf_probe_read_user(&data.filename, sizeof(data.filename), (void *)args->filename);
// 将数据发送到用户态
events.perf_submit(args, &data, sizeof(data));
return 0;
}
'''
# 替换目标 PID
target_pid = int(os.getenv("TARGET_PID")) # 从环境变量中获取目标PID,更灵活
program = program.replace('TARGET_PID', str(target_pid))
# 加载 eBPF 程序
bpf = BPF(text=program)
# 打印输出
def print_event(cpu, data, size):
event = bpf["events"].event(data)
print(f"PID: {event.pid}, Filename: {event.filename.decode('utf-8', 'replace')}")
# 注册回调函数
bpf["events"].open_perf_buffer(print_event)
# 循环读取事件
while True:
try:
bpf.perf_buffer_poll()
except KeyboardInterrupt:
exit()

代码解释:

  1. #include <linux/sched.h>: 引入内核调度相关的头文件,用于获取进程 PID。
  2. struct data_t: 定义了一个结构体,用于存储 PID 和文件名。文件名使用字符数组存储。
  3. BPF_PERF_OUTPUT(events): 定义了一个 BPF 映射,类型为 perf_outputperf_output 是一种特殊的映射,用于将数据从内核态异步地发送到用户态,避免阻塞内核程序的执行。
  4. tracepoint(syscalls, sys_enter_open): 定义了一个 tracepoint 处理函数,挂载到 syscalls:sys_enter_open 这个 tracepoint 上。syscalls 是 tracepoint 的类别,sys_enter_open 是 tracepoint 的名称。
  5. bpf_get_current_pid_tgid(): eBPF 内置函数,用于获取当前进程的 PID 和线程组 ID。返回值是一个 64 位的整数,高 32 位是 PID,低 32 位是线程组 ID。我们这里只关心 PID,所以只取高 32 位。
  6. if (pid != TARGET_PID): 过滤非目标进程的事件。TARGET_PID 需要替换成实际的目标进程 PID。
  7. bpf_probe_read_user(&data.filename, sizeof(data.filename), (void *)args->filename): eBPF 内置函数,用于从用户态内存读取数据。由于文件名位于用户态内存,我们需要使用这个函数来读取。args->filenamesys_enter_open tracepoint 传递的参数,指向用户态的文件名字符串。
  8. events.perf_submit(args, &data, sizeof(data)): 将数据提交到 perf_output 映射,异步地发送到用户态。
  9. bpf = BPF(text=program): 使用 bcc 框架加载 eBPF 程序。
  10. bpf["events"].open_perf_buffer(print_event): 注册一个回调函数 print_event,用于处理从内核态发送过来的事件。
  11. bpf.perf_buffer_poll(): 循环读取 perf_output 映射中的事件,并调用回调函数进行处理。

使用方法:

  1. 安装 bcc: sudo apt-get install bpfcc-tools linux-headers-$(uname -r)
  2. 保存代码为 trace_open.py
  3. 设置环境变量 TARGET_PID: export TARGET_PID=<目标进程PID>
  4. 运行脚本: sudo python3 trace_open.py

进阶:处理 openat 系统调用

openat 系统调用是 open 的一个变体,它允许你相对于一个文件描述符打开文件。为了完整地追踪文件打开操作,你还需要处理 openat 系统调用。你可以通过挂载 syscalls:sys_enter_openat 这个 tracepoint 来实现。openat 的参数略有不同,你需要根据 openat 的参数结构来读取文件名。

安全性和性能注意事项

  • eBPF 程序的安全性至关重要。eBPF 程序运行在内核态,如果程序存在漏洞,可能会导致系统崩溃。因此,eBPF 程序需要经过严格的验证,确保程序的安全性。
  • eBPF 程序的性能也需要考虑。eBPF 程序会消耗 CPU 资源,如果程序过于复杂,可能会影响系统性能。因此,eBPF 程序应该尽可能地简单高效。
  • 限制 eBPF 程序的执行时间。eBPF 程序的执行时间应该尽可能地短,避免长时间占用 CPU 资源。可以使用 bpf_ktime_get_ns() 函数来获取当前时间,并根据时间来控制程序的执行。
  • 使用 BPF 映射来存储数据。BPF 映射是一种高效的键值存储,可以在内核态和用户态之间共享数据。可以使用 BPF 映射来存储一些状态信息,例如文件打开次数等。

总结

eBPF 提供了强大的内核观测能力,可以用于追踪进程的文件打开操作。通过 kprobe 或 tracepoint,我们可以轻松地拦截 openopenat 系统调用,并记录打开的文件信息。但需要注意安全性和性能方面的注意事项,确保 eBPF 程序不会对系统造成负面影响。希望这篇文章能帮助你了解如何使用 eBPF 来追踪进程的文件打开操作。

内核小能手 eBPFLinux系统调用追踪

评论点评

打赏赞助
sponsor

感谢您的支持让我们更好的前行

分享

QRcode

https://www.webkt.com/article/10191