Fanotify用法简介

Fanotify是时下比较先进的Linux文件监控系统,其收集的信息在许多领域中都有广泛的应用前景。因为在项目中碰到,对Fanotify进行了简单的调研,总结为这篇文章。

Fanotify简介

Fanotify是Linux内核提供的一套对于文件系统事件进行监控的API。简单地说,Fanotify可以对于指定的文件/目录/文件系统/挂载点进行检测,一旦捕捉到目标对象产生的特定的事件(Access/Write/Close),可以提供事件相关的信息供应用读取;除此之外,也可以赋予应用决定是否允许该次事件发生的权力,在系统安全的实践中有比较广泛的应用可能。

Fanotify的主要优点

  • 可以选择监控特定范围内的文件;

  • 可以选择监控特定类型的事件;

  • 访问控制:对于文件的访问,可以定制策略,控制是否访问该文件;

  • 缓存机制:避免同一文件的大量重复修改造成的数据冗余;

  • CPU和内存的占用比较低;

Fanotify的主要缺点

  • 没有时间戳;
  • 对内核版本的要求比较高,其中9种类型的文件事件要求Linux 5.0以上的内核版本。

Fanotify 用法

关于Fanotify详细的用法请参考Linux内核手册:

https://www.man7.org/linux/man-pages/man7/fanotify.7.html

Fanotify的API主要使用了了以下几个系统调用:
fanotify_init(2):初始化一个fanotify group,返回一个文件描述符,代表相应的监控事件队列,再利用read系统调用从文件描述符对应的文件中读取事件。这个系统调用中可以设置多种flag参数,用以确定该fanotify group优先级(可以决定是否允许对某一特定文件操作)等特性。

fanotify_mark(2):增加、删除或更改对于文件系统的监控项目。

1
2
int fanotify_mark(int fanotify_fd, unsigned int flags,
uint64_t mask, int dirfd, const char *pathname);

fanotify_mark系统调用接收fanotify_init创建的文件描述符fanotify_fd作为参数,表示修改特定的fanotify group队列。flags表示对目标事件列表增加、修改或删除操作,对列表中的事件是监控还是忽略,以及目标监控文件的范围;mask表示监控(或者忽略)的事件类型;dirfdpathname一起共同确定了监控的范围。

read(2)/write(2)/close(2)

以上三个系统调用都是针对代表fanotify group的文件描述符fanotify_fd,分别代表对其的读取、修改和关闭。

Demo

下面是一个简单的使用Fanotify监控FAN_CLOSE_WRITEFAN_OPEN_PERM两个事件的例子。

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#define _GNU_SOURCE     /* Needed to get O_LARGEFILE definition */
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/fanotify.h>
#include <unistd.h>

/* Read all available fanotify events from the file descriptor 'fd' */

static void
handle_events(int fd)
{
const struct fanotify_event_metadata *metadata;
struct fanotify_event_metadata buf[200];
ssize_t len;
char path[PATH_MAX];
ssize_t path_len;
char procfd_path[PATH_MAX];
struct fanotify_response response;

/* Loop while events can be read from fanotify file descriptor */

for (;;) {

/* Read some events */

len = read(fd, (void *) &buf, sizeof(buf));
if (len == -1 && errno != EAGAIN) {
perror("read");
exit(EXIT_FAILURE);
}

/* Check if end of available data reached */

if (len <= 0)
break;

/* Point to the first event in the buffer */

metadata = buf;

/* Loop over all events in the buffer */

while (FAN_EVENT_OK(metadata, len)) {

/* Check that run-time and compile-time structures match */

if (metadata->vers != FANOTIFY_METADATA_VERSION) {
fprintf(stderr,
"Mismatch of fanotify metadata version.\n");
exit(EXIT_FAILURE);
}

/* metadata->fd contains either FAN_NOFD, indicating a
queue overflow, or a file descriptor (a nonnegative
integer). Here, we simply ignore queue overflow. */

if (metadata->fd >= 0) {

/* Handle open permission event */

if (metadata->mask & FAN_OPEN_PERM) {
printf("FAN_OPEN_PERM: ");

/* Allow file to be opened */

response.fd = metadata->fd;
response.response = FAN_ALLOW;
write(fd, &response,
sizeof(struct fanotify_response));
}

/* Handle closing of writable file event */

if (metadata->mask & FAN_CLOSE_WRITE)
printf("FAN_CLOSE_WRITE: ");

/* Retrieve and print pathname of the accessed file */

snprintf(procfd_path, sizeof(procfd_path),
"/proc/self/fd/%d", metadata->fd);
path_len = readlink(procfd_path, path,
sizeof(path) - 1);
if (path_len == -1) {
perror("readlink");
exit(EXIT_FAILURE);
}

path[path_len] = '\0';
printf("File %s\n", path);

/* Close the file descriptor of the event */

close(metadata->fd);
}

/* Advance to next event */

metadata = FAN_EVENT_NEXT(metadata, len);
}
}
}

int
main(int argc, char *argv[])
{
char buf;
int fd, poll_num;
nfds_t nfds;
struct pollfd fds[2];

/* Check mount point is supplied */

if (argc != 2) {
fprintf(stderr, "Usage: %s MOUNT\n", argv[0]);
exit(EXIT_FAILURE);
}

printf("Press enter key to terminate.\n");

/* Create the file descriptor for accessing the fanotify API */

fd = fanotify_init(FAN_CLOEXEC | FAN_CLASS_CONTENT | FAN_NONBLOCK,
O_RDONLY | O_LARGEFILE);
if (fd == -1) {
perror("fanotify_init");
exit(EXIT_FAILURE);
}

/* Mark the mount for:
- permission events before opening files
- notification events after closing a write-enabled
file descriptor */

if (fanotify_mark(fd, FAN_MARK_ADD | FAN_MARK_MOUNT,
FAN_OPEN_PERM | FAN_CLOSE_WRITE, AT_FDCWD,
argv[1]) == -1) {
perror("fanotify_mark");
exit(EXIT_FAILURE);
}

/* Prepare for polling */

nfds = 2;

/* Console input */

fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;

/* Fanotify input */

fds[1].fd = fd;
fds[1].events = POLLIN;

/* This is the loop to wait for incoming events */

printf("Listening for events.\n");

while (1) {
poll_num = poll(fds, nfds, -1);
if (poll_num == -1) {
if (errno == EINTR) /* Interrupted by a signal */
continue; /* Restart poll() */

perror("poll"); /* Unexpected error */
exit(EXIT_FAILURE);
}

if (poll_num > 0) {
if (fds[0].revents & POLLIN) {

/* Console input is available: empty stdin and quit */

while (read(STDIN_FILENO, &buf, 1) > 0 && buf != '\n')
continue;
break;
}

if (fds[1].revents & POLLIN) {

/* Fanotify events are available */

handle_events(fd);
}
}
}

printf("Listening for events stopped.\n");
exit(EXIT_SUCCESS);
}