]> www.infradead.org Git - users/hch/misc.git/commitdiff
exfat: validate cluster allocation bits of the allocation bitmap
authorNamjae Jeon <linkinjeon@kernel.org>
Sat, 30 Aug 2025 05:44:35 +0000 (14:44 +0900)
committerNamjae Jeon <linkinjeon@kernel.org>
Tue, 30 Sep 2025 04:34:42 +0000 (13:34 +0900)
syzbot created an exfat image with cluster bits not set for the allocation
bitmap. exfat-fs reads and uses the allocation bitmap without checking
this. The problem is that if the start cluster of the allocation bitmap
is 6, cluster 6 can be allocated when creating a directory with mkdir.
exfat zeros out this cluster in exfat_mkdir, which can delete existing
entries. This can reallocate the allocated entries. In addition,
the allocation bitmap is also zeroed out, so cluster 6 can be reallocated.
This patch adds exfat_test_bitmap_range to validate that clusters used for
the allocation bitmap are correctly marked as in-use.

Reported-by: syzbot+a725ab460fc1def9896f@syzkaller.appspotmail.com
Tested-by: syzbot+a725ab460fc1def9896f@syzkaller.appspotmail.com
Reviewed-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
Reviewed-by: Sungjong Seo <sj1557.seo@samsung.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
fs/exfat/balloc.c

index cc01556c9d9b37afb3982b26803e8868b2e8d61d..071448adbd5d974c23b75085587f9d0cece3fb99 100644 (file)
 /*
  *  Allocation Bitmap Management Functions
  */
+static bool exfat_test_bitmap_range(struct super_block *sb, unsigned int clu,
+               unsigned int count)
+{
+       struct exfat_sb_info *sbi = EXFAT_SB(sb);
+       unsigned int start = clu;
+       unsigned int end = clu + count;
+       unsigned int ent_idx, i, b;
+       unsigned int bit_offset, bits_to_check;
+       __le_long *bitmap_le;
+       unsigned long mask, word;
+
+       if (!is_valid_cluster(sbi, start) || !is_valid_cluster(sbi, end - 1))
+               return false;
+
+       while (start < end) {
+               ent_idx = CLUSTER_TO_BITMAP_ENT(start);
+               i = BITMAP_OFFSET_SECTOR_INDEX(sb, ent_idx);
+               b = BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent_idx);
+
+               bitmap_le = (__le_long *)sbi->vol_amap[i]->b_data;
+
+               /* Calculate how many bits we can check in the current word */
+               bit_offset = b % BITS_PER_LONG;
+               bits_to_check = min(end - start,
+                                   (unsigned int)(BITS_PER_LONG - bit_offset));
+
+               /* Create a bitmask for the range of bits to check */
+               if (bits_to_check >= BITS_PER_LONG)
+                       mask = ~0UL;
+               else
+                       mask = ((1UL << bits_to_check) - 1) << bit_offset;
+               word = lel_to_cpu(bitmap_le[b / BITS_PER_LONG]);
+
+               /* Check if all bits in the mask are set */
+               if ((word & mask) != mask)
+                       return false;
+
+               start += bits_to_check;
+       }
+
+       return true;
+}
+
 static int exfat_allocate_bitmap(struct super_block *sb,
                struct exfat_dentry *ep)
 {
        struct exfat_sb_info *sbi = EXFAT_SB(sb);
        long long map_size;
-       unsigned int i, need_map_size;
+       unsigned int i, j, need_map_size;
        sector_t sector;
 
        sbi->map_clu = le32_to_cpu(ep->dentry.bitmap.start_clu);
@@ -58,20 +101,25 @@ static int exfat_allocate_bitmap(struct super_block *sb,
        sector = exfat_cluster_to_sector(sbi, sbi->map_clu);
        for (i = 0; i < sbi->map_sectors; i++) {
                sbi->vol_amap[i] = sb_bread(sb, sector + i);
-               if (!sbi->vol_amap[i]) {
-                       /* release all buffers and free vol_amap */
-                       int j = 0;
-
-                       while (j < i)
-                               brelse(sbi->vol_amap[j++]);
-
-                       kvfree(sbi->vol_amap);
-                       sbi->vol_amap = NULL;
-                       return -EIO;
-               }
+               if (!sbi->vol_amap[i])
+                       goto err_out;
        }
 
+       if (exfat_test_bitmap_range(sb, sbi->map_clu,
+               EXFAT_B_TO_CLU_ROUND_UP(map_size, sbi)) == false)
+               goto err_out;
+
        return 0;
+
+err_out:
+       j = 0;
+       /* release all buffers and free vol_amap */
+       while (j < i)
+               brelse(sbi->vol_amap[j++]);
+
+       kvfree(sbi->vol_amap);
+       sbi->vol_amap = NULL;
+       return -EIO;
 }
 
 int exfat_load_bitmap(struct super_block *sb)