]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
exfat: validate the cluster bitmap bits of directory
authorNamjae Jeon <linkinjeon@kernel.org>
Wed, 12 Nov 2025 00:42:25 +0000 (09:42 +0900)
committerNamjae Jeon <linkinjeon@kernel.org>
Wed, 3 Dec 2025 01:00:16 +0000 (10:00 +0900)
Syzbot created this issue by testing an image that did not have the root
cluster bitmap bit marked. After accessing a file through the root
directory via exfat_lookup, when creating a file again with mkdir,
the root cluster bit can be allocated for direcotry, which can cause
the root cluster to be zeroed out and the same entry can be allocated
in the same cluster. This patch improved this issue by adding
exfat_test_bitmap to validate the cluster bits of the root directory
and directory. And the first cluster bit of the root directory should
never be unset except when storage is corrupted. This bit is set to
allow operations after mount.

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

index 2d2d510f2372cbccbf45574a8fc9b6bb30e4713d..b387bf7df65e85b06a2aa5b64c91580e60061873 100644 (file)
@@ -183,11 +183,10 @@ void exfat_free_bitmap(struct exfat_sb_info *sbi)
        kvfree(sbi->vol_amap);
 }
 
-int exfat_set_bitmap(struct inode *inode, unsigned int clu, bool sync)
+int exfat_set_bitmap(struct super_block *sb, unsigned int clu, bool sync)
 {
        int i, b;
        unsigned int ent_idx;
-       struct super_block *sb = inode->i_sb;
        struct exfat_sb_info *sbi = EXFAT_SB(sb);
 
        if (!is_valid_cluster(sbi, clu))
@@ -202,11 +201,10 @@ int exfat_set_bitmap(struct inode *inode, unsigned int clu, bool sync)
        return 0;
 }
 
-int exfat_clear_bitmap(struct inode *inode, unsigned int clu, bool sync)
+int exfat_clear_bitmap(struct super_block *sb, unsigned int clu, bool sync)
 {
        int i, b;
        unsigned int ent_idx;
-       struct super_block *sb = inode->i_sb;
        struct exfat_sb_info *sbi = EXFAT_SB(sb);
 
        if (!is_valid_cluster(sbi, clu))
@@ -226,6 +224,28 @@ int exfat_clear_bitmap(struct inode *inode, unsigned int clu, bool sync)
        return 0;
 }
 
+bool exfat_test_bitmap(struct super_block *sb, unsigned int clu)
+{
+       int i, b;
+       unsigned int ent_idx;
+       struct exfat_sb_info *sbi = EXFAT_SB(sb);
+
+       if (!sbi->vol_amap)
+               return true;
+
+       if (!is_valid_cluster(sbi, clu))
+               return false;
+
+       ent_idx = CLUSTER_TO_BITMAP_ENT(clu);
+       i = BITMAP_OFFSET_SECTOR_INDEX(sb, ent_idx);
+       b = BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent_idx);
+
+       if (!test_bit_le(b, sbi->vol_amap[i]->b_data))
+               return false;
+
+       return true;
+}
+
 /*
  * If the value of "clu" is 0, it means cluster 2 which is the first cluster of
  * the cluster heap.
index 7229146fe2bf64d551890989ae0a5194d6007be9..3045a58e124ae0f193af2caeef7261b20fe42e00 100644 (file)
@@ -604,6 +604,11 @@ static int exfat_find_location(struct super_block *sb, struct exfat_chain *p_dir
        if (ret)
                return ret;
 
+       if (!exfat_test_bitmap(sb, clu)) {
+               exfat_err(sb, "failed to test cluster bit(%u)", clu);
+               return -EIO;
+       }
+
        /* byte offset in cluster */
        off = EXFAT_CLU_OFFSET(off, sbi);
 
index 38210fb6901c09c4dc04d10b5929840f28e93941..176fef62574cf78435473c8b67806203ec13445f 100644 (file)
@@ -452,8 +452,9 @@ int exfat_count_num_clusters(struct super_block *sb,
 /* balloc.c */
 int exfat_load_bitmap(struct super_block *sb);
 void exfat_free_bitmap(struct exfat_sb_info *sbi);
-int exfat_set_bitmap(struct inode *inode, unsigned int clu, bool sync);
-int exfat_clear_bitmap(struct inode *inode, unsigned int clu, bool sync);
+int exfat_set_bitmap(struct super_block *sb, unsigned int clu, bool sync);
+int exfat_clear_bitmap(struct super_block *sb, unsigned int clu, bool sync);
+bool exfat_test_bitmap(struct super_block *sb, unsigned int clu);
 unsigned int exfat_find_free_bitmap(struct super_block *sb, unsigned int clu);
 int exfat_count_used_clusters(struct super_block *sb, unsigned int *ret_count);
 int exfat_trim_fs(struct inode *inode, struct fstrim_range *range);
index 825083634ba2da4a763aa9853601045363212797..c9c5f2e3a05ebc4af3c2c8d3edbf0c7328499205 100644 (file)
@@ -205,7 +205,7 @@ static int __exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain
                                cur_cmap_i = next_cmap_i;
                        }
 
-                       err = exfat_clear_bitmap(inode, clu, (sync && IS_DIRSYNC(inode)));
+                       err = exfat_clear_bitmap(sb, clu, (sync && IS_DIRSYNC(inode)));
                        if (err)
                                break;
                        clu++;
@@ -233,7 +233,7 @@ static int __exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain
                                cur_cmap_i = next_cmap_i;
                        }
 
-                       if (exfat_clear_bitmap(inode, clu, (sync && IS_DIRSYNC(inode))))
+                       if (exfat_clear_bitmap(sb, clu, (sync && IS_DIRSYNC(inode))))
                                break;
 
                        if (sbi->options.discard) {
@@ -409,7 +409,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
                }
 
                /* update allocation bitmap */
-               if (exfat_set_bitmap(inode, new_clu, sync_bmap)) {
+               if (exfat_set_bitmap(sb, new_clu, sync_bmap)) {
                        ret = -EIO;
                        goto free_cluster;
                }
index 74d451f732c7321c9c42db4a963bda998f4ffd39..bc03f47374d47c264ab1bfdfa470fff8c7e82a36 100644 (file)
@@ -629,6 +629,17 @@ static int __exfat_fill_super(struct super_block *sb,
                goto free_bh;
        }
 
+       if (!exfat_test_bitmap(sb, sbi->root_dir)) {
+               exfat_warn(sb, "failed to test first cluster bit of root dir(%u)",
+                          sbi->root_dir);
+               /*
+                * The first cluster bit of the root directory should never
+                * be unset except when storage is corrupted. This bit is
+                * set to allow operations after mount.
+                */
+               exfat_set_bitmap(sb, sbi->root_dir, false);
+       }
+
        ret = exfat_count_used_clusters(sb, &sbi->used_clusters);
        if (ret) {
                exfat_err(sb, "failed to scan clusters");