一个普通 Linux 用户,没有 sudo 权限,没有 root 密码,也不需要加载任何内核模块。
他只需要对自己创建的文件执行几次正常写入,就可能在几秒钟内,修改原本只有 root 才能修改的系统文件。
没有内核崩溃。
没有明显的错误日志。
修改结果还会直接写入磁盘,重新启动以后依然存在。
更令人意外的是,这个漏洞并不是传统安全研究员首先发现的。
安全公司 Qualys 将一个人工智能模型引入 Linux 内核审计流程,要求它寻找类似 Dirty COW 的竞争条件漏洞。
经过多轮缩小搜索范围,人工智能模型最终把目标锁定在 X F S 文件系统的 reflink 路径,并生成了能够触发问题的概念验证。
随后,Qualys 的研究人员人工检查代码、复现漏洞、验证影响,并与 Linux 内核维护者协调修复。
这个漏洞被命名为 RefluXFS,编号是 C V E 2026 64600。
它从 Linux 4.11 时代一直存在到二零二六年,时间超过九年。
不过,标题中的“AI 把 Linux 内核干穿了”是一种简化说法。
更准确地说,是人工智能在专业研究人员的引导和验证下,发现了一个潜伏九年的 Linux 内核漏洞。
今天,我们不只看新闻标题。
我们将进入 X F S 内部,从 reflink、Copy on Write、data fork、CoW fork、O direct 和 inode lock 开始,逐步还原这个竞争条件到底是怎样发生的。
第一部分:漏洞拆解
一、RefluXFS 到底能做什么
RefluXFS 是一个本地权限提升漏洞。
攻击者必须首先能够以普通用户身份在目标 Linux 系统上运行程序。
所以,它不是一个可以直接从互联网远程攻击服务器的漏洞。
但是,一旦攻击者已经获得普通用户账户,或者通过某个 Web 服务、容器、开发账户进入系统,这个漏洞就可能把普通权限升级为主机 root 权限。
它的核心能力不是绕过文件权限,直接打开系统文件进行写入。
普通用户依然不能正常执行:
打开 /etc/passwd,然后把内容写进去。
Linux 的权限检查会拒绝这种操作。
RefluXFS 做的是另一件更加隐蔽的事情:
攻击者先创建一个属于自己的文件,让这个文件与目标系统文件共享同一组磁盘块。
接着,攻击者只写自己的文件。
但是由于 X F S 内核代码中的竞争条件,这次写入最终可能落到仍然属于目标系统文件的物理磁盘块上。
从文件权限角度看,攻击者从未获得目标文件的写权限。
但是从磁盘块角度看,目标文件的内容已经被修改了。
这就是 RefluXFS 这个名字的含义。
原本应该流向攻击者副本的数据,反向流回了原始文件。
Qualys 的测试显示,漏洞可以修改 /etc/passwd,也可以修改具有 S U I D 权限的程序。写入发生在较低的块设备路径,因此可能不会更新目标 inode 的所有常规元数据,也不会产生明显的内核告警。
二、什么是 XFS reflink
要理解这个漏洞,首先要理解 reflink。
假设磁盘中有一个四千字节的数据块,我们把它叫作物理块 X。
文件 A 的内容存储在物理块 X 中。
现在,我们要把文件 A 复制成文件 B。
传统复制会执行这样的过程:
读取物理块 X。
申请新的物理块 Y。
把数据从 X 复制到 Y。
然后让文件 B 指向 Y。
这种方法很直观,但是会消耗时间、磁盘带宽和额外存储空间。
reflink 使用的是另一种方法。
当程序执行 reflink clone 时,文件系统并不立即复制数据。
它只是让文件 A 和文件 B 同时指向物理块 X。
此时:
文件 A 指向 X。
文件 B也指向 X。
物理块 X 的引用计数从一变成二。
复制几乎可以瞬间完成,而且在两个文件没有发生修改之前,不需要额外的数据空间。
这也是为什么 reflink 经常被称为快速克隆或者轻量级复制。
在 Linux 用户空间中,reflink 可以通过 FICLONE ioctl 实现。
一些版本的 cp 命令也会在文件系统支持时自动尝试 reflink。
重要的一点是:
克隆源文件通常只要求读权限。
创建目标文件则只要求用户对目标目录拥有写权限。
因此,一个普通用户可以读取某个系统文件,并把它 reflink 克隆到自己拥有的文件中。
这本身不是安全漏洞。
因为两个文件开始共享磁盘块以后,只要其中一个文件发生写入,X F S 就应该启动 Copy on Write,把它们安全地分开。
三、Copy on Write 怎样保护原始文件
Copy on Write,简称 C o W,中文一般翻译为写时复制。
假设原始文件 A 和克隆文件 B 都指向物理块 X。
引用计数是二。
现在,用户要修改文件 B。
X F S 不能直接修改 X。
因为文件 A 也在使用 X。
正确流程应该是:
第一步,申请一个新的物理块 Y。
第二步,把需要保留的数据复制到 Y。
第三步,把新的修改写入 Y。
第四步,修改文件 B 的 extent mapping,让文件 B 从此指向 Y。
第五步,把物理块 X 的引用计数从二减到一。
最终结果是:
文件 A 继续指向 X。
文件 B 改为指向 Y。
两个文件彻底分开。
以后修改文件 B,不会再影响文件 A。
从设计上看,这套机制没有问题。
真正的问题出现在多个线程同时写入同一个 reflink 文件,而且写入过程中内核不得不暂时释放一把锁。
四、data fork 和 CoW fork
X F S 的一个 inode 内部可以维护多组 extent mapping。
理解 RefluXFS 时,最重要的是两组:
第一组叫作 data fork。
第二组叫作 CoW fork。
data fork 记录文件当前真正使用的磁盘映射。
例如:
文件 B 的逻辑偏移零,对应物理块 X。
那么,X 就会出现在文件 B 的 data fork 映射中。
CoW fork 则更像一个临时施工区。
当 X F S 准备执行写时复制时,新申请的物理块会先记录在 CoW fork 中。
等数据写入完成以后,X F S 再执行 end C o W,把新映射正式移动到 data fork。
因此,在一次正常的写时复制过程中,映射大致经历:
data fork 指向 X。
CoW fork 临时指向 Y。
写入完成。
data fork 改为指向 Y。
CoW fork 中的临时记录被清理。
旧块 X 的引用计数减少。
RefluXFS 的根本问题是:
内核在释放锁以前读取了 data fork 的映射。
重新获得锁以后,它更新了 CoW fork 的信息,却没有保证自己手中的 data fork 映射仍然有效。
五、两个不同的锁
X F S 的写入路径中存在多种锁。
这里重点看两种。
第一种是 IOLOCK。
它负责协调较高层的文件输入输出操作。
第二种是 ILOCK。
它保护 inode 内部的元数据和 extent mapping。
在对齐的 O direct 写入路径中,多个写线程可以持有共享模式的 IOLOCK。
也就是说,多个写入操作可以并发进入部分 X F S 代码。
但是,当内核需要读取或者修改 data fork 和 CoW fork 时,又需要获取 ILOCK。
问题是,申请 X F S transaction 时可能需要等待日志空间。
如果内核一直持有 ILOCK,然后又等待其他需要同一把锁的操作,就可能形成死锁。
所以,相关代码采取了这样的策略:
先读取 data fork 映射。
暂时释放 ILOCK。
申请 transaction。
重新获取 ILOCK。
继续处理。
这种“释放锁,再重新获取锁”的设计在内核中并不罕见。
真正危险的是:
一旦释放过锁,就不能继续假设锁内读取的数据仍然是最新的。
六、竞争条件怎样形成
现在,我们把整个过程放到时间线上。
画面中有一个原始文件 A。
它指向物理块 X。
攻击者创建克隆文件 B。
文件 A 和文件 B 现在共同指向 X。
X 的引用计数等于二。
接着,两个写线程同时对文件 B 发起 O direct 写入。
我们把它们叫作线程一和线程二。
线程一开始运行
线程一读取文件 B 的 data fork。
它得到的映射是:
文件 B 的逻辑块,对应物理块 X。
我们把这份查询结果叫作 imap。
线程一发现 X 是共享块,需要执行写时复制。
但它还需要申请一个 X F S transaction。
为了避免死锁,线程一暂时释放 ILOCK。
此时,线程一手中仍然保存着旧映射:
imap 指向 X。
线程二进入
线程一释放锁以后,线程二得到运行机会。
线程二也为文件 B 执行写时复制。
它申请新的物理块 Y。
把自己的数据写入 Y。
然后完成 end C o W。
文件 B 的 data fork 被修改:
原来指向 X。
现在改为指向 Y。
物理块 X 的引用计数从二降到一。
此时:
文件 A 仍然指向 X。
文件 B 已经指向 Y。
到这里为止,线程二的操作完全正确。
线程一重新回来
线程一申请到 transaction,重新获得 ILOCK。
按照正常逻辑,它需要再次确认原来的物理块是否仍然共享。
代码确实重新检查了共享状态。
但问题是,它检查时使用的还是释放锁以前保存的旧映射。
也就是物理块 X。
此时,X 的引用计数已经变成一。
于是代码得到结论:
这个块不再共享,可以原地写入。
但是,这个结论少了一半。
X 的确不再共享。
可它也已经不属于文件 B。
文件 B 现在指向 Y。
X 唯一剩下的所有者,是原始文件 A。
然而,线程一手中的 imap 仍然指向 X。
于是 O direct 层按照旧映射,把攻击者准备写入文件 B 的数据直接提交给物理块 X。
结果是:
攻击者写的是自己的文件 B。
磁盘上被修改的却是原始文件 A。
这就是整个 RefluXFS 漏洞。
七、为什么 O_DIRECT 很关键
O direct 的设计目的是减少页缓存参与,让应用程序的数据尽可能直接地在用户缓冲区和存储设备之间传输。
它常见于数据库、大型存储系统和某些高性能 I O 工作负载。
普通 buffered I O 会经过 page cache。
文件映射变化时,页缓存和文件系统路径中可能存在额外的同步与检查机会。
O direct 路径更加直接。
一旦 X F S 把一个物理块映射作为 IOMAP MAPPED 交给 direct I O 层,后面的块写入不会再次完整验证:
这个物理块现在是否仍然属于当前文件?
所以,旧 imap 一旦通过 X F S 层,错误的磁盘地址就可能被直接提交给块设备。
这也是为什么该漏洞造成的是持久化磁盘修改,而不只是内存中的临时异常。
八、真正的代码错误是什么
我们可以把有问题的逻辑简化成下面这段伪代码。
先读取 data fork:
imap 等于当前物理块映射。
然后进入写时复制分配函数:
释放 ILOCK。
申请 transaction。
重新获取 ILOCK。
接着重新检查 CoW 状态和引用计数。
问题在于,重新检查引用计数时使用的还是旧 imap。
也就是说,程序验证了:
旧物理块 X 现在是否共享。
却没有验证:
当前文件现在是否还指向物理块 X。
这是两个完全不同的问题。
一个引用计数为一的块,不一定属于当前 inode。
它可能已经属于另一个 reflink 文件。
所以,这个漏洞本质上不是简单的“忘记加锁”。
它是一个典型的 stale state,也就是过期状态问题。
锁确实重新获得了。
共享状态也确实重新检查了。
但是重新检查所使用的输入数据已经过期。
这类错误尤其难以发现,因为单独阅读每一个步骤,好像都有一定道理。
只有把两个线程的执行顺序交叉排列,错误才会显现出来。
九、补丁怎样修复
Linux 内核的修复思路非常清晰。
在释放 ILOCK 以前,代码先读取 data fork 的 sequence counter。
可以把它理解为 data fork 的版本编号。
接下来释放锁,申请 transaction,再重新获得锁。
重新获得锁以后,再读取一次 sequence counter。
如果前后两个值相同,说明 data fork 在此期间没有发生变化。
旧 imap 仍然可以使用。
如果两个值不同,说明另一个线程可能修改过 data fork。
此时,代码必须重新调用映射查询函数,读取当前的 data fork mapping。
简化后的修复逻辑是:
保存 data fork 版本号。
释放 ILOCK。
申请 transaction。
重新获取 ILOCK。
如果版本号发生变化,重新读取 data fork mapping。
然后,后续共享状态判断和 direct I O 写入都基于最新映射执行。
补丁并没有禁止并发写入。
也没有简单地让内核永远持有 ILOCK。
它保留了原来的并发和防死锁设计,只是在释放锁的窗口之后增加了必要的状态重新验证。
该修复的主线提交是:
2f4acd0fcd862e22eab45690ec2c08c80b6ef2e7
提交标题是:
“xfs: resample the data fork mapping after cycling ILOCK”。
补丁于二零二六年七月十六日进入 Linux 内核源码树,并被回移到多个稳定内核分支。
第二部分:安全演示
一、检查系统是否使用 XFS
下面的演示只检查系统配置,不运行漏洞利用程序。
首先,在终端执行:
|
1 2 |
findmnt -no FSTYPE / |
如果输出是:
|
1 2 |
xfs |
说明根文件系统使用 X F S。
如果输出是 ext4、btrfs 或其他文件系统,那么根文件系统不符合这个漏洞的 X F S 条件。
但是,系统中的其他挂载点仍然可能使用 X F S。
可以执行:
|
1 2 |
findmnt -t xfs |
查看所有 X F S 文件系统。
二、检查 reflink 是否启用
对根文件系统执行:
|
1 2 |
sudo xfs_info / | grep reflink |
如果输出中包含:
|
1 2 |
reflink=1 |
说明这个 X F S 文件系统启用了 reflink。
如果是:
|
1 2 |
reflink=0 |
那么这个文件系统不满足 RefluXFS 的 reflink 条件。
reflink 是在创建 X F S 文件系统时写入超级块的功能。
它不是一个可以临时通过 mount 参数关闭的普通选项。
从较新的 xfsprogs 开始,reflink 已经成为创建 X F S 文件系统时的默认功能之一。
三、检查正在运行的内核
执行:
|
1 2 |
uname -r |
这里需要注意:
不能只根据主版本号判断系统是否已经修复。
企业 Linux 发行版经常把安全补丁回移到较旧的内核版本。
例如,一个仍然显示 5.14 的 R H E L 内核,也可能已经包含针对 RefluXFS 的修复。
相反,只看版本字符串很新,也不能替代发行版安全公告。
最可靠的判断方法是:
检查当前发行版针对 C V E 2026 64600 发布的安全更新。
安装发行版提供的最新内核。
然后重新启动系统,确保正在运行的是更新后的内核。
Red Hat 已经把该漏洞列为 Important,并为多个受影响产品发布了内核更新或 kpatch 更新。Debian 等发行版也在安全跟踪页面列出了相应的修复版本。
四、用动画模拟竞争条件
画面中显示两个文件。
左边是系统文件。
右边是攻击者拥有的克隆文件。
第一帧:
两个文件都指向物理块 X。
引用计数是二。
第二帧:
线程一读取映射,记录 X,然后释放 ILOCK。
第三帧:
线程二申请物理块 Y。
线程二完成写时复制。
克隆文件改为指向 Y。
X 的引用计数降到一。
第四帧:
线程一重新获得 ILOCK。
但它没有重新读取 data fork。
它仍然拿着旧地址 X。
第五帧:
线程一查询 X 的引用计数。
结果是一。
于是把 X 当成克隆文件的私有块。
第六帧:
O direct 将数据写入 X。
但此时 X 唯一的所有者已经是原始系统文件。
在画面上,可以把整个漏洞压缩成一句话:
锁重新获得了,但锁内依赖的数据没有重新读取。
这比简单地说“两个线程同时写文件”更接近漏洞的代码本质。
第三部分:思考
一、为什么这个漏洞能潜伏九年
竞争条件漏洞往往不依赖某一行代码必然执行错误。
它依赖多个线程以特定顺序交错运行。
正常情况下,线程一可能很快申请到 transaction。
它释放 ILOCK 以后,几乎立即又重新获得锁。
线程二来不及完成整个 Copy on Write 流程。
程序看起来完全正常。
只有在日志空间紧张、transaction 分配发生等待、多个 O direct 写入高度并发时,时间窗口才会被放大。
这意味着:
普通功能测试可能无法触发。
单线程测试无法触发。
代码静态检查也可能只看到“释放锁以后重新检查了共享状态”,从而误以为处理已经足够安全。
但真正缺少的不是“重新检查”这四个字。
而是重新检查之前,必须先刷新所有可能在解锁期间发生变化的依赖数据。
二、这与 Dirty COW 有什么相似之处
Dirty COW 是 Linux 历史上著名的 Copy on Write 竞争条件漏洞。
RefluXFS 和 Dirty COW 并不是同一个漏洞,也不发生在同一套代码中。
Dirty COW 主要涉及内存管理和只读映射的写时复制竞争。
RefluXFS 则发生在 X F S 文件系统的 reflink direct I O 路径。
但是,两者有一个共同主题:
系统原本依赖 Copy on Write 维持隔离。
攻击者通过竞争条件,让某次写入绕过了预期的私有副本,最终影响原始对象。
也正因为这种相似性,Qualys 最初给人工智能模型的研究方向,就是寻找类似 Dirty COW 的竞争条件。
人工智能并不是随机浏览代码以后突然产生了意识。
研究人员逐步把范围从整个 Linux 内核缩小到竞争条件,再缩小到内存和文件系统目录,最后缩小到具体文件系统实现。
这更像是人工智能辅助的定向代码审计。
三、AI 在这次事件中到底做了什么
根据 Qualys 公布的过程,人工智能完成了几个重要环节。
第一,分析大规模 Linux 内核代码。
第二,根据研究人员设定的方向寻找竞争条件。
第三,定位 X F S reflink 路径中的可疑逻辑。
第四,生成概念验证,帮助确认漏洞具有实际利用价值。
第五,协助起草最初的安全公告。
但是,后续步骤仍然由人类研究人员完成。
研究人员检查人工智能的推理。
人工复现攻击。
验证每一个技术结论。
与 X F S 维护者和 Linux 内核安全团队协调。
确认补丁是否真正关闭竞争窗口。
所以,这次事件既不能被简单描述为“人工智能自动黑掉 Linux”,也不能被贬低为“人工智能只是搜索代码”。
它更像一种新的安全研究工作流:
人工确定目标。
人工智能扩大搜索范围。
人工智能提出候选路径。
人类完成验证、风险判断和负责任披露。
四、这次补丁带来的代码设计启示
RefluXFS 给内核开发者留下了一个非常清晰的教训。
任何在锁内读取的数据,只要中途释放过锁,就必须默认已经过期。
重新获得同一把锁,并不会让旧数据自动恢复有效。
开发者需要逐项检查:
哪些指针可能改变?
哪些 extent mapping 可能改变?
哪些引用计数可能改变?
哪些对象可能已经被释放、替换或者重新映射?
只重新检查最终状态,可能仍然不够。
因为最终状态的计算过程本身,也可能依赖旧数据。
第二个教训是:
sequence counter 不只是一个性能优化工具。
它可以帮助代码判断:
在没有持锁的时间窗口中,受保护的数据结构是否发生过变化。
如果发生变化,就重新采样。
如果没有变化,才继续使用缓存结果。
第三个教训是:
文件系统安全不能只测试权限检查。
文件权限正确,并不代表底层物理块映射一定正确。
在 reflink、快照、去重、压缩、direct I O 和异步写入越来越普遍的今天,逻辑文件与物理存储之间的映射本身就是安全边界。
五、普通用户应该怎么做
受影响通常需要同时满足三个条件。
第一,系统运行包含该缺陷、而且尚未获得修复的 Linux 内核。
第二,目标文件系统是启用了 reflink 的 X F S。
第三,同一个 X F S 文件系统中,既存在攻击者可以读取的高价值文件,也存在普通用户可以写入的目录。
R H E L 八、九和十,以及一些使用相同技术体系的发行版,默认安装更容易满足这些条件。
Fedora Server、部分 Amazon Linux 系统以及手动选择 X F S 的其他发行版也可能受到影响。
Debian、Ubuntu、Fedora Workstation 和 openSUSE 等系统通常不默认把 X F S 用作根文件系统,但管理员手动使用 X F S 时仍然需要检查。
最重要的处理方式不是修改某个 sysctl,也不是依靠 S E Linux、K A S L R 或容器权限进行临时防护。
可靠方法是:
安装发行版已经发布的内核安全更新。
重新启动系统。
确认正在运行更新后的内核。
对于多用户服务器、托管服务器、开发服务器、C I 节点和允许普通账户登录的企业系统,这个更新应该获得更高优先级。
结论
RefluXFS 最可怕的地方,不是它使用了多么神秘的内核技术。
恰恰相反。
它由许多看起来合理的步骤组成。
reflink 共享磁盘块,是合理的。
Copy on Write 隔离修改,是合理的。
为了避免死锁暂时释放 ILOCK,也是合理的。
重新获得锁以后检查引用计数,似乎同样合理。
但是,这些合理步骤组合在一起时,遗漏了一个关键事实:
锁外的世界已经发生变化。
旧映射不再属于当前文件。
内核却仍然按照旧映射提交了 direct I O 写入。
于是,一个普通用户对自己文件的合法写入,最终修改了 root 拥有的系统文件。
这个漏洞从二零一七年进入 Linux 4.11,一直潜伏到二零二六年。
最终,它不是被一次系统崩溃暴露出来,也不是被普通模糊测试轻易撞中。
它是在人工智能定向分析和人类安全研究员严格验证的合作流程中被发现。
这也许才是 RefluXFS 留给整个软件行业最重要的问题:
当人工智能能够阅读数百万行底层代码,追踪跨函数状态,并主动寻找并发窗口以后,过去那些因为过于复杂而长期隐藏的漏洞,还能隐藏多久?
但它正在改变漏洞被发现的速度。
也正在改变内核开发者、安全研究员和攻击者之间的力量平衡。
这里是芯片园地。
我们不只介绍一条安全新闻。
我们还要进入代码,理解它为什么会发生。
