exfat: move freeing sbi, upcase table and dropping nls into rcu-delayed helper
authorAl Viro <viro@zeniv.linux.org.uk>
Tue, 19 Sep 2023 19:53:32 +0000 (15:53 -0400)
committerAl Viro <viro@zeniv.linux.org.uk>
Sun, 25 Feb 2024 07:10:31 +0000 (02:10 -0500)
That stuff can be accessed by ->d_hash()/->d_compare(); as it is, we have
a hard-to-hit UAF if rcu pathwalk manages to get into ->d_hash() on a filesystem
that is in process of getting shut down.

Besides, having nls and upcase table cleanup moved from ->put_super() towards
the place where sbi is freed makes for simpler failure exits.

Acked-by: Christian Brauner <brauner@kernel.org>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/exfat/exfat_fs.h
fs/exfat/nls.c
fs/exfat/super.c

index 9474cd5..3615954 100644 (file)
@@ -275,6 +275,7 @@ struct exfat_sb_info {
 
        spinlock_t inode_hash_lock;
        struct hlist_head inode_hashtable[EXFAT_HASH_SIZE];
+       struct rcu_head rcu;
 };
 
 #define EXFAT_CACHE_VALID      0
index 705710f..afdf13c 100644 (file)
@@ -655,7 +655,6 @@ static int exfat_load_upcase_table(struct super_block *sb,
        unsigned int sect_size = sb->s_blocksize;
        unsigned int i, index = 0;
        u32 chksum = 0;
-       int ret;
        unsigned char skip = false;
        unsigned short *upcase_table;
 
@@ -673,8 +672,7 @@ static int exfat_load_upcase_table(struct super_block *sb,
                if (!bh) {
                        exfat_err(sb, "failed to read sector(0x%llx)",
                                  (unsigned long long)sector);
-                       ret = -EIO;
-                       goto free_table;
+                       return -EIO;
                }
                sector++;
                for (i = 0; i < sect_size && index <= 0xFFFF; i += 2) {
@@ -701,15 +699,12 @@ static int exfat_load_upcase_table(struct super_block *sb,
 
        exfat_err(sb, "failed to load upcase table (idx : 0x%08x, chksum : 0x%08x, utbl_chksum : 0x%08x)",
                  index, chksum, utbl_checksum);
-       ret = -EINVAL;
-free_table:
-       exfat_free_upcase_table(sbi);
-       return ret;
+       return -EINVAL;
 }
 
 static int exfat_load_default_upcase_table(struct super_block *sb)
 {
-       int i, ret = -EIO;
+       int i;
        struct exfat_sb_info *sbi = EXFAT_SB(sb);
        unsigned char skip = false;
        unsigned short uni = 0, *upcase_table;
@@ -740,8 +735,7 @@ static int exfat_load_default_upcase_table(struct super_block *sb)
                return 0;
 
        /* FATAL error: default upcase table has error */
-       exfat_free_upcase_table(sbi);
-       return ret;
+       return -EIO;
 }
 
 int exfat_create_upcase_table(struct super_block *sb)
index d9d4fa9..fcb6582 100644 (file)
@@ -39,9 +39,6 @@ static void exfat_put_super(struct super_block *sb)
        exfat_free_bitmap(sbi);
        brelse(sbi->boot_bh);
        mutex_unlock(&sbi->s_lock);
-
-       unload_nls(sbi->nls_io);
-       exfat_free_upcase_table(sbi);
 }
 
 static int exfat_sync_fs(struct super_block *sb, int wait)
@@ -600,7 +597,7 @@ static int __exfat_fill_super(struct super_block *sb)
        ret = exfat_load_bitmap(sb);
        if (ret) {
                exfat_err(sb, "failed to load alloc-bitmap");
-               goto free_upcase_table;
+               goto free_bh;
        }
 
        ret = exfat_count_used_clusters(sb, &sbi->used_clusters);
@@ -613,8 +610,6 @@ static int __exfat_fill_super(struct super_block *sb)
 
 free_alloc_bitmap:
        exfat_free_bitmap(sbi);
-free_upcase_table:
-       exfat_free_upcase_table(sbi);
 free_bh:
        brelse(sbi->boot_bh);
        return ret;
@@ -701,12 +696,10 @@ put_inode:
        sb->s_root = NULL;
 
 free_table:
-       exfat_free_upcase_table(sbi);
        exfat_free_bitmap(sbi);
        brelse(sbi->boot_bh);
 
 check_nls_io:
-       unload_nls(sbi->nls_io);
        return err;
 }
 
@@ -771,13 +764,22 @@ static int exfat_init_fs_context(struct fs_context *fc)
        return 0;
 }
 
+static void delayed_free(struct rcu_head *p)
+{
+       struct exfat_sb_info *sbi = container_of(p, struct exfat_sb_info, rcu);
+
+       unload_nls(sbi->nls_io);
+       exfat_free_upcase_table(sbi);
+       exfat_free_sbi(sbi);
+}
+
 static void exfat_kill_sb(struct super_block *sb)
 {
        struct exfat_sb_info *sbi = sb->s_fs_info;
 
        kill_block_super(sb);
        if (sbi)
-               exfat_free_sbi(sbi);
+               call_rcu(&sbi->rcu, delayed_free);
 }
 
 static struct file_system_type exfat_fs_type = {