[PATCH] btrfs: prevent length underflow in extent_fiemap()

Qu Wenruo <[email protected]>
Newsgroups org.kernel.vger.linux-btrfs
Message-ID <bb1a42fcf03d1c3f74382459b47bb9af0c7c3f24.1786430714.git.wqu@suse.com>
When doing a ranged fiemap ioctl, if we have filled the fiemap buffer,
emit_fiemap_extent() will return a special code,
BTRFS_FIEMAP_FLUSH_CACHE, to indicate that we need to unlock the inode
and flush the fiemap cache and retry.

However inside that BTRFS_FIEMAP_FLUSH_CACHE handling, we're reducing
@len by "cache.next_search_offset - @start".

Meanwhile cache.next_search_offset can be beyond @start + @len, this is
common for any extent that crosses @start + @len, as we do not clamp the
extent range to the original range.

This means if we hit an extent extending beyond @start + @len, and we
need to flush the fiemap cache, our @len will underflow to a very large
value.
Then on the next loop, @start is larger than @start + @len, causing an
invalid range to be passed into btrfs_lock_extent().

Prevent such underflow by clamping down the range before retry, and also
enhance validate_extent_state() to catch such odd extent state.

This was reported by Sashiko, which caught this pre-existing bug while
reviewing another patch.

Link: https://sashiko.dev/#/patchset/cover.1785220613.git.wqu%40suse.com
Signed-off-by: Qu Wenruo <[email protected]>
---
 fs/btrfs/extent-io-tree.c |  5 +++--
 fs/btrfs/fiemap.c         | 10 +++++++---
 2 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/fs/btrfs/extent-io-tree.c b/fs/btrfs/extent-io-tree.c
index d6df11f6088c..28ed8e04ecfe 100644
--- a/fs/btrfs/extent-io-tree.c
+++ b/fs/btrfs/extent-io-tree.c
@@ -344,8 +344,9 @@ static void validate_extent_state(const struct extent_io_tree *tree,
 
 	blocksize = btrfs_extent_io_tree_to_fs_info(tree)->sectorsize;
 	ASSERT(IS_ALIGNED(state->start, blocksize) &&
-	       IS_ALIGNED(state->end + 1, blocksize),
-	       "unaligned extent state, blocksize=%u start=%llu end=%llu state=0x%x",
+	       IS_ALIGNED(state->end + 1, blocksize) &&
+	       state->end > state->start,
+	       "invalid extent state, blocksize=%u start=%llu end=%llu state=0x%x",
 	       blocksize, state->start, state->end, state->state);
 }
 
diff --git a/fs/btrfs/fiemap.c b/fs/btrfs/fiemap.c
index 7a2a97180099..53dd186d71f6 100644
--- a/fs/btrfs/fiemap.c
+++ b/fs/btrfs/fiemap.c
@@ -843,13 +843,17 @@ static int extent_fiemap(struct btrfs_inode *inode,
 	btrfs_unlock_extent(&inode->io_tree, range_start, range_end - 1, &cached_state);
 
 	if (ret == BTRFS_FIEMAP_FLUSH_CACHE) {
+		const u64 orig_start = start;
+		const u64 orig_len = len;
+
 		btrfs_release_path(path);
 		ret = flush_fiemap_cache(fieinfo, &cache);
 		if (ret)
 			goto out;
-		len -= cache.next_search_offset - start;
-		start = cache.next_search_offset;
-		goto restart;
+		start = min(orig_start + orig_len, cache.next_search_offset);
+		len = orig_start + orig_len - start;
+		if (len)
+			goto restart;
 	} else if (ret < 0) {
 		goto out;
 	}
-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.