* [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
` (12 more replies)
0 siblings, 13 replies; 15+ 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] 15+ 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
` (11 subsequent siblings)
12 siblings, 0 replies; 15+ 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] 15+ 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
` (10 subsequent siblings)
12 siblings, 0 replies; 15+ 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] 15+ 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
` (9 subsequent siblings)
12 siblings, 0 replies; 15+ 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] 15+ 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 10:47 ` [PATCH 05/12] target: fileio: take freeze protection for aio writes Christian Brauner
` (8 subsequent siblings)
12 siblings, 0 replies; 15+ 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] 15+ 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
` (7 subsequent siblings)
12 siblings, 0 replies; 15+ 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] 15+ 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
` (6 subsequent siblings)
12 siblings, 0 replies; 15+ 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] 15+ 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
` (5 subsequent siblings)
12 siblings, 0 replies; 15+ 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] 15+ 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
` (4 subsequent siblings)
12 siblings, 0 replies; 15+ 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] 15+ 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
` (3 subsequent siblings)
12 siblings, 0 replies; 15+ 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] 15+ 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 12:19 ` Amir Goldstein
2026-11-14 10:47 ` [PATCH 11/12] bpf: fs/xattr: take freeze protection in the xattr kfuncs Christian Brauner
` (2 subsequent siblings)
12 siblings, 1 reply; 15+ 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] 15+ 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 10:47 ` [PATCH 12/12] nfsd: take freeze protection for the truncate on open Christian Brauner
2026-11-14 12:31 ` [PATCH 00/12] fs: take freeze protection for writes to backing files Amir Goldstein
12 siblings, 0 replies; 15+ 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] 15+ 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
2026-11-14 12:31 ` [PATCH 00/12] fs: take freeze protection for writes to backing files Amir Goldstein
12 siblings, 0 replies; 15+ 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] 15+ messages in thread
* Re: [PATCH 10/12] ovl: take freeze protection when storing the uuid
2026-11-14 10:47 ` [PATCH 10/12] ovl: take freeze protection when storing the uuid Christian Brauner
@ 2026-11-14 12:19 ` Amir Goldstein
0 siblings, 0 replies; 15+ messages in thread
From: Amir Goldstein @ 2026-11-14 12:19 UTC (permalink / raw)
To: Christian Brauner
Cc: Jan Kara, 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, stable
On Sat, Nov 14, 2026 at 11:47:17AM +0100, Christian Brauner wrote:
> 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>
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Thanks,
Amir.
> ---
> 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 [flat|nested] 15+ messages in thread
* Re: [PATCH 00/12] fs: take freeze protection for writes to backing files
2026-11-14 10:47 [PATCH 00/12] fs: take freeze protection for writes to backing files Christian Brauner
` (11 preceding siblings ...)
2026-11-14 10:47 ` [PATCH 12/12] nfsd: take freeze protection for the truncate on open Christian Brauner
@ 2026-11-14 12:31 ` Amir Goldstein
12 siblings, 0 replies; 15+ messages in thread
From: Amir Goldstein @ 2026-11-14 12:31 UTC (permalink / raw)
To: Christian Brauner
Cc: Jan Kara, 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, stable
On Sat, Nov 14, 2026 at 11:47:07AM +0100, Christian Brauner wrote:
> 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
>
Claude says you missed another one.
nfs_local_write_iocb_done() does not end_write
its ended too early right after submission.
Thanks,
Amir.
> 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] 15+ messages in thread
end of thread, other threads:[~2026-11-14 12:31 UTC | newest]
Thread overview: 15+ 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 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 12:19 ` Amir Goldstein
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
2026-11-14 12:31 ` [PATCH 00/12] fs: take freeze protection for writes to backing files Amir Goldstein
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox