Hệ điều hành · 03/10/2026

Linux fanotify thực chiến: Giám sát và chặn truy cập file an toàn

inotify phù hợp để biết file nào thay đổi. Khi cần quan sát cả mount/filesystem hoặc tạm dừng một thao tác mở file để scanner quyết định allow/deny, Linux cung cấp fanotify. Sức mạnh này đi kèm queue hữu hạn, file descriptor phải đóng, capability cao và failure mode dễ hiểu sai.

Linux fanotify thực chiến: Giám sát và chặn truy cập file an toàn

inotify phù hợp để biết file nào thay đổi. Khi cần quan sát cả mount/filesystem hoặc tạm dừng một thao tác mở file để scanner quyết định allow/deny, Linux cung cấp fanotify. Sức mạnh này đi kèm queue hữu hạn, file descriptor phải đóng, capability cao và failure mode dễ hiểu sai.

Bài viết xây event loop đúng contract, phân biệt notification với permission events, lựa chọn mark, xử lý FAN_Q_OVERFLOW và thiết kế scanner không tự khóa hệ thống. Nội dung được đối chiếu với fanotify(7), fanotify_init(2), fanotify_mark(2) và tài liệu filesystem monitoring của kernel.

1. Fanotify khác inotify ở đâu?

Fanotify có ba khả năng nổi bật:

  • mark toàn bộ mount hoặc filesystem thay vì gắn watch từng directory;
  • nhận file descriptor hoặc file handle đại diện object;
  • chặn open/access/execute trong kernel cho đến khi listener phản hồi.

Use case gồm malware scanner, hierarchical storage manager, audit agent và cache invalidation. Nếu chỉ theo dõi một cây nhỏ với create/delete/move và không cần mediation, inotify thường nhẹ và đơn giản hơn.

2. Notification event và permission event

Notification event như FAN_OPEN, FAN_MODIFY, FAN_CLOSE_WRITE chỉ thông báo; consumer xử lý bất đồng bộ. Permission event như FAN_OPEN_PERM, FAN_ACCESS_PERM, FAN_OPEN_EXEC_PERM chặn thread gây sự kiện cho tới khi listener ghi FAN_ALLOW hoặc FAN_DENY.

Permission path nằm trực tiếp trên latency của mọi open được mark. Không gọi network service chậm, DNS hoặc model scan không có deadline trong hot path. Một listener treo có thể treo workload trên filesystem.

3. Chọn notification class và capability

  • FAN_CLASS_NOTIF: chỉ notification, mặc định.
  • FAN_CLASS_CONTENT: permission decision sau khi nội dung cuối có sẵn; phù hợp scanner.
  • FAN_CLASS_PRE_CONTENT: listener có thể chuẩn bị/sửa nội dung trước access; phù hợp storage manager.

CONTENT và PRE_CONTENT yêu cầu CAP_SYS_ADMIN. Từ Linux 5.13 (và backport 5.10.220), unprivileged user có thể tạo group hạn chế, nhưng không nhận permission events, phải dùng file-handle reporting và chỉ mark inode—không được mark mount/filesystem.

Đừng cấp container rộng CAP_SYS_ADMIN chỉ để lấy telemetry. Tách agent đặc quyền nhỏ, seccomp/LSM hardening và IPC tối thiểu.

4. Khởi tạo group cho permission scanner

#define _GNU_SOURCE
#include <fcntl.h>
#include <sys/fanotify.h>

int fan_fd = fanotify_init(
    FAN_CLASS_CONTENT | FAN_CLOEXEC | FAN_NONBLOCK,
    O_RDONLY | O_CLOEXEC | O_LARGEFILE
);

if (fan_fd == -1) {
    /* CONFIG_FANOTIFY, capability, limits, flags */
}

FAN_CLOEXEC ngăn listener lọt qua exec. FAN_NONBLOCK phù hợp event loop epoll; khi hết event, read() trả EAGAIN. event_f_flags mô tả FD kernel mở cho event object. Scanner chỉ đọc nên dùng O_RDONLY; mở write có thể gặp ETXTBSY với executable đang chạy.

5. Mark inode, mount hay filesystem?

uint64_t mask = FAN_OPEN_PERM |
                FAN_OPEN_EXEC_PERM |
                FAN_CLOSE_WRITE;

if (fanotify_mark(fan_fd,
                  FAN_MARK_ADD | FAN_MARK_MOUNT,
                  mask,
                  AT_FDCWD,
                  "/srv/data") == -1) {
    /* handle EINVAL, EPERM, ENOSPC, ENODEV */
}
  • Inode/directory mark: phạm vi hẹp; directory monitoring không tự recursive.
  • FAN_MARK_MOUNT: mọi object trong một mount; không bao gồm mount con gắn sau theo cách bạn có thể giả định.
  • FAN_MARK_FILESYSTEM: mọi view/mount của cùng filesystem instance.

Directory-recursive bằng cách thêm mark khi tạo subdirectory có race: event có thể xảy ra trước khi mark mới được gắn. Mount/filesystem mark loại bỏ race cây thư mục nhưng cần đặc quyền và tăng event volume.

6. Đọc buffer đúng cách

char buffer[64 * 1024];
ssize_t len = read(fan_fd, buffer, sizeof(buffer));

if (len == -1 && errno == EAGAIN)
    return;
if (len < 0)
    fatal("fanotify read");

struct fanotify_event_metadata *m;
m = (struct fanotify_event_metadata *) buffer;

while (FAN_EVENT_OK(m, len)) {
    if (m->vers != FANOTIFY_METADATA_VERSION)
        fatal("fanotify ABI mismatch");

    handle_event(fan_fd, m);
    m = FAN_EVENT_NEXT(m, len);
}

Một read() có thể trả nhiều event và event_len có thể lớn hơn struct cố định khi có information record. Không tăng pointer bằng sizeof(struct fanotify_event_metadata). Luôn dùng macro và kiểm tra runtime version.

7. Phản hồi permission trước deadline

static int answer(int fan_fd, int event_fd, bool allowed)
{
    struct fanotify_response response = {
        .fd = event_fd,
        .response = allowed ? FAN_ALLOW : FAN_DENY,
    };

    return write(fan_fd, &response, sizeof(response));
}

Với mỗi permission event đã đọc, phải gửi đúng một response gắn với metadata->fd. Nếu deny, caller thường nhận EPERM. Kernel mới hỗ trợ một số errno tùy class/version, nhưng feature-detect thay vì hard-code theo header build.

Thiết kế timeout cục bộ: cache verdict theo content identity/version; scan nhanh trong process; nếu vượt budget, chọn fail-open hoặc fail-closed theo loại filesystem. Root filesystem và data quarantine thường cần policy khác nhau.

8. Luôn đóng event FD và pidfd

Ở mode FD truyền thống, mỗi event hợp lệ có thể mang một FD đã mở tới object. Consumer sở hữu và phải đóng nó sau khi response/scan hoàn tất:

if (m->fd >= 0) {
    bool allowed = inspect_file(m->fd, m->mask, m->pid);

    if (m->mask & (FAN_OPEN_PERM |
                   FAN_ACCESS_PERM |
                   FAN_OPEN_EXEC_PERM)) {
        if (answer(fan_fd, m->fd, allowed) == -1)
            log_response_error(errno);
    }

    close(m->fd);
}

Quên close sẽ làm cạn file-descriptor limit. Nếu dùng FAN_REPORT_PIDFD, pidfd trong information record cũng phải đóng. Không dùng PID lâu dài làm identity vì PID có thể tái sử dụng; pidfd an toàn hơn cho lifecycle.

9. File handle mode và information records

FAN_REPORT_FID trả file handle thay cho event FD, cần cho các event create/delete/move và phù hợp queue telemetry lớn. Các cờ như FAN_REPORT_DIR_FID, FAN_REPORT_NAME, FAN_REPORT_TARGET_FID, FAN_REPORT_PIDFD có thể tạo nhiều information record trong một event.

Không giả định thứ tự record. Lặp từ metadata_len tới event_len, đọc fanotify_event_info_header.info_type và validate len trước khi cast. File handle là opaque; mở lại bằng open_by_handle_at() cần mount FD và capability, và object có thể đã bị xóa/đổi tên.

Permission events không kết hợp với group nhận diện object bằng file handle; chọn architecture riêng cho mediation và telemetry nếu cần cả hai.

10. FAN_Q_OVERFLOW là mất dữ liệu thật

Khi queue vượt /proc/sys/fs/fanotify/max_queued_events, event bị drop và kernel phát FAN_Q_OVERFLOW. Sau overflow, internal model của agent không còn đáng tin.

Runbook:

  1. tăng counter và phát alert ngay;
  2. đánh dấu state dirty;
  3. reconcile bằng full scan hoặc nguồn sự thật khác;
  4. giảm event volume bằng mark/ignore mask;
  5. tối ưu drain loop, buffer và worker backpressure;
  6. chỉ tăng kernel limit sau capacity test.

FAN_UNLIMITED_QUEUE cần đặc quyền và có thể chuyển vấn đề thành cạn kernel memory. Nó không thay thế flow control.

11. Failure mode quan trọng: listener đóng sẽ allow

Khi mọi FD của fanotify group đóng, pending permission events được đặt thành allowed.

Do đó fanotify permission listener không tự fail-closed khi daemon crash. Nếu nó là control bảo mật:

  • dùng systemd watchdog và restart policy;
  • gating readiness trước khi mở workload;
  • alert khi listener mất mark hoặc restart loop;
  • giữ DAC, mount flags, LSM, IMA/EVM hoặc policy kernel khác làm lớp cưỡng chế;
  • không quảng cáo fanotify scanner là sandbox hoàn chỉnh.

Test kill -9 listener trong staging và ghi lại hành vi ứng dụng đang bị block.

12. Những gì fanotify không quan sát đầy đủ

  • Remote change trên network filesystem không nhất thiết được báo.
  • Access/modify qua mmap(), msync(), munmap() không được cover như nhiều người kỳ vọng.
  • Directory event không đồng nghĩa thay đổi child nếu không yêu cầu event phù hợp.
  • Path lấy qua /proc/self/fd/N chỉ là view tại thời điểm đọc; rename/unlink tạo race và suffix “(deleted)”.
  • Bind mount và namespace có thể tạo nhiều path tới cùng object.
  • Queue events có thể coalesce; không dùng số event làm audit ledger chính xác tuyệt đối.

Dùng FD/file handle và metadata object thay vì path string làm identity khi quyết định bảo mật.

13. Kiến trúc production

Tách pipeline thành:

  1. Reader đặc quyền: epoll/read nhanh, validate event, giữ quyền nhỏ nhất.
  2. Verdict cache: key theo filesystem/object identity và version metadata.
  3. Scanner pool: bounded queue, deadline và concurrency limit.
  4. Responder: gửi permission response trước budget, đóng mọi FD.
  5. Reconciler: full scan khi overflow/restart.
  6. Metrics: queue lag, verdict latency, allow/deny, overflow, open FD, restart và scan timeout.

Không chuyển raw event FD qua process bằng số nguyên; nếu tách process, dùng Unix socket SCM_RIGHTS. Xác thực peer và đóng bản sao ở mọi nhánh lỗi.

Checklist production

  • Kernel có CONFIG_FANOTIFY và access-permission support nếu cần.
  • Chọn đúng NOTIF/CONTENT/PRE_CONTENT và capability tối thiểu.
  • Chọn inode, mount hay filesystem mark theo coverage thực tế.
  • Event loop dùng macro, kiểm tra ABI version và record length.
  • Mọi permission event có đúng một response trong deadline.
  • Mọi event FD/pidfd được đóng ở cả success và error path.
  • FAN_Q_OVERFLOW kích hoạt alert và reconciliation.
  • Listener crash policy tính đến việc pending event được allow.
  • Không dựa vào path/PID string như identity ổn định.
  • Test burst, rename, unlink, mount namespace, mmap, crash và resource exhaustion.

Kết luận

Fanotify là primitive mạnh để quan sát hoặc mediate filesystem access ở quy mô mount/filesystem. Một implementation đúng phải xem event queue là hữu hạn, permission response là real-time contract, event FD là tài nguyên sở hữu và crash listener là tình huống có thể mở quyền. Khi kết hợp deadline, overflow recovery, stable identity và lớp policy kernel độc lập, fanotify có thể làm nền cho scanner và storage agent đáng tin cậy.

Thảo luận

Bình luận 0

Đăng nhập để bình luận

Bạn cần có tài khoản để tham gia thảo luận và trả lời độc giả khác.

Đăng nhậpĐăng ký

Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.