2025-12-23 19:17:16 +08:00

264 lines
6.6 KiB
C

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
char LICENSE[] SEC("license") = "GPL";
// Kind of a boolean map to indicate whether to process events globally
struct
{
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, u32);
__type(value, u8);
} global_processing_flag SEC(".maps");
// eBPF Map to hold the PIDs to be tracked
struct
{
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 256);
__type(key, u32);
__type(value, u8);
} pids SEC(".maps");
// Perf array to send events to user-space
struct
{
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
} events SEC(".maps");
// Structure of event data to be sent to user-space
struct event
{
u32 pid; // process id
u32 tid; // thread id
u8 op; // 'R' or 'W'
u8 stage; // 'E' for entry, 'X' for exit
u8 _pad[6]; // align next u64
u64 bytes;
char comm[32]; // process name
char directory[128]; // directory path
char filename[128]; // file name
};
struct
{
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 16384);
__type(key, u64);
__type(value, u64);
} requests SEC(".maps");
// Function signatures
static __always_inline int is_global_processing_enabled();
static __always_inline int should_track_current_event(struct file *file);
static __always_inline void fill_event_details(struct event *ev, u8 op, u8 stage, size_t count, struct file *file);
static __always_inline void store_file_ptr_during_entry(struct file *file);
static __always_inline struct file *get_file_ptr_from_map(struct pt_regs *ctx);
// [ENTRY] Kprobes for Read and Write
SEC("kprobe/vfs_write")
int BPF_KPROBE(kprobe__vfs_write,
struct file *file,
const char *buf,
size_t count,
loff_t *pos)
{
if (!file)
return 0;
if (!is_global_processing_enabled())
return 0;
if (!should_track_current_event(file))
return 0;
store_file_ptr_during_entry(file);
struct event ev = {};
fill_event_details(&ev, 'W', 'E', count, file);
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &ev, sizeof(ev));
return 0;
}
SEC("kprobe/vfs_read")
int BPF_KPROBE(kprobe__vfs_read,
struct file *file,
char *buf,
size_t count,
loff_t *pos)
{
if (!file)
return 0;
if (!is_global_processing_enabled())
return 0;
if (!should_track_current_event(file))
return 0;
/* store file pointer for the corresponding exit probe */
store_file_ptr_during_entry(file);
struct event ev = {};
fill_event_details(&ev, 'R', 'E', count, file);
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &ev, sizeof(ev));
return 0;
}
// [EXIT] Kretprobes for Read and Write
SEC("kretprobe/vfs_write")
int BPF_KRETPROBE(kretprobe__vfs_write)
{
if (!is_global_processing_enabled())
return 0;
s64 ret = (s64)PT_REGS_RC(ctx);
struct file *file = get_file_ptr_from_map(ctx);
if (!file)
return 0;
if (!should_track_current_event(file))
return 0;
struct event ev = {};
fill_event_details(&ev, 'W', 'X', (size_t)ret, file);
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &ev, sizeof(ev));
return 0;
}
SEC("kretprobe/vfs_read")
int BPF_KRETPROBE(kretprobe__vfs_read)
{
if (!is_global_processing_enabled())
return 0;
s64 ret = (s64)PT_REGS_RC(ctx);
struct file *file = get_file_ptr_from_map(ctx);
if (!file)
return 0;
if (!should_track_current_event(file))
return 0;
struct event ev = {};
fill_event_details(&ev, 'R', 'X', (size_t)ret, file);
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &ev, sizeof(ev));
return 0;
}
// Helper functions
static __always_inline int is_global_processing_enabled()
{
u32 key = 0;
u8 *val = bpf_map_lookup_elem(&global_processing_flag, &key);
if (val && *val)
{
return 1;
}
return 0;
}
static __always_inline int should_track_current_event(struct file *file)
{
u64 tg = bpf_get_current_pid_tgid();
u32 pid = (u32)(tg >> 32);
// Check pids in track list
u8 *val = bpf_map_lookup_elem(&pids, &pid);
if (val)
{
// Track only regular file operations
unsigned long i_mode = BPF_CORE_READ(file, f_inode, i_mode);
const unsigned long S_IFREG = 0100000UL;
const unsigned long S_IFMT = 0170000UL;
if ((i_mode & S_IFMT) != S_IFREG)
return 0;
// Allowed to track
return 1;
}
return 0;
}
static __always_inline void store_file_ptr_during_entry(struct file *file)
{
u64 key = bpf_get_current_pid_tgid();
u64 file_ptr = (u64)(uintptr_t)file;
bpf_map_update_elem(&requests, &key, &file_ptr, BPF_ANY);
}
static __always_inline struct file *get_file_ptr_from_map(struct pt_regs *ctx)
{
s64 ret = (s64)PT_REGS_RC(ctx);
u64 key = bpf_get_current_pid_tgid();
// Check in requests map
u64 *file_ptr_p = bpf_map_lookup_elem(&requests, &key);
struct file *f = NULL;
if (file_ptr_p)
{
f = (struct file *)(uintptr_t)(*file_ptr_p);
// Cleanup
bpf_map_delete_elem(&requests, &key);
}
return f;
}
static __always_inline void fill_event_details(struct event *ev, u8 op, u8 stage, size_t count, struct file *file)
{
u64 tg = bpf_get_current_pid_tgid();
// [ 63 .. 32 ][ 31 .. 0 ]
// TGID TID
ev->pid = (u32)(tg >> 32);
ev->tid = (u32)tg;
ev->op = op;
ev->stage = stage;
ev->bytes = count;
ev->filename[0] = '\0';
ev->directory[0] = '\0';
ev->comm[0] = '\0';
bpf_get_current_comm(&ev->comm, sizeof(ev->comm));
// Read the filename
struct dentry *d = BPF_CORE_READ(file, f_path.dentry);
if (d)
{
const char *fname = (const char *)BPF_CORE_READ(d, d_name.name);
if (fname)
{
long ret_fn = bpf_core_read_str(ev->filename, sizeof(ev->filename), fname);
if (ret_fn < 0)
{
/* on failure leave filename empty */
ev->filename[0] = '\0';
}
}
// Read parent dentry's name into directory
struct dentry *parent = BPF_CORE_READ(d, d_parent);
if (parent)
{
const char *pname = (const char *)BPF_CORE_READ(parent, d_name.name);
if (pname)
{
long ret_dir = bpf_core_read_str(ev->directory, sizeof(ev->directory), pname);
if (ret_dir < 0)
{
ev->directory[0] = '\0';
}
}
}
}
}