]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
btrfs: fix folio leak in submit_one_async_extent()
authorBoris Burkov <boris@bur.io>
Wed, 7 May 2025 19:42:24 +0000 (12:42 -0700)
committerDavid Sterba <dsterba@suse.com>
Mon, 12 May 2025 19:39:13 +0000 (21:39 +0200)
If btrfs_reserve_extent() fails while submitting an async_extent for a
compressed write, then we fail to call free_async_extent_pages() on the
async_extent and leak its folios. A likely cause for such a failure
would be btrfs_reserve_extent() failing to find a large enough
contiguous free extent for the compressed extent.

I was able to reproduce this by:

1. mount with compress-force=zstd:3
2. fallocating most of a filesystem to a big file
3. fragmenting the remaining free space
4. trying to copy in a file which zstd would generate large compressed
   extents for (vmlinux worked well for this)

Step 4. hits the memory leak and can be repeated ad nauseam to
eventually exhaust the system memory.

Fix this by detecting the case where we fallback to uncompressed
submission for a compressed async_extent and ensuring that we call
free_async_extent_pages().

Fixes: 131a821a243f ("btrfs: fallback if compressed IO fails for ENOSPC")
CC: stable@vger.kernel.org # 6.1+
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Co-developed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Boris Burkov <boris@bur.io>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/inode.c

index d295a37fa0491fb1d0e4cd94ecd9e3a57c24b0bd..c1bd17915f8165a6f82d6c43261fcb046cff2f18 100644 (file)
@@ -1109,6 +1109,7 @@ static void submit_one_async_extent(struct async_chunk *async_chunk,
        struct extent_state *cached = NULL;
        struct extent_map *em;
        int ret = 0;
+       bool free_pages = false;
        u64 start = async_extent->start;
        u64 end = async_extent->start + async_extent->ram_size - 1;
 
@@ -1129,7 +1130,10 @@ static void submit_one_async_extent(struct async_chunk *async_chunk,
        }
 
        if (async_extent->compress_type == BTRFS_COMPRESS_NONE) {
+               ASSERT(!async_extent->folios);
+               ASSERT(async_extent->nr_folios == 0);
                submit_uncompressed_range(inode, async_extent, locked_folio);
+               free_pages = true;
                goto done;
        }
 
@@ -1145,6 +1149,7 @@ static void submit_one_async_extent(struct async_chunk *async_chunk,
                 * fall back to uncompressed.
                 */
                submit_uncompressed_range(inode, async_extent, locked_folio);
+               free_pages = true;
                goto done;
        }
 
@@ -1186,6 +1191,8 @@ static void submit_one_async_extent(struct async_chunk *async_chunk,
 done:
        if (async_chunk->blkcg_css)
                kthread_associate_blkcg(NULL);
+       if (free_pages)
+               free_async_extent_pages(async_extent);
        kfree(async_extent);
        return;