]> git.ipfire.org Git - thirdparty/kernel/stable.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)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 20 Aug 2025 16:30:47 +0000 (18:30 +0200)
[ Upstream commit 99f9a97dce39ad413c39b92c90393bbd6778f3fd ]

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>
Signed-off-by: Sasha Levin <sashal@kernel.org>
fs/exfat/dir.c
fs/exfat/fatent.c
fs/exfat/namei.c
fs/exfat/super.c

index 9d8848872fe8ac3d656f06a7547c169d0c6b5435..1c428f7f83f5d9f75a34fc1e3ab752a75df2f629 100644 (file)
@@ -1015,6 +1015,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;
@@ -1152,6 +1153,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;
                }
        }
 
@@ -1214,6 +1219,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);
@@ -1246,6 +1252,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 8df5ad6ebb10cb1357de3d7ea14e512c49fb4861..0c60ddc24c54a800e3912e09090337a1233e6605 100644 (file)
@@ -461,5 +461,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 7b3951951f8af1c84abc2c7b921a4219f28f05c2..e9624eb61cbc9ac4d5b914ca07b38fcd34795836 100644 (file)
@@ -888,6 +888,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);
@@ -924,6 +925,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 bd57844414aa6d3b2e874fb875f1679499b54a8c..7aaf1ed6aee9107e95431478681b91a52827fd96 100644 (file)
@@ -370,13 +370,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;
@@ -389,12 +388,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);
@@ -608,7 +604,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);
@@ -625,6 +622,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");
@@ -657,6 +666,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)
@@ -675,7 +685,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;
@@ -710,7 +720,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;