ToB企服应用市场:ToB评测及商务社交产业平台

标题: 【安全】Linux Fanotify使用入门 [打印本页]

作者: 愛在花開的季節    时间: 2024-7-23 11:20
标题: 【安全】Linux Fanotify使用入门
1 Fanotify vs Inotify

在实现某些功能时,可能需要获取某个文件执行的操作,一种可能的方案是用Audit的路径监控,但是Audit存在性能和内核稳定性标题,这个时候就可以其他的文件变更检测机制。
inotify可以监控文件被创建、修改和访问的事件,当文件大概目录发生变化时,可以得到变更的事件,但是inotify有个比较大的不敷,就是无法得到执行操作的进程Pid。fanotify是另一种文件监控机制,在使用上两者雷同,都是先调用init函数初始化句柄,然后调用雷同watch的函数添加监控路径,再使用select+read的方式读取出变化的事件,根据事件中给出的参数获取文件的路径、事件类型以及其他需要的数据。与inotify相比,fanotify的优势在于:

2 Fanotify Demo

  1. #include <sys/fanotify.h>
  2. #include <fcntl.h>
  3. #include <stdio.h>
  4. #include <unistd.h>
  5. #include <errno.h>
  6. #include <stdlib.h>
  7. #include <string>
  8. #include <string.h>
  9. int main(int argc, char *argv[]) {
  10.     int fan_fd, fd;
  11.     if(argc < 2) {
  12.         printf("usage: ./main path\n");
  13.         return -1;
  14.     }
  15.     // 初始化fanotify的描述符,后续的操作都是通过该描述符
  16.     fan_fd = fanotify_init(FAN_CLASS_NOTIF, O_RDONLY);
  17.     if (fan_fd < 0) {
  18.         perror("fanotify_init");
  19.         return -1;
  20.     }
  21.     // 增加监控路径
  22.     if (fanotify_mark(fan_fd, FAN_MARK_ADD, FAN_ACCESS|FAN_MODIFY|FAN_EVENT_ON_CHILD, AT_FDCWD, argv[1]) < 0) {
  23.         perror("fanotify_mark");
  24.         return -1;
  25.     }
  26.     fd_set rfds;
  27.     while (1) {
  28.         // 使用select+read读取变更的事件
  29.         FD_ZERO(&rfds);
  30.         FD_SET(fan_fd, &rfds);
  31.         struct timeval timeo = {.tv_sec = 0, .tv_usec = 500000};
  32.         char buf[4096] = {0};
  33.         int ret = select(fan_fd + 1, &rfds, NULL, NULL, &timeo);
  34.         if (ret < 0) {
  35.             close(fan_fd);
  36.             fan_fd = -1;
  37.             break;
  38.         } else if (ret == 0) {
  39.             continue;
  40.         }
  41.         struct fanotify_event_metadata* metadata =
  42.             reinterpret_cast<struct fanotify_event_metadata*>(buf);
  43.         ssize_t len = read(fan_fd, buf, sizeof(buf));
  44.         if (len == -1 && errno == EAGAIN) {
  45.             continue;
  46.         }
  47.         if (len == -1) {
  48.             perror("read");
  49.             exit(EXIT_FAILURE);
  50.         }
  51.         // 遍历返回的变更事件
  52.         for (; FAN_EVENT_OK(metadata, len);
  53.             metadata = FAN_EVENT_NEXT(metadata, len)) {
  54.             printf("fd=%d pid=%d ", metadata->fd, metadata->pid);
  55.             // 根据返回的事件中的mask判断事件类型
  56.             if (metadata->mask & FAN_ACCESS) {
  57.                 printf("event=read ");
  58.             } else if (metadata->mask & FAN_MODIFY) {
  59.                 printf("event=write ");
  60.             } else {
  61.                 printf("event=other ");
  62.             }
  63.             // 根据返回事件中的fd读取操作的文件路径
  64.             char path[1024] = {0};
  65.             char flink[1024] = {0};
  66.             sprintf(flink, "/proc/self/fd/%d", metadata->fd);
  67.             if (readlink(flink, path, sizeof(path)) < 0) {
  68.                 printf("readlink %s failed: %s\n", flink, strerror(errno));
  69.                 close(metadata->fd);
  70.                 printf("\n");
  71.                 continue;
  72.             }
  73.             printf("path=%s\n", path);
  74.             // 此处不能少,由于该fd是在当前进程中,需要在处理完该事件后关闭
  75.             close(metadata->fd);
  76.         }
  77.     }
  78.     close(fan_fd);
  79.     return 0;
  80. }
复制代码
3 Fanotify API

通过上述示例代码发现,使用fanotify监控路径的根本套路是:

因此,使用过程中重点是需要了解fanotify_init和fanotify_mark两个API的参数和寄义。
3.1 fanotify_init

  1. #include <fcntl.h>
  2. #include <sys/fanotify.h>
  3. int fanotify_init(unsigned int flags, unsigned int event_f_flags);
复制代码
fanotify_init函数用于初始化文件描述符,flags参数可以使用两类标志,一类是通知类型(notification classes),另一类是对fanotify的文件描述符的一些属性设置。
通知类型有三类:

如果程序只需要获取事件,而不需要进行访问控制,可以用默认值,否则就需要根据使用场景进行选择。别的,根据寄义,三类事件也是按照从上到下的顺序接收的。
设置fanotify的文件描述符的属性的标志:

除了上述标志位,后续的内核还提供其他的标志位,可以在事件上上报更多数据:

flags可以设置通知类型中的一种和属性设置中的若干标志。
event_f_flags用于设置收到的事件中的文件描述符的属性,常用的标志位有:

当然,也可以设置其他标志位:O_WRONLY、O_RDWR、O_APPEND、O_DSYNC、O_NOATIME、O_NONBLOCK、O_SYNC。
3.2 fanotify_mark

  1. #include <sys/fanotify.h>
  2. int fanotify_mark(int fanotify_fd, unsigned int flags,
  3.                   uint64_t mask, int dirfd, const char *pathname);
复制代码
fanotify_mark函数用于操作监控路径,可以增加监控路径,也可以删除监控路径。
fanotify_mark有5个参数:

flags是表明需要对监控点进行何种操作,是增照旧减:

flags可以从上述三个操作中选择一个,然后再结合以下标志位设置:

除了上述标志位,kernel>=4.20还提供FAN_MARK_FILESYSTEM,表示监控pathname所在的整个文件系统。
mask表明要监控的事件类型:

以上是从内核版本2.6.37开始支持的事件类型,也有更高版本支持的事件类型:

监控路径则依赖dirfd和pathname两个参数:

3.3 三种监控模式

前面说过,fanotify支持三种监控模式:

4 容器场景使用的标题

从上述可以看出,fanotify在5.1内核增加了许多能力,因此,在使用时需要考虑内核的版本标题,别的,在容器场景也存在一些标题。
如果是监控主机上的路径,directed结合FAN_EVENT_ON_CHILD标志就只能监控目录以及目录下的文件,per-mount可以监控pathname所在的挂载点中所有文件大概目录的变化,而global模式可以监控整个文件系统。
从功能实现角度来看,如果需要监控某个路径下的文件变化,盼望可以只设置该路径就行,也就是,当监控/etc目录时,也可以监控到/etc/xxx/目录下的文件变化。
如果是监控容器中的路径,如果使用directed和global模式,就跟主机上的一样,监控merged路径;如果使用per-mount,由于不能跨定名空间,需要使用setns进入到容器所在的定名空间,再添加监控路径,实现起来会麻烦许多。
在处理文件的变更事件时,通过metadata->fd读取到的文件路径是容器中的路径,metadata->pid是宿主机上的进程Pid,通过/proc/pid/cgroup可以得到容器ID,固然可以根据容器ID调用docker大概CRI的接口获取merged路径,但是也比较麻烦。
5 参考文档



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4