Linux SCSI subsystem development
 help / color / mirror / Atom feed
* [PATCH 00/12] fs: take freeze protection for writes to backing files
@ 2026-11-14 10:47 Christian Brauner
  2026-11-14 10:47 ` [PATCH 01/12] fs: add kiocb_start_write_trylock() Christian Brauner
                   ` (11 more replies)
  0 siblings, 12 replies; 16+ messages in thread
From: Christian Brauner @ 2026-11-14 10:47 UTC (permalink / raw)
  To: Jan Kara
  Cc: Shakeel Butt, Alexander Viro, Jens Axboe, linux-fsdevel,
	linux-kernel, io-uring, Christoph Hellwig, linux-block,
	linux-nvme, linux-scsi, target-devel, linux-unionfs, bpf,
	linux-nfs, Christian Brauner (Amutable), stable

While testing the pseudo filesystem series I noticed that zloop, the
nvme target's file backend and the fileio backend of the scsi target
with fd_async_io=1 call ->write_iter() of their backing files without
taking freeze protection. So their writes go straight through to a
frozen filesystem. ext4 complains loudly:

    WARNING: fs/ext4/ext4_jbd2.c:90 at ext4_journal_check_start+0x1f6/0x2d0

Loop doesn't get it right for discard and write zeroes. Those end up
calling ->fallocate() of the backing file directly. The fileio backend
does the same for UNMAP. And zloop records its write pointers on flush
with vfs_setxattr() which leaves freeze protection to the caller. So all
of them modify a frozen filesystem just the same. Fix them too.

Looking for more of the same turned up a few simple ones. coredump
truncates the core file, overlayfs stores its uuid xattr on mount, nfsd
truncates on OPEN and the BPF xattr kfuncs modify xattrs, all without
freeze protection. Fix those as well. The ones that need more work in
their subsystems I have reported separately.

The fileio backend also frees its aio command in the completion handler
while the filesystem may still be using the kiocb. KASAN catches ext4
doing that for direct reads on v7.4-rc1. ext4 gets its own fix (cf. [1])
but fileio should hold a reference across the submission like loop,
zloop and fs/backing-file.c do.

[1]: https://lore.kernel.org/r/20261113-work-ext4-dio_read_kiocb-v1-1-172a88be803c@kernel.org

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
Christian Brauner (12):
      fs: add kiocb_start_write_trylock()
      zloop: take freeze protection for writes
      nvmet: take freeze protection for writes to files
      target: fileio: don't free the aio command under the filesystem
      target: fileio: take freeze protection for aio writes
      loop: take freeze protection for discard and write zeroes
      zloop: take freeze protection when recording write pointers
      target: fileio: take freeze protection for unmap
      coredump: take freeze protection for the truncate
      ovl: take freeze protection when storing the uuid
      bpf: fs/xattr: take freeze protection in the xattr kfuncs
      nfsd: take freeze protection for the truncate on open

 drivers/block/loop.c              |  2 ++
 drivers/block/zloop.c             | 31 ++++++++++++++++++++-------
 drivers/nvme/target/io-cmd-file.c | 37 +++++++++++++++++++++++++-------
 drivers/target/target_core_file.c | 44 ++++++++++++++++++++++++++++++++-------
 fs/bpf_fs_kfuncs.c                |  8 +++++++
 fs/coredump.c                     |  6 ++++--
 fs/nfsd/nfs4state.c               | 10 ++++++++-
 fs/overlayfs/util.c               |  8 +++++--
 include/linux/fs.h                | 22 ++++++++++++++++++++
 io_uring/rw.c                     |  9 +-------
 10 files changed, 142 insertions(+), 35 deletions(-)
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20261113-work-fs-frozen_writes-042ca762bfb8


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH 01/12] fs: add kiocb_start_write_trylock()
  2026-11-14 10:47 [PATCH 00/12] fs: take freeze protection for writes to backing files Christian Brauner
@ 2026-11-14 10:47 ` Christian Brauner
  2026-11-14 10:47 ` [PATCH 02/12] zloop: take freeze protection for writes Christian Brauner
                   ` (10 subsequent siblings)
  11 siblings, 0 replies; 16+ messages in thread
From: Christian Brauner @ 2026-11-14 10:47 UTC (permalink / raw)
  To: Jan Kara
  Cc: Shakeel Butt, Alexander Viro, Jens Axboe, linux-fsdevel,
	linux-kernel, io-uring, Christoph Hellwig, linux-block,
	linux-nvme, linux-scsi, target-devel, linux-unionfs, bpf,
	linux-nfs, Christian Brauner (Amutable), stable

kiocb_start_write() waits until a frozen filesystem is thawed. A
nonblocking submission can't use it. io_uring open-codes the trylock
variant for its IOCB_NOWAIT writes.

Add kiocb_start_write_trylock() next to kiocb_start_write() and use it
in io_uring. The nvme target's file backend is going to need it as
well.

No functional changes.

Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 include/linux/fs.h | 22 ++++++++++++++++++++++
 io_uring/rw.c      |  9 +--------
 2 files changed, 23 insertions(+), 8 deletions(-)

diff --git a/include/linux/fs.h b/include/linux/fs.h
index 99caef43ff3c4..eefc2c9d4af19 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2801,6 +2801,28 @@ static inline void kiocb_start_write(struct kiocb *iocb)
 	__sb_writers_release(inode->i_sb, SB_FREEZE_WRITE);
 }
 
+/**
+ * kiocb_start_write_trylock - try to get write access to a superblock for
+ *			       async file io
+ * @iocb: the io context we want to submit the write with
+ *
+ * This is kiocb_start_write() for a submission that must not wait until a
+ * frozen filesystem is thawed. Should be matched with a call to
+ * kiocb_end_write() if it succeeds.
+ *
+ * Return: true if write access was acquired, false if it wasn't.
+ */
+static inline bool kiocb_start_write_trylock(struct kiocb *iocb)
+{
+	struct inode *inode = file_inode(iocb->ki_filp);
+
+	if (!sb_start_write_trylock(inode->i_sb))
+		return false;
+	/* Fool lockdep just like kiocb_start_write() does. */
+	__sb_writers_release(inode->i_sb, SB_FREEZE_WRITE);
+	return true;
+}
+
 /**
  * kiocb_end_write - drop write access to a superblock after async file io
  * @iocb: the io context we sumbitted the write with
diff --git a/io_uring/rw.c b/io_uring/rw.c
index 3a8559ff4dc66..a0a16fc521455 100644
--- a/io_uring/rw.c
+++ b/io_uring/rw.c
@@ -1095,9 +1095,6 @@ int io_read_mshot(struct io_kiocb *req, unsigned int issue_flags)
 
 static bool io_kiocb_start_write(struct io_kiocb *req, struct kiocb *kiocb)
 {
-	struct inode *inode;
-	bool ret;
-
 	if (!(req->flags & REQ_F_ISREG))
 		return true;
 	if (!(kiocb->ki_flags & IOCB_NOWAIT)) {
@@ -1105,11 +1102,7 @@ static bool io_kiocb_start_write(struct io_kiocb *req, struct kiocb *kiocb)
 		return true;
 	}
 
-	inode = file_inode(kiocb->ki_filp);
-	ret = sb_start_write_trylock(inode->i_sb);
-	if (ret)
-		__sb_writers_release(inode->i_sb, SB_FREEZE_WRITE);
-	return ret;
+	return kiocb_start_write_trylock(kiocb);
 }
 
 int io_write(struct io_kiocb *req, unsigned int issue_flags)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 02/12] zloop: take freeze protection for writes
  2026-11-14 10:47 [PATCH 00/12] fs: take freeze protection for writes to backing files Christian Brauner
  2026-11-14 10:47 ` [PATCH 01/12] fs: add kiocb_start_write_trylock() Christian Brauner
@ 2026-11-14 10:47 ` Christian Brauner
  2026-11-14 10:47 ` [PATCH 03/12] nvmet: take freeze protection for writes to files Christian Brauner
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 16+ messages in thread
From: Christian Brauner @ 2026-11-14 10:47 UTC (permalink / raw)
  To: Jan Kara
  Cc: Shakeel Butt, Alexander Viro, Jens Axboe, linux-fsdevel,
	linux-kernel, io-uring, Christoph Hellwig, linux-block,
	linux-nvme, linux-scsi, target-devel, linux-unionfs, bpf,
	linux-nfs, Christian Brauner (Amutable), stable

zloop calls ->write_iter() of the zone files without taking freeze
protection. So a write to a zloop device goes through to a filesystem
that is frozen. With the zone files on ext4:

  WARNING: fs/ext4/ext4_jbd2.c:90 at ext4_journal_check_start+0x1f6/0x2d0, CPU#3: kworker/u16:4/507
  Workqueue: zloop0 zloop_cmd_workfn
  Call Trace:
   <TASK>
   __ext4_journal_start_sb+0x7a/0x470
   ext4_dirty_inode+0xa1/0x120
   __mark_inode_dirty+0x154/0x1110
   file_update_time_flags+0x3ab/0x4b0
   ext4_buffered_write_iter+0x143/0x630
   ext4_file_write_iter+0x7b9/0x1570
   zloop_do_rw+0x8ac/0xe10
   zloop_cmd_workfn+0xac5/0x1ff0
   process_one_work+0x946/0x1820
   worker_thread+0x4f1/0xd60
   kthread+0x364/0x460
   ret_from_fork+0x525/0x770
   ret_from_fork_asm+0x1a/0x30
   </TASK>

Take freeze protection around the write like loop does. A write that
wasn't queued drops it right away. A write that was queued drops it in
the completion callback.

Note that zloop_rw() also called the completion callback for I/O that
wasn't queued or that failed before it was submitted. It completes
these itself now so the callback only ever sees queued I/O.

Fixes: eb0570c7df23 ("block: new zoned loop block device driver")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/block/zloop.c | 22 +++++++++++++++++-----
 1 file changed, 17 insertions(+), 5 deletions(-)

diff --git a/drivers/block/zloop.c b/drivers/block/zloop.c
index 65634a39b0bfc..34ed925e86f1a 100644
--- a/drivers/block/zloop.c
+++ b/drivers/block/zloop.c
@@ -514,6 +514,10 @@ static void zloop_rw_complete(struct kiocb *iocb, long ret)
 {
 	struct zloop_cmd *cmd = container_of(iocb, struct zloop_cmd, iocb);
 
+	/* A queued write still holds freeze protection. */
+	if (req_op(blk_mq_rq_from_pdu(cmd)) != REQ_OP_READ)
+		kiocb_end_write(iocb);
+
 	cmd->ret = ret;
 	zloop_put_cmd(cmd);
 }
@@ -527,6 +531,7 @@ static int zloop_do_rw(struct zloop_cmd *cmd)
 	struct zloop_zone *zone = &zlo->zones[rq_zone_no(rq)];
 	struct req_iterator rq_iter;
 	struct iov_iter iter;
+	int ret;
 
 	if (rq->bio != rq->biotail) {
 		struct bio_vec tmp, *bvec;
@@ -566,9 +571,14 @@ static int zloop_do_rw(struct zloop_cmd *cmd)
 		cmd->iocb.ki_flags = IOCB_DIRECT;
 	cmd->iocb.ki_ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_NONE, 0);
 
-	if (rw == ITER_SOURCE)
-		return zone->file->f_op->write_iter(&cmd->iocb, &iter);
-	return zone->file->f_op->read_iter(&cmd->iocb, &iter);
+	if (rw == ITER_DEST)
+		return zone->file->f_op->read_iter(&cmd->iocb, &iter);
+
+	kiocb_start_write(&cmd->iocb);
+	ret = zone->file->f_op->write_iter(&cmd->iocb, &iter);
+	if (ret != -EIOCBQUEUED)
+		kiocb_end_write(&cmd->iocb);
+	return ret;
 }
 
 static int zloop_seq_write_prep(struct zloop_cmd *cmd)
@@ -690,8 +700,10 @@ static void zloop_rw(struct zloop_cmd *cmd)
 		ret = zloop_do_rw(cmd);
 	}
 out:
-	if (ret != -EIOCBQUEUED)
-		zloop_rw_complete(&cmd->iocb, ret);
+	if (ret != -EIOCBQUEUED) {
+		cmd->ret = ret;
+		zloop_put_cmd(cmd);
+	}
 	zloop_put_cmd(cmd);
 }
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 03/12] nvmet: take freeze protection for writes to files
  2026-11-14 10:47 [PATCH 00/12] fs: take freeze protection for writes to backing files Christian Brauner
  2026-11-14 10:47 ` [PATCH 01/12] fs: add kiocb_start_write_trylock() Christian Brauner
  2026-11-14 10:47 ` [PATCH 02/12] zloop: take freeze protection for writes Christian Brauner
@ 2026-11-14 10:47 ` Christian Brauner
  2026-11-14 10:47 ` [PATCH 04/12] target: fileio: don't free the aio command under the filesystem Christian Brauner
                   ` (8 subsequent siblings)
  11 siblings, 0 replies; 16+ messages in thread
From: Christian Brauner @ 2026-11-14 10:47 UTC (permalink / raw)
  To: Jan Kara
  Cc: Shakeel Butt, Alexander Viro, Jens Axboe, linux-fsdevel,
	linux-kernel, io-uring, Christoph Hellwig, linux-block,
	linux-nvme, linux-scsi, target-devel, linux-unionfs, bpf,
	linux-nfs, Christian Brauner (Amutable), stable

The file backend calls ->write_iter() of the backing file without
taking freeze protection. So a write to a namespace goes through to a
filesystem that is frozen. With the backing file on ext4 and
buffered_io:

  WARNING: fs/ext4/ext4_jbd2.c:90 at ext4_journal_check_start+0x1f6/0x2d0, CPU#1: kworker/1:1/56
  Workqueue: nvmet-buffered-io-wq nvmet_file_buffered_io_work
  Call Trace:
   <TASK>
   __ext4_journal_start_sb+0x7a/0x470
   ext4_dirty_inode+0xa1/0x120
   __mark_inode_dirty+0x154/0x1110
   file_update_time_flags+0x3ab/0x4b0
   ext4_buffered_write_iter+0x143/0x630
   ext4_file_write_iter+0x7b9/0x1570
   nvmet_file_submit_bvec+0x2e7/0x590
   nvmet_file_execute_io+0x460/0x830
   process_one_work+0x946/0x1820
   worker_thread+0x4f1/0xd60
   kthread+0x364/0x460
   ret_from_fork+0x525/0x770
   ret_from_fork_asm+0x1a/0x30
   </TASK>

The same happens with direct I/O.

Take freeze protection around the write. A write that wasn't queued
drops it right away. A write that was queued drops it in the completion
callback. nvmet_file_io_done() is also called for I/O that wasn't queued
so queued I/O gets its own callback.

Note that buffered I/O is tried with IOCB_NOWAIT first. That attempt
must not wait until the filesystem is thawed. So it only tries to get
freeze protection and fails with -EAGAIN otherwise. The write is then
retried from the workqueue like any other write that would have
blocked.

Fixes: d5eff33ee6f8 ("nvmet: add simple file backed ns support")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/nvme/target/io-cmd-file.c | 37 ++++++++++++++++++++++++++++++-------
 1 file changed, 30 insertions(+), 7 deletions(-)

diff --git a/drivers/nvme/target/io-cmd-file.c b/drivers/nvme/target/io-cmd-file.c
index 695963dd932ee..2a2be95263733 100644
--- a/drivers/nvme/target/io-cmd-file.c
+++ b/drivers/nvme/target/io-cmd-file.c
@@ -77,27 +77,39 @@ static ssize_t nvmet_file_submit_bvec(struct nvmet_req *req, loff_t pos,
 		unsigned long nr_segs, size_t count, int ki_flags)
 {
 	struct kiocb *iocb = &req->f.iocb;
-	ssize_t (*call_iter)(struct kiocb *iocb, struct iov_iter *iter);
+	struct file *file = req->ns->file;
 	struct iov_iter iter;
+	ssize_t ret;
 	int rw;
 
 	if (req->cmd->rw.opcode == nvme_cmd_write) {
 		if (req->cmd->rw.control & cpu_to_le16(NVME_RW_FUA))
 			ki_flags |= IOCB_DSYNC;
-		call_iter = req->ns->file->f_op->write_iter;
 		rw = ITER_SOURCE;
 	} else {
-		call_iter = req->ns->file->f_op->read_iter;
 		rw = ITER_DEST;
 	}
 
 	iov_iter_bvec(&iter, rw, req->f.bvec, nr_segs, count);
 
 	iocb->ki_pos = pos;
-	iocb->ki_filp = req->ns->file;
-	iocb->ki_flags = ki_flags | iocb->ki_filp->f_iocb_flags;
+	iocb->ki_filp = file;
+	iocb->ki_flags = ki_flags | file->f_iocb_flags;
 
-	return call_iter(iocb, &iter);
+	if (rw == ITER_DEST)
+		return file->f_op->read_iter(iocb, &iter);
+
+	/* The IOCB_NOWAIT attempt must not wait for a thaw. */
+	if (ki_flags & IOCB_NOWAIT) {
+		if (!kiocb_start_write_trylock(iocb))
+			return -EAGAIN;
+	} else {
+		kiocb_start_write(iocb);
+	}
+	ret = file->f_op->write_iter(iocb, &iter);
+	if (ret != -EIOCBQUEUED)
+		kiocb_end_write(iocb);
+	return ret;
 }
 
 static void nvmet_file_io_done(struct kiocb *iocb, long ret)
@@ -117,6 +129,17 @@ static void nvmet_file_io_done(struct kiocb *iocb, long ret)
 	nvmet_req_complete(req, status);
 }
 
+static void nvmet_file_io_complete(struct kiocb *iocb, long ret)
+{
+	struct nvmet_req *req = container_of(iocb, struct nvmet_req, f.iocb);
+
+	/* A queued write still holds freeze protection. */
+	if (req->cmd->rw.opcode == nvme_cmd_write)
+		kiocb_end_write(iocb);
+
+	nvmet_file_io_done(iocb, ret);
+}
+
 static bool nvmet_file_execute_io(struct nvmet_req *req, int ki_flags)
 {
 	ssize_t nr_bvec = req->sg_cnt;
@@ -175,7 +198,7 @@ static bool nvmet_file_execute_io(struct nvmet_req *req, int ki_flags)
 	 * for the IOCB_NOWAIT case.
 	 */
 	if (!(ki_flags & IOCB_NOWAIT))
-		req->f.iocb.ki_complete = nvmet_file_io_done;
+		req->f.iocb.ki_complete = nvmet_file_io_complete;
 
 	ret = nvmet_file_submit_bvec(req, pos, bv_cnt, total_len, ki_flags);
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 04/12] target: fileio: don't free the aio command under the filesystem
  2026-11-14 10:47 [PATCH 00/12] fs: take freeze protection for writes to backing files Christian Brauner
                   ` (2 preceding siblings ...)
  2026-11-14 10:47 ` [PATCH 03/12] nvmet: take freeze protection for writes to files Christian Brauner
@ 2026-11-14 10:47 ` Christian Brauner
  2026-11-14 11:25   ` sashiko-bot
  2026-11-14 10:47 ` [PATCH 05/12] target: fileio: take freeze protection for aio writes Christian Brauner
                   ` (7 subsequent siblings)
  11 siblings, 1 reply; 16+ messages in thread
From: Christian Brauner @ 2026-11-14 10:47 UTC (permalink / raw)
  To: Jan Kara
  Cc: Shakeel Butt, Alexander Viro, Jens Axboe, linux-fsdevel,
	linux-kernel, io-uring, Christoph Hellwig, linux-block,
	linux-nvme, linux-scsi, target-devel, linux-unionfs, bpf,
	linux-nfs, Christian Brauner (Amutable), stable

With fd_async_io=1 the fileio backend allocates a command that holds the
kiocb for every read and write and frees it in the completion callback.
Nothing keeps the command alive until ->read_iter() or ->write_iter()
return. But a filesystem may use the kiocb after it queued the I/O. ext4
does that for direct reads:

  BUG: KASAN: slab-use-after-free in ext4_dio_read_iter+0x5e1/0x690
  Workqueue: target_submission target_queued_submit_work
  Call Trace:
   <TASK>
   dump_stack_lvl+0x5d/0x80
   print_report+0x14b/0x4b0
   kasan_report+0x108/0x130
   ext4_dio_read_iter+0x5e1/0x690
   fd_execute_rw+0xd30/0x1700
   __target_execute_cmd+0x53/0x190
   transport_generic_new_cmd+0x65a/0xdb0
   __target_submit+0x12c/0x380
   target_queued_submit_work+0x73/0x270
   process_one_work+0x946/0x1820
   worker_thread+0x4f1/0xd60
   kthread+0x364/0x460
   ret_from_fork+0x525/0x770
   ret_from_fork_asm+0x1a/0x30
   </TASK>

  Allocated by task 59:
   __kmalloc_noprof+0x223/0x830
   fd_execute_rw+0x766/0x1700
   __target_execute_cmd+0x53/0x190
   transport_generic_new_cmd+0x65a/0xdb0
   __target_submit+0x12c/0x380
   target_queued_submit_work+0x73/0x270

  Freed by task 14:
   kfree+0x267/0x6a0
   blk_update_request+0x3f5/0x1260
   blk_mq_end_request+0x59/0x450
   blk_complete_reqs+0xb0/0x130
   handle_softirqs+0x20c/0x860
   run_ksoftirqd+0x31/0x60

The read completed and freed the command before ext4_dio_read_iter() got
to file_accessed(iocb->ki_filp).

Do what loop, zloop and fs/backing-file.c do and hold a reference to the
command until the I/O is submitted.

Fixes: 769031c92c8b ("scsi: target: target/file: Add support of direct and async I/O")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/target/target_core_file.c | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/drivers/target/target_core_file.c b/drivers/target/target_core_file.c
index ab9824a4852f1..4dace29b6d662 100644
--- a/drivers/target/target_core_file.c
+++ b/drivers/target/target_core_file.c
@@ -15,6 +15,7 @@
 #include <linux/timer.h>
 #include <linux/blkdev.h>
 #include <linux/slab.h>
+#include <linux/refcount.h>
 #include <linux/spinlock.h>
 #include <linux/module.h>
 #include <linux/vmalloc.h>
@@ -244,10 +245,17 @@ static void fd_destroy_device(struct se_device *dev)
 struct target_core_file_cmd {
 	unsigned long	len;
 	struct se_cmd	*cmd;
+	refcount_t	ref;
 	struct kiocb	iocb;
 	struct bio_vec	bvecs[];
 };
 
+static void cmd_rw_aio_put(struct target_core_file_cmd *cmd)
+{
+	if (refcount_dec_and_test(&cmd->ref))
+		kfree(cmd);
+}
+
 static void cmd_rw_aio_complete(struct kiocb *iocb, long ret)
 {
 	struct target_core_file_cmd *cmd;
@@ -259,7 +267,7 @@ static void cmd_rw_aio_complete(struct kiocb *iocb, long ret)
 	else
 		target_complete_cmd(cmd->cmd, SAM_STAT_GOOD);
 
-	kfree(cmd);
+	cmd_rw_aio_put(cmd);
 }
 
 static sense_reason_t
@@ -288,6 +296,8 @@ fd_execute_rw_aio(struct se_cmd *cmd, struct scatterlist *sgl, u32 sgl_nents,
 
 	iov_iter_bvec(&iter, is_write, aio_cmd->bvecs, sgl_nents, len);
 
+	/* The filesystem may still use the kiocb after it queued the I/O. */
+	refcount_set(&aio_cmd->ref, 2);
 	aio_cmd->cmd = cmd;
 	aio_cmd->len = len;
 	aio_cmd->iocb.ki_pos = cmd->t_task_lba * dev->dev_attrib.block_size;
@@ -305,6 +315,7 @@ fd_execute_rw_aio(struct se_cmd *cmd, struct scatterlist *sgl, u32 sgl_nents,
 
 	if (ret != -EIOCBQUEUED)
 		cmd_rw_aio_complete(&aio_cmd->iocb, ret);
+	cmd_rw_aio_put(aio_cmd);
 
 	return 0;
 }

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 05/12] target: fileio: take freeze protection for aio writes
  2026-11-14 10:47 [PATCH 00/12] fs: take freeze protection for writes to backing files Christian Brauner
                   ` (3 preceding siblings ...)
  2026-11-14 10:47 ` [PATCH 04/12] target: fileio: don't free the aio command under the filesystem Christian Brauner
@ 2026-11-14 10:47 ` Christian Brauner
  2026-11-14 10:47 ` [PATCH 06/12] loop: take freeze protection for discard and write zeroes Christian Brauner
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 16+ messages in thread
From: Christian Brauner @ 2026-11-14 10:47 UTC (permalink / raw)
  To: Jan Kara
  Cc: Shakeel Butt, Alexander Viro, Jens Axboe, linux-fsdevel,
	linux-kernel, io-uring, Christoph Hellwig, linux-block,
	linux-nvme, linux-scsi, target-devel, linux-unionfs, bpf,
	linux-nfs, Christian Brauner (Amutable), stable

With fd_async_io=1 the fileio backend calls ->write_iter() of the
backing file without taking freeze protection. So a write to the LUN
goes through to a filesystem that is frozen. With the backing file on
ext4:

  WARNING: fs/ext4/ext4_jbd2.c:90 at ext4_journal_check_start+0x1f6/0x2d0, CPU#0: kworker/0:2/40
  Workqueue: target_submission target_queued_submit_work
  Call Trace:
   <TASK>
   __ext4_journal_start_sb+0x7a/0x470
   ext4_dirty_inode+0xa1/0x120
   __mark_inode_dirty+0x154/0x1110
   file_update_time_flags+0x3ab/0x4b0
   ext4_file_write_iter+0xf11/0x1570
   fd_execute_rw+0xb01/0x1700
   __target_execute_cmd+0x53/0x190
   tcm_loop_write_pending+0xe/0x20
   transport_generic_new_cmd+0x612/0xdb0
   __target_submit+0x12c/0x380
   target_queued_submit_work+0x73/0x270
   process_one_work+0x946/0x1820
   worker_thread+0x4f1/0xd60
   kthread+0x364/0x460
   ret_from_fork+0x525/0x770
   ret_from_fork_asm+0x1a/0x30
   </TASK>

The synchronous path doesn't have that problem. vfs_iter_write() takes
freeze protection.

Take freeze protection around the write. A write that wasn't queued
drops it right away. A write that was queued drops it in the completion
callback.

Note that the completion callback can't tell from the command whether
it completes a write. COMPARE AND WRITE is a write but reads the data to
compare first. So remember the direction of the I/O in the aio command.

Fixes: 769031c92c8b ("scsi: target: target/file: Add support of direct and async I/O")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/target/target_core_file.c | 31 +++++++++++++++++++++++--------
 1 file changed, 23 insertions(+), 8 deletions(-)

diff --git a/drivers/target/target_core_file.c b/drivers/target/target_core_file.c
index 4dace29b6d662..27b266a092b18 100644
--- a/drivers/target/target_core_file.c
+++ b/drivers/target/target_core_file.c
@@ -246,6 +246,7 @@ struct target_core_file_cmd {
 	unsigned long	len;
 	struct se_cmd	*cmd;
 	refcount_t	ref;
+	bool		is_write;
 	struct kiocb	iocb;
 	struct bio_vec	bvecs[];
 };
@@ -256,12 +257,8 @@ static void cmd_rw_aio_put(struct target_core_file_cmd *cmd)
 		kfree(cmd);
 }
 
-static void cmd_rw_aio_complete(struct kiocb *iocb, long ret)
+static void cmd_rw_aio_done(struct target_core_file_cmd *cmd, long ret)
 {
-	struct target_core_file_cmd *cmd;
-
-	cmd = container_of(iocb, struct target_core_file_cmd, iocb);
-
 	if (ret != cmd->len)
 		target_complete_cmd(cmd->cmd, SAM_STAT_CHECK_CONDITION);
 	else
@@ -270,6 +267,19 @@ static void cmd_rw_aio_complete(struct kiocb *iocb, long ret)
 	cmd_rw_aio_put(cmd);
 }
 
+static void cmd_rw_aio_complete(struct kiocb *iocb, long ret)
+{
+	struct target_core_file_cmd *cmd;
+
+	cmd = container_of(iocb, struct target_core_file_cmd, iocb);
+
+	/* A queued write still holds freeze protection. */
+	if (cmd->is_write)
+		kiocb_end_write(iocb);
+
+	cmd_rw_aio_done(cmd, ret);
+}
+
 static sense_reason_t
 fd_execute_rw_aio(struct se_cmd *cmd, struct scatterlist *sgl, u32 sgl_nents,
 	      enum dma_data_direction data_direction)
@@ -300,6 +310,7 @@ fd_execute_rw_aio(struct se_cmd *cmd, struct scatterlist *sgl, u32 sgl_nents,
 	refcount_set(&aio_cmd->ref, 2);
 	aio_cmd->cmd = cmd;
 	aio_cmd->len = len;
+	aio_cmd->is_write = is_write;
 	aio_cmd->iocb.ki_pos = cmd->t_task_lba * dev->dev_attrib.block_size;
 	aio_cmd->iocb.ki_filp = file;
 	aio_cmd->iocb.ki_complete = cmd_rw_aio_complete;
@@ -308,13 +319,17 @@ fd_execute_rw_aio(struct se_cmd *cmd, struct scatterlist *sgl, u32 sgl_nents,
 	if (is_write && (cmd->se_cmd_flags & SCF_FUA))
 		aio_cmd->iocb.ki_flags |= IOCB_DSYNC;
 
-	if (is_write)
+	if (is_write) {
+		kiocb_start_write(&aio_cmd->iocb);
 		ret = file->f_op->write_iter(&aio_cmd->iocb, &iter);
-	else
+		if (ret != -EIOCBQUEUED)
+			kiocb_end_write(&aio_cmd->iocb);
+	} else {
 		ret = file->f_op->read_iter(&aio_cmd->iocb, &iter);
+	}
 
 	if (ret != -EIOCBQUEUED)
-		cmd_rw_aio_complete(&aio_cmd->iocb, ret);
+		cmd_rw_aio_done(aio_cmd, ret);
 	cmd_rw_aio_put(aio_cmd);
 
 	return 0;

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 06/12] loop: take freeze protection for discard and write zeroes
  2026-11-14 10:47 [PATCH 00/12] fs: take freeze protection for writes to backing files Christian Brauner
                   ` (4 preceding siblings ...)
  2026-11-14 10:47 ` [PATCH 05/12] target: fileio: take freeze protection for aio writes Christian Brauner
@ 2026-11-14 10:47 ` Christian Brauner
  2026-11-14 10:47 ` [PATCH 07/12] zloop: take freeze protection when recording write pointers Christian Brauner
                   ` (5 subsequent siblings)
  11 siblings, 0 replies; 16+ messages in thread
From: Christian Brauner @ 2026-11-14 10:47 UTC (permalink / raw)
  To: Jan Kara
  Cc: Shakeel Butt, Alexander Viro, Jens Axboe, linux-fsdevel,
	linux-kernel, io-uring, Christoph Hellwig, linux-block,
	linux-nvme, linux-scsi, target-devel, linux-unionfs, bpf,
	linux-nfs, Christian Brauner (Amutable), stable

Discard and write zeroes requests end up in lo_fallocate() which calls
->fallocate() of the backing file directly. That skips the freeze
protection vfs_fallocate() would have taken. So they go straight
through to a filesystem that is frozen. With the backing file on ext4:

  WARNING: fs/ext4/ext4_jbd2.c:90 at ext4_journal_check_start+0x1f6/0x2d0, CPU#3: kworker/u16:1/44
  Workqueue: loop1 loop_rootcg_workfn
  Call Trace:
   <TASK>
   __ext4_journal_start_sb+0x7a/0x470
   ext4_dirty_inode+0xa1/0x120
   __mark_inode_dirty+0x154/0x1110
   file_update_time_flags+0x3ab/0x4b0
   ext4_fallocate+0x286/0x3440
   lo_fallocate+0x13e/0x290
   loop_process_work+0x486/0xe70
   process_one_work+0x946/0x1820
   worker_thread+0x4f1/0xd60
   kthread+0x364/0x460
   ret_from_fork+0x525/0x770
   ret_from_fork_asm+0x1a/0x30
   </TASK>

Take freeze protection around ->fallocate() like vfs_fallocate() does.

Fixes: dfaa2ef68e80 ("loop: add discard support for loop devices")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/block/loop.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/block/loop.c b/drivers/block/loop.c
index a3325ceab484a..77f0601475bb9 100644
--- a/drivers/block/loop.c
+++ b/drivers/block/loop.c
@@ -259,7 +259,9 @@ static int lo_fallocate(struct loop_device *lo, struct request *rq, loff_t pos,
 	if (!bdev_max_discard_sectors(lo->lo_device))
 		return -EOPNOTSUPP;
 
+	file_start_write(file);
 	ret = file->f_op->fallocate(file, mode, pos, blk_rq_bytes(rq));
+	file_end_write(file);
 	if (unlikely(ret && ret != -EINVAL && ret != -EOPNOTSUPP))
 		return -EIO;
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 07/12] zloop: take freeze protection when recording write pointers
  2026-11-14 10:47 [PATCH 00/12] fs: take freeze protection for writes to backing files Christian Brauner
                   ` (5 preceding siblings ...)
  2026-11-14 10:47 ` [PATCH 06/12] loop: take freeze protection for discard and write zeroes Christian Brauner
@ 2026-11-14 10:47 ` Christian Brauner
  2026-11-14 10:47 ` [PATCH 08/12] target: fileio: take freeze protection for unmap Christian Brauner
                   ` (4 subsequent siblings)
  11 siblings, 0 replies; 16+ messages in thread
From: Christian Brauner @ 2026-11-14 10:47 UTC (permalink / raw)
  To: Jan Kara
  Cc: Shakeel Butt, Alexander Viro, Jens Axboe, linux-fsdevel,
	linux-kernel, io-uring, Christoph Hellwig, linux-block,
	linux-nvme, linux-scsi, target-devel, linux-unionfs, bpf,
	linux-nfs, Christian Brauner (Amutable), stable

With discard_write_cache a flush records the write pointer of each
active zone in an xattr of its zone file. zloop_record_safe_wps() uses
vfs_setxattr() for that which leaves freeze protection to the caller.
So a flush rewrites the xattrs on a filesystem that is frozen. With the
zone files on ext4:

  WARNING: fs/ext4/ext4_jbd2.c:90 at ext4_journal_check_start+0x1f6/0x2d0, CPU#0: kworker/u16:2/46
  Workqueue: zloop0 zloop_cmd_workfn
  Call Trace:
   <TASK>
   __ext4_journal_start_sb+0x7a/0x470
   ext4_xattr_set+0x20d/0x380
   __vfs_setxattr+0x10f/0x170
   __vfs_setxattr_noperm+0xf4/0x5e0
   vfs_setxattr+0x14d/0x3d0
   zloop_cmd_workfn+0x384/0x1ff0
   process_one_work+0x946/0x1820
   worker_thread+0x4f1/0xd60
   kthread+0x364/0x460
   ret_from_fork+0x525/0x770
   ret_from_fork_asm+0x1a/0x30
   </TASK>

Take freeze protection around vfs_setxattr() with mnt_want_write_file()
like fsetxattr() does.

Fixes: 829def1e35ca ("zloop: forget write cache on force removal")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/block/zloop.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/drivers/block/zloop.c b/drivers/block/zloop.c
index 34ed925e86f1a..a695aa8405d22 100644
--- a/drivers/block/zloop.c
+++ b/drivers/block/zloop.c
@@ -730,8 +730,13 @@ static int zloop_record_safe_wps(struct zloop_device *zlo)
 
 		if (!zloop_zone_is_active(zone))
 			continue;
-		ret = vfs_setxattr(file_mnt_idmap(file), file_dentry(file),
-				"user.zloop.wp", &zone->wp, sizeof(zone->wp), 0);
+		ret = mnt_want_write_file(file);
+		if (!ret) {
+			ret = vfs_setxattr(file_mnt_idmap(file),
+					file_dentry(file), "user.zloop.wp",
+					&zone->wp, sizeof(zone->wp), 0);
+			mnt_drop_write_file(file);
+		}
 		if (ret) {
 			pr_err("%pg: failed to record write pointer (%d)\n",
 				zlo->disk->part0, ret);

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 08/12] target: fileio: take freeze protection for unmap
  2026-11-14 10:47 [PATCH 00/12] fs: take freeze protection for writes to backing files Christian Brauner
                   ` (6 preceding siblings ...)
  2026-11-14 10:47 ` [PATCH 07/12] zloop: take freeze protection when recording write pointers Christian Brauner
@ 2026-11-14 10:47 ` Christian Brauner
  2026-11-14 10:47 ` [PATCH 09/12] coredump: take freeze protection for the truncate Christian Brauner
                   ` (3 subsequent siblings)
  11 siblings, 0 replies; 16+ messages in thread
From: Christian Brauner @ 2026-11-14 10:47 UTC (permalink / raw)
  To: Jan Kara
  Cc: Shakeel Butt, Alexander Viro, Jens Axboe, linux-fsdevel,
	linux-kernel, io-uring, Christoph Hellwig, linux-block,
	linux-nvme, linux-scsi, target-devel, linux-unionfs, bpf,
	linux-nfs, Christian Brauner (Amutable), stable

UNMAP and WRITE SAME with UNMAP=1 end up in fd_execute_unmap() which
calls ->fallocate() of the backing file directly. That skips the freeze
protection vfs_fallocate() would have taken. So they go straight
through to a filesystem that is frozen, with and without fd_async_io.
With the backing file on ext4:

  WARNING: fs/ext4/ext4_jbd2.c:90 at ext4_journal_check_start+0x1f6/0x2d0, CPU#1: kworker/1:1/50
  Workqueue: target_submission target_queued_submit_work
  Call Trace:
   <TASK>
   __ext4_journal_start_sb+0x7a/0x470
   ext4_dirty_inode+0xa1/0x120
   __mark_inode_dirty+0x154/0x1110
   file_update_time_flags+0x3ab/0x4b0
   ext4_fallocate+0x286/0x3440
   fd_execute_unmap+0x1eb/0x6e0
   sbc_execute_write_same_unmap+0x93/0xe0
   __target_execute_cmd+0x53/0x190
   tcm_loop_write_pending+0xe/0x20
   transport_generic_new_cmd+0x612/0xdb0
   __target_submit+0x12c/0x380
   target_queued_submit_work+0x73/0x270
   process_one_work+0x946/0x1820
   worker_thread+0x4f1/0xd60
   kthread+0x364/0x460
   ret_from_fork+0x525/0x770
   ret_from_fork_asm+0x1a/0x30
   </TASK>

Take freeze protection around ->fallocate() like vfs_fallocate() does.

Fixes: 70d3ae5c8075 ("target/file: Add WRITE_SAME w/ UNMAP=1 emulation support")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/target/target_core_file.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/target/target_core_file.c b/drivers/target/target_core_file.c
index 27b266a092b18..3fe4035b22a94 100644
--- a/drivers/target/target_core_file.c
+++ b/drivers/target/target_core_file.c
@@ -596,7 +596,9 @@ fd_execute_unmap(struct se_cmd *cmd, sector_t lba, sector_t nolb)
 		if (!file->f_op->fallocate)
 			return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE;
 
+		file_start_write(file);
 		ret = file->f_op->fallocate(file, mode, pos, len);
+		file_end_write(file);
 		if (ret < 0) {
 			pr_warn("FILEIO: fallocate() failed: %d\n", ret);
 			return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE;

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 09/12] coredump: take freeze protection for the truncate
  2026-11-14 10:47 [PATCH 00/12] fs: take freeze protection for writes to backing files Christian Brauner
                   ` (7 preceding siblings ...)
  2026-11-14 10:47 ` [PATCH 08/12] target: fileio: take freeze protection for unmap Christian Brauner
@ 2026-11-14 10:47 ` Christian Brauner
  2026-11-14 10:47 ` [PATCH 10/12] ovl: take freeze protection when storing the uuid Christian Brauner
                   ` (2 subsequent siblings)
  11 siblings, 0 replies; 16+ messages in thread
From: Christian Brauner @ 2026-11-14 10:47 UTC (permalink / raw)
  To: Jan Kara
  Cc: Shakeel Butt, Alexander Viro, Jens Axboe, linux-fsdevel,
	linux-kernel, io-uring, Christoph Hellwig, linux-block,
	linux-nvme, linux-scsi, target-devel, linux-unionfs, bpf,
	linux-nfs, Christian Brauner (Amutable), stable

coredump_file() truncates the core file after filp_open() returned.
The open took freeze protection for the create but dropped it again
before returning and coredump_write() only takes it for the dump
itself. So a freeze that lands in between lets the truncate modify a
frozen filesystem.

Take freeze protection around the truncate like do_ftruncate() does.

Fixes: 14da9200140f ("fs: Protect write paths by sb_start_write - sb_end_write")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/coredump.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/fs/coredump.c b/fs/coredump.c
index 82ef9d115999a..cc6a0c7e48ce2 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -1060,8 +1060,10 @@ static bool coredump_file(struct core_name *cn, struct coredump_params *cprm,
 	}
 	if (!(file->f_mode & FMODE_CAN_WRITE))
 		return false;
-	if (do_truncate(idmap, file->f_path.dentry, 0, 0, file))
-		return false;
+	scoped_guard(super_write, inode->i_sb) {
+		if (do_truncate(idmap, file->f_path.dentry, 0, 0, file))
+			return false;
+	}
 
 	cprm->file = no_free_ptr(file);
 	return true;

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 10/12] ovl: take freeze protection when storing the uuid
  2026-11-14 10:47 [PATCH 00/12] fs: take freeze protection for writes to backing files Christian Brauner
                   ` (8 preceding siblings ...)
  2026-11-14 10:47 ` [PATCH 09/12] coredump: take freeze protection for the truncate Christian Brauner
@ 2026-11-14 10:47 ` Christian Brauner
  2026-11-14 10:47 ` [PATCH 11/12] bpf: fs/xattr: take freeze protection in the xattr kfuncs Christian Brauner
  2026-11-14 10:47 ` [PATCH 12/12] nfsd: take freeze protection for the truncate on open Christian Brauner
  11 siblings, 0 replies; 16+ messages in thread
From: Christian Brauner @ 2026-11-14 10:47 UTC (permalink / raw)
  To: Jan Kara
  Cc: Shakeel Butt, Alexander Viro, Jens Axboe, linux-fsdevel,
	linux-kernel, io-uring, Christoph Hellwig, linux-block,
	linux-nvme, linux-scsi, target-devel, linux-unionfs, bpf,
	linux-nfs, Christian Brauner (Amutable), stable

ovl_init_uuid_xattr() stores the persistent uuid in an xattr on the
upper root. That happens after ovl_make_workdir() dropped its write
access to the upper mount and before ovl_get_indexdir() takes it
again. So a freeze of the upper filesystem that lands in between lets
the mount modify a frozen filesystem.

Take write access to the upper mount around the xattr update.

Fixes: d9544c1b0d9e ("ovl: store persistent uuid/fsid with uuid=on")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/overlayfs/util.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c
index 521717209b2e4..67aa7b71e9c83 100644
--- a/fs/overlayfs/util.c
+++ b/fs/overlayfs/util.c
@@ -829,8 +829,12 @@ bool ovl_init_uuid_xattr(struct super_block *sb, struct ovl_fs *ofs,
 
 	/* Try to store persistent uuid */
 	set = true;
-	res = ovl_setxattr(ofs, upperpath->dentry, OVL_XATTR_UUID, uuid.b,
-			   UUID_SIZE);
+	res = mnt_want_write(ovl_upper_mnt(ofs));
+	if (!res) {
+		res = ovl_setxattr(ofs, upperpath->dentry, OVL_XATTR_UUID,
+				   uuid.b, UUID_SIZE);
+		mnt_drop_write(ovl_upper_mnt(ofs));
+	}
 	if (res)
 		goto fail;
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 11/12] bpf: fs/xattr: take freeze protection in the xattr kfuncs
  2026-11-14 10:47 [PATCH 00/12] fs: take freeze protection for writes to backing files Christian Brauner
                   ` (9 preceding siblings ...)
  2026-11-14 10:47 ` [PATCH 10/12] ovl: take freeze protection when storing the uuid Christian Brauner
@ 2026-11-14 10:47 ` Christian Brauner
  2026-11-14 11:11   ` sashiko-bot
  2026-11-14 10:47 ` [PATCH 12/12] nfsd: take freeze protection for the truncate on open Christian Brauner
  11 siblings, 1 reply; 16+ messages in thread
From: Christian Brauner @ 2026-11-14 10:47 UTC (permalink / raw)
  To: Jan Kara
  Cc: Shakeel Butt, Alexander Viro, Jens Axboe, linux-fsdevel,
	linux-kernel, io-uring, Christoph Hellwig, linux-block,
	linux-nvme, linux-scsi, target-devel, linux-unionfs, bpf,
	linux-nfs, Christian Brauner (Amutable), stable

bpf_set_dentry_xattr() and bpf_remove_dentry_xattr() go through
__vfs_setxattr() and __vfs_removexattr() which leave freeze protection
to the caller. BPF LSM programs call them from sleepable hooks such as
file_open where nobody holds it. So a program can modify a frozen
filesystem.

The hook may already hold freeze protection or locks a frozen
filesystem would make us wait under. So don't wait for a thaw. Fail
with -EBUSY if the filesystem is frozen.

The _locked variants are only used from hooks that run inside setattr,
setxattr, removexattr, unlink and rmdir. Their callers hold freeze
protection already.

Fixes: 56467292794b ("bpf: fs/xattr: Add BPF kfuncs to set and remove xattrs")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/bpf_fs_kfuncs.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
index 35ccd98a4de64..7f61e2efa08ba 100644
--- a/fs/bpf_fs_kfuncs.c
+++ b/fs/bpf_fs_kfuncs.c
@@ -295,9 +295,13 @@ __bpf_kfunc int bpf_set_dentry_xattr(struct dentry *dentry, const char *name__st
 	if (!inode)
 		return -EINVAL;
 
+	/* The hook may hold locks or freeze protection already. */
+	if (!sb_start_write_trylock(inode->i_sb))
+		return -EBUSY;
 	inode_lock(inode);
 	ret = bpf_set_dentry_xattr_locked(dentry, name__str, value_p, flags);
 	inode_unlock(inode);
+	sb_end_write(inode->i_sb);
 	return ret;
 }
 
@@ -323,9 +327,13 @@ __bpf_kfunc int bpf_remove_dentry_xattr(struct dentry *dentry, const char *name_
 	if (!inode)
 		return -EINVAL;
 
+	/* The hook may hold locks or freeze protection already. */
+	if (!sb_start_write_trylock(inode->i_sb))
+		return -EBUSY;
 	inode_lock(inode);
 	ret = bpf_remove_dentry_xattr_locked(dentry, name__str);
 	inode_unlock(inode);
+	sb_end_write(inode->i_sb);
 	return ret;
 }
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 12/12] nfsd: take freeze protection for the truncate on open
  2026-11-14 10:47 [PATCH 00/12] fs: take freeze protection for writes to backing files Christian Brauner
                   ` (10 preceding siblings ...)
  2026-11-14 10:47 ` [PATCH 11/12] bpf: fs/xattr: take freeze protection in the xattr kfuncs Christian Brauner
@ 2026-11-14 10:47 ` Christian Brauner
  11 siblings, 0 replies; 16+ messages in thread
From: Christian Brauner @ 2026-11-14 10:47 UTC (permalink / raw)
  To: Jan Kara
  Cc: Shakeel Butt, Alexander Viro, Jens Axboe, linux-fsdevel,
	linux-kernel, io-uring, Christoph Hellwig, linux-block,
	linux-nvme, linux-scsi, target-devel, linux-unionfs, bpf,
	linux-nfs, Christian Brauner (Amutable), stable

An OPEN that truncates an existing file ends up in nfsd4_truncate()
which calls nfsd_setattr() on the already verified file handle. For a
verified file handle nfsd_setattr() leaves fh_want_write() to the
caller but nfsd4_truncate() never takes it, and do_lookup_open() has
dropped its own write access by then. So a freeze of the exported
filesystem that lands in between lets the truncate modify a frozen
filesystem.

Take write access around nfsd_setattr() like nfsd4_setattr() does.

Fixes: 9f67f189939e ("nfsd: notify_change needs elevated write count")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/nfsd/nfs4state.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 1de6c6d757c3e..e561903b9180f 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -6681,11 +6681,19 @@ nfsd4_truncate(struct svc_rqst *rqstp, struct svc_fh *fh,
 	struct nfsd_attrs attrs = {
 		.na_iattr	= &iattr,
 	};
+	__be32 status;
+	int err;
+
 	if (!open->op_truncate)
 		return 0;
 	if (!(open->op_share_access & NFS4_SHARE_ACCESS_WRITE))
 		return nfserr_inval;
-	return nfsd_setattr(rqstp, fh, &attrs, NULL);
+	err = fh_want_write(fh);
+	if (err)
+		return nfserrno(err);
+	status = nfsd_setattr(rqstp, fh, &attrs, NULL);
+	fh_drop_write(fh);
+	return status;
 }
 
 static __be32 nfs4_get_vfs_file(struct svc_rqst *rqstp, struct nfs4_file *fp,

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH 11/12] bpf: fs/xattr: take freeze protection in the xattr kfuncs
  2026-11-14 10:47 ` [PATCH 11/12] bpf: fs/xattr: take freeze protection in the xattr kfuncs Christian Brauner
@ 2026-11-14 11:11   ` sashiko-bot
  2026-11-14 11:37     ` Christian Brauner
  0 siblings, 1 reply; 16+ messages in thread
From: sashiko-bot @ 2026-11-14 11:11 UTC (permalink / raw)
  To: Christian Brauner
  Cc: linux-nvme, linux-scsi, target-devel, bpf, Chuck Lever,
	Jeff Layton, Anna Schumaker

Thank you for your contribution! Sashiko AI review found no new issues
in this patch, but noticed 1 related pre-existing issue it does not
introduce:
- [High] bpf: deadlock in bpf_set_dentry_xattr() via
  bpf_lsm_inode_need_killpriv() [1]
--

commit 486e046b7935f74b8ef238345d31e9322cf39132
Author: Christian Brauner <brauner@kernel.org>

bpf: fs/xattr: take freeze protection in the xattr kfuncs

This commit takes superblock freeze protection via
sb_start_write_trylock() in bpf_set_dentry_xattr() and
bpf_remove_dentry_xattr() so that BPF LSM programs cannot modify a frozen
filesystem.

> diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
> index 35ccd98a4de64..7f61e2efa08ba 100644
> --- a/fs/bpf_fs_kfuncs.c
> +++ b/fs/bpf_fs_kfuncs.c
> @@ -295,9 +295,13 @@ __bpf_kfunc int bpf_set_dentry_xattr(struct dentry *dentry, const char *name__st
>  	if (!inode)
>  		return -EINVAL;
>  
> +	/* The hook may hold locks or freeze protection already. */
> +	if (!sb_start_write_trylock(inode->i_sb))
> +		return -EBUSY;
>  	inode_lock(inode);
>  	ret = bpf_set_dentry_xattr_locked(dentry, name__str, value_p, flags);
>  	inode_unlock(inode);
> +	sb_end_write(inode->i_sb);
>  	return ret;
>  }

[Severity: High]
This problem wasn't introduced by this patch, but can calling
bpf_set_dentry_xattr() or bpf_remove_dentry_xattr() from
bpf_lsm_inode_need_killpriv() deadlock on inode_lock(inode) [1]?

During file writes, callers of file_remove_privs_flags() ->
dentry_needs_remove_privs() -> security_inode_need_killpriv() already hold
inode_lock(inode).  Because bpf_lsm_inode_need_killpriv is missing from
d_inode_locked_hooks, the verifier routes xattr kfunc calls from that hook
to the unlocked variants instead of the _locked ones, causing the thread
to self-deadlock on inode->i_rwsem.

[1] https://sashiko.dev/#/bug/linux-8b38dfb7-78dd-457e-8674-7955f142df39

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261114-work-fs-frozen_writes-v1-0-f5ae61c898b1@kernel.org?part=11

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 04/12] target: fileio: don't free the aio command under the filesystem
  2026-11-14 10:47 ` [PATCH 04/12] target: fileio: don't free the aio command under the filesystem Christian Brauner
@ 2026-11-14 11:25   ` sashiko-bot
  0 siblings, 0 replies; 16+ messages in thread
From: sashiko-bot @ 2026-11-14 11:25 UTC (permalink / raw)
  To: Christian Brauner
  Cc: linux-nvme, linux-scsi, target-devel, bpf, Chuck Lever,
	Jeff Layton, Anna Schumaker

Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Medium] target: fileio: premature target_complete_cmd() in
  cmd_rw_aio_complete()
--

commit 6e1a6f1140a7e9133b9c8d928debaca48c4124cf
Author: Christian Brauner <brauner@kernel.org>

target: fileio: don't free the aio command under the filesystem

This commit adds a reference count to struct target_core_file_cmd so that
the command and its embedded kiocb are not freed before ->read_iter() or
->write_iter() return.

> diff --git a/drivers/target/target_core_file.c b/drivers/target/target_core_file.c
> index ab9824a4852f1..4dace29b6d662 100644
> --- a/drivers/target/target_core_file.c
> +++ b/drivers/target/target_core_file.c
[ ... ]
> @@ -244,10 +245,17 @@ static void fd_destroy_device(struct se_device *dev)
>  struct target_core_file_cmd {
>  	unsigned long	len;
>  	struct se_cmd	*cmd;
> +	refcount_t	ref;
>  	struct kiocb	iocb;
>  	struct bio_vec	bvecs[];
>  };
>  
> +static void cmd_rw_aio_put(struct target_core_file_cmd *cmd)
> +{
> +	if (refcount_dec_and_test(&cmd->ref))
> +		kfree(cmd);
> +}
> +
>  static void cmd_rw_aio_complete(struct kiocb *iocb, long ret)
>  {
>  	struct target_core_file_cmd *cmd;
> @@ -259,7 +267,7 @@ static void cmd_rw_aio_complete(struct kiocb *iocb, long ret)
>  	else
>  		target_complete_cmd(cmd->cmd, SAM_STAT_GOOD);
>  
> -	kfree(cmd);
> +	cmd_rw_aio_put(cmd);
>  }

[Severity: Medium]
Should target_complete_cmd() also wait until the final reference drops in
cmd_rw_aio_put(), as loop, zloop, and target_core_iblock do?

fd_execute_rw_aio() sets aio_cmd->iocb.ki_filp to fd_dev->fd_file without
get_file(), relying on the in-flight se_cmd to hold se_lun->lun_ref and
keep the backend device alive.  Calling target_complete_cmd() before
->read_iter() or ->write_iter() returns drops lun_ref early, which allows
concurrent LUN unlinking and device removal to close fd_dev->fd_file while
the filesystem is still using iocb->ki_filp:

CPU0                              CPU1
fd_execute_rw_aio()
  ext4_dio_read_iter()
    ...
      submit_bio()
                                  cmd_rw_aio_complete()
                                    target_complete_cmd()
                                      ...
                                        percpu_ref_put(&lun->lun_ref)
                                  core_tpg_remove_lun()
                                  target_free_device()
                                    fd_destroy_device()
                                      filp_close(fd_dev->fd_file, NULL)
    file_accessed(iocb->ki_filp)  <-- use-after-free

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261114-work-fs-frozen_writes-v1-0-f5ae61c898b1@kernel.org?part=4

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 11/12] bpf: fs/xattr: take freeze protection in the xattr kfuncs
  2026-11-14 11:11   ` sashiko-bot
@ 2026-11-14 11:37     ` Christian Brauner
  0 siblings, 0 replies; 16+ messages in thread
From: Christian Brauner @ 2026-11-14 11:37 UTC (permalink / raw)
  To: sashiko-reviews
  Cc: linux-nvme, linux-scsi, target-devel, bpf, Chuck Lever,
	Jeff Layton, Anna Schumaker

On Sat, Nov 14, 2026 at 11:11:36AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found no new issues
> in this patch, but noticed 1 related pre-existing issue it does not
> introduce:
> - [High] bpf: deadlock in bpf_set_dentry_xattr() via
>   bpf_lsm_inode_need_killpriv() [1]
> --
> 
> commit 486e046b7935f74b8ef238345d31e9322cf39132
> Author: Christian Brauner <brauner@kernel.org>
> 
> bpf: fs/xattr: take freeze protection in the xattr kfuncs
> 
> This commit takes superblock freeze protection via
> sb_start_write_trylock() in bpf_set_dentry_xattr() and
> bpf_remove_dentry_xattr() so that BPF LSM programs cannot modify a frozen
> filesystem.
> 
> > diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
> > index 35ccd98a4de64..7f61e2efa08ba 100644
> > --- a/fs/bpf_fs_kfuncs.c
> > +++ b/fs/bpf_fs_kfuncs.c
> > @@ -295,9 +295,13 @@ __bpf_kfunc int bpf_set_dentry_xattr(struct dentry *dentry, const char *name__st
> >  	if (!inode)
> >  		return -EINVAL;
> >  
> > +	/* The hook may hold locks or freeze protection already. */
> > +	if (!sb_start_write_trylock(inode->i_sb))
> > +		return -EBUSY;
> >  	inode_lock(inode);
> >  	ret = bpf_set_dentry_xattr_locked(dentry, name__str, value_p, flags);
> >  	inode_unlock(inode);
> > +	sb_end_write(inode->i_sb);
> >  	return ret;
> >  }
> 
> [Severity: High]
> This problem wasn't introduced by this patch, but can calling
> bpf_set_dentry_xattr() or bpf_remove_dentry_xattr() from
> bpf_lsm_inode_need_killpriv() deadlock on inode_lock(inode) [1]?

Yes, I've been pointing that out quite a few times already.
This needs bpf infrastructure changes to be able to automatically track
the locking context a function can be called from (or something
similar).

^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2026-11-14 11:37 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-14 10:47 [PATCH 00/12] fs: take freeze protection for writes to backing files Christian Brauner
2026-11-14 10:47 ` [PATCH 01/12] fs: add kiocb_start_write_trylock() Christian Brauner
2026-11-14 10:47 ` [PATCH 02/12] zloop: take freeze protection for writes Christian Brauner
2026-11-14 10:47 ` [PATCH 03/12] nvmet: take freeze protection for writes to files Christian Brauner
2026-11-14 10:47 ` [PATCH 04/12] target: fileio: don't free the aio command under the filesystem Christian Brauner
2026-11-14 11:25   ` sashiko-bot
2026-11-14 10:47 ` [PATCH 05/12] target: fileio: take freeze protection for aio writes Christian Brauner
2026-11-14 10:47 ` [PATCH 06/12] loop: take freeze protection for discard and write zeroes Christian Brauner
2026-11-14 10:47 ` [PATCH 07/12] zloop: take freeze protection when recording write pointers Christian Brauner
2026-11-14 10:47 ` [PATCH 08/12] target: fileio: take freeze protection for unmap Christian Brauner
2026-11-14 10:47 ` [PATCH 09/12] coredump: take freeze protection for the truncate Christian Brauner
2026-11-14 10:47 ` [PATCH 10/12] ovl: take freeze protection when storing the uuid Christian Brauner
2026-11-14 10:47 ` [PATCH 11/12] bpf: fs/xattr: take freeze protection in the xattr kfuncs Christian Brauner
2026-11-14 11:11   ` sashiko-bot
2026-11-14 11:37     ` Christian Brauner
2026-11-14 10:47 ` [PATCH 12/12] nfsd: take freeze protection for the truncate on open Christian Brauner

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox