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 v0 1/2] exfat: add volume limit bounds checks
Date: Thu, 30 Apr 2026 01:08:23 +0900 [thread overview]
Message-ID: <20260429160824.409881-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 | 47 +++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 45 insertions(+), 2 deletions(-)
diff --git a/fs/exfat/super.c b/fs/exfat/super.c
index 95d87e2d7717..075130fd992a 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,33 @@ static int exfat_verify_boot_region(struct super_block *sb)
return 0;
}
+static int exfat_check_volume_sizes(struct super_block *sb)
+{
+ struct exfat_sb_info *sbi = EXFAT_SB(sb);
+ /* sector to start of last cluster */
+ const sector_t lc_start = exfat_cluster_to_sector(sbi, sbi->num_clusters - 1);
+ const sector_t lc_end = lc_start + sbi->sect_per_clus;
+ const sector_t last_sector = lc_end - 1;
+ struct buffer_head *bh;
+
+ /* Volume length(metadata in boot sector) bounds */
+ if (sbi->num_sectors < (unsigned long long)lc_end) {
+ exfat_err(sb, "number of cluster out of volume length bounds : %llu < %lld",
+ sbi->num_sectors, lc_end);
+ return -EINVAL;
+ }
+
+ /* Actually try reading the last sector to see if it's within the blockdev bounds */
+ bh = sb_bread(sb, last_sector);
+ if (bh == NULL) {
+ exfat_err(sb, "last sector read failed : %lld", last_sector);
+ return -EIO;
+ }
+ brelse(bh);
+
+ return 0;
+}
+
/* mount the file system volume */
static int __exfat_fill_super(struct super_block *sb,
struct exfat_chain *root_clu)
@@ -610,6 +647,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-04-29 16:08 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-29 16:08 David Timber [this message]
2026-04-29 16:08 ` [PATCH v0 2/2] exfat: print warning upon block size calibration David Timber
2026-04-30 7:52 ` [PATCH v0 1/2] exfat: add volume limit bounds checks Yuezhang.Mo
2026-04-30 9: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=20260429160824.409881-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