Skip to content

Commit 1aff297

Browse files
adam900710kdave
authored andcommitted
btrfs: avoid access-beyond-folio for bs > ps encoded writes
[POTENTIAL BUG] If the system page size is 4K and fs block size is 8K, and max_inline mount option is set to 6K, we can inline a 6K sized data extent. Then a encoded write submitted a compressed extent which is at file offset 0, and the compressed length is 6K, which is allowed to be inlined. Now a read beyond page boundary is triggered inside write_extent_buffer() from insert_inline_extent(). [CAUSE] Currently the function __cow_file_range_inline() can only accept a single folio. For regular compressed write path, we always allocate the compressed folios using the minimal order matching the block size, thus the @compressed_folio should always cover a full fs block thus it is fine. But for encoded writes, they allocate page size folios, this means we can hit a case where the compressed data is smaller than block size but still larger than page size, in that case __cow_file_range_inline() will be called with @compressed_size larger than a page. In that case we will trigger a read beyond the folio inside insert_inline_extent(). Thankfully this is not that common, as the default max_inline is only 2048 bytes, smaller than PAGE_SIZE, and bs > ps support is still experimental. [FIX] We need to either allow insert_inline_extent() to accept a page array to properly support such case, or reject such inline extent. The latter is a much simpler solution, and considering bs > ps will stay as a corner case and non-default max_inline will be even rarer, I don't think we really need to fulfill such niche. So just reject any inline extent that's larger than PAGE_SIZE, and add an extra ASSERT() to insert_inline_extent() to catch such beyond-boundary access. Fixes: ec20799 ("btrfs: enable encoded read/write/send for bs > ps cases") Signed-off-by: Qu Wenruo <wqu@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
1 parent c1c050f commit 1aff297

1 file changed

Lines changed: 18 additions & 4 deletions

File tree

fs/btrfs/inode.c

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -480,13 +480,15 @@ static int insert_inline_extent(struct btrfs_trans_handle *trans,
480480
ASSERT(size <= sectorsize);
481481

482482
/*
483-
* The compressed size also needs to be no larger than a sector.
484-
* That's also why we only need one page as the parameter.
483+
* The compressed size also needs to be no larger than a page.
484+
* That's also why we only need one folio as the parameter.
485485
*/
486-
if (compressed_folio)
486+
if (compressed_folio) {
487487
ASSERT(compressed_size <= sectorsize);
488-
else
488+
ASSERT(compressed_size <= PAGE_SIZE);
489+
} else {
489490
ASSERT(compressed_size == 0);
491+
}
490492

491493
if (compressed_size && compressed_folio)
492494
cur_size = compressed_size;
@@ -573,6 +575,18 @@ static bool can_cow_file_range_inline(struct btrfs_inode *inode,
573575
if (offset != 0)
574576
return false;
575577

578+
/*
579+
* Even for bs > ps cases, cow_file_range_inline() can only accept a
580+
* single folio.
581+
*
582+
* This can be problematic and cause access beyond page boundary if a
583+
* page sized folio is passed into that function.
584+
* And encoded write is doing exactly that.
585+
* So here limits the inlined extent size to PAGE_SIZE.
586+
*/
587+
if (size > PAGE_SIZE || compressed_size > PAGE_SIZE)
588+
return false;
589+
576590
/* Inline extents are limited to sectorsize. */
577591
if (size > fs_info->sectorsize)
578592
return false;

0 commit comments

Comments
 (0)