0xlane/pagecache-guard

GitHub: 0xlane/pagecache-guard

一个基于 fanotify 和 O_DIRECT 的 Linux 运行时完整性防护工具,用于检测并拦截页缓存篡改攻击(如 Dirty Pipe、Copy Fail),弥补传统文件完整性监控无法发现纯内存篡改的缺陷。

Stars: 2 | Forks: 2

# pagecache-guard **[中文文档](README.zh-CN.md)** 一个运行时完整性防护工具,用于检测并拦截 Linux 页缓存篡改攻击。 它使用 `fanotify` + `O_DIRECT` 将页缓存内容与磁盘上的内容进行比较,覆盖多种攻击面: - **SUID/SGID 二进制文件** — 拦截 `execve()`,阻止被篡改的二进制文件运行 - **守护进程执行的文件** — 进程树分析检测由 `crond`、`systemd`、`atd` 运行的文件 - **关键配置文件** — 对 `/etc/passwd`、`/etc/profile`、PAM 模块、`ld.so.preload` 进行 inode 级别的监控 - **共享库** — 定期扫描已映射的库 ## 为什么需要这个工具 页缓存损坏漏洞允许攻击者修改**只读**文件在内存中的内容: | CVE | 名称 | 年份 | O_DIRECT 可检测 | |-----|------|------|:-------------------:| | CVE-2026-43284 / CVE-2026-43500 | Dirty Frag | 2026 | ✅ | | CVE-2026-31431 | Copy Fail | 2026 | ✅ | | CVE-2022-0847 | Dirty Pipe | 2022 | ✅ | | CVE-2016-5195 | Dirty COW | 2016 | ❌ | 传统的安全工具(文件完整性监控器、镜像扫描器、fs-verity)通过页缓存读取数据,因此**无法检测**这些攻击——它们会将篡改后的数据视为“正常”。`O_DIRECT` 完全绕过页缓存,直接从磁盘读取,使其成为检测纯页缓存篡改(Copy Fail、Dirty Pipe、Dirty Frag)的唯一可靠方法。Dirty COW 是个例外——它会通过页回写将损坏的数据写回磁盘,因此 `O_DIRECT` 读取到的也是相同的篡改内容。Dirty COW 需要通过传统的文件完整性检查(AIDE / `rpm -V` / Tripwire)来检测。 ## 工作原理 ``` ┌──────────────────────────────────────────────────────────────┐ │ pagecache_guard (v0.2) │ │ │ │ FAN_OPEN_EXEC_PERM (mount mark) ────────────────────────── │ │ On execve(): │ │ 1. SUID/SGID binary? → O_DIRECT check (block/allow) │ │ 2. Parent is crond/systemd/atd? → O_DIRECT check │ │ 3. Neither? → FAN_ALLOW (zero overhead) │ │ │ │ FAN_OPEN_PERM (inode marks) ────────────────────────────── │ │ On open() of marked files: │ │ /etc/passwd, /etc/profile, PAM modules, ld.so.preload │ │ → O_DIRECT check (block/allow) │ │ │ │ Periodic O_DIRECT scan (background thread) ─────────────── │ │ Already-mapped shared libraries (libnss, libpam, etc.) │ │ → Alert only (cannot block, already mmap'd) │ └──────────────────────────────────────────────────────────────┘ ``` ``` flowchart TD A[Start Guard] --> B[Scan SUID/SGID files] B --> C[Set up fanotify\nmount marks + inode marks] C --> D{Event received} D --> E{SUID/SGID\nbinary?} E -- Yes --> G{UID = 0?} E -- No --> F{Parent is\nwatched daemon?} F -- Yes --> I F -- No --> P{Inode-marked\nfile?} P -- Yes --> I P -- No --> Q[FAN_ALLOW\nSkip] G -- Yes --> H[Skip check] G -- No --> I[O_DIRECT vs\nPage Cache] I --> J{Match?} J -- Yes --> K[FAN_ALLOW] J -- No --> L[FAN_DENY\nBlock + Alert] Q --> D H --> D K --> D L --> D ``` ## 快速开始 ``` # 仅 SUID 模式 (向后兼容,与 v0.1 相同) sudo python3 pagecache_guard.py /usr /bin /sbin # 完全保护 — SUID + daemon-exec + 关键文件 + 定期扫描 sudo python3 -m pagecache_guard \ --watch-daemon crond,anacron,atd,systemd \ --watch-file /etc/passwd /etc/profile /etc/ld.so.preload \ --watch-pam /lib64/security \ --watch-lib /lib64/libnss_files.so.2 /lib64/libpam.so.0 \ /usr /bin /sbin # 仅添加 daemon-exec 检测 (最简单的扩展) sudo python3 -m pagecache_guard --watch-daemon crond,systemd /usr # Dry-run 模式 (仅告警,不阻止) sudo python3 -m pagecache_guard --dry-run /usr # 定期重新扫描新的 SUID 文件 (每 5 分钟) sudo python3 -m pagecache_guard --rescan-interval 300 /usr # 记录到 syslog + 文件 sudo python3 -m pagecache_guard --syslog --log-file /var/log/pagecache_guard.log /usr # 同时检查 root 执行 sudo python3 -m pagecache_guard --check-root /usr ``` ## 示例输出 ``` 2026-05-09 14:20:01 INFO Scanning for SUID/SGID files in: /usr 2026-05-09 14:20:03 INFO Found 21 SUID/SGID files 2026-05-09 14:20:03 INFO Watching daemon parents: anacron, atd, crond, systemd 2026-05-09 14:20:03 INFO Auto-discovered 12 PAM modules in /lib64/security 2026-05-09 14:20:03 INFO Inode marks set for 15 files 2026-05-09 14:20:03 INFO Periodic scanner started: 2 libs, interval=5s 2026-05-09 14:20:03 INFO Guard active [ENFORCE] features=[SUID, daemon-exec, inode-watch(15), periodic-scan(2)] check_root=False # 已阻止被篡改的 SUID 二进制文件: 2026-05-09 14:20:38 WARNING [ALERT] BLOCKED pid=2677362 uid=1000 /usr/bin/su reason=suid (page cache tampered at offset 0) # 已阻止被篡改的 cron 脚本: 2026-05-09 14:21:00 WARNING [ALERT] BLOCKED pid=2677500 uid=0 /usr/local/bin/backup.sh reason=daemon:crond (page cache tampered at offset 128) # 已阻止被篡改的 PAM 模块: 2026-05-09 14:21:05 WARNING [ALERT] BLOCKED pid=2677510 uid=0 /lib64/security/pam_unix.so reason=inode_watch (page cache tampered at offset 4096) ``` 在用户侧: ``` $ /usr/bin/su bash: /usr/bin/su: Operation not permitted (exit 126) ``` ## 代码结构 ``` pagecache-guard/ ├── pagecache_guard/ # Python package (v0.2) │ ├── __init__.py │ ├── __main__.py # CLI entry point (argparse + orchestration) │ ├── config.py # Constants and libc handle │ ├── core.py # O_DIRECT read, integrity check, checksum cache │ ├── fanotify_handler.py # fanotify setup, mount marks, event loop │ ├── process_tree.py # /proc traversal for daemon-child detection │ ├── inode_watcher.py # FAN_OPEN_PERM inode marks for critical files │ └── periodic_scanner.py # Background thread for mapped-lib scanning ├── pagecache_guard.py # Backward-compatible single-file entry point ├── poc/ # Exploitation PoCs │ ├── host-attacks/ # 7 host-side attack path PoCs │ ├── poc_marker.py │ ├── verify_marker.py │ └── shocker_copyfail.py ├── README.md └── README.zh-CN.md ``` ## 系统要求 | 组件 | 推荐 | 最低 | 备注 | |-----------|-------------|---------|-------| | **内核** | >= 5.0 | >= 2.6.37 | 5.0+ 支持 `FAN_OPEN_EXEC_PERM`;在较旧的内核上自动降级为 `FAN_OPEN_PERM` | | **RHEL 8** | 4.18.0 | — | 已回移 `FAN_OPEN_EXEC_PERM`(已验证) | | **文件系统** | ext4 / XFS / Btrfs | — | 必须支持 `O_DIRECT` | | **权限** | root | `CAP_SYS_ADMIN` | fanotify 权限事件所需 | | **Python** | 3.6+ | 3.6 | 使用了 f-strings 和 `os.splice` | ## 检测范围 v0.2 将覆盖范围从仅限 SUID 扩展到了 **7/7** 种主机端攻击路径(PoC 见 `poc/host-attacks/`): | # | 攻击路径 | 机制 | 时机 | 能否拦截? | |---|-------------|-----------|--------|:----------:| | 1 | SUID/SGID 二进制文件覆盖 | `FAN_OPEN_EXEC_PERM` + SUID 检查 | 在 execve 时 | ✅ | | 2 | `/etc/passwd` UID 篡改 | `FAN_OPEN_PERM` inode 标记 | 在被 NSS 打开时 | ✅ | | 3 | PAM 模块绕过 | `FAN_OPEN_PERM` inode 标记 | 在认证期间被 dlopen 时 | ✅ | | 4 | 共享库(新加载) | `FAN_OPEN_PERM` inode 标记 | 在被 dlopen 打开时 | ✅ | | 4' | 共享库(已映射) | 周期性 O_DIRECT 扫描 | 轮询(默认 5 秒) | ❌ 仅告警 | | 5 | `/etc/profile` 命令注入 | `FAN_OPEN_PERM` inode 标记 | 在被 shell source 时 | ✅ | | 6 | Cron/systemd 执行的文件 | `FAN_OPEN_EXEC_PERM` + 父进程为守护进程 | 在 execve 时 | ✅ | | 7 | `ld.so.preload` 劫持 | `FAN_OPEN_PERM` inode 标记 | 在被 ld.so 打开时 | ✅ | | — | 容器逃逸(层共享) | 周期性 O_DIRECT 扫描 | 轮询 | ❌ 仅告警 | **7 种路径中的 6 种**提供实时拦截;只有已映射的共享库是仅告警模式(轮询)。 ## PoC 脚本 | 脚本 | 用途 | |--------|---------| | `poc/poc_marker.py` | 触发 Copy Fail 以将 `0xDEADBEEF` 写入文件的页缓存 | | `poc/verify_marker.py` | 验证标记是否可见(测试跨容器页缓存共享) | | `poc/shocker_copyfail.py` | Shocker + Copy Fail 组合 — 通过 `CAP_DAC_READ_SEARCH` 逃逸容器 | | `poc/host-attacks/` | **7 种主机端利用 PoC**:passwd UID、PAM 绕过、共享库、profile 注入、cron 脚本、ld.so.preload、SUID ELF(参见 [README](poc/host-attacks/README.md)) | **警告**:PoC 脚本需要存在漏洞的内核,且仅供授权研究使用。 ## 技术细节 ### 为什么使用 O_DIRECT? 页缓存损坏攻击修改了内核内存中的文件缓存,而不经过 VFS 写入路径。这意味着: - **无脏页标志** — `sync` 不会将损坏的数据刷新到磁盘 - **文件完整性监控失效** — AIDE/OSSEC 等工具通过页缓存读取,会将篡改后的数据视为正常 - **镜像扫描器失效** — Trivy/Grype 扫描的是压缩后的层 blob,而不是页缓存 - **`docker diff` 失效** — 仅检查 overlayfs 上层的更改 - **fs-verity 失效** — 仅在磁盘到缓存读取时验证,无法检测缓存内的修改 `O_DIRECT` 是唯一能够绕过页缓存直接从块设备读取的标准 POSIX 机制,这使其在检测此类攻击方面具有独特优势。 ### 为什么跳过 root? Root 已经拥有全部权限 — 对于 root 用户而言,SUID 提权毫无意义。跳过 root 可以减少开销,并避免系统服务产生的干扰噪音。 在容器逃逸场景中,攻击者(作为容器 root)损坏了页缓存,但执行被篡改 SUID 二进制文件的**受害者**是主机上的非 root 用户 — 防护工具能正确拦截此类行为。 ### 正常更新期间的误报 如果某个 SUID 二进制文件正在更新(例如通过 `yum update`),页缓存和磁盘内容可能会暂时不一致。然而,Linux 内核会阻止执行具有活动写入文件描述符的文件(`ETXTBSY`),因此正常的更新过程不会触发误报拦截。 ## 相关研究 **作者的研究文章:** - [深入理解 Copy Fail:Linux 页缓存漏洞的根本原因、利用与检测](https://reinject.top/en/posts/linux-security/copy-fail-cve-2026-31431/) **参考链接:** - [Copy Fail — xint.io](https://xint.io/posts/copy-fail-cve-2026-31431/) — 原始漏洞披露和技术分析 - [NVD 上的 CVE-2026-31431](https://nvd.nist.gov/vuln/detail/CVE-2026-31431) - [内核修复提交](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=a664bf3d603d) ## 许可证 MIT
标签:0day挖掘, API接口, Claude, Copy Fail, CVE检测, Dirty COW, Dirty Frag, Dirty Pipe, DNS 解析, eBPF替代方案, fanotify, Hpfeeds, O_DIRECT, PoC, SGID, SUID, Web报告查看器, 共享库保护, 内存安全, 内存页缓存篡改, 内核安全, 安全防护, 提权漏洞, 暴力破解, 本地提权, 系统安全加固, 系统调用, 网络安全, 运行时完整性, 配置文件监控, 隐私保护, 页缓存