# vcheck 通过 SSH 审计远程 Linux 主机是否存在 Copy Fail 和 Dirty Frag 内核模块漏洞,并可选择应用缓解措施: | CVE | 名称 | 受影响的模块 | | -------------- | ------------------ | ------------------------------------------------- | | CVE-2026-31431 | Copy Fail | `algif_aead` | | CVE-2026-43284 | Dirty Frag (IPsec) | `esp4`, `esp6`, `ipcomp4`, `ipcomp6`, `xfrm_user` | | CVE-2026-43500 | Dirty Frag (RxRPC) | `rxrpc`, `kafs` | 对于每个受影响的模块,vcheck 会报告它当前是否已加载、是否内置于运行的内核中、在内核日志中是否有过去的痕迹、是否有活跃的 `AF_ALG` 套接字(仅针对 Copy Fail),以及是否已在 `/etc/modprobe.d/` 下被列入黑名单。 使用 `-fix` 时,vcheck 会报告初始状态,为尚未被列入黑名单的任何模块写入一个 `cve-XXXX-XXXXX-disable.conf` 片段,然后重新执行检查并报告最终状态。使用 `-fix -unload` 时,vcheck 还会尝试卸载修复前已加载的受影响模块,然后通过最终扫描验证它们是否仍在加载。使用 `-fix -rebuild-initramfs` 时,vcheck 会在写入片段后仅为当前运行的内核重建 initramfs,从而将黑名单嵌入下一次启动映像中。较旧的内核条目保留其原始 initramfs 作为回退。 ## 仅在仅检查运行后使用 `-fix` **务必先运行不带 `-fix` 的 vcheck。** 阅读报告并确认受影响模块在此主机上可以安全禁用后,再使用 `-fix` 重新运行。禁用合法工作负载所依赖的内核模块可能会影响用户并破坏应用程序。 特别需要注意: - 只有**当所有受影响模块当前均未加载**时 – 即每个模块的报告均为 `mitigated` 或 `module not blacklisted`(没有 `VULNERABLE` 或 `blacklisted but currently loaded` 行)– 才可将 `-fix` 视为安全。已加载的模块几乎总是意味着主机上某些内容正在活动使用它;在黑名单之前确认这一点。 - IPsec 模块(`esp4`、`esp6`、`ipcomp4`、`ipcomp6`、`xfrm_user`)是所有 IPsec/strongSwan/基于 IPsec 的 WireGuard/IKE 部署所必需的。`ipcomp4`/`ipcomp6` 模块实现了 IPComp 负载压缩,即使在未明确配置时也可能作为 IPsec SA 的一部分自动协商。不要在 VPN 网关、IPsec 端点或任何 `ip xfrm policy` 返回规则的主机上将它们列入黑名单。请注意,`xfrm_algo` 框架模块特意**不**在此列表中 — 根据供应商指南(Red Hat、Ubuntu、AWS),阻止 ESP 和 IPComp 协议模块以及 `xfrm_user` netlink 配置接口就足够了,而将 `xfrm_algo` 列入黑名单会破坏所有其他 xfrm 转换,没有任何额外好处。 - RxRPC 模块(`rxrpc`、`kafs`)是所有挂载 AFS 文件系统的主机所必需的。禁用它们将在下次启动时破坏这些挂载。 - `algif_aead` 通过 `AF_ALG` 套接字族暴露内核密码学功能。应用程序代码很少直接使用它,但在将其列入黑名单之前,请通过列出实时套接字 (`ss -p --af-alg`) 并检查用户空间消费者来确认。 vcheck 写入的黑名单片段仅在模块加载时生效(通常是下次启动,或在系统空闲时执行 `modprobe -r <module>`)。一个已经加载的模块即使在 `-fix` 之后也会继续运行——vcheck 会将其报告为 `blacklisted but currently loaded; run 'modprobe -r' or reboot`。将 `-unload` 与 `-fix` 一起传递时,vcheck 会在写入黑名单片段后为已加载的受影响模块运行 `modprobe -r`。仅当你已确认这些模块可以从运行中的内核安全移除时再使用它。 将 `-rebuild-initramfs` 与 `-fix` 一起传递时,会为**当前运行的内核独有**重新生成 initramfs(Debian/Ubuntu 上为 `update-initramfs -u -k $(uname -r)`,RHEL/Fedora 上为 `dracut -f --kver $(uname -r)`)。其他已安装的内核保留其现有 initramfs 不变,因此如果重启后出现问题,你可以从启动菜单中选择较旧的内核条目进行恢复。未来的内核安装会从当前的 `/etc/modprobe.d/` 状态重建自己的 initramfs,因此黑名单会自动传播,无需重新运行 vcheck。如果既不存在 `update-initramfs` 也不存在 `dracut`(例如 Arch、Alpine、不可变镜像),vcheck 会发出警告并继续——请在重启前使用发行版工具手动重建。 重建可能需要几分钟(尤其是在驱动程序众多的主机上使用 `dracut`),这可能会超过诊断用的 `-command-timeout`。它使用自己的 `-initramfs-timeout`(默认 `10m`)来运行,以便重建获得所需的时间,而快速检查保持其紧凑的预算。对于慢速硬件,可增大 `-initramfs-timeout`,或传入 `0` 完全禁用超时。在长时间运行的远程命令期间,vcheck 默认每 `30s` 发送一次 SSH 保活请求,以防止 NAT/防火墙空闲计时器断开连接。使用 `-ssh-keepalive` 调整此值,或传入 `0` 禁用它。 ## 安装 **Homebrew(macOS):** ```bash brew install --cask krisiasty/tap/vcheck ``` **预编译二进制文件** 适用于 Linux、macOS 和 Windows,发布在 [releases 页面](https://github.com/krisiasty/vcheck/releases) 上。 **从源代码编译**(需要 Go 1.26+): ```bash go install github.com/krisiasty/vcheck@latest ``` ## 使用方法 ```text vcheck -host HOST [flags] ``` | 标志项 | 默认值 | 描述 | | -------------------- | ------------ | ------------------------------------------------------------------------------------------ | | `-host` | (必需) | 远程主机 | | `-user` | `$USER` | 远程用户 | | `-port` | `22` | 远程 SSH 端口 | | `-agent` | `true` | 使用 SSH 代理进行身份验证 | | `-identity` | (空) | 私钥文件路径(如果加密则提示输入密码短语) | | `-password` | `false` | 提示输入 SSH 密码 | | `-insecure` | `false` | 接受尚未记录在 `known_hosts` 中的主机密钥;与已记录密钥不匹配时仍会失败 | | `-fix` | `false` | 为尚未被列入黑名单的模块写入 `/etc/modprobe.d` 片段 | | `-unload` | `false` | 与 `-fix` 一起使用时,在黑名单后卸载受影响的模块 | | `-rebuild-initramfs` | `false` | 与 `-fix` 一起使用时,仅为运行中的内核重建 initramfs(`update-initramfs` 或 `dracut`) | | `-skip-logs` | `false` | 跳过内核日志历史检查 | | `-timeout` | `15s` | SSH 连接超时 | | `-ssh-keepalive` | `30s` | SSH 保活间隔(`0` 禁用) | | `-command-timeout` | `30s` | 远程命令超时(`0` 禁用) | | `-initramfs-timeout` | `10m` | initramfs 重建步骤的超时(`0` 禁用);仅与 `-rebuild-initramfs` 一起使用 | | `-debug` | `false` | 增加日志详细程度 | | `-version` | `false` | 显示版本并退出 | `-agent`、`-identity` 或 `-password` 中至少有一个必须能产生可用的身份验证方法。方法按列出的顺序尝试。 目标主机上需要 sudo 权限。如果已配置无密码 sudo,工具将静默进行;否则会提示输入一次密码(输入被隐藏),并通过 `sudo -S` 为后续每个命令提供密码。 ## 退出代码 | 代码 | 含义 | | ---- | ------------------------------------------------------------------------------ | | `0` | 所有受影响的模块已列入黑名单、已卸载、未内置于内核且非活跃状态 | | `1` | 用法错误 | | `2` | SSH 连接失败 | | `3` | Sudo 身份验证失败 | | `4` | 一个或多个模块未列入黑名单(无当前暴露) | | `5` | 一个或多个模块当前已加载、内置于内核或正在使用中 | | `99` | 内部/检查失败 | ## 示例输出 ### 完全缓解的主机 ```console $ vcheck -host host.example.com -identity ~/.ssh/id_ed25519 INF connected user=ops host=host.example.com port=22 INF checking vulnerability cve=CVE-2026-31431 name="Copy Fail" INF checking vulnerability cve=CVE-2026-43284 name="Dirty Frag (IPsec)" INF checking vulnerability cve=CVE-2026-43500 name="Dirty Frag (RxRPC)" INF mitigated cve=CVE-2026-31431 module=algif_aead INF mitigated cve=CVE-2026-43284 module=esp4 INF mitigated cve=CVE-2026-43284 module=esp6 INF mitigated cve=CVE-2026-43284 module=ipcomp4 INF mitigated cve=CVE-2026-43284 module=ipcomp6 INF mitigated cve=CVE-2026-43284 module=xfrm_user INF mitigated cve=CVE-2026-43500 module=rxrpc INF mitigated cve=CVE-2026-43500 module=kafs ``` ### 未缓解且部分加载——纯检查 ```console $ vcheck -host host.example.com -identity ~/.ssh/id_ed25519 INF connected user=ops host=host.example.com port=22 INF checking vulnerability cve=CVE-2026-31431 name="Copy Fail" INF checking vulnerability cve=CVE-2026-43284 name="Dirty Frag (IPsec)" INF checking vulnerability cve=CVE-2026-43500 name="Dirty Frag (RxRPC)" INF mitigated cve=CVE-2026-31431 module=algif_aead ERR VULNERABLE cve=CVE-2026-43284 module=esp4 loaded=true ERR module not blacklisted cve=CVE-2026-43284 module=esp6 ERR module not blacklisted cve=CVE-2026-43284 module=ipcomp4 ERR module not blacklisted cve=CVE-2026-43284 module=ipcomp6 ERR module not blacklisted cve=CVE-2026-43284 module=xfrm_user ERR module not blacklisted cve=CVE-2026-43500 module=rxrpc ERR module not blacklisted cve=CVE-2026-43500 module=kafs ``` ### 同一主机使用 `-fix`——首次运行 ```console $ vcheck -fix -host host.example.com -identity ~/.ssh/id_ed25519 INF connected user=ops host=host.example.com port=22 INF checking vulnerability cve=CVE-2026-31431 name="Copy Fail" INF checking vulnerability cve=CVE-2026-43284 name="Dirty Frag (IPsec)" INF checking vulnerability cve=CVE-2026-43500 name="Dirty Frag (RxRPC)" INF findings before fix INF mitigated cve=CVE-2026-31431 module=algif_aead ERR VULNERABLE cve=CVE-2026-43284 module=esp4 loaded=true ERR module not blacklisted cve=CVE-2026-43284 module=esp6 ERR module not blacklisted cve=CVE-2026-43284 module=ipcomp4 ERR module not blacklisted cve=CVE-2026-43284 module=ipcomp6 ERR module not blacklisted cve=CVE-2026-43284 module=xfrm_user ERR module not blacklisted cve=CVE-2026-43500 module=rxrpc ERR module not blacklisted cve=CVE-2026-43500 module=kafs INF writing modprobe.d snippet path=/etc/modprobe.d/cve-2026-43284-disable.conf modules="[esp4 esp6 ipcomp4 ipcomp6 xfrm_user]" INF writing modprobe.d snippet path=/etc/modprobe.d/cve-2026-43500-disable.conf modules="[rxrpc kafs]" INF re-scanning after fix snippets_written=2 INF checking vulnerability cve=CVE-2026-31431 name="Copy Fail" INF checking vulnerability cve=CVE-2026-43284 name="Dirty Frag (IPsec)" INF checking vulnerability cve=CVE-2026-43500 name="Dirty Frag (RxRPC)" INF findings after fix INF mitigated cve=CVE-2026-31431 module=algif_aead ERR blacklisted but currently loaded; run 'modprobe -r' or reboot cve=CVE-2026-43284 module=esp4 INF mitigated cve=CVE-2024-43284 module=esp6 INF mitigated cve=CVE-2026-43284 module=ipcomp4 INF mitigated cve=CVE-2026-43284 module=ipcomp6 INF mitigated cve=CVE-2026-43284 module=xfrm_user INF mitigated cve=CVE-2026-43500 module=rxrpc INF mitigated cve=CVE-2026-43500 module=kafs ``` 黑名单已就位,但 `esp4` 在写入片段之前已加载到内核中。重启或在目标主机上运行 `sudo modprobe -r esp4` 以完全清除它。 ### 第二次运行——esp4 仍在加载 ```console $ vcheck -fix -host host.example.com -identity ~/.ssh/id_ed25519 INF connected user=ops host=host.example.com port=22 INF checking vulnerability cve=CVE-2026-31431 name="Copy Fail" INF checking vulnerability cve=CVE-2026-43284 name="Dirty Frag (IPsec)" INF checking vulnerability cve=CVE-2026-43500 name="Dirty Frag (RxRPC)" INF findings before fix INF mitigated cve=CVE-2026-31431 module=algif_aead ERR blacklisted but currently loaded; run 'modprobe -r' or reboot cve=CVE-2026-43284 module=esp4 INF mitigated cve=CVE-2026-43284 module=esp6 INF mitigated cve=CVE-2026-43284 module=ipcomp4 INF mitigated cve=CVE-2026-43284 module=ipcomp6 INF mitigated cve=CVE-2026-43284 module=xfrm_user INF mitigated cve=CVE-2026-43500 module=rxrpc INF mitigated cve=CVE-2026-43500 module=kafs INF fix: nothing to do — all affected modules already blacklisted ``` ### 第三次运行——`modprobe -r esp4` 之后 ```console $ vcheck -host host.example.com -identity ~/.ssh/id_ed25519 INF connected user=ops host=host.example.com port=22 INF checking vulnerability cve=CVE-2026-31431 name="Copy Fail" INF checking vulnerability cve=CVE-2026-43284 name="Dirty Frag (IPsec)" INF checking vulnerability cve=CVE-2026-43500 name="Dirty Frag (RxRPC)" INF mitigated cve=CVE-2026-31431 module=algif_aead INF mitigated cve=CVE-2026-43284 module=esp4 INF mitigated cve=CVE-2026-43284 module=esp6 INF mitigated cve=CVE-2026-43284 module=ipcomp4 INF mitigated cve=CVE-2026-43284 module=ipcomp6 INF mitigated cve=CVE-2026-43284 module=xfrm_user INF mitigated cve=CVE-2026-43500 module=rxrpc INF mitigated cve=CVE-2026-43500 module=kafs ``` ### 使用 `-rebuild-initramfs` ```console $ vcheck -fix -rebuild-initramfs -host host.example.com -identity ~/.ssh/id_ed25519 ... INF writing modprobe.d snippet path=/etc/modprobe.d/cve-2026-43284-disable.conf modules="[esp4 esp6 ipcomp4 ipcomp6 xfrm_user]" INF writing modprobe.d snippet path=/etc/modprobe.d/cve-2026-43500-disable.conf modules="[rxrpc kafs]" INF rebuilding initramfs for running kernel timeout=10m0s INF initramfs rebuilt for running kernel tool=update-initramfs INF re-scanning after fix snippets_written=2 ... ``` 仅重建当前运行内核的 initramfs。其他已安装的内核保留其现有 initramfs,因此启动菜单中的较旧条目在新建的 initramfs 引发问题时可作为有效的回退。在既没有 `update-initramfs` 也没有 `dracut` 的主机上,vcheck 会记录 `WRN no supported initramfs tool found ...` 并继续执行。 ### 首次连接的主机——`-insecure` ```console $ vcheck -insecure -host host.example.com -identity ~/.ssh/id_ed25519 WRN host key not in known_hosts; accepting due to -insecure host=host.example.com:22 remote=192.0.2.42:22 fingerprint=SHA256:AAAAEXAMPLEfingerPrint000000000000000000000 INF connected user=ops host=host.example.com port=22 INF checking vulnerability cve=CVE-2026-31431 name="Copy Fail" ... ``` `-insecure` 接受尚未在 `known_hosts` 中的主机。如果主机已记录但呈现不同的密钥,连接仍然会失败——该标志仅适用于“首次接触”的覆盖,并非用于抑制已知主机的中间人警告。 ## 检测详情 - **已加载:** 一次性获取 `lsmod` 的输出,并将模块名称与第一列匹配。 - **内置于内核:** 检查 `/lib/modules/$(uname -r)/modules.builtin` 和 `/sys/module`,因为 modprobe 片段无法禁用编译到运行内核中的代码。 - **已列入黑名单:** 搜索 `/etc/modprobe.d/` 下的每个文件(`grep -rE`),查找 `install <module> /bin/false` 指令——与 vcheck 使用 `-fix` 写入的形式相同。其他禁用形式(例如 `blacklist`)不会被识别。 - **历史活动:** 首先查阅 `journalctl -k`,回退到 `/var/log/kern.log`;每个模块保留最后五条匹配行。传入 `-skip-logs` 可跳过此步骤。 - **活跃套接字(仅 `algif_aead`):** `ss -p --af-alg` 列出打开的 `AF_ALG` 套接字;除标题行以外的任何输出都计为活跃使用。 所有命令通过 `sudo` 运行,并由 `-command-timeout` 限定时间——需要特权访问才能读取 `/var/log/kern.log`、列出 `AF_ALG` 套接字以及在 `/etc/modprobe.d/` 下写入内容。使用 `-unload` 时,还需要特权访问才能运行 `modprobe -r`。使用 `-rebuild-initramfs` 时,vcheck 运行 `update-initramfs -u -k $(uname -r)`(Debian/Ubuntu)或 `dracut -f --kver $(uname -r)`(RHEL/Fedora),这些也需要 root 权限。