]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
iomap: fix out-of-bounds bitmap_set() with zero-length range
authorZhang Yi <yi.zhang@huawei.com>
Tue, 14 Jul 2026 08:23:24 +0000 (16:23 +0800)
committerChristian Brauner <brauner@kernel.org>
Thu, 23 Jul 2026 09:26:28 +0000 (11:26 +0200)
ifs_set_range_dirty() and ifs_set_range_uptodate() compute last_blk
as (off + len - 1) >> i_blkbits.  When off is 0 and len is 0, the
unsigned subtraction underflows to SIZE_MAX, producing a huge
last_blk and nr_blks value that causes bitmap_set() to write far
beyond the ifs->state allocation.

Regarding ifs_set_range_uptodate(), it is temporarily safe because len
cannot be passed in as 0. However, for ifs_set_range_dirty() this is
reachable from __iomap_write_end(): when copy_folio_from_iter_atomic()
returns 0 (e.g. user buffer fault) and the folio is already uptodate,
the guard at the top of __iomap_write_end() does not trigger because
!folio_test_uptodate() is false, and iomap_set_range_dirty() is called
with copied == 0.

Add a !len guard to both functions before the computation, so that a
zero-length range is a no-op.

Fixes: 4ce02c679722 ("iomap: Add per-block dirty state tracking to improve performance")
Cc: stable@vger.kernel.org # v6.6
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
Link: https://patch.msgid.link/20260714082325.325163-5-yi.zhang@huaweicloud.com
Reviewed-by: Joanne Koong <joannelkoong@gmail.com>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
fs/iomap/buffered-io.c

index 0cf62e516827a6f2154b46c71f29bc7e0d592933..3a3ac3051fb00a4c670b5bb3d3c09347ea8c3404 100644 (file)
@@ -68,11 +68,13 @@ static bool ifs_set_range_uptodate(struct folio *folio,
                struct iomap_folio_state *ifs, size_t off, size_t len)
 {
        struct inode *inode = folio->mapping->host;
-       unsigned int first_blk = off >> inode->i_blkbits;
-       unsigned int last_blk = (off + len - 1) >> inode->i_blkbits;
-       unsigned int nr_blks = last_blk - first_blk + 1;
+       unsigned int first_blk, last_blk;
 
-       bitmap_set(ifs->state, first_blk, nr_blks);
+       if (len) {
+               first_blk = off >> inode->i_blkbits;
+               last_blk = (off + len - 1) >> inode->i_blkbits;
+               bitmap_set(ifs->state, first_blk, last_blk - first_blk + 1);
+       }
        return ifs_is_fully_uptodate(folio, ifs);
 }
 
@@ -204,13 +206,17 @@ static void ifs_set_range_dirty(struct folio *folio,
 {
        struct inode *inode = folio->mapping->host;
        unsigned int blks_per_folio = i_blocks_per_folio(inode, folio);
-       unsigned int first_blk = (off >> inode->i_blkbits);
-       unsigned int last_blk = (off + len - 1) >> inode->i_blkbits;
-       unsigned int nr_blks = last_blk - first_blk + 1;
+       unsigned int first_blk, last_blk;
        unsigned long flags;
 
+       if (!len)
+               return;
+
+       first_blk = off >> inode->i_blkbits;
+       last_blk = (off + len - 1) >> inode->i_blkbits;
        spin_lock_irqsave(&ifs->state_lock, flags);
-       bitmap_set(ifs->state, first_blk + blks_per_folio, nr_blks);
+       bitmap_set(ifs->state, first_blk + blks_per_folio,
+                  last_blk - first_blk + 1);
        spin_unlock_irqrestore(&ifs->state_lock, flags);
 }