* FAILED: patch "[PATCH] btrfs: fix extent map leak in NOCOW direct I/O write" failed to apply to 6.6-stable tree
@ 2026-09-03 13:24 gregkh
2026-09-07 1:03 ` [PATCH 6.6.y 1/3] btrfs: prepare btrfs_punch_hole_lock_range() for large data folios Sasha Levin
0 siblings, 1 reply; 4+ messages in thread
From: gregkh @ 2026-09-03 13:24 UTC (permalink / raw)
To: shuangpeng.kernel, dsterba, fdmanana, wqu; +Cc: stable
The patch below does not apply to the 6.6-stable tree.
If someone wants it applied there, or to any other stable or longterm
tree, then please email the backport, including the original git commit
id to <stable@vger.kernel.org>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.6.y
git checkout FETCH_HEAD
git cherry-pick -x 3f950867c307c5413d628a153ac44915bd117ffd
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<stable@vger.kernel.org>' --in-reply-to '2026090310-yeah-payday-4448@gregkh' --subject-prefix 'PATCH 6.6.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 3f950867c307c5413d628a153ac44915bd117ffd Mon Sep 17 00:00:00 2001
From: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
Date: Sun, 5 Jul 2026 01:46:35 -0400
Subject: [PATCH] btrfs: fix extent map leak in NOCOW direct I/O write
btrfs_dio_iomap_begin() calls btrfs_get_extent(), which returns an
extent map reference that must be dropped on all exit paths.
For direct writes into a NOCOW range, btrfs_get_blocks_direct_write()
keeps using that extent map and asks btrfs_create_dio_extent() to
allocate the ordered extent. If that fails, for example because
btrfs_alloc_ordered_extent() fails, the function returns the error
without dropping the input extent map. The PREALLOC path avoided this by
dropping the input extent map before replacing it with the newly created
one.
Check the error from btrfs_create_dio_extent() before replacing the
map and drop the input extent map on failure.
Fixes: 5f9a8a51d8b9 ("Btrfs: add semaphore to synchronize direct IO writes with fsync")
CC: stable@vger.kernel.org
Reviewed-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
index b80dc45c3ff9..27d227f5bdc0 100644
--- a/fs/btrfs/direct-io.c
+++ b/fs/btrfs/direct-io.c
@@ -280,17 +280,24 @@ static int btrfs_get_blocks_direct_write(struct extent_map **map,
em2 = btrfs_create_dio_extent(BTRFS_I(inode), dio_data, start,
&file_extent, type);
btrfs_dec_nocow_writers(bg);
- if (type == BTRFS_ORDERED_PREALLOC) {
+ if (IS_ERR(em2)) {
+ ret = PTR_ERR(em2);
+ btrfs_free_extent_map(em);
+ *map = NULL;
+ goto out;
+ }
+
+ /*
+ * True NOCOW writes don't need to create a new extent map,
+ * while PREALLOC writes must replace the existing one.
+ */
+ if (em2) {
+ ASSERT(type == BTRFS_ORDERED_PREALLOC);
btrfs_free_extent_map(em);
*map = em2;
em = em2;
}
- if (IS_ERR(em2)) {
- ret = PTR_ERR(em2);
- goto out;
- }
-
dio_data->nocow_done = true;
} else {
/* Our caller expects us to free the input extent map. */
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 6.6.y 1/3] btrfs: prepare btrfs_punch_hole_lock_range() for large data folios
2026-09-03 13:24 FAILED: patch "[PATCH] btrfs: fix extent map leak in NOCOW direct I/O write" failed to apply to 6.6-stable tree gregkh
@ 2026-09-07 1:03 ` Sasha Levin
2026-09-07 1:03 ` [PATCH 6.6.y 2/3] btrfs: allow folios to be released while ordered extent is finishing Sasha Levin
2026-09-07 1:03 ` [PATCH 6.6.y 3/3] btrfs: fix extent map leak in NOCOW direct I/O write Sasha Levin
0 siblings, 2 replies; 4+ messages in thread
From: Sasha Levin @ 2026-09-07 1:03 UTC (permalink / raw)
To: stable; +Cc: Qu Wenruo, Filipe Manana, David Sterba, Sasha Levin
From: Qu Wenruo <wqu@suse.com>
[ Upstream commit 1e5773e0bab761c853eaf7286394905a544ef02d ]
The function btrfs_punch_hole_lock_range() needs to make sure there is
no other folio in the range, thus it goes with filemap_range_has_page(),
which works pretty fine.
But if we have large folios, under the following case
filemap_range_has_page() will always return true, forcing
btrfs_punch_hole_lock_range() to do a very time consuming busy loop:
start end
| |
|//|//|//|//| | | | | | | | |//|//|
\ / \ /
Folio A Folio B
In the above case, folio A and B contain our start/end indexes, and there
are no other folios in the range. Thus we do not need to retry inside
btrfs_punch_hole_lock_range().
To prepare for large data folios, introduce a helper,
check_range_has_page(), which will:
- Shrink the search range towards page boundaries
If the rounded down end (exclusive, otherwise it can underflow when @end
is inside the folio at file offset 0) is no larger than the rounded up
start, it means the range contains no other pages other than the ones
covering @start and @end.
Can return false directly in that case.
- Grab all the folios inside the range
- Skip any large folios that cover the start and end indexes
- If any other folios are found return true
- Otherwise return false
This new helper is going to handle both large folios and regular ones.
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Stable-dep-of: 3f950867c307 ("btrfs: fix extent map leak in NOCOW direct I/O write")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/btrfs/file.c | 67 +++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 56 insertions(+), 11 deletions(-)
diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index a188467a9fb3d..bd389a1ae29dc 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -2221,11 +2221,29 @@ static int find_first_non_hole(struct btrfs_inode *inode, u64 *start, u64 *len)
return ret;
}
-static void btrfs_punch_hole_lock_range(struct inode *inode,
- const u64 lockstart,
- const u64 lockend,
- struct extent_state **cached_state)
+/*
+ * Check if there is no folio in the range.
+ *
+ * We cannot utilize filemap_range_has_page() in a filemap with large folios
+ * as we can hit the following false positive:
+ *
+ * start end
+ * | |
+ * |//|//|//|//| | | | | | | | |//|//|
+ * \ / \ /
+ * Folio A Folio B
+ *
+ * That large folio A and B cover the start and end indexes.
+ * In that case filemap_range_has_page() will always return true, but the above
+ * case is fine for btrfs_punch_hole_lock_range() usage.
+ *
+ * So here we only ensure that no other folios is in the range, excluding the
+ * head/tail large folio.
+ */
+static bool check_range_has_page(struct inode *inode, u64 start, u64 end)
{
+ struct folio_batch fbatch;
+ bool ret = false;
/*
* For subpage case, if the range is not at page boundary, we could
* have pages at the leading/tailing part of the range.
@@ -2236,17 +2254,45 @@ static void btrfs_punch_hole_lock_range(struct inode *inode,
*
* And do not decrease page_lockend right now, as it can be 0.
*/
- const u64 page_lockstart = round_up(lockstart, PAGE_SIZE);
- const u64 page_lockend = round_down(lockend + 1, PAGE_SIZE);
+ const u64 page_lockstart = round_up(start, PAGE_SIZE);
+ const u64 page_lockend = round_down(end + 1, PAGE_SIZE);
+ const pgoff_t start_index = page_lockstart >> PAGE_SHIFT;
+ const pgoff_t end_index = (page_lockend - 1) >> PAGE_SHIFT;
+ pgoff_t tmp = start_index;
+ int found_folios;
+
+ /* The same page or adjacent pages. */
+ if (page_lockend <= page_lockstart)
+ return false;
+
+ folio_batch_init(&fbatch);
+ found_folios = filemap_get_folios(inode->i_mapping, &tmp, end_index, &fbatch);
+ for (int i = 0; i < found_folios; i++) {
+ struct folio *folio = fbatch.folios[i];
+ /* A large folio begins before the start. Not a target. */
+ if (folio->index < start_index)
+ continue;
+ /* A large folio extends beyond the end. Not a target. */
+ if (folio->index + folio_nr_pages(folio) > end_index)
+ continue;
+ /* A folio doesn't cover the head/tail index. Found a target. */
+ ret = true;
+ break;
+ }
+ folio_batch_release(&fbatch);
+ return ret;
+}
+
+static void btrfs_punch_hole_lock_range(struct inode *inode,
+ const u64 lockstart, const u64 lockend,
+ struct extent_state **cached_state)
+{
while (1) {
truncate_pagecache_range(inode, lockstart, lockend);
lock_extent(&BTRFS_I(inode)->io_tree, lockstart, lockend,
cached_state);
- /* The same page or adjacent pages. */
- if (page_lockend <= page_lockstart)
- break;
/*
* We can't have ordered extents in the range, nor dirty/writeback
* pages, because we have locked the inode's VFS lock in exclusive
@@ -2257,8 +2303,7 @@ static void btrfs_punch_hole_lock_range(struct inode *inode,
* locking the range check if we have pages in the range, and if
* we do, unlock the range and retry.
*/
- if (!filemap_range_has_page(inode->i_mapping, page_lockstart,
- page_lockend - 1))
+ if (!check_range_has_page(inode, lockstart, lockend))
break;
unlock_extent(&BTRFS_I(inode)->io_tree, lockstart, lockend,
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 6.6.y 2/3] btrfs: allow folios to be released while ordered extent is finishing
2026-09-07 1:03 ` [PATCH 6.6.y 1/3] btrfs: prepare btrfs_punch_hole_lock_range() for large data folios Sasha Levin
@ 2026-09-07 1:03 ` Sasha Levin
2026-09-07 1:03 ` [PATCH 6.6.y 3/3] btrfs: fix extent map leak in NOCOW direct I/O write Sasha Levin
1 sibling, 0 replies; 4+ messages in thread
From: Sasha Levin @ 2026-09-07 1:03 UTC (permalink / raw)
To: stable; +Cc: Filipe Manana, David Sterba, Sasha Levin
From: Filipe Manana <fdmanana@suse.com>
[ Upstream commit 32c523c578e8489f55663ce8a8860079c8deb414 ]
When the release_folio callback (from struct address_space_operations) is
invoked we don't allow the folio to be released if its range is currently
locked in the inode's io_tree, as it may indicate the folio may be needed
by the task that locked the range.
However if the range is locked because an ordered extent is finishing,
then we can safely allow the folio to be released because ordered extent
completion doesn't need to use the folio at all.
When we are under memory pressure, the kernel starts writeback of dirty
pages (folios) with the goal of releasing the pages from the page cache
after writeback completes, however this often is not possible on btrfs
because:
* Once the writeback completes we queue the ordered extent completion;
* Once the ordered extent completion starts, we lock the range in the
inode's io_tree (at btrfs_finish_one_ordered());
* If the release_folio callback is called while the folio's range is
locked in the inode's io_tree, we don't allow the folio to be
released, so the kernel has to try to release memory elsewhere,
which may result in triggering more writeback or releasing other
pages from the page cache which may be more useful to have around
for applications.
In contrast, when the release_folio callback is invoked after writeback
finishes and before ordered extent completion starts or locks the range,
we allow the folio to be released, as well as when the release_folio
callback is invoked after ordered extent completion unlocks the range.
Improve on this by detecting if the range is locked for ordered extent
completion and if it is, allow the folio to be released. This detection
is achieved by adding a new extent flag in the io_tree that is set when
the range is locked during ordered extent completion.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
[ sashal: Reduced backport -- upstream 32c523c578e84 touches 4 file(s), this
backport carries 4. Not backported here:
fs/btrfs/extent-io-tree.c
This note is generated from the file lists only; see the resolution record
for the reasoning. ]
Stable-dep-of: 3f950867c307 ("btrfs: fix extent map leak in NOCOW direct I/O write")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/btrfs/extent-io-tree.h | 5 +++++
fs/btrfs/extent_io.c | 16 +++++++++++-----
fs/btrfs/file.c | 1 +
fs/btrfs/inode.c | 4 +++-
4 files changed, 20 insertions(+), 6 deletions(-)
diff --git a/fs/btrfs/extent-io-tree.h b/fs/btrfs/extent-io-tree.h
index 28c23a23d1210..8b25fe2d6094f 100644
--- a/fs/btrfs/extent-io-tree.h
+++ b/fs/btrfs/extent-io-tree.h
@@ -30,6 +30,11 @@ enum {
* that is left for the ordered extent completion.
*/
ENUM_BIT(EXTENT_DELALLOC_NEW),
+ /*
+ * Mark that a range is being locked for finishing an ordered extent.
+ * Used together with EXTENT_LOCKED.
+ */
+ ENUM_BIT(EXTENT_FINISHING_ORDERED),
/*
* When an ordered extent successfully completes for a region marked as
* a new delalloc range, use this flag when clearing a new delalloc
diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
index 536c400e5964f..2ae9e953658f1 100644
--- a/fs/btrfs/extent_io.c
+++ b/fs/btrfs/extent_io.c
@@ -2352,18 +2352,24 @@ static int try_release_extent_state(struct extent_io_tree *tree,
u64 end = start + PAGE_SIZE - 1;
int ret = 1;
- if (test_range_bit(tree, start, end, EXTENT_LOCKED, 0, NULL)) {
+ /*
+ * We can release the page if it's locked only for ordered extent
+ * completion, since that doesn't require using the page.
+ */
+ if (test_range_bit(tree, start, end, EXTENT_LOCKED, 0, NULL) &&
+ !test_range_bit(tree, start, end, EXTENT_FINISHING_ORDERED, 0,
+ NULL)) {
ret = 0;
} else {
u32 clear_bits = ~(EXTENT_LOCKED | EXTENT_NODATASUM |
EXTENT_DELALLOC_NEW | EXTENT_CTLBITS |
- EXTENT_QGROUP_RESERVED);
+ EXTENT_QGROUP_RESERVED |
+ EXTENT_FINISHING_ORDERED);
/*
* At this point we can safely clear everything except the
- * locked bit, the nodatasum bit and the delalloc new bit.
- * The delalloc new bit will be cleared by ordered extent
- * completion.
+ * locked, nodatasum, delalloc new and finishing ordered bits.
+ * The delalloc new bit will be cleared by ordered extent completion.
*/
ret = __clear_extent_bit(tree, start, end, clear_bits, NULL, NULL);
diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index bd389a1ae29dc..bcdc852f7d008 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -5,6 +5,7 @@
#include <linux/fs.h>
#include <linux/pagemap.h>
+#include <linux/pagevec.h>
#include <linux/time.h>
#include <linux/init.h>
#include <linux/string.h>
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index e843531201bef..23918e72a71f6 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -3144,8 +3144,10 @@ int btrfs_finish_one_ordered(struct btrfs_ordered_extent *ordered_extent)
goto out;
}
- clear_bits |= EXTENT_LOCKED;
+ clear_bits |= EXTENT_LOCKED | EXTENT_FINISHING_ORDERED;
lock_extent(io_tree, start, end, &cached_state);
+ set_extent_bit(io_tree, start, end, EXTENT_FINISHING_ORDERED,
+ &cached_state);
if (freespace_inode)
trans = btrfs_join_transaction_spacecache(root);
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 6.6.y 3/3] btrfs: fix extent map leak in NOCOW direct I/O write
2026-09-07 1:03 ` [PATCH 6.6.y 1/3] btrfs: prepare btrfs_punch_hole_lock_range() for large data folios Sasha Levin
2026-09-07 1:03 ` [PATCH 6.6.y 2/3] btrfs: allow folios to be released while ordered extent is finishing Sasha Levin
@ 2026-09-07 1:03 ` Sasha Levin
1 sibling, 0 replies; 4+ messages in thread
From: Sasha Levin @ 2026-09-07 1:03 UTC (permalink / raw)
To: stable; +Cc: Shuangpeng Bai, Qu Wenruo, Filipe Manana, David Sterba,
Sasha Levin
From: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
[ Upstream commit 3f950867c307c5413d628a153ac44915bd117ffd ]
btrfs_dio_iomap_begin() calls btrfs_get_extent(), which returns an
extent map reference that must be dropped on all exit paths.
For direct writes into a NOCOW range, btrfs_get_blocks_direct_write()
keeps using that extent map and asks btrfs_create_dio_extent() to
allocate the ordered extent. If that fails, for example because
btrfs_alloc_ordered_extent() fails, the function returns the error
without dropping the input extent map. The PREALLOC path avoided this by
dropping the input extent map before replacing it with the newly created
one.
Check the error from btrfs_create_dio_extent() before replacing the
map and drop the input extent map on failure.
Fixes: 5f9a8a51d8b9 ("Btrfs: add semaphore to synchronize direct IO writes with fsync")
CC: stable@vger.kernel.org
Reviewed-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Shuangpeng Bai <shuangpeng.kernel@gmail.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
[ moved the direct-I/O change to inode.c and used the older free_extent_map() helper ]
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/btrfs/inode.c | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 23918e72a71f6..5b2645147397e 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -7462,17 +7462,24 @@ static int btrfs_get_blocks_direct_write(struct extent_map **map,
len, orig_block_len,
ram_bytes, type);
btrfs_dec_nocow_writers(bg);
- if (type == BTRFS_ORDERED_PREALLOC) {
- free_extent_map(em);
- *map = em2;
- em = em2;
- }
-
if (IS_ERR(em2)) {
ret = PTR_ERR(em2);
+ free_extent_map(em);
+ *map = NULL;
goto out;
}
+ /*
+ * True NOCOW writes don't need to create a new extent map,
+ * while PREALLOC writes must replace the existing one.
+ */
+ if (em2) {
+ ASSERT(type == BTRFS_ORDERED_PREALLOC);
+ free_extent_map(em);
+ *map = em2;
+ em = em2;
+ }
+
dio_data->nocow_done = true;
} else {
/* Our caller expects us to free the input extent map. */
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-09-07 1:03 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-03 13:24 FAILED: patch "[PATCH] btrfs: fix extent map leak in NOCOW direct I/O write" failed to apply to 6.6-stable tree gregkh
2026-09-07 1:03 ` [PATCH 6.6.y 1/3] btrfs: prepare btrfs_punch_hole_lock_range() for large data folios Sasha Levin
2026-09-07 1:03 ` [PATCH 6.6.y 2/3] btrfs: allow folios to be released while ordered extent is finishing Sasha Levin
2026-09-07 1:03 ` [PATCH 6.6.y 3/3] btrfs: fix extent map leak in NOCOW direct I/O write Sasha Levin
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.