]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
exfat: add cluster chain loop check for dir
authorYuezhang Mo <Yuezhang.Mo@sony.com>
Tue, 18 Mar 2025 09:00:49 +0000 (17:00 +0800)
committerNamjae Jeon <linkinjeon@kernel.org>
Thu, 31 Jul 2025 23:34:23 +0000 (08:34 +0900)
An infinite loop may occur if the following conditions occur due to
file system corruption.

(1) Condition for exfat_count_dir_entries() to loop infinitely.
    - The cluster chain includes a loop.
    - There is no UNUSED entry in the cluster chain.

(2) Condition for exfat_create_upcase_table() to loop infinitely.
    - The cluster chain of the root directory includes a loop.
    - There are no UNUSED entry and up-case table entry in the cluster
      chain of the root directory.

(3) Condition for exfat_load_bitmap() to loop infinitely.
    - The cluster chain of the root directory includes a loop.
    - There are no UNUSED entry and bitmap entry in the cluster chain
      of the root directory.

(4) Condition for exfat_find_dir_entry() to loop infinitely.
    - The cluster chain includes a loop.
    - The unused directory entries were exhausted by some operation.

(5) Condition for exfat_check_dir_empty() to loop infinitely.
    - The cluster chain includes a loop.
    - The unused directory entries were exhausted by some operation.
    - All files and sub-directories under the directory are deleted.

This commit adds checks to break the above infinite loop.

Signed-off-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
fs/exfat/dir.c
fs/exfat/fatent.c
fs/exfat/namei.c
fs/exfat/super.c

index 3103b932b674619adc168cf26f24d3e1f7a3b8ff..ee060e26f51d2a46b43d13ccc93f4cb870155b3b 100644 (file)
@@ -996,6 +996,7 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei,
        struct exfat_hint_femp candi_empty;
        struct exfat_sb_info *sbi = EXFAT_SB(sb);
        int num_entries = exfat_calc_num_entries(p_uniname);
+       unsigned int clu_count = 0;
 
        if (num_entries < 0)
                return num_entries;
@@ -1133,6 +1134,10 @@ rewind:
                } else {
                        if (exfat_get_next_cluster(sb, &clu.dir))
                                return -EIO;
+
+                       /* break if the cluster chain includes a loop */
+                       if (unlikely(++clu_count > EXFAT_DATA_CLUSTER_COUNT(sbi)))
+                               goto not_found;
                }
        }
 
@@ -1195,6 +1200,7 @@ int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir)
        int i, count = 0;
        int dentries_per_clu;
        unsigned int entry_type;
+       unsigned int clu_count = 0;
        struct exfat_chain clu;
        struct exfat_dentry *ep;
        struct exfat_sb_info *sbi = EXFAT_SB(sb);
@@ -1227,6 +1233,12 @@ int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir)
                } else {
                        if (exfat_get_next_cluster(sb, &(clu.dir)))
                                return -EIO;
+
+                       if (unlikely(++clu_count > sbi->used_clusters)) {
+                               exfat_fs_error(sb, "FAT or bitmap is corrupted");
+                               return -EIO;
+                       }
+
                }
        }
 
index 23065f948ae7527c50a438610383626d15f4f112..232cc7f8ab92fcf2a13a9392f07c328864cdc6a1 100644 (file)
@@ -490,5 +490,15 @@ int exfat_count_num_clusters(struct super_block *sb,
        }
 
        *ret_count = count;
+
+       /*
+        * since exfat_count_used_clusters() is not called, sbi->used_clusters
+        * cannot be used here.
+        */
+       if (unlikely(i == sbi->num_clusters && clu != EXFAT_EOF_CLUSTER)) {
+               exfat_fs_error(sb, "The cluster chain has a loop");
+               return -EIO;
+       }
+
        return 0;
 }
index fede0283d6e21f6c56bd0981706fd91267f76089..f5f1c4e8a29fd21cca4a516c54e83588a24e615f 100644 (file)
@@ -890,6 +890,7 @@ static int exfat_check_dir_empty(struct super_block *sb,
 {
        int i, dentries_per_clu;
        unsigned int type;
+       unsigned int clu_count = 0;
        struct exfat_chain clu;
        struct exfat_dentry *ep;
        struct exfat_sb_info *sbi = EXFAT_SB(sb);
@@ -926,6 +927,10 @@ static int exfat_check_dir_empty(struct super_block *sb,
                } else {
                        if (exfat_get_next_cluster(sb, &(clu.dir)))
                                return -EIO;
+
+                       /* break if the cluster chain includes a loop */
+                       if (unlikely(++clu_count > EXFAT_DATA_CLUSTER_COUNT(sbi)))
+                               break;
                }
        }
 
index ea5c1334a2143496c80a02a04c12851f45888182..8926e63f5bb7e12b602a9a8b1850f0b0a638c9d9 100644 (file)
@@ -341,13 +341,12 @@ static void exfat_hash_init(struct super_block *sb)
                INIT_HLIST_HEAD(&sbi->inode_hashtable[i]);
 }
 
-static int exfat_read_root(struct inode *inode)
+static int exfat_read_root(struct inode *inode, struct exfat_chain *root_clu)
 {
        struct super_block *sb = inode->i_sb;
        struct exfat_sb_info *sbi = EXFAT_SB(sb);
        struct exfat_inode_info *ei = EXFAT_I(inode);
-       struct exfat_chain cdir;
-       int num_subdirs, num_clu = 0;
+       int num_subdirs;
 
        exfat_chain_set(&ei->dir, sbi->root_dir, 0, ALLOC_FAT_CHAIN);
        ei->entry = -1;
@@ -360,12 +359,9 @@ static int exfat_read_root(struct inode *inode)
        ei->hint_stat.clu = sbi->root_dir;
        ei->hint_femp.eidx = EXFAT_HINT_NONE;
 
-       exfat_chain_set(&cdir, sbi->root_dir, 0, ALLOC_FAT_CHAIN);
-       if (exfat_count_num_clusters(sb, &cdir, &num_clu))
-               return -EIO;
-       i_size_write(inode, num_clu << sbi->cluster_size_bits);
+       i_size_write(inode, EXFAT_CLU_TO_B(root_clu->size, sbi));
 
-       num_subdirs = exfat_count_dir_entries(sb, &cdir);
+       num_subdirs = exfat_count_dir_entries(sb, root_clu);
        if (num_subdirs < 0)
                return -EIO;
        set_nlink(inode, num_subdirs + EXFAT_MIN_SUBDIR);
@@ -578,7 +574,8 @@ static int exfat_verify_boot_region(struct super_block *sb)
 }
 
 /* mount the file system volume */
-static int __exfat_fill_super(struct super_block *sb)
+static int __exfat_fill_super(struct super_block *sb,
+               struct exfat_chain *root_clu)
 {
        int ret;
        struct exfat_sb_info *sbi = EXFAT_SB(sb);
@@ -595,6 +592,18 @@ static int __exfat_fill_super(struct super_block *sb)
                goto free_bh;
        }
 
+       /*
+        * Call exfat_count_num_cluster() before searching for up-case and
+        * bitmap directory entries to avoid infinite loop if they are missing
+        * and the cluster chain includes a loop.
+        */
+       exfat_chain_set(root_clu, sbi->root_dir, 0, ALLOC_FAT_CHAIN);
+       ret = exfat_count_num_clusters(sb, root_clu, &root_clu->size);
+       if (ret) {
+               exfat_err(sb, "failed to count the number of clusters in root");
+               goto free_bh;
+       }
+
        ret = exfat_create_upcase_table(sb);
        if (ret) {
                exfat_err(sb, "failed to load upcase table");
@@ -627,6 +636,7 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc)
        struct exfat_sb_info *sbi = sb->s_fs_info;
        struct exfat_mount_options *opts = &sbi->options;
        struct inode *root_inode;
+       struct exfat_chain root_clu;
        int err;
 
        if (opts->allow_utime == (unsigned short)-1)
@@ -645,7 +655,7 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc)
        sb->s_time_min = EXFAT_MIN_TIMESTAMP_SECS;
        sb->s_time_max = EXFAT_MAX_TIMESTAMP_SECS;
 
-       err = __exfat_fill_super(sb);
+       err = __exfat_fill_super(sb, &root_clu);
        if (err) {
                exfat_err(sb, "failed to recognize exfat type");
                goto check_nls_io;
@@ -680,7 +690,7 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc)
 
        root_inode->i_ino = EXFAT_ROOT_INO;
        inode_set_iversion(root_inode, 1);
-       err = exfat_read_root(root_inode);
+       err = exfat_read_root(root_inode, &root_clu);
        if (err) {
                exfat_err(sb, "failed to initialize root inode");
                goto put_inode;