Fanotify是时下比较先进的Linux文件监控系统,其收集的信息在许多领域中都有广泛的应用前景。因为在项目中碰到,对Fanotify进行了简单的调研,总结为这篇文章。
Fanotify简介
Fanotify是Linux内核提供的一套对于文件系统事件进行监控的API。简单地说,Fanotify可以对于指定的文件/目录/文件系统/挂载点进行检测,一旦捕捉到目标对象产生的特定的事件(Access/Write/Close),可以提供事件相关的信息供应用读取;除此之外,也可以赋予应用决定是否允许该次事件发生的权力,在系统安全的实践中有比较广泛的应用可能。
Fanotify的主要优点
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
表示监控(或者忽略)的事件类型;dirfd
和pathname
一起共同确定了监控的范围。
read(2) /write(2) /close(2)
以上三个系统调用都是针对代表fanotify group的文件描述符fanotify_fd
,分别代表对其的读取、修改和关闭。
Demo
下面是一个简单的使用Fanotify监控FAN_CLOSE_WRITE
和FAN_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 #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> 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 ; for (;;) { len = read(fd, (void *) &buf, sizeof (buf)); if (len == -1 && errno != EAGAIN) { perror("read" ); exit (EXIT_FAILURE); } if (len <= 0 ) break ; metadata = buf; while (FAN_EVENT_OK(metadata, len)) { if (metadata->vers != FANOTIFY_METADATA_VERSION) { fprintf (stderr , "Mismatch of fanotify metadata version.\n" ); exit (EXIT_FAILURE); } if (metadata->fd >= 0 ) { if (metadata->mask & FAN_OPEN_PERM) { printf ("FAN_OPEN_PERM: " ); response.fd = metadata->fd; response.response = FAN_ALLOW; write(fd, &response, sizeof (struct fanotify_response)); } if (metadata->mask & FAN_CLOSE_WRITE) printf ("FAN_CLOSE_WRITE: " ); 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(metadata->fd); } 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]; if (argc != 2 ) { fprintf (stderr , "Usage: %s MOUNT\n" , argv[0 ]); exit (EXIT_FAILURE); } printf ("Press enter key to terminate.\n" ); fd = fanotify_init(FAN_CLOEXEC | FAN_CLASS_CONTENT | FAN_NONBLOCK, O_RDONLY | O_LARGEFILE); if (fd == -1 ) { perror("fanotify_init" ); exit (EXIT_FAILURE); } 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); } nfds = 2 ; fds[0 ].fd = STDIN_FILENO; fds[0 ].events = POLLIN; fds[1 ].fd = fd; fds[1 ].events = POLLIN; printf ("Listening for events.\n" ); while (1 ) { poll_num = poll(fds, nfds, -1 ); if (poll_num == -1 ) { if (errno == EINTR) continue ; perror("poll" ); exit (EXIT_FAILURE); } if (poll_num > 0 ) { if (fds[0 ].revents & POLLIN) { while (read(STDIN_FILENO, &buf, 1 ) > 0 && buf != '\n' ) continue ; break ; } if (fds[1 ].revents & POLLIN) { handle_events(fd); } } } printf ("Listening for events stopped.\n" ); exit (EXIT_SUCCESS); }