fanotify 是系统内核提供的高级文件事件通知机制,相较于 inotify 和 dnotify,它支持对整个挂载点或文件系统的全局监控,并可在用户空间决策是否允许文件操作(通过 FAN_ALLOW/FAN_DENY 响应),从而实现内核级的强制访问控制。自内核 5.1 起,fanotify 支持监控文件打开(FAN_OPEN_PERM)和权限检查(FAN_ACCESS_PERM)事件,使其可用于防病毒扫描、数据防泄漏(DLP)、敏感文件访问拦截等场景。openEuler 24.03 LTS 基于 6.6+ 内核,默认启用 fanotify 并支持权限决策模式,适用于合规审计、高安全终端防护及自动化策略执行。本文说明如何在 openEuler 24.03 LTS 上开发 fanotify 监控程序,对特定路径的文件访问实施运行时授权控制。
一、环境前提
- 操作系统:openEuler 24.03 LTS
- 内核版本:≥ 5.1(openEuler 24.03 LTS 默认内核 ≥ 6.6)
- 内核配置:CONFIG_FANOTIFY=y、CONFIG_FANOTIFY_ACCESS_PERMISSIONS=y(默认启用)
- 监控目标:拦截对 /etc/shadow 的读取;阻止非授权进程写入 /opt/secret;记录所有对财务目录的访问尝试
- 权限要求:需 CAP_SYS_ADMIN 能力(通常以 root 运行监控程序)
- 依赖库:glibc、libcap(用于能力管理)
二、理解 fanotify 权限决策模式
fanotify 可工作于两种模式:
- 通知模式(notification-only):仅上报事件,不干预操作(如日志记录);
- 权限模式(access permission):内核暂停文件操作,等待用户空间程序返回 FAN_ALLOW 或 FAN_DENY。
关键事件类型:
FAN_OPEN_PERM:在 open() 系统调用前触发; FAN_ACCESS_PERM:在 faccessat() 等权限检查前触发。
监控范围可设为:
- 单个文件描述符(通常为挂载点根目录);
- 整个文件系统(通过
/ 或指定挂载点)。
三、编写 fanotify 权限控制程序(C 示例)
以下程序监控对 /etc/shadow 的读取并拒绝非白名单进程:
实际部署中应通过 /proc/<pid>/exe 验证进程身份,并缓存白名单。
四、性能与范围优化
- 避免监控高频路径:如 /tmp、/var/log,以免造成性能瓶颈;
- 使用 FAN_MARK_FILESYSTEM(内核 ≥ 5.1)替代 FAN_MARK_MOUNT,覆盖 bind mount;
- 限制事件类型:仅订阅必要事件(如 FAN_OPEN_PERM),不监听 FAN_CLOSE_WRITE;
- 批量处理:利用 fanotify 的队列机制减少上下文切换。
五、与现有安全框架协同
- 与 audit 协同:fanotify 拦截后,可触发 audit 规则记录拒绝事件;
- 与 SELinux 协同:fanotify 在 VFS 层介入,早于 LSM 钩子,可作为前置过滤;
- 与 eBPF 协同:eBPF 可用于预过滤进程 PID,fanotify 用于最终决策,降低用户态负载。
fanotify 是系统内核提供的高级文件事件通知机制,相较于 inotify 和 dnotify,它支持对整个挂载点或文件系统的全局监控,并可在用户空间决策是否允许文件操作(通过 FAN_ALLOW/FAN_DENY 响应),从而实现内核级的强制访问控制。自内核 5.1 起,fanotify 支持监控文件打开(FAN_OPEN_PERM)和权限检查(FAN_ACCESS_PERM)事件,使其可用于防病毒扫描、数据防泄漏(DLP)、敏感文件访问拦截等场景。openEuler 24.03 LTS 基于 6.6+ 内核,默认启用 fanotify 并支持权限决策模式,适用于合规审计、高安全终端防护及自动化策略执行。本文说明如何在 openEuler 24.03 LTS 上开发 fanotify 监控程序,对特定路径的文件访问实施运行时授权控制。
一、环境前提
二、理解 fanotify 权限决策模式
fanotify 可工作于两种模式:
关键事件类型:
FAN_OPEN_PERM:在 open() 系统调用前触发;FAN_ACCESS_PERM:在 faccessat() 等权限检查前触发。监控范围可设为:
/或指定挂载点)。三、编写 fanotify 权限控制程序(C 示例)
以下程序监控对
/etc/shadow的读取并拒绝非白名单进程:#define _GNU_SOURCE #include <sys/fanotify.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <errno.h> int main(void) { int fan_fd, fd; char buf[4096]; struct fanotify_event_metadata *metadata; struct fanotify_response response; // 创建 fanotify 实例,启用权限决策 fan_fd = fanotify_init( FAN_CLASS_CONTENT | FAN_UNLIMITED_QUEUE | FAN_UNLIMITED_MARKS, O_RDONLY | O_LARGEFILE ); if (fan_fd < 0) return 1; // 监控整个根文件系统上的 FAN_OPEN_PERM 事件 fanotify_mark(fan_fd, FAN_MARK_MOUNT, FAN_OPEN_PERM, AT_FDCWD, "/"); while (1) { ssize_t len = read(fan_fd, buf, sizeof(buf)); if (len <= 0) continue; metadata = (struct fanotify_event_metadata *)buf; while (FAN_EVENT_OK(metadata, len)) { if (metadata->mask & FAN_OPEN_PERM) { // 获取文件路径(简化:实际需从 /proc/self/fd 读取) char path[256], link[256]; snprintf(path, sizeof(path), "/proc/self/fd/%d", metadata->fd); ssize_t r = readlink(path, link, sizeof(link) - 1); if (r > 0) { link[r] = '\0'; // 拦截对 /etc/shadow 的访问 if (strcmp(link, "/etc/shadow") == 0) { // 此处可检查 metadata->pid 是否在白名单中 response.fd = metadata->fd; response.response = FAN_DENY; // 拒绝访问 write(fan_fd, &response, sizeof(response)); close(metadata->fd); metadata = FAN_EVENT_NEXT(metadata, len); continue; } } // 允许其他访问 response.fd = metadata->fd; response.response = FAN_ALLOW; write(fan_fd, &response, sizeof(response)); } close(metadata->fd); metadata = FAN_EVENT_NEXT(metadata, len); } } close(fan_fd); return 0; }四、性能与范围优化
五、与现有安全框架协同