文章总结: 本文披露Linux内核NFS/NFSD子系统四个漏洞:CVE-2026-89710pNFS布局段引用泄漏(三次回归)、CVE-2026-89713NFSD截断权限TOCTOU竞态、CVE-2026-89711NFSDWARN_ON触发空指针路径、CVE-2026-89715NFSlocalio引用泄漏。漏洞可致内存耗尽、数据完整性破坏及netns无法销毁。建议升级内核至含修复commit的稳定版本,并定期监控NFS客户端内存使用。 综合评分: 88 文章分类: 漏洞分析,应急响应,解决方案
Linux NFS/NFSD 四连击:布局泄漏 + 空指针 + TOCTOU + 引用泄漏
撅人
2026年9月13日 15:50 广东
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
🟠 导语 · NFS 子系统的四个经典内核 bug
运行 NFS 服务器或 pNFS 客户端的内核运维团队请注意!🚨
2026 年 9 月 12 日,Linux 内核 NFS/NFSD 子系统一次性披露四个漏洞,涵盖引用泄漏、空指针解引用、TOCTOU 竞态和 WARN_ON 触发——四个 bug 都是内核安全中反复出现的经典模式。
按影响排序:
• CVE-2026-89710:pNFS 布局段引用泄漏——修复 drain 被删除后三次回归,内存持续泄漏
• CVE-2026-89713:NFSD 截断权限 TOCTOU——append-only 文件可被并发截断绕过权限检查
• CVE-2026-89711:NFSD WARN_ON 触发——NFS reexport 场景下触发空指针路径
• CVE-2026-89715:NFS localio 引用泄漏——阻止 netns 销毁,持久资源泄漏
四个漏洞都不是”远程代码执行”级别的高危——但长时间运行会导致内存耗尽、数据完整性破坏、网络命名空间无法销毁等慢性问题。在长时间运行的 NFS 服务器上,这些 bug 的累积影响不可忽视。
🔍 漏洞速览
| | | | | | | — | — | — | — | — | | CVE | 89710 | 89713 | 89711 | 89715 | | 子系统 | pNFS (NFSv4.1) | NFSD setattr | NFSD mode_check | NFS localio | | 类型 | 引用泄漏(内存泄漏) | TOCTOU 竞态 | WARN_ON 触发 / 空指针 | 引用泄漏 | | 根因 | drain 被删后三次回归 | 截断权限检查在锁外 | NFS reexport 下 dentry 无 lookup | 错误路径未释放引用 | | 影响 | 内存持续泄漏 → OOM | append-only 文件被截断 | WARN_ON 触发 panic / 空指针 | netns 无法销毁 | | 严重度 | 中高 | 中高 | 中 | 中 |
📦 CVE-2026-89710 详解:pNFS 布局段引用泄漏(三次回归 bug)
这个漏洞的核心:pnfs_layout_process() 的 out_forget 路径不清理 free_me 列表,导致布局段引用泄漏。这个 drain 被加了、删了、又没加回来——三次回归。
第 1 层:pNFS 布局段是什么?
pNFS(Parallel NFS)是 NFSv4.1 的并行扩展,允许客户端直接访问数据服务器(DS)进行数据传输,绕过元数据服务器(MDS)。”布局段”(layout segment)是 MDS 分配给客户端的元数据,描述客户端可以访问哪些数据块的什么范围。每个布局段有引用计数——布局头(layout header)持有引用,客户端使用时增加引用,使用完毕后释放。
当 MDS 返回新的布局状态 ID 时,pnfs_layout_process() 需要处理旧的布局段——调用 pnfs_mark_matching_lsegs_return() 把旧段标记为无效并移到 free_me 列表,然后跳转到 out_forget。
第 2 层:out_forget 路径不 drain free_me
问题在于——out_forget 跳转目标从不调用 pnfs_free_lseg_list() 来释放 free_me 列表中的段。只有成功路径才调用 drain。对于空闲缓存段,布局头是唯一引用持有者——引用计数在第一次递减时归零,段被移到 free_me 但从不释放。每次 MDS 返回新状态 ID 就泄漏一批布局段。
// 漏洞代码(简化)
pnfs_layout_process() {
// 新 stateid 到来,旧段移到 free_me
pnfs_mark_matching_lsegs_return(…, &free_me);
goto out_forget;
out_forget:
// ⚠️ 不调用 pnfs_free_lseg_list(&free_me)!
// 段被移到 free_me 但从不释放 → 泄漏
}
第 3 层:三次回归的 commit 历史
这个 drain 被加了、删了、又没加回来——三次回归:
✅ Commit 814b8497(”Fix a layout segment leak”)——第一次修复,在 out_forget 路径添加了 pnfs_free_lseg_list(&free_me) drain 调用
❌ Commit 08bd8dbe(”Try to return invalid layout”)——第一次回归,把 drain 目标从 free_me 改为 lo->plh_return_segs(在别处 drain),删除了对 free_me 的 drain
❌ Commit fb700ef0(”Simplify layout return”)——第二次回归,把目标从 plh_return_segs 改回 free_me,但没有恢复 drain 调用
✅ 本次修复——恢复 pnfs_free_lseg_list() 调用,第三次修复同一个 bug
✅ 修复:恢复 out_forget 路径的 pnfs_free_lseg_list(&free_me) 调用。
🔒 CVE-2026-89713 详解:NFSD 截断权限 TOCTOU 竞态
这个漏洞的核心:nfsd_setattr() 在 inode_lock() 之前用无锁读取的文件大小判断是否需要截断权限检查,但实际截断在锁内执行——TOCTOU 窗口允许并发 append 绕过 append-only 检查。
第 1 层:append-only 文件的保护机制
Linux 的 append-only 文件(通过 chattr +a 设置)只允许追加数据,不允许截断。IS_APPEND(inode) 检查会拒绝任何缩小文件大小的操作。VFS 的 truncate 系统调用在调用 notify_change() 前自己做 append-only 检查。
第 2 层:TOCTOU 窗口
nfsd_setattr() 在获取 inode_lock() 之前,用无锁读取的 i_size 判断请求的大小是否需要 NFSD_MAY_TRUNC。但实际的 ATTR_SIZE 更新是在锁内由 notify_change() 执行的。
TOCTOU 窗口:如果客户端发来的 SETATTR 请求大小在当时不缩小文件(不需要截断权限),一个并发的 append 操作可以在 nfsd_setattr() 获取 inode_lock() 之前扩展文件。然后 notify_change() 在锁内执行了一个真实的截断操作——但没有经过 NFSD_MAY_TRUNC 检查(该检查会拒绝 IS_APPEND(inode))。
① 客户端发送 SETATTR,请求大小 = 当前 i_size(不缩小)→ 不需要 NFSD_MAY_TRUNC
② 并发 append 扩展文件(i_size 增大)
③ nfsd_setattr() 获取 inode_lock()
④ notify_change() 执行 ATTR_SIZE → 实际截断了文件!
⑤ append-only 检查被绕过 → 数据丢失 💥
✅ 修复方案
1️⃣ 将写计数获取与截断权限检查分离——get_write_access() 在锁内 setattr 工作前调用
2️⃣ 在 inode_lock() 后重新检查——请求大小是否低于 i_size_read(inode),在 notify_change(ATTR_SIZE) 之前
3️⃣ 避免无锁的 i_size 读取——所有决策基于锁内读取的大小
⚠️ CVE-2026-89711 详解:NFSD WARN_ON 触发空指针路径
这个漏洞的核心:nfsd_mode_check() 中的 WARN_ON_ONCE 基于一个错误假设——认为所有目录 dentry 都有 i_op->lookup。NFS reexport 场景下这个假设不成立,WARN_ON 被触发。
第 1 层:错误假设的来源
Commit e75b23f9e323(”check d_can_lookup in fh_verify of directories”)在 nfsd_mode_check() 中添加了 WARN_ON_ONCE,基于一个假设:所有目录类型的 dentry 都有 i_op->lookup 方法。
第 2 层:NFS reexport 下的反例
当 NFSD 导出一个 NFS 文件系统(NFS reexport)时,nfsd_mode_check() 遇到的 dentry 可能没有 i_op->lookup——这发生在 NFS 挂载点(NFS_ATTR_FATTR_MOUNTPOINT)和 NFSv4 引用(NFS_ATTR_FATTR_V4_REFERRAL)的处理中。
后果:
• 如果内核配置了 panic_on_warn,WARN_ON 会触发 kernel panic
• 如果不 panic,后续的 lookup_one_unlocked() → __lookup_slow() 会调用 inode->i_op->lookup()——没有 NULL 检查,空指针解引用
修复保留了 nfserr_notdir 返回值——它在没有 lookup 方法时返回错误,阻止客户端 LOOKUP 进入这类 dentry,避免空指针解引用。只是删除了 WARN_ON。
✅ 修复:删除 WARN_ON_ONCE(),保留 nfserr_notdir 返回。
🔗 CVE-2026-89715 详解:NFS localio 引用泄漏阻止 netns 销毁
这个漏洞的核心:nfs_uuid_add_file() 与 nfs_uuid_put() 竞态时返回 -ENXIO,错误路径不释放 caller-owned 的 nfsd_file 引用和 nfsd_net 引用——每次发生泄漏一个 nfsd_file 和一个 netns 引用。
第 1 层:竞态条件
当 nfs_uuid_add_file() 与 nfs_uuid_put() 正在销毁 uuid->net 时发生竞态,nfs_uuid_add_file() 返回 -ENXIO,不通过 rcu_assign_pointer() 发布 nfl->nfs_uuid。
第 2 层:泄漏了什么?
nfsd_open_local_fh() 返回 localio 时持有一个 caller-owned +1 nfsd_file 引用(来自 nfsd_file_get())和一个 entry-time nfsd_net 引用(来自 nfsd_net_try_get()),嵌入为 nf->nf_net。两者在错误路径上泄漏。
每次泄漏钉住一个 nfsd_file(及底层 struct file、dentry、inode)和一个 nfsd_net_ref → 阻止 nfsd_net 和 netns 销毁。
第 3 层:关闭路径是 no-op
错误路径上 nfl->nfs_uuid 未发布,关闭路径 nfs_close_local_fh() 读取 nfs_uuid = rcu_dereference(nfl->nfs_uuid) 得到 NULL,直接返回——关闭路径永远什么都不做。
✅ 修复方案
1️⃣ 通过已有的 helper 释放 caller-owned file ref 和 net ref——使用栈本地 RCU 指针让 helper 能 xchg 取出
2️⃣ 设置 localio = ERR_PTR(-ENXIO)——调用者不会解引用已清除的 slot
3️⃣ trailing nfs_to_nfsd_net_put(net) 释放外层 net ref——三个 nfsd_net_try_get() 在错误分支上都平衡
⏱️ 3 秒自查
1. 检查内核版本
uname -r
各稳定分支修复版本(参考):
6.12.x / 6.18.x / 7.1.x / 7.2.x 分支
未升级到最新稳定版 → 可能受影响
2. 检查 NFS/pNFS 是否在使用
检查是否加载了 nfs/nfsd 模块
lsmod | grep -E “nfs|nfsd”
检查是否有 NFS 挂载
mount | grep nfs
检查是否导出 NFS(NFSD)
cat /proc/fs/nfsd/threads 2>/dev/null
有输出 → 在运行 NFSD
3. 检查 dmesg 中的 NFS 相关警告
dmesg | grep -iE “WARN|nfsd_mode_check|pnfs|layout.*leak|localio”
检查是否有 append-only 文件被异常截断
lsattr /your/export/dir/ | grep ‘+a’
有 +a 属性的文件 → CVE-89713 攻击面
🛡️ 修复方案
✅ 方案一:升级内核(根治)
将内核升级到包含以下 commit 的稳定版本:
CVE-89710 修复 commit
32ac1b0b / 36e3f13b / 7c5b0e81 / ee5a386c
CVE-89711 修复 commit
7ef182a8 / a275de3b / aa0cf48a / b55b4d88
CVE-89713 修复 commit
3afa17d9 / 44086254 / b778e0e0 / d8352da1
CVE-89715 修复 commit
5215e734 / 9f59b054 / ca018c19
⚠️ 方案二:临时缓解
CVE-89710(pNFS 布局泄漏):
• 定期重启 NFS 客户端释放泄漏的布局段
• 监控 pNFS 客户端内存使用,发现持续增长时重启
CVE-89713(TOCTOU 截断):
• 对 append-only 文件进行额外审计
• 避免在 NFS 导出的 append-only 文件上并发 append + SETATTR
CVE-89711(WARN_ON):
• 避免在 panic_on_warn=1 的系统上做 NFS reexport
• 如不需要 reexport,避免嵌套 NFS 导出
CVE-89715(localio 引用泄漏):
• 如不使用 localio 功能,可禁用 NFS localio
• 定期检查 netns 是否有残留无法销毁
🕵️ 排查清单
☐ 确认内核版本是否包含四个 CVE 的修复 commit
☐ 检查是否在使用 pNFS(CVE-89710 攻击面)
☐ 检查 NFS 导出目录中是否有 append-only 文件(CVE-89713 攻击面)
☐ 检查是否在做 NFS reexport(CVE-89711 攻击面)
☐ 检查是否启用 NFS localio(CVE-89715 攻击面)
☐ 检查 dmesg 中是否有 NFS 相关的 WARN 或 panic 记录
☐ 检查 NFS 客户端内存是否持续增长(CVE-89710 泄漏迹象)
☐ 检查是否有 netns 无法销毁的情况(CVE-89715 泄漏迹象)
⚠️ 安全提醒
这四个漏洞揭示了内核安全中两个反复出现的经典模式——“回归 bug”和“TOCTOU 竞态”。
关于”回归 bug”的教训(CVE-89710): 这个漏洞最令人沮丧的是——它被修了三次。Commit 814b8497 第一次修复,commit 08bd8dbe 第一次回归,commit fb700ef0 第二次回归(改回原来的目标但忘了恢复 drain 调用)。内核代码的回归是一个系统性问题——代码审查时往往只关注”新功能是否正确”,而忽略了”现有修复是否被破坏”。当重构代码时,如果改动了 drain 的目标列表,就必须确保所有路径仍然有对应的 drain。这类 bug 在内核中并不罕见——类似的引用泄漏回归在 VFS、内存管理等子系统都发生过。
关于”TOCTOU 竞态”的教训(CVE-89713): TOCTOU(Time-of-Check-to-Time-of-Use)是内核安全中最经典的竞态模式。这里的 check 是”文件大小是否需要截断权限”,use 是”notify_change() 执行截断”——两者之间有 inode_lock() 的窗口。任何”在锁外检查、在锁内执行”的模式都存在 TOCTOU 风险。正确的做法是——所有安全决策必须基于锁内读取的状态。VFS 的 truncate 系统调用自己做 append-only 检查,但 NFSD 绕过了 VFS 路径,所以必须在 NFSD 层面自己做锁内检查。
关于”WARN_ON 不当使用”的教训(CVE-89711): WARN_ON_ONCE 本意是”这里不应该发生,如果发生了请警告开发者”。但 WARN_ON 基于”假设”——如果假设是错的,WARN_ON 本身就是 bug。这里假设”所有目录 dentry 都有 i_op->lookup”,但 NFS reexport 场景下这个假设不成立。更危险的是——如果系统配置了 panic_on_warn=1,WARN_ON 直接触发 kernel panic,把一个”警告”变成了”拒绝服务”。移除基于错误假设的 WARN_ON 是正确的做法——保留 nfserr_notdir 返回值来阻止空指针解引用。
关于”错误路径引用泄漏”的教训(CVE-89715): 错误路径上的引用泄漏是内核中非常常见的问题——成功路径的代码往往写得很仔细,但错误路径经常遗漏释放步骤。这里的竞态条件使得 nfl->nfs_uuid 未发布,导致关闭路径是 no-op,而 entry-time 获取的引用无人释放。每个错误路径都必须审计所有已获取的引用是否被正确释放——这不是”加分项”,是”必须项”。
转发给你们内核运维团队、NFS 服务器管理员和存储安全团队——长时间运行的 NFS 服务器尤其需要关注。
📎 官方参考
• CVE-2026-89710 commit:git.kernel.org/stable/c/32ac1b0b
• CVE-2026-89711 commit:git.kernel.org/stable/c/7ef182a8
• CVE-2026-89713 commit:git.kernel.org/stable/c/3afa17d9
• CVE-2026-89715 commit:git.kernel.org/stable/c/5215e734
本文仅供安全研究与防御参考,修复请以 Linux 内核官方稳定分支为准。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:撅人 《Linux NFS/NFSD 四连击:布局泄漏 + 空指针 + TOCTOU + 引用泄漏》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








![[0914]一周重点情报汇总|天际友盟情报站](/images/random/titlepic/7.jpg)

评论