exfat: validate cluster allocation bits of the allocation bitmap

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>
This commit is contained in:
Namjae Jeon 2025-08-30 14:44:35 +09:00
parent 6dfba10838
commit 79c1587b6c

View File

@ -26,12 +26,55 @@
/*
* 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)