Skip to contentSkip to conversation

FAILED: patch "[PATCH] btrfs: fix extent map leak in NOCOW direct I/O write" failed to apply to 5.10-stable tree

PATCH New [email protected] 2 participants 3 messages Sep 3 – Sep 7

Subsystems (2)
lore
Share & tools
lore

[email protected]

author maintainer
#0 lore raw

The patch below does not apply to the 5.10-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 <[email protected]>.

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-5.10.y
git checkout FETCH_HEAD
git cherry-pick -x 3f950867c307c5413d628a153ac44915bd117ffd
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026090311-oblivion-chunk-24d7@gregkh' --subject-prefix 'PATCH 5.10.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 <[email protected]>
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")
fs/btrfs/direct-io.c +13 -6
index b80dc45c3ff9..27d227f5bdc0 100644
@@ -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. */
maintainer
#1 lore raw

PATCH 1/2 btrfs: rename functions to allocate and free extent maps

From: Filipe Manana <[email protected]>

[ Upstream commit ae98ae2a50d72b01d0a61cb21e1ac75bfcebc5f3 ]

These functions are exported and don't have a 'btrfs_' prefix in their names, which goes against coding style conventions. Rename them to have such prefix, making it clear they are from btrfs and avoiding potential collisions in the future with functions defined elsewhere outside btrfs.

[ sashal: Reduced backport -- upstream ae98ae2a50d72 touches 13 file(s), this
backport carries 15. Not backported here:
fs/btrfs/defrag.c
fs/btrfs/direct-io.c
fs/btrfs/zoned.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")

---
 fs/btrfs/block-group.c            | 18 ++++-----
 fs/btrfs/compression.c            |  8 ++--
 fs/btrfs/dev-replace.c            |  2 +-
 fs/btrfs/extent_io.c              | 24 ++++++------
 fs/btrfs/extent_map.c             | 18 ++++-----
 fs/btrfs/extent_map.h             |  4 +-
 fs/btrfs/file.c                   | 48 +++++++++++------------
 fs/btrfs/inode.c                  | 64 +++++++++++++++----------------
 fs/btrfs/ioctl.c                  | 10 ++---
 fs/btrfs/relocation.c             |  4 +-
 fs/btrfs/scrub.c                  |  2 +-
 fs/btrfs/tests/extent-map-tests.c | 52 ++++++++++++-------------
 fs/btrfs/tests/inode-tests.c      | 42 ++++++++++----------
 fs/btrfs/tree-log.c               |  4 +-
 fs/btrfs/volumes.c                | 54 +++++++++++++-------------
 15 files changed, 177 insertions(+), 177 deletions(-)
fs/btrfs/block-group.c +9 -9
index c4e3c1a5de059..c04595b695ab3 100644
@@ -1162,7 +1162,7 @@ int btrfs_remove_block_group(struct btrfs_trans_handle *trans,
 		remove_extent_mapping(em_tree, em);
 		write_unlock(&em_tree->lock);
 		/* once for the tree */
-		free_extent_map(em);
+		btrfs_free_extent_map(em);
 	}
 
 out:
@@ -1208,7 +1208,7 @@ struct btrfs_trans_handle *btrfs_start_trans_remove_block_group(
 	 */
 	map = em->map_lookup;
 	num_items = 3 + map->num_stripes;
-	free_extent_map(em);
+	btrfs_free_extent_map(em);
 
 	return btrfs_start_transaction_fallback_global_rsv(fs_info->extent_root,
 							   num_items);
@@ -1591,7 +1591,7 @@ static int read_bg_from_eb(struct btrfs_fs_info *fs_info, struct btrfs_key *key,
 	}
 
 out_free_em:
-	free_extent_map(em);
+	btrfs_free_extent_map(em);
 	return ret;
 }
 
@@ -1734,7 +1734,7 @@ int btrfs_rmap_block(struct btrfs_fs_info *fs_info, u64 chunk_start,
 	*naddrs = nr;
 	*stripe_len = io_stripe_size;
 out:
-	free_extent_map(em);
+	btrfs_free_extent_map(em);
 	return ret;
 }
 
@@ -1861,7 +1861,7 @@ static int check_chunk_block_group_mappings(struct btrfs_fs_info *fs_info)
 	"chunk start=%llu len=%llu doesn't have corresponding block group",
 				     em->start, em->len);
 			ret = -EUCLEAN;
-			free_extent_map(em);
+			btrfs_free_extent_map(em);
 			break;
 		}
 		if (bg->start != em->start || bg->length != em->len ||
@@ -1874,12 +1874,12 @@ static int check_chunk_block_group_mappings(struct btrfs_fs_info *fs_info)
 				bg->start, bg->length,
 				bg->flags & BTRFS_BLOCK_GROUP_TYPE_MASK);
 			ret = -EUCLEAN;
-			free_extent_map(em);
+			btrfs_free_extent_map(em);
 			btrfs_put_block_group(bg);
 			break;
 		}
 		start = em->start + em->len;
-		free_extent_map(em);
+		btrfs_free_extent_map(em);
 		btrfs_put_block_group(bg);
 	}
 	return ret;
@@ -3440,8 +3440,8 @@ void btrfs_unfreeze_block_group(struct btrfs_block_group *block_group)
 		write_unlock(&em_tree->lock);
 
 		/* once for us and once for the tree */
-		free_extent_map(em);
-		free_extent_map(em);
+		btrfs_free_extent_map(em);
+		btrfs_free_extent_map(em);
 
 		/*
 		 * We may have left one free space entry and other possible
fs/btrfs/compression.c +4 -4
index 646416b5940e9..cffabf2b6327e 100644
@@ -565,13 +565,13 @@ static noinline int add_ra_bio_pages(struct inode *inode,
 		if (!em || last_offset < em->start ||
 		    (last_offset + PAGE_SIZE > extent_map_end(em)) ||
 		    (em->block_start >> 9) != cb->orig_bio->bi_iter.bi_sector) {
-			free_extent_map(em);
+			btrfs_free_extent_map(em);
 			unlock_extent(tree, last_offset, end);
 			unlock_page(page);
 			put_page(page);
 			break;
 		}
-		free_extent_map(em);
+		btrfs_free_extent_map(em);
 
 		if (page->index == end_index) {
 			char *userpage;
@@ -662,7 +662,7 @@ blk_status_t btrfs_submit_compressed_read(struct inode *inode, struct bio *bio,
 	em_len = em->len;
 	em_start = em->start;
 
-	free_extent_map(em);
+	btrfs_free_extent_map(em);
 	em = NULL;
 
 	cb->len = bio->bi_iter.bi_size;
@@ -779,7 +779,7 @@ blk_status_t btrfs_submit_compressed_read(struct inode *inode, struct bio *bio,
 fail1:
 	kfree(cb);
 out:
-	free_extent_map(em);
+	btrfs_free_extent_map(em);
 	return ret;
 }
 
fs/btrfs/dev-replace.c +1 -1
index 9a6055659c1a6..894d0b76986cd 100644
@@ -683,7 +683,7 @@ static void btrfs_dev_replace_update_device_in_mapping_tree(
 			if (srcdev == map->stripes[i].dev)
 				map->stripes[i].dev = tgtdev;
 		start = em->start + em->len;
-		free_extent_map(em);
+		btrfs_free_extent_map(em);
 	} while (start);
 	write_unlock(&em_tree->lock);
 }
fs/btrfs/extent_io.c +12 -12
index 38405087cc842..a70948e7c8365 100644
@@ -2495,7 +2495,7 @@ static struct io_failure_record *btrfs_get_io_failure_record(struct inode *inode
 	}
 
 	if (em->start > start || em->start + em->len <= start) {
-		free_extent_map(em);
+		btrfs_free_extent_map(em);
 		em = NULL;
 	}
 	read_unlock(&em_tree->lock);
@@ -2517,7 +2517,7 @@ static struct io_failure_record *btrfs_get_io_failure_record(struct inode *inode
 		    logical, start, failrec->len);
 
 	failrec->logical = logical;
-	free_extent_map(em);
+	btrfs_free_extent_map(em);
 
 	/* Set the bits in the private failure tree */
 	ret = set_extent_bits(failure_tree, start, end,
@@ -3120,7 +3120,7 @@ __get_extent_map(struct inode *inode, struct page *page, size_t pg_offset,
 			return em;
 		}
 
-		free_extent_map(em);
+		btrfs_free_extent_map(em);
 		*em_cached = NULL;
 	}
 
@@ -3275,7 +3275,7 @@ int btrfs_do_readpage(struct page *page, struct extent_map **em_cached,
 		if (prev_em_start)
 			*prev_em_start = em->start;
 
-		free_extent_map(em);
+		btrfs_free_extent_map(em);
 		em = NULL;
 
 		/* we've found a hole, just zero and go on */
@@ -3518,7 +3518,7 @@ static noinline_for_stack int __extent_writepage_io(struct btrfs_inode *inode,
 		offset = em->block_start + extent_offset;
 		block_start = em->block_start;
 		compressed = test_bit(EXTENT_FLAG_COMPRESSED, &em->flags);
-		free_extent_map(em);
+		btrfs_free_extent_map(em);
 		em = NULL;
 
 		/*
@@ -4366,7 +4366,7 @@ void extent_readahead(struct readahead_control *rac)
 	}
 
 	if (em_cached)
-		free_extent_map(em_cached);
+		btrfs_free_extent_map(em_cached);
 
 	if (bio) {
 		if (submit_one_bio(bio, 0, bio_flags))
@@ -4463,7 +4463,7 @@ int try_release_extent_mapping(struct page *page, gfp_t mask)
 			if (test_bit(EXTENT_FLAG_PINNED, &em->flags) ||
 			    em->start != start) {
 				write_unlock(&map->lock);
-				free_extent_map(em);
+				btrfs_free_extent_map(em);
 				break;
 			}
 			if (test_range_bit(tree, em->start,
@@ -4503,13 +4503,13 @@ int try_release_extent_mapping(struct page *page, gfp_t mask)
 			 */
 			remove_extent_mapping(map, em);
 			/* once for the rb tree */
-			free_extent_map(em);
+			btrfs_free_extent_map(em);
 next:
 			start = extent_map_end(em);
 			write_unlock(&map->lock);
 
 			/* once for us */
-			free_extent_map(em);
+			btrfs_free_extent_map(em);
 
 			cond_resched(); /* Allow large-extent preemption. */
 		}
@@ -4546,7 +4546,7 @@ static struct extent_map *get_extent_skip_holes(struct btrfs_inode *inode,
 
 		/* this is a hole, advance to the next extent */
 		offset = extent_map_end(em);
-		free_extent_map(em);
+		btrfs_free_extent_map(em);
 		if (offset >= last)
 			break;
 	}
@@ -4840,7 +4840,7 @@ int extent_fiemap(struct btrfs_inode *inode, struct fiemap_extent_info *fieinfo,
 		if (test_bit(EXTENT_FLAG_PREALLOC, &em->flags))
 			flags |= FIEMAP_EXTENT_UNWRITTEN;
 
-		free_extent_map(em);
+		btrfs_free_extent_map(em);
 		em = NULL;
 		if ((em_start >= last) || em_len == (u64)-1 ||
 		   (last == (u64)-1 && isize <= em_end)) {
@@ -4869,7 +4869,7 @@ int extent_fiemap(struct btrfs_inode *inode, struct fiemap_extent_info *fieinfo,
 out_free:
 	if (!ret)
 		ret = emit_last_fiemap_cache(fieinfo, &cache);
-	free_extent_map(em);
+	btrfs_free_extent_map(em);
 out:
 	unlock_extent_cached(&inode->io_tree, start, start + len - 1,
 			     &cached_state);
fs/btrfs/extent_map.c +9 -9
index bd6229fb2b6f0..f1b01acef8881 100644
@@ -45,9 +45,9 @@ void extent_map_tree_init(struct extent_map_tree *tree)
  *
  * Allocate a new extent_map structure.  The new structure is
  * returned with a reference count of one and needs to be
- * freed using free_extent_map()
+ * freed using btrfs_free_extent_map()
  */
-struct extent_map *alloc_extent_map(void)
+struct extent_map *btrfs_alloc_extent_map(void)
 {
 	struct extent_map *em;
 	em = kmem_cache_zalloc(extent_map_cache, GFP_NOFS);
@@ -69,7 +69,7 @@ struct extent_map *alloc_extent_map(void)
  * Drops the reference out on @em by one and free the structure
  * if the reference count hits zero.
  */
-void free_extent_map(struct extent_map *em)
+void btrfs_free_extent_map(struct extent_map *em)
 {
 	if (!em)
 		return;
@@ -264,7 +264,7 @@ static void try_merge_map(struct extent_map_tree *tree, struct extent_map *em)
 
 			rb_erase_cached(&merge->rb_node, &tree->map);
 			RB_CLEAR_NODE(&merge->rb_node);
-			free_extent_map(merge);
+			btrfs_free_extent_map(merge);
 		}
 	}
 
@@ -278,7 +278,7 @@ static void try_merge_map(struct extent_map_tree *tree, struct extent_map *em)
 		RB_CLEAR_NODE(&merge->rb_node);
 		em->mod_len = (merge->mod_start + merge->mod_len) - em->mod_start;
 		em->generation = max(em->generation, merge->generation);
-		free_extent_map(merge);
+		btrfs_free_extent_map(merge);
 	}
 }
 
@@ -325,7 +325,7 @@ int unpin_extent_cache(struct extent_map_tree *tree, u64 start, u64 len,
 		em->mod_len = em->len;
 	}
 
-	free_extent_map(em);
+	btrfs_free_extent_map(em);
 out:
 	write_unlock(&tree->lock);
 	return ret;
@@ -620,7 +620,7 @@ int btrfs_add_extent_mapping(struct btrfs_fs_info *fs_info,
 		 */
 		if (start >= existing->start &&
 		    start < extent_map_end(existing)) {
-			free_extent_map(em);
+			btrfs_free_extent_map(em);
 			*em_in = existing;
 			ret = 0;
 		} else {
@@ -634,14 +634,14 @@ int btrfs_add_extent_mapping(struct btrfs_fs_info *fs_info,
 			ret = merge_extent_mapping(em_tree, existing,
 						   em, start);
 			if (ret) {
-				free_extent_map(em);
+				btrfs_free_extent_map(em);
 				*em_in = NULL;
 				WARN_ONCE(ret,
 "unexpected error %d: merge existing(start %llu len %llu) with em(start %llu len %llu)\n",
 					  ret, existing->start, existing->len,
 					  orig_start, orig_len);
 			}
-			free_extent_map(existing);
+			btrfs_free_extent_map(existing);
 		}
 	}
 
fs/btrfs/extent_map.h +2 -2
index 8e217337dff90..c4169aac5ab55 100644
@@ -85,8 +85,8 @@ void replace_extent_mapping(struct extent_map_tree *tree,
 			    struct extent_map *new,
 			    int modified);
 
-struct extent_map *alloc_extent_map(void);
-void free_extent_map(struct extent_map *em);
+struct extent_map *btrfs_alloc_extent_map(void);
+void btrfs_free_extent_map(struct extent_map *em);
 int __init extent_map_init(void);
 void __cold extent_map_exit(void);
 int unpin_extent_cache(struct extent_map_tree *tree, u64 start, u64 len, u64 gen);
fs/btrfs/file.c +24 -24

169 lines not on this page · raw message

fs/btrfs/inode.c +32 -32

236 lines not on this page · raw message

fs/btrfs/ioctl.c +5 -5
index 16c397062809f..22d040fe8c698 100644
@@ -1073,7 +1073,7 @@ static int check_defrag_in_cache(struct inode *inode, u64 offset, u32 thresh)
 
 	if (em) {
 		end = extent_map_end(em);
-		free_extent_map(em);
+		btrfs_free_extent_map(em);
 		if (end - offset > thresh)
 			return 0;
 	}
@@ -1200,7 +1200,7 @@ static bool defrag_check_next_extent(struct inode *inode, struct extent_map *em)
 		 (em->block_len > SZ_128K && next->block_len > SZ_128K))
 		ret = false;
 
-	free_extent_map(next);
+	btrfs_free_extent_map(next);
 	return ret;
 }
 
@@ -1260,7 +1260,7 @@ static int should_defrag_range(struct inode *inode, u64 start, u32 thresh,
 		*defrag_end = 0;
 	}
 
-	free_extent_map(em);
+	btrfs_free_extent_map(em);
 	return ret;
 }
 
@@ -1409,13 +1409,13 @@ static int cluster_pages_for_defrag(struct inode *inode,
 			goto out_unlock_range;
 		}
 		if (em->block_start >= EXTENT_MAP_LAST_BYTE) {
-			free_extent_map(em);
+			btrfs_free_extent_map(em);
 			/* Ok, 0 means we did not defrag anything */
 			ret = 0;
 			goto out_unlock_range;
 		}
 		search_start = extent_map_end(em);
-		free_extent_map(em);
+		btrfs_free_extent_map(em);
 	}
 
 	clear_extent_bit(&BTRFS_I(inode)->io_tree, page_start,
fs/btrfs/relocation.c +2 -2
index 5b921e6ed94e2..11e8a7ee5e638 100644
@@ -2653,7 +2653,7 @@ int setup_extent_mapping(struct inode *inode, u64 start, u64 end,
 	struct extent_map *em;
 	int ret = 0;
 
-	em = alloc_extent_map();
+	em = btrfs_alloc_extent_map();
 	if (!em)
 		return -ENOMEM;
 
@@ -2669,7 +2669,7 @@ int setup_extent_mapping(struct inode *inode, u64 start, u64 end,
 		ret = add_extent_mapping(em_tree, em, 0);
 		write_unlock(&em_tree->lock);
 		if (ret != -EEXIST) {
-			free_extent_map(em);
+			btrfs_free_extent_map(em);
 			break;
 		}
 		btrfs_drop_extent_cache(BTRFS_I(inode), start, end, 0);
fs/btrfs/scrub.c +1 -1
index c8d033deb8ab8..1d4cd90fa5efc 100644
@@ -3408,7 +3408,7 @@ static noinline_for_stack int scrub_chunk(struct scrub_ctx *sctx,
 		}
 	}
 out:
-	free_extent_map(em);
+	btrfs_free_extent_map(em);
 
 	return ret;
 }
fs/btrfs/tests/extent-map-tests.c +26 -26

171 lines not on this page · raw message

fs/btrfs/tests/inode-tests.c +21 -21

168 lines not on this page · raw message

fs/btrfs/tree-log.c +2 -2
index cdb5a2770faf3..553838abc055f 100644
@@ -4524,7 +4524,7 @@ static int btrfs_log_changed_extents(struct btrfs_trans_handle *trans,
 		 */
 		if (ret) {
 			clear_em_logging(tree, em);
-			free_extent_map(em);
+			btrfs_free_extent_map(em);
 			continue;
 		}
 
@@ -4533,7 +4533,7 @@ static int btrfs_log_changed_extents(struct btrfs_trans_handle *trans,
 		ret = log_one_extent(trans, inode, root, em, path, ctx);
 		write_lock(&tree->lock);
 		clear_em_logging(tree, em);
-		free_extent_map(em);
+		btrfs_free_extent_map(em);
 	}
 	WARN_ON(!list_empty(&extents));
 	write_unlock(&tree->lock);
fs/btrfs/volumes.c +27 -27

194 lines not on this page · raw message

Signature · 1 line
2.53.0
maintainer
#2 lore raw

PATCH 2/2 btrfs: fix extent map leak in NOCOW direct I/O write

From: Shuangpeng Bai <[email protected]>

[ 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")
[ relocated the ownership fix from fs/btrfs/direct-io.c to the equivalent code in fs/btrfs/inode.c ]
---
 fs/btrfs/inode.c | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)
fs/btrfs/inode.c +14 -5
index 7f2aab8eb8705..98d46638a0572 100644
@@ -7339,15 +7339,24 @@ static int btrfs_get_blocks_direct_write(struct extent_map **map,
 						      len, orig_block_len,
 						      ram_bytes, type);
 			btrfs_dec_nocow_writers(fs_info, block_start);
-			if (type == BTRFS_ORDERED_PREALLOC) {
+			if (IS_ERR(em2)) {
+				ret = PTR_ERR(em2);
 				btrfs_free_extent_map(em);
-				*map = em = em2;
+				*map = NULL;
+				goto out;
 			}
 
-			if (em2 && IS_ERR(em2)) {
-				ret = PTR_ERR(em2);
-				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;
 			}
+
 			/*
 			 * For inode marked NODATACOW or extent marked PREALLOC,
 			 * use the existing or preallocated extent, so does not
Signature · 1 line
2.53.0