开发者
openEuler 基于系统内核 fanotify 的全局文件访问监控与强制访问控制策略
openEuler 基于系统内核 fanotify 的全局文件访问监控与强制访问控制策略
原创
发表于03/06
2990

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 的读取并拒绝非白名单进程:

#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;
}

实际部署中应通过 /proc/&lt;pid&gt;/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 用于最终决策,降低用户态负载。
收藏举报
Level 1
0
帖子
0
粉丝
0
获赞