[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