在我们的一个CentOS 7.8
系统上,/tmp
目录下偶尔会有一些随机名称的文件产生。业务比较久远,已经不清楚这些文件的来源,需要确定是哪个进程在创建它们。
Linux
以VFS: Virtual File System
通用文件模型的方式在物理存储介质上的文件系统和用户接口之间建立一个虚拟文件系统的抽象层。其中最重要的两个结构是:
inode
: 存放文件的一般信息,每个inode
结构都有自己的编号,这个号码唯一标识了文件系统中的文件。
dentry
: 存放文件名称信息,以及与对应文件进行链接的有关信息。
VFS
创建文件的最主要的步骤是:
- 为要创建的文件创建一个
dentry
结构
- 为要创建的文件创建一个
inode
结构并分配inode
编号
- 将
inode
编号和文件名映射关系保存在所分配的dentry
结构中
- 将
dentry
写入到父目录的数据区
这个过程中会调用到vfs_create
函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| int vfs_create(struct inode *dir, struct dentry *dentry, umode_t mode, bool want_excl) { int error = may_create(dir, dentry); if (error) return error;
if (!dir->i_op->create) return -EACCES; mode &= S_IALLUGO; mode |= S_IFREG; error = security_inode_create(dir, dentry, mode); if (error) return error; error = dir->i_op->create(dir, dentry, mode, want_excl); if (!error) fsnotify_create(dir, dentry); return error; }
|
其中,dentry
参数是文件的目录项结构,dir
参数是dentry
结构对应的inode
结构。vfs_create
检查是否可以在dir
指定的目录下创建该dentry
结构指定的文件对象,最后调用真实文件系统的create
函数指针创建文件。
我们可以跟踪vfs_create
来找出究竟是哪个进程在创建特定文件。
要动态跟踪内核函数,第一时间想到可以使用systemtap
来实现。但最近发现CentOS7.6
(内核版本:3.10.0-940.el7.x86_64
)添加了eBPF
的支持,而BCC
工具更是基于eBPF
提供了更简单的跟踪内核的方法。BCC
提供的kprobe
和kretprobe
可以在内核函数开始或者返回前,执行我们自定义的代码。我们选择使用BCC
来实现vfs_create
跟踪。
BCC
的安装可以参考:
https://www.redhat.com/en/blog/introduction-ebpf-red-hat-enterprise-linux-7
因为系统上创建文件比较频繁,因而要调查的数据输出较多,我们可以跟据父目录的inode
编号来对数据进行过滤, 跟踪程序的主体逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int kprobe__vfs_create(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry) { struct qstr d_name = dentry->d_name;
if (dir->i_ino != 12345678) return 0;
if (d_name.len == 0) return 0;
bpf_trace_printk("FNAME: %s\\n", d_name.name);
return 0; };
|
编写BCC
跟踪工具file_create
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| from __future__ import print_function from bcc import BPF import argparse
examples = """file_create: ./file_create # trace all vfs_create() ./file_create -i 67149893 # trace file create in directory with inode """ parser = argparse.ArgumentParser( description="Trace file create", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=examples) parser.add_argument("-i", "--inode", help="trace this inode only") parser.add_argument("--ebpf", action="store_true", help=argparse.SUPPRESS) args = parser.parse_args()
bpf_source = """ #include <uapi/linux/ptrace.h> #include <linux/fs.h> #include <linux/sched.h>
int kprobe__vfs_create(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry) { struct qstr d_name = dentry->d_name;
FILTER
if (d_name.len == 0) return 0;
bpf_trace_printk("FNAME: %s\\n", d_name.name);
return 0; };
"""
if args.inode: bpf_source = bpf_source.replace('FILTER', 'if (dir->i_ino != %s) { return 0; }' % args.inode) else: bpf_source = bpf_source.replace('FILTER', '')
if args.ebpf: print(bpf_source) if args.ebpf: exit()
bpf = BPF(text = bpf_source) print("Started...") bpf.trace_print()
|
查看/tmp
目录的inode
编号:
1 2
| [root@default ebpf] 67149893 /tmp/
|
启动跟踪工具:
1
| python file_create -i 67149893
|
在另一个终端上, 在/tmp
目录下创建一个测试文件:
可以看到跟踪工具输出了相应进程的cmdline
和pid
,以及创建的文件名称:
1 2 3
| [root@default ebpf] Started... touch-3366 [000] d... 3077.238910: : FNAME: 12345
|
这样,那些随机文件再次被创建时我们就获取到对应进程的信息了。