From 49c5d168a3a8f4eb27d44a2a22b7e8a856ca601f Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 11 Feb 2026 15:11:28 -0500 Subject: [PATCH 01/18] udf: fix nls leak on udf_fill_super() failure On all failure exits that go to error_out there we have already moved the nls reference from uopt->nls_map to sbi->s_nls_map, leaving NULL behind. Fixes: c4e89cc674ac ("udf: convert to new mount API") Signed-off-by: Al Viro --- fs/udf/super.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/udf/super.c b/fs/udf/super.c index b2f168b0a0d1..97a51c64ad48 100644 --- a/fs/udf/super.c +++ b/fs/udf/super.c @@ -2320,7 +2320,7 @@ static int udf_fill_super(struct super_block *sb, struct fs_context *fc) error_out: iput(sbi->s_vat_inode); - unload_nls(uopt->nls_map); + unload_nls(sbi->s_nls_map); if (lvid_open) udf_close_lvid(sb); brelse(sbi->s_lvid_bh); From d568a43f6dbba3ba006304d95fd09862bd482a2f Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 23 Jul 2026 12:34:46 +0100 Subject: [PATCH 02/18] afs: Fix afs_fs_fetch_data() to set call->async Fix afs_fs_fetch_data() to set call->async on an async operation as does afs_fs_fetch_data64(). Fixes: eddf51f2bb2c ("afs: Make {Y,}FS.FetchData an asynchronous operation") Link: https://sashiko.dev/#/patchset/20260702144919.172295-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260723113452.566619-2-dhowells@redhat.com cc: Marc Dionne cc: Jeffrey Altman cc: linux-afs@lists.infradead.org cc: stable@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/afs/fsclient.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/afs/fsclient.c b/fs/afs/fsclient.c index a2ffd60889f8..626e1d37b915 100644 --- a/fs/afs/fsclient.c +++ b/fs/afs/fsclient.c @@ -477,6 +477,9 @@ void afs_fs_fetch_data(struct afs_operation *op) if (!call) return afs_op_nomem(op); + if (op->flags & AFS_OPERATION_ASYNC) + call->async = true; + /* marshall the parameters */ bp = call->request; bp[0] = htonl(FSFETCHDATA); From 222052c6be186f2074b3a4d741d5de200f654c43 Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 23 Jul 2026 12:34:47 +0100 Subject: [PATCH 03/18] afs: Fix afs_fs_fetch_data() to subtract transferred from len Fix afs_fs_fetch_data() to subtract subreq->transferred from subreq->len rather than adding it. Fixes: f28fc2010d62 ("afs: Eliminate afs_read") Link: https://sashiko.dev/#/patchset/20260713081022.2186481-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260723113452.566619-3-dhowells@redhat.com cc: Marc Dionne cc: Jeffrey Altman cc: linux-afs@lists.infradead.org cc: stable@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/afs/fsclient.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/afs/fsclient.c b/fs/afs/fsclient.c index 626e1d37b915..1a3f186a6a11 100644 --- a/fs/afs/fsclient.c +++ b/fs/afs/fsclient.c @@ -487,7 +487,7 @@ void afs_fs_fetch_data(struct afs_operation *op) bp[2] = htonl(vp->fid.vnode); bp[3] = htonl(vp->fid.unique); bp[4] = htonl(lower_32_bits(subreq->start + subreq->transferred)); - bp[5] = htonl(lower_32_bits(subreq->len + subreq->transferred)); + bp[5] = htonl(lower_32_bits(subreq->len - subreq->transferred)); call->fid = vp->fid; trace_afs_make_fs_call(call, &vp->fid); From 4af1ec68d54b3871155914d584fb10669c41a861 Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 23 Jul 2026 12:34:48 +0100 Subject: [PATCH 04/18] afs: Fix UAF when sending a message In afs_make_call(), there's a race with async call reception and destruction. If a call is dispatched that doesn't have call->write_iter set (used to specify the data content for FS.StoreData), then the first rxrpc_kernel_send_data() will not set MSG_MORE in the msghdr. Once rxrpc_send_data() queues the last request packet, the response could come in at any time and cause the call to be completed and put. However, afs_make_call() will look at the call again to see it ->write_iter should be handled - something it's only allowed to do if it has its own ref on the call. Whilst this is the case for synchronous calls, it isn't true for async calls such as FS.FetchData. There's also a potential UAF in afs_make_call() in the event that an asynchronous call is being sent, but the call fails in some way (e.g. it gets aborted from the server). The problem there is that afs_make_call() tries to abort a call if the rxrpc send fails, but the asynchronous notification from rxrpc may have caused the afs_call to be torn down. generic/650 plays games with randomly taking CPUs offline, and can interject a significant delay such that the call is deallocated before afs_make_call() gets to check call->write_iter - and a UAF ensues (caught by KASAN). BUG: KASAN: slab-use-after-free in afs_make_call+0x1c90/0x2210 [kafs] Read of size 8 at addr ffff888035e050e8 by task fsstress/1409 Fix this by making afs_make_op_call() give the op->call its own ref rather than transferring the caller's ref to it and then dropping the ref when afs_make_call() returns. This also means that the afs_make_call() func never loses its ref on the call now. Fixes: eddf51f2bb2c ("afs: Make {Y,}FS.FetchData an asynchronous operation") Fixes: e49c7b2f6de7 ("afs: Build an abstraction around an "operation" concept") Link: https://sashiko.dev/#/patchset/20260702144919.172295-1-dhowells%40redhat.com Reported-by: Marc Dionne Signed-off-by: David Howells Link: https://patch.msgid.link/20260723113452.566619-4-dhowells@redhat.com cc: Jeffrey Altman cc: linux-afs@lists.infradead.org cc: stable@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/afs/internal.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/afs/internal.h b/fs/afs/internal.h index 601f01e5c15f..290873bac89b 100644 --- a/fs/afs/internal.h +++ b/fs/afs/internal.h @@ -1421,7 +1421,7 @@ static inline void afs_make_op_call(struct afs_operation *op, struct afs_call *c { struct afs_addr_list *alist = op->estate->addresses; - op->call = call; + op->call = afs_get_call(call, afs_call_trace_get); op->type = call->type; call->op = op; call->key = op->key; @@ -1429,6 +1429,7 @@ static inline void afs_make_op_call(struct afs_operation *op, struct afs_call *c call->peer = rxrpc_kernel_get_peer(alist->addrs[op->addr_index].peer); call->service_id = op->server->service_id; afs_make_call(call, gfp); + afs_put_call(call); } static inline void afs_extract_begin(struct afs_call *call, void *buf, size_t size) From a81fc9266e1c5fef9ccf675a9b44b2f4ab464923 Mon Sep 17 00:00:00 2001 From: Yichong Chen Date: Mon, 27 Jul 2026 14:07:12 +0100 Subject: [PATCH 05/18] netfs: clear PG_private_2 on copy-to-cache append failure netfs_pgpriv2_copy_to_cache() marks the folio with PG_private_2 before netfs_pgpriv2_copy_folio() appends it to the copy-to-cache rolling buffer. If the append fails, the folio is not queued for cache writeback, so the PG_private_2 state and its reference must be released immediately. Fixes: e2d46f2ec332 ("netfs: Change the read result collector to only use one work item") Signed-off-by: Yichong Chen Signed-off-by: David Howells Link: https://patch.msgid.link/20260727130716.1099906-2-dhowells@redhat.com cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/read_pgpriv2.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/netfs/read_pgpriv2.c b/fs/netfs/read_pgpriv2.c index a1489aa29f78..7eacc58abadb 100644 --- a/fs/netfs/read_pgpriv2.c +++ b/fs/netfs/read_pgpriv2.c @@ -54,6 +54,7 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_request *creq, struct folio /* Attach the folio to the rolling buffer. */ if (rolling_buffer_append(&creq->buffer, folio, 0) < 0) { + folio_end_private_2(folio); clear_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &creq->flags); return; } From 37a1c535c80c67d98668d190c7432f9ebda43310 Mon Sep 17 00:00:00 2001 From: Yichong Chen Date: Mon, 27 Jul 2026 14:07:13 +0100 Subject: [PATCH 06/18] netfs: handle single writeback rolling buffer allocation failure netfs_write_folio_single() takes an extra folio reference before appending the folio to the rolling buffer. rolling_buffer_append() can fail if it cannot allocate another folio_queue. Check the return value and drop the extra folio reference before returning the error. Fixes: 49866ce7ea8d ("netfs: Add support for caching single monolithic objects such as AFS dirs") Signed-off-by: Yichong Chen Signed-off-by: David Howells Link: https://patch.msgid.link/20260727130716.1099906-3-dhowells@redhat.com cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/write_issue.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c index f2761c99795a..14efe4cb9393 100644 --- a/fs/netfs/write_issue.c +++ b/fs/netfs/write_issue.c @@ -720,6 +720,7 @@ static int netfs_write_folio_single(struct netfs_io_request *wreq, size_t iter_off = 0; size_t fsize = folio_size(folio), flen; loff_t fpos = folio_pos(folio); + ssize_t ret; bool to_eof = false; bool no_debug = false; @@ -748,7 +749,11 @@ static int netfs_write_folio_single(struct netfs_io_request *wreq, /* Attach the folio to the rolling buffer. */ folio_get(folio); - rolling_buffer_append(&wreq->buffer, folio, NETFS_ROLLBUF_PUT_MARK); + ret = rolling_buffer_append(&wreq->buffer, folio, NETFS_ROLLBUF_PUT_MARK); + if (ret < 0) { + folio_put(folio); + return ret; + } /* Move the submission point forward to allow for write-streaming data * not starting at the front of the page. We don't do write-streaming From 87eb3d272dcbcbbfe5c1576c10e5dc72810cf1f6 Mon Sep 17 00:00:00 2001 From: Yichong Chen Date: Mon, 27 Jul 2026 14:07:14 +0100 Subject: [PATCH 07/18] netfs: release readahead folios on iterator preparation failure netfs_prepare_read_iterator() batches readahead folios in put_batch so that the folio references can be dropped after the I/O iterator has been prepared. If rolling_buffer_load_from_ra() fails after earlier folios have been batched, the function returns immediately and leaves those references held. Release the batch before returning the error. Fixes: 06fa229ceb36 ("netfs: Abstract out a rolling folio buffer implementation") Signed-off-by: Yichong Chen Signed-off-by: David Howells Link: https://patch.msgid.link/20260727130716.1099906-4-dhowells@redhat.com cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/buffered_read.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 24a8a5418e31..3d86414ee40f 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -102,8 +102,10 @@ static ssize_t netfs_prepare_read_iterator(struct netfs_io_subrequest *subreq, added = rolling_buffer_load_from_ra(&rreq->buffer, ractl, &put_batch); - if (added < 0) + if (added < 0) { + folio_batch_release(&put_batch); return added; + } rreq->submitted += added; } folio_batch_release(&put_batch); From 1d78d56c43ef3768183e8370e7367b162700e049 Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 27 Jul 2026 14:07:15 +0100 Subject: [PATCH 08/18] netfs: Fix folio_queue ENOMEM in writeback by adding a mempool Fix the handling of folio_queue allocation failure in writeback by adding a mempool and passing in gfp_t flags to the rolling buffer functions that allocate memory, using the mempool if gfp != GFP_KERNEL. This is then extended upwards and the gfp to be used for a request is stored in the netfs_io_request struct and is then used for both requests and subrequests, eliminating the sleeping loops there. The failure caused: folio != NULL WARNING: fs/netfs/write_issue.c:603 at netfs_writepages+0x883/0xa10 fs/netfs/write_issue.c:603, CPU#3: syz.0.17/5919 Fixes: cd0277ed0c18 ("netfs: Use new folio_queue data type and iterator instead of xarray iter") Reported-by: syzbot+0da43efa72f88bd3a8af@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=0da43efa72f88bd3a8af Signed-off-by: David Howells Link: https://patch.msgid.link/20260727130716.1099906-5-dhowells@redhat.com Tested-by: syzbot+0da43efa72f88bd3a8af@syzkaller.appspotmail.com cc: Paulo Alcantara cc: Yun Zhou cc: Matthew Wilcox cc: Christoph Hellwig cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/buffered_read.c | 6 +++--- fs/netfs/internal.h | 1 + fs/netfs/main.c | 7 +++++++ fs/netfs/objects.c | 30 +++++++++++++++++------------- fs/netfs/read_pgpriv2.c | 2 +- fs/netfs/rolling_buffer.c | 22 +++++++++++++--------- fs/netfs/write_issue.c | 10 +++++----- include/linux/netfs.h | 1 + include/linux/rolling_buffer.h | 6 +++--- 9 files changed, 51 insertions(+), 34 deletions(-) diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 3d86414ee40f..7fdfa4f27e34 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -361,7 +361,7 @@ void netfs_readahead(struct readahead_control *ractl) netfs_rreq_expand(rreq, ractl); rreq->submitted = rreq->start; - if (rolling_buffer_init(&rreq->buffer, rreq->debug_id, ITER_DEST) < 0) + if (rolling_buffer_init(&rreq->buffer, rreq->debug_id, ITER_DEST, rreq->gfp) < 0) goto cleanup_free; netfs_read_to_pagecache(rreq, ractl); @@ -380,10 +380,10 @@ static int netfs_create_singular_buffer(struct netfs_io_request *rreq, struct fo { ssize_t added; - if (rolling_buffer_init(&rreq->buffer, rreq->debug_id, ITER_DEST) < 0) + if (rolling_buffer_init(&rreq->buffer, rreq->debug_id, ITER_DEST, rreq->gfp) < 0) return -ENOMEM; - added = rolling_buffer_append(&rreq->buffer, folio, rollbuf_flags); + added = rolling_buffer_append(&rreq->buffer, folio, rollbuf_flags, rreq->gfp); if (added < 0) return added; rreq->submitted = rreq->start + added; diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index d889caa401dc..420ee7b26580 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -43,6 +43,7 @@ extern struct list_head netfs_io_requests; extern spinlock_t netfs_proc_lock; extern mempool_t netfs_request_pool; extern mempool_t netfs_subrequest_pool; +extern mempool_t netfs_folioq_pool; #ifdef CONFIG_PROC_FS static inline void netfs_proc_add_rreq(struct netfs_io_request *rreq) diff --git a/fs/netfs/main.c b/fs/netfs/main.c index 73da6c9f5777..927badf3989d 100644 --- a/fs/netfs/main.c +++ b/fs/netfs/main.c @@ -28,6 +28,7 @@ static struct kmem_cache *netfs_request_slab; static struct kmem_cache *netfs_subrequest_slab; mempool_t netfs_request_pool; mempool_t netfs_subrequest_pool; +mempool_t netfs_folioq_pool; #ifdef CONFIG_PROC_FS LIST_HEAD(netfs_io_requests); @@ -108,6 +109,9 @@ static int __init netfs_init(void) { int ret = -ENOMEM; + if (mempool_init_kmalloc_pool(&netfs_folioq_pool, 100, sizeof(struct folio_queue)) < 0) + goto error_folioq_pool; + netfs_request_slab = kmem_cache_create("netfs_request", sizeof(struct netfs_io_request), 0, SLAB_HWCACHE_ALIGN | SLAB_ACCOUNT, @@ -160,6 +164,8 @@ error_subreq: error_reqpool: kmem_cache_destroy(netfs_request_slab); error_req: + mempool_exit(&netfs_folioq_pool); +error_folioq_pool: return ret; } fs_initcall(netfs_init); @@ -172,5 +178,6 @@ static void __exit netfs_exit(void) kmem_cache_destroy(netfs_subrequest_slab); mempool_exit(&netfs_request_pool); kmem_cache_destroy(netfs_request_slab); + mempool_exit(&netfs_folioq_pool); } module_exit(netfs_exit); diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c index b8c4918d3dcd..01461a74642d 100644 --- a/fs/netfs/objects.c +++ b/fs/netfs/objects.c @@ -7,7 +7,6 @@ #include #include -#include #include "internal.h" static void netfs_free_request(struct work_struct *work); @@ -26,17 +25,23 @@ struct netfs_io_request *netfs_alloc_request(struct address_space *mapping, struct netfs_io_request *rreq; mempool_t *mempool = ctx->ops->request_pool ?: &netfs_request_pool; struct kmem_cache *cache = mempool->pool_data; + gfp_t gfp = GFP_KERNEL; int ret; - for (;;) { - rreq = mempool_alloc(mempool, GFP_KERNEL); - if (rreq) - break; - msleep(10); + /* Writeback is part of memory reclaim and must not fail due to ENOMEM. */ + if (origin == NETFS_WRITEBACK || origin == NETFS_WRITEBACK_SINGLE) { + gfp = GFP_NOFS; /* Allows use of mempools. */ + + rreq = mempool_alloc(mempool, gfp); + } else { + rreq = mempool->alloc(gfp, mempool->pool_data); + if (!rreq) + return ERR_PTR(-ENOMEM); } memset(rreq, 0, kmem_cache_size(cache)); INIT_WORK(&rreq->cleanup_work, netfs_free_request); + rreq->gfp = gfp; rreq->start = start; rreq->len = len; rreq->origin = origin; @@ -200,13 +205,12 @@ struct netfs_io_subrequest *netfs_alloc_subrequest(struct netfs_io_request *rreq mempool_t *mempool = rreq->netfs_ops->subrequest_pool ?: &netfs_subrequest_pool; struct kmem_cache *cache = mempool->pool_data; - for (;;) { - subreq = mempool_alloc(rreq->netfs_ops->subrequest_pool ?: &netfs_subrequest_pool, - GFP_KERNEL); - if (subreq) - break; - msleep(10); - } + if (rreq->gfp == GFP_KERNEL) + subreq = mempool->alloc(rreq->gfp, mempool->pool_data); + else + subreq = mempool_alloc(mempool, rreq->gfp); + if (!subreq) + return NULL; memset(subreq, 0, kmem_cache_size(cache)); INIT_WORK(&subreq->work, NULL); diff --git a/fs/netfs/read_pgpriv2.c b/fs/netfs/read_pgpriv2.c index 7eacc58abadb..c31190993b76 100644 --- a/fs/netfs/read_pgpriv2.c +++ b/fs/netfs/read_pgpriv2.c @@ -53,7 +53,7 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_request *creq, struct folio trace_netfs_folio(folio, netfs_folio_trace_store_copy); /* Attach the folio to the rolling buffer. */ - if (rolling_buffer_append(&creq->buffer, folio, 0) < 0) { + if (rolling_buffer_append(&creq->buffer, folio, 0, creq->gfp) < 0) { folio_end_private_2(folio); clear_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &creq->flags); return; diff --git a/fs/netfs/rolling_buffer.c b/fs/netfs/rolling_buffer.c index a17fbf9853a4..8c0026836f9c 100644 --- a/fs/netfs/rolling_buffer.c +++ b/fs/netfs/rolling_buffer.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -27,7 +28,10 @@ struct folio_queue *netfs_folioq_alloc(unsigned int rreq_id, gfp_t gfp, { struct folio_queue *fq; - fq = kmalloc_obj(*fq, gfp); + if (gfp == GFP_KERNEL) + fq = netfs_folioq_pool.alloc(gfp, netfs_folioq_pool.pool_data); + else + fq = mempool_alloc(&netfs_folioq_pool, gfp); if (fq) { netfs_stat(&netfs_n_folioq); folioq_init(fq, rreq_id); @@ -50,7 +54,7 @@ void netfs_folioq_free(struct folio_queue *folioq, { trace_netfs_folioq(folioq, trace); netfs_stat_d(&netfs_n_folioq); - kfree(folioq); + mempool_free(folioq, &netfs_folioq_pool); } EXPORT_SYMBOL(netfs_folioq_free); @@ -60,11 +64,11 @@ EXPORT_SYMBOL(netfs_folioq_free); * consumer. */ int rolling_buffer_init(struct rolling_buffer *roll, unsigned int rreq_id, - unsigned int direction) + unsigned int direction, gfp_t gfp) { struct folio_queue *fq; - fq = netfs_folioq_alloc(rreq_id, GFP_NOFS, netfs_trace_folioq_rollbuf_init); + fq = netfs_folioq_alloc(rreq_id, gfp, netfs_trace_folioq_rollbuf_init); if (!fq) return -ENOMEM; @@ -77,14 +81,14 @@ int rolling_buffer_init(struct rolling_buffer *roll, unsigned int rreq_id, /* * Add another folio_queue to a rolling buffer if there's no space left. */ -int rolling_buffer_make_space(struct rolling_buffer *roll) +int rolling_buffer_make_space(struct rolling_buffer *roll, gfp_t gfp) { struct folio_queue *fq, *head = roll->head; if (!folioq_full(head)) return 0; - fq = netfs_folioq_alloc(head->rreq_id, GFP_NOFS, netfs_trace_folioq_make_space); + fq = netfs_folioq_alloc(head->rreq_id, gfp, netfs_trace_folioq_make_space); if (!fq) return -ENOMEM; fq->prev = head; @@ -122,7 +126,7 @@ ssize_t rolling_buffer_load_from_ra(struct rolling_buffer *roll, int nr, ix, to; ssize_t size = 0; - if (rolling_buffer_make_space(roll) < 0) + if (rolling_buffer_make_space(roll, GFP_KERNEL) < 0) return -ENOMEM; fq = roll->head; @@ -153,12 +157,12 @@ ssize_t rolling_buffer_load_from_ra(struct rolling_buffer *roll, * Append a folio to the rolling buffer. */ ssize_t rolling_buffer_append(struct rolling_buffer *roll, struct folio *folio, - unsigned int flags) + unsigned int flags, gfp_t gfp) { ssize_t size = folio_size(folio); int slot; - if (rolling_buffer_make_space(roll) < 0) + if (rolling_buffer_make_space(roll, gfp) < 0) return -ENOMEM; slot = folioq_append(roll->head, folio); diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c index 14efe4cb9393..2d9cfcd43658 100644 --- a/fs/netfs/write_issue.c +++ b/fs/netfs/write_issue.c @@ -108,7 +108,7 @@ struct netfs_io_request *netfs_create_write_req(struct address_space *mapping, ictx = netfs_inode(wreq->inode); if (is_cacheable) fscache_begin_write_operation(&wreq->cache_resources, netfs_i_cookie(ictx)); - if (rolling_buffer_init(&wreq->buffer, wreq->debug_id, ITER_SOURCE) < 0) + if (rolling_buffer_init(&wreq->buffer, wreq->debug_id, ITER_SOURCE, wreq->gfp) < 0) goto nomem; wreq->cleaned_to = wreq->start; @@ -167,7 +167,7 @@ void netfs_prepare_write(struct netfs_io_request *wreq, */ if (iov_iter_is_folioq(wreq_iter) && wreq_iter->folioq_slot >= folioq_nr_slots(wreq_iter->folioq)) - rolling_buffer_make_space(&wreq->buffer); + rolling_buffer_make_space(&wreq->buffer, wreq->gfp); subreq = netfs_alloc_subrequest(wreq); subreq->source = stream->source; @@ -334,7 +334,7 @@ static int netfs_write_folio(struct netfs_io_request *wreq, _enter(""); - if (rolling_buffer_make_space(&wreq->buffer) < 0) + if (rolling_buffer_make_space(&wreq->buffer, wreq->gfp) < 0) return -ENOMEM; /* netfs_perform_write() may shift i_size around the page or from out @@ -436,7 +436,7 @@ static int netfs_write_folio(struct netfs_io_request *wreq, } /* Attach the folio to the rolling buffer. */ - rolling_buffer_append(&wreq->buffer, folio, 0); + rolling_buffer_append(&wreq->buffer, folio, 0, wreq->gfp); /* Move the submission point forward to allow for write-streaming data * not starting at the front of the page. We don't do write-streaming @@ -749,7 +749,7 @@ static int netfs_write_folio_single(struct netfs_io_request *wreq, /* Attach the folio to the rolling buffer. */ folio_get(folio); - ret = rolling_buffer_append(&wreq->buffer, folio, NETFS_ROLLBUF_PUT_MARK); + ret = rolling_buffer_append(&wreq->buffer, folio, NETFS_ROLLBUF_PUT_MARK, wreq->gfp); if (ret < 0) { folio_put(folio); return ret; diff --git a/include/linux/netfs.h b/include/linux/netfs.h index 1bc120d61c5b..d0b62d53eea9 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -255,6 +255,7 @@ struct netfs_io_request { unsigned long long cleaned_to; /* Position we've cleaned folios to */ unsigned long long abandon_to; /* Position to abandon folios to */ const struct folio *no_unlock_folio; /* Don't unlock this folio after read */ + gfp_t gfp; /* GFP flags to use */ unsigned int direct_bv_count; /* Number of elements in direct_bv[] */ unsigned int debug_id; unsigned int rsize; /* Maximum read size (0 for none) */ diff --git a/include/linux/rolling_buffer.h b/include/linux/rolling_buffer.h index ac15b1ffdd83..9e5dad29669c 100644 --- a/include/linux/rolling_buffer.h +++ b/include/linux/rolling_buffer.h @@ -43,13 +43,13 @@ struct rolling_buffer_snapshot { #define ROLLBUF_MARK_2 BIT(1) int rolling_buffer_init(struct rolling_buffer *roll, unsigned int rreq_id, - unsigned int direction); -int rolling_buffer_make_space(struct rolling_buffer *roll); + unsigned int direction, gfp_t gfp); +int rolling_buffer_make_space(struct rolling_buffer *roll, gfp_t gfp); ssize_t rolling_buffer_load_from_ra(struct rolling_buffer *roll, struct readahead_control *ractl, struct folio_batch *put_batch); ssize_t rolling_buffer_append(struct rolling_buffer *roll, struct folio *folio, - unsigned int flags); + unsigned int flags, gfp_t gfp); struct folio_queue *rolling_buffer_delete_spent(struct rolling_buffer *roll); void rolling_buffer_clear(struct rolling_buffer *roll); From 79055d82772b9584f259b747fe40ff56a076678d Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 28 Jul 2026 14:26:32 +0200 Subject: [PATCH 09/18] binfmt_misc: don't let an 'F' entry pin its own instance An entry registered with 'F' opens its interpreter at registration time and holds that file until the entry is freed. Any entry nobody removes by hand only gets closed once the binfmt_misc superblock is shut down. If the interpreter lives on a mount that keeps that superblock alive the two pin each other: binfmt_misc sb -> inode -> entry -> interp_file -> vfsmount -> binfmt_misc sb TL;DR the file is never closed. Once the mount namespace is gone there is nothing left to unregister through either. There are two ways to trigger this bug: - Point the interpreter at the instance itself. Its files are regular files owned by the mounter and both bm_get_inode() and simple_fill_super() leave i_op at empty_iops. So notify_change() falls back to simple_setattr() and chmod +x works. We never set SB_I_NOEXEC and so open_exec() accepts it. - Use the instance as an overlayfs lower layer. The overlay superblock holds a clone_private_mount() of every layer until it is destroyed and that clone is in no namespace. So umount_tree() never reaches it. That's a DoS. And it isn't only the superblock that leaks. It pins the user namespace it was mounted in, so every iteration permanently eats one of the caller's user namespace charges. So let's just do the sane thing. SB_I_NOEXEC makes open_exec() fail on the instance's own files and s_stack_depth makes overlayfs reject the layer before it ever takes a clone. That also covers the ecryptfs and fuse passthrough variants. What 'F' promises is unchanged. The stable tag is narrower than the Fixes tags on purpose. Before sandboxed mounts this needed global root against the single instance everyone shares, and the change doesn't apply to those trees anyway. Note that SB_I_NODEV is implicitly raised for userns mounts but raise it explicitly here as well. Link: https://patch.msgid.link/20260728-work-binfmt_misc-selfpin-v1-1-74df5daeca5b@kernel.org Fixes: 948b701a607f ("binfmt_misc: add persistent opened binary handler for containers") Fixes: 21ca59b365c0 ("binfmt_misc: enable sandboxed mounts") Cc: stable@vger.kernel.org # v6.7+ Signed-off-by: Christian Brauner (Amutable) --- fs/binfmt_misc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c index 5de615ca7a75..47aeb2b68d3e 100644 --- a/fs/binfmt_misc.c +++ b/fs/binfmt_misc.c @@ -937,6 +937,10 @@ static int bm_fill_super(struct super_block *sb, struct fs_context *fc) if (WARN_ON(user_ns != current_user_ns())) return -EINVAL; + /* Never exec off this instance and never let anything stack on it. */ + sb->s_iflags |= SB_I_NOEXEC | SB_I_NODEV; + sb->s_stack_depth = FILESYSTEM_MAX_STACK_DEPTH; + /* * Lazily allocate a new binfmt_misc instance for this namespace, i.e. * do it here during the first mount of binfmt_misc. We don't need to From db1856ea9196cf6e015d12199a34c0b9313c7bfa Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 10 Jul 2026 11:33:02 +0200 Subject: [PATCH 10/18] binfmt_misc: restore write access when removing an entry Registering an entry with the MISC_FMT_OPEN_FILE flag opens the interpreter via open_exec() which denies write access to it for as long as the entry exists. Removing the entry closes the interpreter file via filp_close() but never restores write access, leaving the inode's i_writecount permanently negative. Opening the interpreter for writing keeps failing with ETXTBSY long after the entry is gone until the inode is evicted from the inode cache. Commit 90f601b497d7 ("binfmt_misc: restore write access before closing files opened by open_exec()") fixed the same imbalance in the error path of bm_register_write() but the actual removal path has been leaking the write denial since the introduction of the flag. Restore write access in put_binfmt_handler() before closing the interpreter file. Link: https://patch.msgid.link/20260710-work-binfmt_misc-locking-v3-1-a162f7cb58d6@kernel.org Fixes: 948b701a607f ("binfmt_misc: add persistent opened binary handler for containers") Cc: stable@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/binfmt_misc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c index 47aeb2b68d3e..adab06d18550 100644 --- a/fs/binfmt_misc.c +++ b/fs/binfmt_misc.c @@ -162,8 +162,10 @@ static Node *get_binfmt_handler(struct binfmt_misc *misc, static void put_binfmt_handler(Node *e) { if (refcount_dec_and_test(&e->users)) { - if (e->flags & MISC_FMT_OPEN_FILE) + if (e->flags & MISC_FMT_OPEN_FILE) { + exe_file_allow_write_access(e->interp_file); filp_close(e->interp_file, NULL); + } kfree(e); } } From fa5990ca8fd917003e526036bcc50413edb9722c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 10 Jul 2026 11:33:03 +0200 Subject: [PATCH 11/18] binfmt_misc: use exe_file_deny_write_access() for the interpreter clone For MISC_FMT_OPEN_FILE entries load_misc_binary() clones the registered interpreter file and denies write access to the clone via plain deny_write_access(). The clone is installed as bprm->interpreter and later released by the exec machinery through exe_file_allow_write_access() which skips the i_writecount increment for files with FMODE_FSNOTIFY_HSM set. The deny and allow side can therefore come to different conclusions when pre-content watches are in play: if a pre-content watch is added to the interpreter after registration every subsequent exec through that entry takes a write denial on the clone that is never paired with a write allowance, driving the interpreter inode's i_writecount further down with each exec and leaving the interpreter unwritable even after the entry and all its users are gone. Take the write denial via exe_file_deny_write_access() so both sides of the pairing base their decision on the same file mode, and propagate failure instead of silently ignoring it: an interpreter that is concurrently open for writing now fails the exec with ETXTBSY, exactly like an interpreter freshly opened via open_exec() would. Link: https://patch.msgid.link/20260710-work-binfmt_misc-locking-v3-2-a162f7cb58d6@kernel.org Fixes: 0357ef03c94e ("fs: don't block write during exec on pre-content watched files") Cc: stable@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/binfmt_misc.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c index adab06d18550..bf7d6b975825 100644 --- a/fs/binfmt_misc.c +++ b/fs/binfmt_misc.c @@ -249,8 +249,14 @@ static int load_misc_binary(struct linux_binprm *bprm) if (fmt->flags & MISC_FMT_OPEN_FILE) { interp_file = file_clone_open(fmt->interp_file); - if (!IS_ERR(interp_file)) - deny_write_access(interp_file); + if (!IS_ERR(interp_file)) { + int err = exe_file_deny_write_access(interp_file); + + if (err) { + fput(interp_file); + interp_file = ERR_PTR(err); + } + } } else { interp_file = open_exec(fmt->interpreter); } From 8e85d50ba1117fd446bf9a250bd8a97d48384bdc Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 10 Jul 2026 11:33:04 +0200 Subject: [PATCH 12/18] binfmt_misc: reject a flag character as the field delimiter The registration string starts with a user chosen delimiter that separates the individual fields. So that the field parsers terminate even on a truncated string create_entry() pads the buffer with that same delimiter: memset(buf + count, del, 8); Most fields are scanned for the delimiter with strchr()/scanarg() and happily stop on the padding. The flags field is different: instead of scanning for the delimiter check_special_flags() consumes the flag characters 'P', 'O', 'C' and 'F' and stops at the first byte that is none of them, relying on the trailing delimiter to end the scan. If the delimiter is itself a flag character the padding no longer acts as a terminator. The scan swallows all eight padding bytes and keeps reading past the end of the allocation until it hits a byte that is not a flag character. For example registering PaPEPPxPPiP with 'P' as the delimiter (name "a", type extension, magic "x", interpreter "i", empty flags) leaves the flag scan running off the end of the buffer. The registration is rejected in the end because the parser does not stop exactly at buf + count, but only after the out of bounds read has already happened. With an unlucky allocation layout the scan can walk into an unmapped page; under KASAN it is reported as a slab out of bounds read. binfmt_misc mounts are available to unprivileged users in a user namespace so the read is reachable without privileges. Reject a delimiter that is one of the flag characters up front. Such a registration was always rejected anyway, only after the out of bounds read, so no valid registration string changes meaning. Link: https://patch.msgid.link/20260710-work-binfmt_misc-locking-v3-3-a162f7cb58d6@kernel.org Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/binfmt_misc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c index bf7d6b975825..a73a37b8a013 100644 --- a/fs/binfmt_misc.c +++ b/fs/binfmt_misc.c @@ -384,6 +384,10 @@ static Node *create_entry(const char __user *buffer, size_t count) pr_debug("register: delim: %#x {%c}\n", del, del); + /* A flag-char delimiter runs the flag scan off the buffer. */ + if (del == 'P' || del == 'O' || del == 'C' || del == 'F') + goto einval; + /* Pad the buffer with the delim to simplify parsing below. */ memset(buf + count, del, 8); From b8206f516fe7cbe785cf44bf09c17c438d7c3cad Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 28 Jul 2026 15:48:10 +0200 Subject: [PATCH 13/18] binfmt_misc: don't leak the user namespace when the mount fails bm_get_tree() takes a reference to the user namespace and hands it to get_tree_keyed() as the sget key. sget_fc() moves that reference into sb->s_fs_info and clears fc->s_fs_info, so from that point on the superblock owns it and bm_free() doesn't see it anymore. The superblock drops it in ->put_super(). But generic_shutdown_super() only calls ->put_super() from inside the if (sb->s_root) branch, so nothing releases it when bm_fill_super() fails: - The kzalloc_obj() failure leaves s_root NULL and the whole branch is skipped. - A simple_fill_super() failure in the file loop leaves s_root set, but s_op still points at simple_super_operations, which has no ->put_super(). bm_fill_super() installs s_ops only once simple_fill_super() returned success, and installing it earlier wouldn't help either because simple_fill_super() overwrites s_op. Either way vfs_get_super() calls deactivate_locked_super() and the reference is gone for good. binfmt_misc mounts are available in a user namespace and both the inode and the dentry cache are SLAB_ACCOUNT, so an unprivileged caller under a tight memory cgroup can fail simple_fill_super() on demand and leak one user namespace per attempt. Drop the reference in ->kill_sb() instead, which runs unconditionally, the same way nfsd and rpc_pipefs release their keyed s_fs_info. That also stops ->put_super() from clearing s_fs_info while the superblock is still on @fs_supers. generic_shutdown_super() leaves it there on purpose so that sget_fc() keeps finding it until kill_sb() has run, but a NULL s_fs_info makes test_keyed_super() miss it, so a concurrent mount for the same user namespace skips the grab_super() wait and creates a second superblock for a namespace that is still being torn down. Link: https://patch.msgid.link/20260728-work-binfmt_misc-usernsleak-v1-1-dbd8d5e626e7@kernel.org Fixes: 21ca59b365c0 ("binfmt_misc: enable sandboxed mounts") Cc: stable@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/binfmt_misc.c | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c index a73a37b8a013..c97f10b48b5b 100644 --- a/fs/binfmt_misc.c +++ b/fs/binfmt_misc.c @@ -921,18 +921,9 @@ static const struct file_operations bm_status_operations = { /* Superblock handling */ -static void bm_put_super(struct super_block *sb) -{ - struct user_namespace *user_ns = sb->s_fs_info; - - sb->s_fs_info = NULL; - put_user_ns(user_ns); -} - static const struct super_operations s_ops = { .statfs = simple_statfs, .evict_inode = bm_evict_inode, - .put_super = bm_put_super, }; static int bm_fill_super(struct super_block *sb, struct fs_context *fc) @@ -990,13 +981,12 @@ static int bm_fill_super(struct super_block *sb, struct fs_context *fc) /* * When the binfmt_misc superblock for this userns is shutdown * ->enabled might have been set to false and we don't reinitialize - * ->enabled again in put_super() as someone might already be mounting - * binfmt_misc again. It also would be pointless since by the time - * ->put_super() is called we know that the binary type list for this - * bintfmt_misc mount is empty making load_misc_binary() return - * -ENOEXEC independent of whether ->enabled is true. Instead, if - * someone mounts binfmt_misc for the first time or again we simply - * reset ->enabled to true. + * ->enabled again during shutdown as someone might already be mounting + * binfmt_misc again. It also would be pointless since by then we know + * that the binary type list for this binfmt_misc mount is empty making + * load_misc_binary() return -ENOEXEC independent of whether ->enabled + * is true. Instead, if someone mounts binfmt_misc for the first time or + * again we simply reset ->enabled to true. */ misc->enabled = true; @@ -1022,6 +1012,14 @@ static const struct fs_context_operations bm_context_ops = { .get_tree = bm_get_tree, }; +static void bm_kill_sb(struct super_block *sb) +{ + struct user_namespace *user_ns = sb->s_fs_info; + + kill_anon_super(sb); + put_user_ns(user_ns); +} + static int bm_init_fs_context(struct fs_context *fc) { fc->ops = &bm_context_ops; @@ -1038,7 +1036,7 @@ static struct file_system_type bm_fs_type = { .name = "binfmt_misc", .init_fs_context = bm_init_fs_context, .fs_flags = FS_USERNS_MOUNT, - .kill_sb = kill_anon_super, + .kill_sb = bm_kill_sb, }; MODULE_ALIAS_FS("binfmt_misc"); From cf6c993c0feca7984797e634deba3c80342e199a Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Sat, 25 Jul 2026 16:00:04 +0800 Subject: [PATCH 14/18] fscrypt: use the mount idmap for the owner check in fscrypt_ioctl_set_policy() fscrypt_ioctl_set_policy() calls inode_owner_or_capable() with &nop_mnt_idmap before allowing an encryption policy to be set, instead of the idmap of the mount the ioctl was issued on. fscrypt is used by filesystems that support idmapped mounts (e.g. ext4, f2fs), so on such a mount this compares the caller's fsuid against the unmapped on-disk owner rather than the mapped owner: the actual owner can be wrongly denied with -EACCES and an unrelated caller wrongly allowed. Use file_mnt_idmap(filp) instead. Fixes: 14f3db5542e6 ("ext4: support idmapped mounts") Cc: stable@vger.kernel.org Signed-off-by: Zhan Xusheng Link: https://patch.msgid.link/20260725080004.929328-1-zhanxusheng1024@gmail.com Signed-off-by: Eric Biggers --- fs/crypto/policy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c index 9915e39362db..c80b24a941ad 100644 --- a/fs/crypto/policy.c +++ b/fs/crypto/policy.c @@ -534,7 +534,7 @@ int fscrypt_ioctl_set_policy(struct file *filp, const void __user *arg) return -EFAULT; policy.version = version; - if (!inode_owner_or_capable(&nop_mnt_idmap, inode)) + if (!inode_owner_or_capable(file_mnt_idmap(filp), inode)) return -EACCES; ret = mnt_want_write_file(filp); From d2f96bcb89d36d488a10e3bcf819b98536968286 Mon Sep 17 00:00:00 2001 From: Andrey Albershteyn Date: Mon, 27 Jul 2026 11:43:52 +0200 Subject: [PATCH 15/18] fs,fsverity: remove check for fsverity being enabled in setattr_prepare() The check that fs-verity is available in the kernel is not necessary here. Filesystems could have fsverity files even without fs-verity enabled. In that case, truncate on fsverity file will succeed, what this check is trying to prevent. Fixes: e9734653c523 ("fs,fsverity: reject size changes on fsverity files in setattr_prepare") Cc: stable@vger.kernel.org Signed-off-by: Andrey Albershteyn Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260727094352.1734826-1-aalbersh@kernel.org Signed-off-by: Eric Biggers --- fs/attr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/attr.c b/fs/attr.c index 4f437fabb7f0..71888ac903c2 100644 --- a/fs/attr.c +++ b/fs/attr.c @@ -176,7 +176,7 @@ int setattr_prepare(struct mnt_idmap *idmap, struct dentry *dentry, * covered by the open-time check because sys_truncate() takes a * path, not an open file. */ - if (IS_ENABLED(CONFIG_FS_VERITY) && IS_VERITY(inode)) + if (IS_VERITY(inode)) return -EPERM; error = inode_newsize_ok(inode, attr->ia_size); From 0680cbbf39ca61c70be16141b5259f822e7cdb3b Mon Sep 17 00:00:00 2001 From: Boris Burkov Date: Mon, 27 Jul 2026 15:23:30 -0700 Subject: [PATCH 16/18] btrfs: trigger cow fixup via dirty_folio() The problem scenario: If we have a folio mmapped shared and then somebody does a dio read with that folio as the read destination, then it is possible that the dio will see a dirty destination page when it starts (and thus skip dirtying and just GUP pin it) but then while it is doing the read, btrfs finishes writing it back and by the endio, the folio is clean. In that case, the dio read must re-dirty the folio with aops->dirty_folio(): btrfs_check_read_bio() |- __iomap_dio_bio_end_io() from btrfs_bio_end_io() |- bio_check_pages_dirty() |- bio_dirty_fn() |- bio_release_pages(bio, true) |- __bio_release_pages(bio, mark_dirty == true) |- folio_lock() |- folio_mark_dirty() |- aops->dirty_folio() |- folio_unlock() A data block normally moves through writeback as follows: TASK folio_lock write clean -> dirty bit + delalloc folio_unlock WRITEBACK for-each-dirty-folio: folio_lock run_delalloc delalloc consumed -> dirty bit + OE submission dirty bit consumed -> writeback bit + OE folio_unlock ENDIO endio OE bytes accounted OE finish writeback -> clean; destroy OE Three critical invariants that this path maintains are: I1. Any dirty block is covered by delalloc xor an ordered extent I2. Any dirty block covered by an OE will be submitted into that OE I3. Any dirty block already submitted into an OE will not be submitted again into the same OE. These ensure that the block will be written exactly once. It is clear that not reserving delalloc for the re-dirty case violates I1. This situation, even without bs < folio_size, has long required btrfs to fixup such dirty pages during writeback with an asynchronous worker that is allowed to do this expensive work and writeback does not proceed for a folio while it is doing this work. Commit 247e743cbe6e ("Btrfs: Use async helpers to deal with pages that have been improperly dirtied") introduced the COW fixup to catch exactly this class at writeback, way back in 2008. Since then, there have been many advances to prevent most of the causes of such re-dirtying and we thought we could get away with removing the annoying cow-fixup in the hope of simplifying writeback for large folio support. Commit b2a9f217ad3f ("btrfs: remove the COW fixup mechanism") Commit 4927b141877c ("btrfs: remove folio ordered flag and subpage bitmap") Since it turns out this assumption was incorrect, as evidenced by the report and attendant reproducers, we must reintroduce the fixup concept. This is of course critically further complicated by bs < folio_size. In that case, rather than just a folio dirty bit, we have a bitmap for the dirty blocks in the folio. And the (also broken) invariant is: I4. folio dirty IFF at least one block bitmap dirty. The original report of a stall on a misinterpreted empty bitmap is exactly evidence of a violation of I4. It is exactly because of bs < folio_size we don't want to simply revert the removal patches. The original fixup was not properly bs < folio_size aware, which motivated removal in the first place. So we wish to build a bs < folio_size aware fixup. One other important detail from the old design, any normal write that happens after a re-dirty but before a fixup is racing with the cow fixup to do the delalloc reservation, therefore it must cancel the fixup state. If it arrives after the reservation exists, it will be a normal dirty overwrite. This critically informs the design in a pretty clear way. fixup requiring re-dirty has folio granularity, while cancellation has delalloc (block) granularity so while we only ever produce fixup in chunks of folios, we must be able to clear it in blocks. Therefore we must track the blocks needing fixup at block granularity. The obvious way to do this is with a new bitmap in btrfs_folio_state, but it is desirable to avoid that if possible. Unfortunately, I don't think it is possible and the reason is subtle and leans on a sort of extreme reproducer, but I think can be explained relatively succinctly. Consider a folio whose two halves will land in different ordered extents (can be accomplished with tricks using nodatasum) and a dio read is running with it as the shared mmap destination. 1. The front half: a. folio comes clean on a normal write b. dio read completes into the folio marking it fixup. c. a write comes for the previous folio for a range extending into this folio, this is a cancellation of the fixup which reserves space. d. writeback runs on the range *not* overlapping the folio. This half remains dirty but is now covered by an OE and is awaiting writeback running on its range to be submitted and finish the OE. 2. The back half: a. the folio is part of an OE that gets far enough along to clear writeback. b. dio read completes into the folio marking it fixup. After this, the folio's front half is dirty in the "normal" sense, it needs to be submitted to the OE waiting for it. It's a cancelled fixup. Meanwhile, the second half is a true fresh fixup. So at this point if we run writeback on this folio, we genuinely can't know what to do without block level information. If we submit it, we submit unreserved dirty from the back half. If we don't, we will never finish the OE waiting for it. So it's either a corruption or a deadlock. Thus, the full high level design picture: - btrfs_data_dirty_folio(): For out of band non-reserving dirties, mark still-clean blocks inside EOF dirty and set their fixup bits (the event carries no range, so every clean block is suspect). Already-dirty blocks are covered or pending and are left alone. - Writeback: skip fixup blocks and enqueue work for them - writepage_fixup(): for each fixup block do the fixup reservation in a worker, after which the blocks can be written back normally. - Typical reserving write paths cancel fixup state for the ranges they cover with btrfs_folio_cancel_fixup() Link: https://lore.kernel.org/linux-btrfs/20260721191152.101118-1-borntraeger@linux.ibm.com/ Assisted-by: LLM Reviewed-by: Qu Wenruo Signed-off-by: Boris Burkov Signed-off-by: David Sterba --- fs/btrfs/btrfs_inode.h | 1 + fs/btrfs/disk-io.c | 7 +- fs/btrfs/extent_io.c | 113 ++++++++++++++++++ fs/btrfs/fs.h | 12 ++ fs/btrfs/inode.c | 200 +++++++++++++++++++++++++++++++- fs/btrfs/subpage.c | 216 ++++++++++++++++++++++++++++++++++- fs/btrfs/subpage.h | 41 ++++++- include/trace/events/btrfs.h | 35 ++++++ 8 files changed, 613 insertions(+), 12 deletions(-) diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h index 7fdc6c3fd066..1082fa92c145 100644 --- a/fs/btrfs/btrfs_inode.h +++ b/fs/btrfs/btrfs_inode.h @@ -600,6 +600,7 @@ int btrfs_prealloc_file_range_trans(struct inode *inode, loff_t actual_len, u64 *alloc_hint); int btrfs_run_delalloc_range(struct btrfs_inode *inode, struct folio *locked_folio, u64 start, u64 end, struct writeback_control *wbc); +void btrfs_queue_writepage_fixup(struct btrfs_inode *inode, struct folio *folio); int btrfs_encoded_io_compression_from_extent(struct btrfs_fs_info *fs_info, int compress_type); int btrfs_encoded_read_regular_fill_pages(struct btrfs_inode *inode, diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 36332df9a0f1..6bb70c43a63f 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -1760,6 +1760,8 @@ static int read_backup_root(struct btrfs_fs_info *fs_info, u8 priority) /* helper to cleanup workers */ static void btrfs_stop_all_workers(struct btrfs_fs_info *fs_info) { + if (fs_info->fixup_workers) + destroy_workqueue(fs_info->fixup_workers); btrfs_destroy_workqueue(fs_info->delalloc_workers); btrfs_destroy_workqueue(fs_info->workers); if (fs_info->endio_workers) @@ -1967,6 +1969,9 @@ static int btrfs_init_workqueues(struct btrfs_fs_info *fs_info) fs_info->caching_workers = btrfs_alloc_workqueue(fs_info, "cache", flags, max_active, 0); + fs_info->fixup_workers = + alloc_ordered_workqueue("btrfs-fixup", ordered_flags); + fs_info->endio_workers = alloc_workqueue("btrfs-endio", flags, max_active); fs_info->endio_meta_workers = @@ -1992,7 +1997,7 @@ static int btrfs_init_workqueues(struct btrfs_fs_info *fs_info) fs_info->endio_workers && fs_info->endio_meta_workers && fs_info->endio_write_workers && fs_info->endio_freespace_worker && fs_info->rmw_workers && - fs_info->caching_workers && + fs_info->caching_workers && fs_info->fixup_workers && fs_info->delayed_workers && fs_info->qgroup_rescan_workers && fs_info->discard_ctl.discard_workers)) { return -ENOMEM; diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index de5785117a47..f032f0858f40 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -1440,6 +1440,115 @@ static bool find_next_delalloc_bitmap(struct folio *folio, return true; } +/* + * Debug checks for fixup selection logic to help ensure the invariants + * we expect for fixup marking hold in practice. + * + * - A dirty block without a fixup bit is covered by delalloc or a running + * ordered extent (it was dirtied by a reserving write path). + * - A block with a fixup bit is never covered by delalloc: every delalloc + * setter holds the folio lock and cancels the fixup state of the blocks + * it covers (btrfs_folio_set_dirty()) before releasing it. + */ +static void debug_check_writepage_fixup(struct btrfs_inode *inode, u64 start, + u32 len, bool needs_fixup) +{ + struct btrfs_ordered_extent *ordered; + bool delalloc; + + if (!IS_ENABLED(CONFIG_BTRFS_DEBUG)) + return; + + delalloc = btrfs_test_range_bit_exists(&inode->io_tree, start, + start + len - 1, EXTENT_DELALLOC); + if (needs_fixup) { + if (unlikely(delalloc)) + DEBUG_WARN("writeback: delalloc and fixup conflict. ino %llu start %llu", + btrfs_ino(inode), start); + } else { + if (delalloc) + return; + + ordered = btrfs_lookup_ordered_range(inode, start, len); + if (unlikely(!ordered)) + DEBUG_WARN("dirty block, no delalloc, fixup, ordered. ino %llu start %llu", + btrfs_ino(inode), start); + else + btrfs_put_ordered_extent(ordered); + } +} + +/* + * Handle folios dirtied without a delalloc reservation, e.g. + * O_DIRECT read into a MAP_SHARED mapping dirtying via set_page_dirty_lock(). + * + * btrfs_data_dirty_folio() records the affected blocks in the fixup bitmap + * and the folio fixup flag and we check them here in writeback. + * + * Don't submit such blocks and queue work for the fixup worker to reserve + * space for them so that they can be submitted properly by writeback. + * + * Return 1 if the folio needed fixup, 0 if not, and a negative error code + * on error. + */ +static noinline_for_stack int writepage_fixup(struct btrfs_inode *inode, + struct folio *folio, + struct btrfs_bio_ctrl *bio_ctrl) +{ + struct btrfs_fs_info *fs_info = inode_to_fs_info(&inode->vfs_inode); + const unsigned int blocks_per_folio = btrfs_blocks_per_folio(fs_info, folio); + const u32 sectorsize = fs_info->sectorsize; + const u64 page_start = folio_pos(folio); + bool found_fixup = false; + unsigned int bit; + + /* + * A folio was dirtied without calling aops->dirty_folio() which we + * explicitly assert is not allowed. + */ + if (unlikely(bitmap_empty(bio_ctrl->submit_bitmap, blocks_per_folio))) { + DEBUG_WARN(); + btrfs_err_rl(fs_info, + "root %lld ino %llu folio %llu is dirty with an empty dirty bitmap", + btrfs_root_id(inode->root), btrfs_ino(inode), + folio_pos(folio)); + return -EUCLEAN; + } + + /* Cheap check on the folio flag. Set iff the fixup bitmap is non-empty. */ + if (likely(!folio_test_fixup_pending(folio))) + return 0; + + for_each_set_bit(bit, bio_ctrl->submit_bitmap, blocks_per_folio) { + const u64 start = page_start + (bit << fs_info->sectorsize_bits); + const bool needs_fixup = btrfs_folio_test_fixup(fs_info, folio, + start, sectorsize); + + debug_check_writepage_fixup(inode, start, sectorsize, needs_fixup); + if (needs_fixup) { + bitmap_clear(bio_ctrl->submit_bitmap, bit, 1); + found_fixup = true; + } + } + if (likely(found_fixup)) { + btrfs_queue_writepage_fixup(inode, folio); + folio_redirty_for_writepage(bio_ctrl->wbc, folio); + if (bitmap_empty(bio_ctrl->submit_bitmap, blocks_per_folio)) { + folio_unlock(folio); + return 1; + } + return 0; + } + /* We should always find fixup if the folio fixup flag was set. */ + DEBUG_WARN(); + btrfs_err_rl(fs_info, + "root %lld ino %llu folio %llu is fixup with an empty fixup bitmap", + btrfs_root_id(inode->root), btrfs_ino(inode), + folio_pos(folio)); + + return -EUCLEAN; +} + /* * Do all of the delayed allocation setup. * @@ -1492,6 +1601,10 @@ static noinline_for_stack int writepage_delalloc(struct btrfs_inode *inode, /* Save the dirty bitmap as our submission bitmap will be a subset of it. */ btrfs_copy_subpage_dirty_bitmap(fs_info, folio, bio_ctrl->submit_bitmap); + ret = writepage_fixup(inode, folio, bio_ctrl); + if (ret) + return ret; + for_each_set_bitrange(start_bit, end_bit, bio_ctrl->submit_bitmap, blocks_per_folio) { u64 start = page_start + (start_bit << fs_info->sectorsize_bits); diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h index 7ee9ec2b0efb..f7f343fbe732 100644 --- a/fs/btrfs/fs.h +++ b/fs/btrfs/fs.h @@ -713,6 +713,8 @@ struct btrfs_fs_info { struct btrfs_workqueue *endio_write_workers; struct btrfs_workqueue *endio_freespace_worker; struct btrfs_workqueue *caching_workers; + + struct workqueue_struct *fixup_workers; struct btrfs_workqueue *delayed_workers; struct task_struct *transaction_kthread; @@ -1200,6 +1202,16 @@ static inline void btrfs_wake_unfinished_drop(struct btrfs_fs_info *fs_info) clear_and_wake_up_bit(BTRFS_FS_UNFINISHED_DROPS, &fs_info->flags); } +/* + * We use the folio owner_2 flag to indicate the folio has blocks that were + * dirtied without a space reservation and need the writepage fixup before + * writeback. For bs < folio_size the fixup bitmap tracks the affected + * blocks. + */ +#define folio_test_fixup_pending(folio) folio_test_owner_2(folio) +#define folio_set_fixup_pending(folio) folio_set_owner_2(folio) +#define folio_clear_fixup_pending(folio) folio_clear_owner_2(folio) + #define BTRFS_FS_ERROR(fs_info) (READ_ONCE((fs_info)->fs_error)) #define BTRFS_FS_LOG_CLEANUP_ERROR(fs_info) \ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 04ea10b61bbb..98b31a090626 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -2812,6 +2812,163 @@ int btrfs_set_extent_delalloc(struct btrfs_inode *inode, u64 start, u64 end, EXTENT_DELALLOC | extra_bits, cached_state); } +struct btrfs_writepage_fixup { + struct folio *folio; + struct btrfs_inode *inode; + struct work_struct work; +}; + +/* + * Do the real fixup work of reserving space for the blocks a folio's fixup + * state records. Queued by writepage_fixup() when writeback found the bits set. + * + * Since the fixup can be cancelled by a task dirtying with a reservation, we must + * re-check the state of fixup under the folio lock. + */ +static void btrfs_writepage_fixup_worker(struct work_struct *work) +{ + struct btrfs_writepage_fixup *fixup = + container_of(work, struct btrfs_writepage_fixup, work); + struct extent_state *cached_state = NULL; + struct extent_changeset *data_reserved = NULL; + unsigned long delalloc_bitmap[BITS_TO_LONGS(BTRFS_MAX_BLOCKS_PER_FOLIO)] = { 0 }; + struct folio *folio = fixup->folio; + struct btrfs_inode *inode = fixup->inode; + struct btrfs_fs_info *fs_info = inode->root->fs_info; + const unsigned int blocks_per_folio = btrfs_blocks_per_folio(fs_info, folio); + const u32 sectorsize = fs_info->sectorsize; + const u64 page_start = folio_pos(folio); + const u64 page_end = folio_next_pos(folio) - 1; + unsigned int start_bit; + unsigned int end_bit; + unsigned int bit; + bool reserved; + int ret; + + /* + * We would prefer to reserve under the folio lock when we know exactly + * which blocks need a reservation. Unfortunately, since the reservation + * can go into flushers which can go into writeback, which takes folio + * locks, that is not possible. Therefore, we have to reserve for the + * whole folio here, then release what we didn't end up needing once we + * figure it out. + * + * Also note the slightly strange error checking. If fixup is actually + * not set, we don't need to mark an error on the mapping. So hang on to + * ret until after we lock and find out if we actually care. + */ + ret = btrfs_delalloc_reserve_space(inode, &data_reserved, page_start, + folio_size(folio)); + reserved = (ret == 0); +again: + folio_lock(folio); + + if (!folio->mapping || !folio_test_fixup_pending(folio)) { + ret = 0; + goto out; + } + if (ret) + goto out; + + btrfs_lock_extent(&inode->io_tree, page_start, page_end, &cached_state); + + for (bit = 0; bit < blocks_per_folio; bit++) { + struct btrfs_ordered_extent *ordered; + const u64 start = page_start + (bit << fs_info->sectorsize_bits); + + if (test_bit(bit, delalloc_bitmap)) + continue; + if (!btrfs_folio_test_fixup(fs_info, folio, start, sectorsize)) + continue; + /* + * Any task that sets EXTENT_DELALLOC clears the fixup bits + * under the folio lock, so it should be impossible to observe + * both under the lock. Setting delalloc twice would wrongly + * double account the space. + */ + if (IS_ENABLED(CONFIG_BTRFS_DEBUG) && + unlikely(btrfs_test_range_bit_exists(&inode->io_tree, start, + start + sectorsize - 1, + EXTENT_DELALLOC))) { + DEBUG_WARN("fixup worker: delalloc and fixup conflict. ino %llu start %llu", + btrfs_ino(inode), start); + btrfs_folio_clear_fixup(fs_info, folio, start, sectorsize); + continue; + } + ordered = btrfs_lookup_ordered_range(inode, start, sectorsize); + if (ordered) { + trace_btrfs_writepage_fixup_defer(inode, ordered); + btrfs_unlock_extent(&inode->io_tree, page_start, + page_end, &cached_state); + folio_unlock(folio); + btrfs_start_ordered_extent(ordered); + btrfs_put_ordered_extent(ordered); + goto again; + } + ret = btrfs_set_extent_delalloc(inode, start, + start + sectorsize - 1, 0, + &cached_state); + if (ret) + break; + trace_btrfs_writepage_fixup_reserve(inode, start, sectorsize); + btrfs_folio_clear_fixup(fs_info, folio, start, sectorsize); + set_bit(bit, delalloc_bitmap); + } + + btrfs_unlock_extent(&inode->io_tree, page_start, page_end, &cached_state); +out: + if (ret < 0) { + /* Failure here is analogous to failure in writeback. */ + mapping_set_error(folio->mapping, ret); + btrfs_folio_clear_fixup_dirty(fs_info, folio, page_start, + folio_size(folio)); + } + if (reserved) { + btrfs_delalloc_release_extents(inode, folio_size(folio)); + for_each_clear_bitrange(start_bit, end_bit, delalloc_bitmap, + blocks_per_folio) + btrfs_delalloc_release_space(inode, data_reserved, + page_start + (start_bit << fs_info->sectorsize_bits), + (end_bit - start_bit) << fs_info->sectorsize_bits, + true); + } + folio_unlock(folio); + folio_put(folio); + kfree(fixup); + extent_changeset_free(data_reserved); + btrfs_add_delayed_iput(inode); +} + +/* + * Queue space reservation fixup work for blocks dirtied without a space reservation. + * + * Should be used by writeback while holding the folio locked. + * + * If we fail to queue fixup, then the folio state is unchanged and a future + * writeback pass will still see it. + */ +void btrfs_queue_writepage_fixup(struct btrfs_inode *inode, struct folio *folio) +{ + struct btrfs_fs_info *fs_info = inode->root->fs_info; + struct btrfs_writepage_fixup *fixup; + + fixup = kzalloc_obj(*fixup, GFP_NOFS); + if (!fixup) + return; + + /* + * This is called from within extent_write_cache_pages() which + * has successfully done an igrab(). But that will be released at the + * end of the writeback pass. We need to extend it for the worker as well. + */ + ihold(&inode->vfs_inode); + folio_get(folio); + INIT_WORK(&fixup->work, btrfs_writepage_fixup_worker); + fixup->folio = folio; + fixup->inode = inode; + queue_work(fs_info->fixup_workers, &fixup->work); +} + /* * Clear the old accounting flags and set EXTENT_DELALLOC for the range. * @@ -7507,6 +7664,12 @@ static void btrfs_invalidate_folio(struct folio *folio, size_t offset, folio_wait_writeback(folio); wait_subpage_spinlock(folio); + /* + * The invalidated blocks are going away; drop any fixup blocks among + * them, data included, as they have no space reservation. + */ + btrfs_folio_clear_fixup_dirty(fs_info, folio, page_start + offset, length); + /* * For subpage case, we have call sites like * btrfs_punch_hole_lock_range() which passes range not aligned to @@ -10548,6 +10711,41 @@ static const struct file_operations btrfs_dir_file_operations = { .setlease = generic_setlease, }; +/* + * The folio is going dirty without a btrfs delalloc space reservation. + * This requires a fixup before writeback which we might sleep so cannot + * run in this context, so we merely set state on the folio indicating it + * needs fixup before writeback. + * + * Note that there is no range in the input, so the whole folio is marked + * dirty and fixup. + * + * We believe that all callers of dirty_folio either: + * - take the folio lock (e.g. pinned folio release notification). + * - take the pte lock but must be running on a dirty pte which means + * page_mkwrite() ran on it and reserved the space. zap_pte_range() cannot + * race with writeback cleaning the folio because writeback runs + * folio_mkclean() which also uses the pte lock and revokes outstanding + * writable mappings. + * Therefore, an additional folio private lock (a la bfs->lock for all cases, + * not just subpage) is not necessary. + */ +static bool btrfs_data_dirty_folio(struct address_space *mapping, + struct folio *folio) +{ + struct btrfs_inode *inode = BTRFS_I(mapping->host); + struct btrfs_fs_info *fs_info = inode->root->fs_info; + const u64 page_start = folio_pos(folio); + const u64 range_end = min_t(u64, folio_next_pos(folio), + round_up(i_size_read(&inode->vfs_inode), + fs_info->sectorsize)); + + if (range_end > page_start) + btrfs_folio_set_fixup_dirty(fs_info, folio, page_start, + range_end - page_start); + return filemap_dirty_folio(mapping, folio); +} + /* * btrfs doesn't support the bmap operation because swapfiles * use bmap to make a mapping of extents in the file. They assume @@ -10568,7 +10766,7 @@ static const struct address_space_operations btrfs_aops = { .launder_folio = btrfs_launder_folio, .release_folio = btrfs_release_folio, .migrate_folio = btrfs_migrate_folio, - .dirty_folio = filemap_dirty_folio, + .dirty_folio = btrfs_data_dirty_folio, .error_remove_folio = generic_error_remove_folio, .swap_activate = btrfs_swap_activate, .swap_deactivate = btrfs_swap_deactivate, diff --git a/fs/btrfs/subpage.c b/fs/btrfs/subpage.c index 2a9397be8116..27dd677ca687 100644 --- a/fs/btrfs/subpage.c +++ b/fs/btrfs/subpage.c @@ -345,18 +345,57 @@ void btrfs_subpage_clear_uptodate(const struct btrfs_fs_info *fs_info, spin_unlock_irqrestore(&bfs->lock, flags); } +/* + * folio_mark_dirty() for a folio we are dirtying with a space reservation. + * + * Dirtiers without a reservation use btrfs_data_dirty_folio(). + */ +static void btrfs_folio_mark_dirty(struct folio *folio) +{ + struct address_space *mapping = folio_mapping(folio); + + if (!mapping || !mapping->host || !is_data_inode(BTRFS_I(mapping->host))) { + folio_mark_dirty(folio); + return; + } + if (folio_test_reclaim(folio)) + folio_clear_reclaim(folio); + filemap_dirty_folio(mapping, folio); +} + +/* + * The set helper of the dirty ops, so it only runs for folios without a + * fixup bitmap: for those the folio flag is the whole fixup state, and this + * reserving write covers the block, so retire it. Metadata never has the + * flag set and only pays the test. + */ +static void btrfs_folio_mark_dirty_reserved(struct folio *folio) +{ + if (folio_test_fixup_pending(folio)) + folio_clear_fixup_pending(folio); + btrfs_folio_mark_dirty(folio); +} + void btrfs_subpage_set_dirty(const struct btrfs_fs_info *fs_info, struct folio *folio, u64 start, u32 len) { struct btrfs_folio_state *bfs = folio_get_private(folio); - unsigned int start_bit = subpage_calc_start_bit(fs_info, folio, + unsigned int dirty_bit = subpage_calc_start_bit(fs_info, folio, dirty, start, len); + unsigned int fixup_bit = subpage_calc_start_bit(fs_info, folio, + fixup, start, len); + const unsigned int nbits = len >> fs_info->sectorsize_bits; unsigned long flags; spin_lock_irqsave(&bfs->lock, flags); - bitmap_set(bfs->bitmaps, start_bit, len >> fs_info->sectorsize_bits); + bitmap_set(bfs->bitmaps, dirty_bit, nbits); + /* Proper dirtying obviates the need for fixup. */ + bitmap_clear(bfs->bitmaps, fixup_bit, nbits); + if (folio_test_fixup_pending(folio) && + subpage_test_bitmap_all_zero(fs_info, folio, fixup)) + folio_clear_fixup_pending(folio); spin_unlock_irqrestore(&bfs->lock, flags); - folio_mark_dirty(folio); + btrfs_folio_mark_dirty(folio); } static void folio_clear_tags(struct folio *folio) @@ -457,6 +496,172 @@ void btrfs_subpage_clear_writeback(const struct btrfs_fs_info *fs_info, spin_unlock_irqrestore(&bfs->lock, flags); } +void btrfs_subpage_clear_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + struct btrfs_folio_state *bfs = folio_get_private(folio); + unsigned int start_bit = subpage_calc_start_bit(fs_info, folio, + fixup, start, len); + unsigned long flags; + + spin_lock_irqsave(&bfs->lock, flags); + bitmap_clear(bfs->bitmaps, start_bit, len >> fs_info->sectorsize_bits); + if (subpage_test_bitmap_all_zero(fs_info, folio, fixup)) + folio_clear_fixup_pending(folio); + spin_unlock_irqrestore(&bfs->lock, flags); +} + +/* + * In one pass under bfs->lock, mark every block with a clear dirty bit in the + * range both dirty and needing fixup. + * + * Only called from the dirty_folio callback, which owns the folio-level + * dirty flag; calling folio_mark_dirty() here would recurse. + * + * The folio fixup flag and bits are both set under bfs->lock so that a + * writeback pass observing the new bits also observes the flag. + */ +static void btrfs_subpage_set_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + struct btrfs_folio_state *bfs = folio_get_private(folio); + unsigned int dirty_bit = subpage_calc_start_bit(fs_info, folio, + dirty, start, len); + unsigned int fixup_bit = subpage_calc_start_bit(fs_info, folio, + fixup, start, len); + const unsigned int nbits = len >> fs_info->sectorsize_bits; + unsigned long flags; + bool marked = false; + + spin_lock_irqsave(&bfs->lock, flags); + for (unsigned int i = 0; i < nbits; i++) { + if (test_bit(dirty_bit + i, bfs->bitmaps)) + continue; + set_bit(dirty_bit + i, bfs->bitmaps); + set_bit(fixup_bit + i, bfs->bitmaps); + marked = true; + } + if (marked) + folio_set_fixup_pending(folio); + spin_unlock_irqrestore(&bfs->lock, flags); +} + +/* + * Mark the still-clean blocks of a folio dirty and needing fixup, for + * btrfs_data_dirty_folio(). + * + * A subpage block size folio that is not uptodate is left alone: its clean + * blocks may hold content that was never read in, which must not be marked + * dirty. + */ +void btrfs_folio_set_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + if (!btrfs_is_subpage(fs_info, folio)) { + if (!folio_test_dirty(folio)) + folio_set_fixup_pending(folio); + return; + } + if (!folio_test_uptodate(folio)) + return; + btrfs_subpage_set_fixup_dirty(fs_info, folio, start, len); +} + +/* + * Drop the fixup blocks inside the range: clear both their fixup and dirty + * bits. + * + * Fixup blocks carry no space reservation, so their fixup and dirty bits + * must be dropped together. Clearing only the fixup bit would leave a + * dirty block without a reservation which is not a valid state. + * + * Returns true if the folio has no dirty blocks left. + */ +static bool btrfs_subpage_clear_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + struct btrfs_folio_state *bfs = folio_get_private(folio); + unsigned int dirty_bit = subpage_calc_start_bit(fs_info, folio, + dirty, start, len); + unsigned int fixup_bit = subpage_calc_start_bit(fs_info, folio, + fixup, start, len); + const unsigned int nbits = len >> fs_info->sectorsize_bits; + unsigned long flags; + bool last; + + spin_lock_irqsave(&bfs->lock, flags); + for (unsigned int i = 0; i < nbits; i++) { + if (!test_bit(fixup_bit + i, bfs->bitmaps)) + continue; + clear_bit(fixup_bit + i, bfs->bitmaps); + clear_bit(dirty_bit + i, bfs->bitmaps); + } + if (subpage_test_bitmap_all_zero(fs_info, folio, fixup)) + folio_clear_fixup_pending(folio); + last = subpage_test_bitmap_all_zero(fs_info, folio, dirty); + spin_unlock_irqrestore(&bfs->lock, flags); + return last; +} + +/* + * Drop the fixup blocks inside the range, for callers discarding their data: + * btrfs_invalidate_folio() and the writepage fixup worker's error path. + * + * Callers that have just reserved space for a block want + * btrfs_folio_clear_fixup() instead - there the block stays dirty and gets + * written. + * + * The range can be byte-granular (an unaligned truncate through + * btrfs_invalidate_folio()); only blocks fully inside it are dropped, as a + * partially covered block still holds live data outside the range. For + * single-block folios the folio flag is the fixup state, so it is dropped + * only when the range covers the whole folio. + */ +void btrfs_folio_clear_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + u64 aligned_start; + u64 aligned_end; + + /* The folio flag is set whenever any fixup bitmap bit is. */ + if (!folio_test_fixup_pending(folio)) + return; + if (!btrfs_is_subpage(fs_info, folio)) { + if (start <= folio_pos(folio) && + start + len >= folio_next_pos(folio)) { + folio_clear_fixup_pending(folio); + folio_clear_dirty_for_io(folio); + } + return; + } + btrfs_subpage_clamp_range(folio, &start, &len); + aligned_start = round_up(start, fs_info->sectorsize); + aligned_end = round_down(start + len, fs_info->sectorsize); + if (aligned_end <= aligned_start) + return; + if (btrfs_subpage_clear_fixup_dirty(fs_info, folio, aligned_start, + aligned_end - aligned_start)) + folio_clear_dirty_for_io(folio); +} + +bool btrfs_folio_test_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + if (!btrfs_is_subpage(fs_info, folio)) + return folio_test_fixup_pending(folio); + return btrfs_subpage_test_fixup(fs_info, folio, start, len); +} + +void btrfs_folio_clear_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + if (!btrfs_is_subpage(fs_info, folio)) { + folio_clear_fixup_pending(folio); + return; + } + btrfs_subpage_clear_fixup(fs_info, folio, start, len); +} + /* * Unlike set/clear which is dependent on each page status, for test all bits * are tested in the same way. @@ -480,6 +685,7 @@ bool btrfs_subpage_test_##name(const struct btrfs_fs_info *fs_info, \ IMPLEMENT_BTRFS_SUBPAGE_TEST_OP(uptodate); IMPLEMENT_BTRFS_SUBPAGE_TEST_OP(dirty); IMPLEMENT_BTRFS_SUBPAGE_TEST_OP(writeback); +IMPLEMENT_BTRFS_SUBPAGE_TEST_OP(fixup); /* * Note that, in selftests (extent-io-tests), we can have empty fs_info passed @@ -571,8 +777,8 @@ bool btrfs_meta_folio_test_##name(struct folio *folio, const struct extent_buffe } IMPLEMENT_BTRFS_PAGE_OPS(uptodate, folio_mark_uptodate, folio_clear_uptodate, folio_test_uptodate); -IMPLEMENT_BTRFS_PAGE_OPS(dirty, folio_mark_dirty, folio_clear_dirty_for_io, - folio_test_dirty); +IMPLEMENT_BTRFS_PAGE_OPS(dirty, btrfs_folio_mark_dirty_reserved, + folio_clear_dirty_for_io, folio_test_dirty); IMPLEMENT_BTRFS_PAGE_OPS(writeback, folio_start_writeback, folio_end_writeback, folio_test_writeback); diff --git a/fs/btrfs/subpage.h b/fs/btrfs/subpage.h index c6d7394e6418..9aceba93c818 100644 --- a/fs/btrfs/subpage.h +++ b/fs/btrfs/subpage.h @@ -14,15 +14,15 @@ struct folio; /* * Extra info for subpage bitmap. * - * For subpage we pack all uptodate/dirty/writeback bitmaps into + * For subpage we pack all uptodate/dirty/writeback/fixup bitmaps into * one larger bitmap. * * This structure records how they are organized in the bitmap: * - * /- uptodate /- dirty /- writeback - * | | | - * v v v - * |u|u|u|u|........|u|u|d|d|.......|d|d|w|w|.......|w|w| + * /- uptodate /- dirty /- writeback /- fixup + * | | | | + * v v v v + * |u|u|u|u|........|u|u|d|d|.......|d|d|w|w|.....|w|w|f|f|.....|f|f| * |< sectors_per_page >| * * Unlike regular macro-like enums, here we do not go upper-case names, as @@ -40,6 +40,14 @@ enum { */ btrfs_bitmap_nr_writeback, + /* + * Blocks dirtied by the dirty_folio callback instead of a reserving + * write path (e.g. set_page_dirty_lock() on a GUP pin). They have + * no space reservation and need the writepage fixup before they can + * be submitted. + */ + btrfs_bitmap_nr_fixup, + btrfs_bitmap_nr_max }; @@ -165,6 +173,29 @@ DECLARE_BTRFS_SUBPAGE_OPS(uptodate); DECLARE_BTRFS_SUBPAGE_OPS(dirty); DECLARE_BTRFS_SUBPAGE_OPS(writeback); +/* + * Fixup bit helpers. + * + * The fixup bit is data-only and has no plain set helper (setting happens + * together with dirtying in btrfs_subpage_set_fixup_dirty()), so it does not + * go through DECLARE_BTRFS_SUBPAGE_OPS(). For single-block folios the + * folio_*_fixup_pending() flag takes the place of the bitmap. + */ +void btrfs_subpage_clear_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); +bool btrfs_subpage_test_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); +bool btrfs_folio_test_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); +void btrfs_folio_set_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); +/* For a block that just got its space reserved; it stays dirty. */ +void btrfs_folio_clear_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); +/* For callers discarding the data; clears the dirty bits too. */ +void btrfs_folio_clear_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); + /* * Helper for error cleanup, where a folio will have its dirty flag cleared, * with writeback started and finished. diff --git a/include/trace/events/btrfs.h b/include/trace/events/btrfs.h index 4c5c47c5edb7..6c1438f6a4d3 100644 --- a/include/trace/events/btrfs.h +++ b/include/trace/events/btrfs.h @@ -689,6 +689,41 @@ DEFINE_EVENT(btrfs__ordered_extent, btrfs_ordered_extent_lookup_first, TP_ARGS(inode, ordered) ); +/* + * The writepage fixup worker deferred a block because this still-running + * ordered extent covers it. + */ +DEFINE_EVENT(btrfs__ordered_extent, btrfs_writepage_fixup_defer, + + TP_PROTO(const struct btrfs_inode *inode, + const struct btrfs_ordered_extent *ordered), + + TP_ARGS(inode, ordered) +); + +/* The writepage fixup worker reserved space for a block and set delalloc. */ +TRACE_EVENT(btrfs_writepage_fixup_reserve, + + TP_PROTO(const struct btrfs_inode *inode, u64 start, u32 len), + + TP_ARGS(inode, start, len), + + TP_STRUCT__entry_btrfs( + __field( u64, ino ) + __field( u64, start ) + __field( u32, len ) + ), + + TP_fast_assign_btrfs(inode->root->fs_info, + __entry->ino = btrfs_ino(inode); + __entry->start = start; + __entry->len = len; + ), + + TP_printk_btrfs("ino=%llu start=%llu len=%u", + __entry->ino, __entry->start, __entry->len) +); + DEFINE_EVENT(btrfs__ordered_extent, btrfs_ordered_extent_split, TP_PROTO(const struct btrfs_inode *inode, From c679ce3be6cb63763d68ab9b5d9d73ddc0a40762 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 29 Jun 2026 14:52:29 +0200 Subject: [PATCH 17/18] iomap: add a separate bio_set for iomap_split_ioend iomap_split_ioend can split bios that already come from iomap_ioend_bioset and thus deadlock when the bioset is exhausted. Add a separate bio_set to avoid this deadlock. Christian Brauner says: Mark iomap_ioend_split_bioset static as it is only used in ioend.c, fixing the sparse warning reported by the kernel test robot. Fixes: 5fcbd555d483 ("iomap: split bios to zone append limits in the submission handlers") Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260629125229.3400726-1-hch@lst.de Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/ioend.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c index 30468d51b5ad..fb636dce43af 100644 --- a/fs/iomap/ioend.c +++ b/fs/iomap/ioend.c @@ -13,6 +13,7 @@ struct bio_set iomap_ioend_bioset; EXPORT_SYMBOL_GPL(iomap_ioend_bioset); +static struct bio_set iomap_ioend_split_bioset; struct iomap_ioend *iomap_init_ioend(struct inode *inode, struct bio *bio, loff_t file_offset, u16 ioend_flags) @@ -488,7 +489,8 @@ struct iomap_ioend *iomap_split_ioend(struct iomap_ioend *ioend, sector_offset = ALIGN_DOWN(sector_offset << SECTOR_SHIFT, i_blocksize(ioend->io_inode)) >> SECTOR_SHIFT; - split = bio_split(bio, sector_offset, GFP_NOFS, &iomap_ioend_bioset); + split = bio_split(bio, sector_offset, GFP_NOFS, + &iomap_ioend_split_bioset); if (IS_ERR(split)) return ERR_CAST(split); split->bi_private = bio->bi_private; @@ -511,8 +513,23 @@ EXPORT_SYMBOL_GPL(iomap_split_ioend); static int __init iomap_ioend_init(void) { - return bioset_init(&iomap_ioend_bioset, 4 * (PAGE_SIZE / SECTOR_SIZE), + const unsigned int nr_mempool_entries = 4 * (PAGE_SIZE / SECTOR_SIZE); + int error; + + error = bioset_init(&iomap_ioend_bioset, nr_mempool_entries, offsetof(struct iomap_ioend, io_bio), BIOSET_NEED_BVECS); + if (error) + return error; + error = bioset_init(&iomap_ioend_split_bioset, nr_mempool_entries, + offsetof(struct iomap_ioend, io_bio), + BIOSET_NEED_BVECS); + if (error) + goto out_exit_ioend_bioset; + return 0; + +out_exit_ioend_bioset: + bioset_exit(&iomap_ioend_bioset); + return error; } fs_initcall(iomap_ioend_init); From ed2bf9cdb72aa836c434a2bd1807d8bdfaf77fe0 Mon Sep 17 00:00:00 2001 From: Boris Burkov Date: Thu, 30 Jul 2026 09:38:02 -0700 Subject: [PATCH 18/18] btrfs: flush the fixup workers during close_ctree Reintroducing the COW fixup worker brought back the unmount race fixed by commit 41fd1e94066a ("btrfs: wait for fixup workers before stopping cleaner kthread during umount") without bringing back the fix. A fixup work item queued by the final writeback pass can still be in flight when close_ctree() stops the cleaner kthread and frees the fs roots. While destroy_workqueue() drains the queue, that happens after the cleaner thread was freed, so btrfs_add_delayed_iput() called from the fixup worker is no longer safe (not to mention that we are already in BTRFS_FS_STATE_NO_DELAYED_IPUT when it runs). Therefore we need to bring back explicitly flushing the fixup workqueue as in Filipe's original fix. The first flush will catch all the fixup writeback queued during the final sync before umount, but some of that might hit memory allocation errors and stay fixup in the blocks/folio, leading any subsequent writeback triggered *inside* umount (e.g. reclaim workers shutting down) to hit it and queue again. To fix that, and the possibility of any really long-lived pinned folios getting marked, deny queueing new fixup during umount. That allows us to flush twice (once before doing a real writeback pass to get the actual data, second time to clean up any rather unlikely stragglers right before declaring BTRFS_FS_STATE_NO_DELAYED_IPUT) and be certain nothing got re-queued. Reproduced by injecting a one-shot 30s sleep at the head of btrfs_writepage_fixup_worker() on a KASAN kernel, running the normal reproducing read dio workload before unmount and then observing: BUG: KASAN: slab-use-after-free in _raw_spin_lock_irqsave+0x35/0x50 Read of size 1 at addr ffff88810b4b08f8 by task kworker/u32:5/219 Workqueue: btrfs-fixup btrfs_writepage_fixup_worker [btrfs] Call Trace: _raw_spin_lock_irqsave+0x35/0x50 try_to_wake_up+0xc0/0x18c0 btrfs_writepage_fixup_worker+0x7f3/0xf20 [btrfs] ... Fixes: 4be9c7da6860 ("btrfs: trigger cow fixup via dirty_folio()") Assisted-by: LLM (reproduction, analysis) Signed-off-by: Boris Burkov Signed-off-by: David Sterba --- fs/btrfs/disk-io.c | 21 +++++++++++++++++++++ fs/btrfs/inode.c | 17 +++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 6bb70c43a63f..8bdc94d3ddee 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -4361,6 +4361,18 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) /* clear out the rbtree of defraggable inodes */ btrfs_cleanup_defrag_inodes(fs_info); + /* + * Before the unmount, we sync down all the writeback which can + * generate fixup work. We are about to run delalloc for autodefrag so + * piggy back on that by also flushing the fixup work which can also + * generate delalloc we would like to get run. + * + * After this, it is still possible that some thread doing writeback is + * in btrfs_queue_writepage_fixup() and might finish queueing some final + * work, racing the btrfs_fs_closing() check there. + */ + flush_workqueue(fs_info->fixup_workers); + /* * Handle the error fs first, as it will flush and wait for all ordered * extents. This will generate delayed iputs, thus we want to handle @@ -4438,6 +4450,15 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) cancel_work_sync(&fs_info->preempt_reclaim_work); cancel_work_sync(&fs_info->em_shrinker_work); + /* + * Reclaim workers can run writeback which can queue fixup. + * After the above cancel_work_sync() calls, any such queueing attempts are + * guaranteed to see btrfs_fs_closing(), so at this point we can genuinely fully + * flush the fixup workqueue. This relies on the belief that *now* no thread can + * still be sitting in btrfs_queue_writepage_fixup(). + */ + flush_workqueue(fs_info->fixup_workers); + /* * Run delayed iputs again because an async reclaim worker may have * added new ones if it was flushing delalloc: diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 98b31a090626..9b1bf2e03497 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -2952,6 +2952,23 @@ void btrfs_queue_writepage_fixup(struct btrfs_inode *inode, struct folio *folio) struct btrfs_fs_info *fs_info = inode->root->fs_info; struct btrfs_writepage_fixup *fixup; + /* + * Disallow queueing more fixup during unmount to break the cycle + * of writeback queuing fixup queuing writeback etc. + * + * If it actually hit, then something which was fixup wasn't written + * which we should warn about. + */ + if (btrfs_fs_closing(fs_info)) { + btrfs_warn_rl(fs_info, + "dropping unqueued fixup blocks at unmount. root %lld ino %llu folio %llu", + btrfs_root_id(inode->root), btrfs_ino(inode), + folio_pos(folio)); + btrfs_folio_clear_fixup_dirty(fs_info, folio, + folio_pos(folio), folio_size(folio)); + return; + } + fixup = kzalloc_obj(*fixup, GFP_NOFS); if (!fixup) return;