From 564a610cd15ef6f3f6b0aafcdc2f060594b6b4ba Mon Sep 17 00:00:00 2001 From: David Timber Date: Tue, 5 May 2026 21:28:06 +0900 Subject: [PATCH 1/3] exfat: add volume limit bounds checks If the user inadvertenly truncates an exFAT volume(mistakenly shrinks the partition or simply dd'ing from a larger removable device to a smaller one. eg: device marketed as having 8GB capatity < 8GiB), the kernel exFAT obliviously mounts the volume and operates on it. No error is reported to userspace unless the filesystem is accessed with O_SYNC or O_DIRECT. Off by one sector test: # truncate -s 1073741824 img # mkfs.exfat img # truncate -s 1073741312 img # mount -t exfat img ... The existing filesystem implementations, prime examples being XFS and ext*, refuse to mount the volume with such condition. Introduce the checks similar checks in-place to exFAT. Also, to prevent UB, add checks against exFAT volumes with maliciously a crafted main boot sectors with the ClusterCount field equal to or larger than (2^32 - 11) as per format spec. Link: https://github.com/exfatprogs/exfatprogs/issues/353 Signed-off-by: David Timber --- fs/exfat/super.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/fs/exfat/super.c b/fs/exfat/super.c index 83396fd265cda..588312caf1e59 100644 --- a/fs/exfat/super.c +++ b/fs/exfat/super.c @@ -431,6 +431,7 @@ static int exfat_read_boot_sector(struct super_block *sb) { struct boot_sector *p_boot; struct exfat_sb_info *sbi = EXFAT_SB(sb); + unsigned int nb_clusters; /* set block size to read super block */ if (!sb_min_blocksize(sb, 512)) { @@ -501,8 +502,17 @@ static int exfat_read_boot_sector(struct super_block *sb) sbi->data_start_sector = le32_to_cpu(p_boot->clu_offset); sbi->num_sectors = le64_to_cpu(p_boot->vol_length); /* because the cluster index starts with 2 */ - sbi->num_clusters = le32_to_cpu(p_boot->clu_count) + - EXFAT_RESERVED_CLUSTERS; + nb_clusters = le32_to_cpu(p_boot->clu_count); + /* + * The inclusive comparison in the following check seems a bit off(quite + * literally), but the exFAT format section 3.1.9 says + * "lesser of the following". Aye, aye, captain. + */ + if (nb_clusters >= EXFAT_MAX_NUM_CLUSTER) { + exfat_err(sb, "bogus number of clusters : %u", nb_clusters); + return -EINVAL; + } + sbi->num_clusters = nb_clusters + EXFAT_RESERVED_CLUSTERS; sbi->root_dir = le32_to_cpu(p_boot->root_cluster); sbi->dentries_per_clu = 1 << @@ -586,6 +596,34 @@ static int exfat_verify_boot_region(struct super_block *sb) return 0; } +static inline int exfat_check_volume_sizes(struct super_block *sb) +{ + struct exfat_sb_info *sbi = EXFAT_SB(sb); + /* last sector of exFAT volume == end of last cluster */ + const sector_t lc_end = exfat_cluster_to_sector(sbi, sbi->num_clusters); + /* + * Do the calculations in bytes because the blocksize may have been + * calibrated in exfat_calibrate_blocksize(), and bdev_nr_sectors() + * always reports in 512-byte units. + */ + const unsigned long long vol_size = EXFAT_BLK_TO_B(sbi->num_sectors, sb); + const unsigned long long bdev_size = (unsigned long long)bdev_nr_bytes(sb->s_bdev); + + if (sbi->num_sectors < (unsigned long long)lc_end) { + exfat_err(sb, "number of clusters out of volume length bounds : num_sectors=%llu, last_cluster_sector=%lld", + sbi->num_sectors, lc_end); + return -EINVAL; + } + + if (bdev_size < vol_size) { + exfat_err(sb, "volume length out of bounds : dev=%llu, vol=%lld", + bdev_size, vol_size); + return -EINVAL; + } + + return 0; +} + /* mount the file system volume */ static int __exfat_fill_super(struct super_block *sb, struct exfat_chain *root_clu) @@ -605,6 +643,12 @@ static int __exfat_fill_super(struct super_block *sb, goto free_bh; } + ret = exfat_check_volume_sizes(sb); + if (ret) { + exfat_warn(sb, "volume bounds check failed. Please run fsck"); + 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 From 16d0b0659e8ea2a7e1b683865f4666ed28704195 Mon Sep 17 00:00:00 2001 From: David Timber Date: Tue, 5 May 2026 21:28:07 +0900 Subject: [PATCH 2/3] exfat: print warning upon block size calibration If the block size specified in the exFAT volume boot sector is different from the actual logical block size of the device, many implementations including FUSE-exfat, macos and previous versions of Windows are not able to mount the volume. A possible scenario in which this can happen is when the user dd's the volume in a 4K-blocksize device("Advanced Format") to a 512-blocksize device. This is a design issue inherent to the exFAT format itself which layouts the structures of exFAT volumes aligned to the sector size rather than large byte sizes as seen with other modern file systems. Print a kind warning about this potential compatibility issue. Link: https://github.com/exfatprogs/exfatprogs/issues/349 Signed-off-by: David Timber --- fs/exfat/super.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/exfat/super.c b/fs/exfat/super.c index 588312caf1e59..722bfc8854589 100644 --- a/fs/exfat/super.c +++ b/fs/exfat/super.c @@ -409,6 +409,8 @@ static int exfat_calibrate_blocksize(struct super_block *sb, int logical_sect) } if (logical_sect > sb->s_blocksize) { + const unsigned long saved_bs = sb->s_blocksize; + brelse(sbi->boot_bh); sbi->boot_bh = NULL; @@ -423,6 +425,10 @@ static int exfat_calibrate_blocksize(struct super_block *sb, int logical_sect) sb->s_blocksize); return -EIO; } + + exfat_warn(sb, "blocksize calibrated from device logical block size(%lu) to volume sector size(%d)!\n" + "Other implementations may not be able to handle this volume.", + saved_bs, logical_sect); } return 0; } From d652158d4fb46cb14554548832f43252584cd170 Mon Sep 17 00:00:00 2001 From: David Timber Date: Tue, 5 May 2026 21:28:08 +0900 Subject: [PATCH 3/3] exfat: fix memory leak (upcase table) Fix memory leak conditions due to exfat_free_upcase_table() not being called. Signed-off-by: David Timber --- fs/exfat/super.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/exfat/super.c b/fs/exfat/super.c index 722bfc8854589..651e32b1e1c76 100644 --- a/fs/exfat/super.c +++ b/fs/exfat/super.c @@ -702,6 +702,7 @@ static int __exfat_fill_super(struct super_block *sb, exfat_free_bitmap(sbi); free_bh: brelse(sbi->boot_bh); + exfat_free_upcase_table(sbi); return ret; } @@ -797,6 +798,7 @@ static int exfat_get_tree(struct fs_context *fc) static void exfat_free_sbi(struct exfat_sb_info *sbi) { + exfat_free_upcase_table(sbi); exfat_free_iocharset(sbi); kfree(sbi); }