From: David Timber <dxdt@dev.snart.me>
To: Namjae Jeon <linkinjeon@kernel.org>,
Sungjong Seo <sj1557.seo@samsung.com>,
Yuezhang Mo <yuezhang.mo@sony.com>
Cc: linux-fsdevel@vger.kernel.org, David Timber <dxdt@dev.snart.me>
Subject: [PATCH v1 1/3] exfat: add volume limit bounds checks
Date: Tue, 5 May 2026 21:28:06 +0900 [thread overview]
Message-ID: <20260505122808.728020-1-dxdt@dev.snart.me> (raw)
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 <dxdt@dev.snart.me>
---
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 95d87e2d7717..15e99766cb8c 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 <<
@@ -591,6 +601,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)
@@ -610,6 +648,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
--
2.53.0.1.ga224b40d3f.dirty
next reply other threads:[~2026-05-05 12:28 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-05 12:28 David Timber [this message]
2026-05-05 12:28 ` [PATCH v1 2/3] exfat: print warning upon block size calibration David Timber
2026-05-05 12:28 ` [PATCH v1 3/3] exfat: fix memory leak (upcase table) David Timber
2026-05-07 12:04 ` Yuezhang.Mo
2026-05-10 23:10 ` David Timber
2026-05-07 12:04 ` [PATCH v1 1/3] exfat: add volume limit bounds checks Yuezhang.Mo
2026-05-10 23:17 ` David Timber
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260505122808.728020-1-dxdt@dev.snart.me \
--to=dxdt@dev.snart.me \
--cc=linkinjeon@kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=sj1557.seo@samsung.com \
--cc=yuezhang.mo@sony.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox