[PATCH] adfs: validate zone map parameters and clamp dm_endbit in adfs_map_layout()

From: Hui Peng

Date: Sat Sep 19 2026 - 18:28:25 EST


In fs/adfs/map.c and fs/adfs/super.c, validate nzones and zone_spare on
mount to prevent divide-by-zero in adfs_map_lookup() and adfs_statfs(),
and clamp dm_endbit / map offsets to the underlying buffer_head block
size in adfs_map_layout(), lookup_zone(), and scan_free_map().

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@xxxxxxxxx>
---
diff --git a/fs/adfs/map.c b/fs/adfs/map.c
index 9d535a2ca2d1..6ca7ebf22388 100644
--- a/fs/adfs/map.c
+++ b/fs/adfs/map.c
@@ -68,17 +68,25 @@ static DEFINE_RWLOCK(adfs_map_lock);
static int lookup_zone(const struct adfs_discmap *dm, const unsigned int idlen,
const u32 frag_id, unsigned int *offset)
{
- const unsigned int endbit = dm->dm_endbit;
+ const unsigned int max_endbit = dm->dm_bh->b_size * 8;
+ const unsigned int endbit = min(dm->dm_endbit, max_endbit);
const u32 idmask = (1 << idlen) - 1;
unsigned char *map = dm->dm_bh->b_data;
unsigned int start = dm->dm_startbit;
- unsigned int freelink, fragend;
- u32 frag;
+ unsigned int freelink, fragend = 0;
+ u32 frag = 0;
+
+ if (8 + idlen >= endbit || (8 >> 3) + 4 > dm->dm_bh->b_size)
+ goto error;

frag = GET_FRAG_ID(map, 8, idmask & 0x7fff);
freelink = frag ? 8 + frag : 0;

do {
+ if (start + idlen >= endbit ||
+ (start >> 3) + 4 > dm->dm_bh->b_size)
+ goto error;
+
frag = GET_FRAG_ID(map, start, idmask);

fragend = find_next_bit_le(map, endbit, start + idlen);
@@ -114,15 +122,19 @@ static int lookup_zone(const struct adfs_discmap *dm, const unsigned int idlen,
static unsigned int
scan_free_map(struct adfs_sb_info *asb, struct adfs_discmap *dm)
{
- const unsigned int endbit = dm->dm_endbit;
+ const unsigned int max_endbit = dm->dm_bh->b_size * 8;
+ const unsigned int endbit = min(dm->dm_endbit, max_endbit);
const unsigned int idlen = asb->s_idlen;
const unsigned int frag_idlen = idlen <= 15 ? idlen : 15;
const u32 idmask = (1 << frag_idlen) - 1;
unsigned char *map = dm->dm_bh->b_data;
- unsigned int start = 8, fragend;
+ unsigned int start = 8, fragend = 0;
u32 frag;
unsigned long total = 0;

+ if (start + idlen >= endbit || (start >> 3) + 4 > dm->dm_bh->b_size)
+ goto error;
+
/*
* get fragment id
*/
@@ -137,6 +149,9 @@ scan_free_map(struct adfs_sb_info *asb, struct adfs_discmap *dm)

do {
start += frag;
+ if (start + idlen >= endbit ||
+ (start >> 3) + 4 > dm->dm_bh->b_size)
+ goto error;

frag = GET_FRAG_ID(map, start, idmask);

@@ -310,9 +325,12 @@ static void adfs_map_layout(struct adfs_discmap *dm, unsigned int nzones,
struct adfs_discrecord *dr)
{
unsigned int zone, zone_size;
+ unsigned int max_endbit = 8U << dr->log2secsize;
u64 size;

- zone_size = (8 << dr->log2secsize) - le16_to_cpu(dr->zone_spare);
+ zone_size = max_endbit - le16_to_cpu(dr->zone_spare);
+ if (32 + zone_size > max_endbit)
+ zone_size = max_endbit - 32;

dm[0].dm_bh = NULL;
dm[0].dm_startblk = 0;
@@ -327,7 +345,13 @@ static void adfs_map_layout(struct adfs_discmap *dm, unsigned int nzones,
}

size = adfs_disc_size(dr) >> dr->log2bpmb;
- size -= (nzones - 1) * zone_size - ADFS_DR_SIZE_BITS;
+ size += ADFS_DR_SIZE_BITS;
+ if (size > (u64)(nzones - 1) * zone_size)
+ size -= (u64)(nzones - 1) * zone_size;
+ else
+ size = 0;
+ if (size > zone_size)
+ size = zone_size;
dm[nzones - 1].dm_endbit = 32 + size;
}

diff --git a/fs/adfs/super.c b/fs/adfs/super.c
index 888aa81a6b39..5b570a4769da 100644
--- a/fs/adfs/super.c
+++ b/fs/adfs/super.c
@@ -87,6 +87,16 @@ static int adfs_checkdiscrecord(struct adfs_discrecord *dr)
if (dr->unused52[i] != 0)
return 1;

+ /* At least one zone and one allocation ID per zone are required */
+ if (!dr->nzones && !dr->nzones_high)
+ return 1;
+ if (le16_to_cpu(dr->zone_spare) < 32 ||
+ le16_to_cpu(dr->zone_spare) >= (8U << dr->log2secsize))
+ return 1;
+ if (((1U << dr->log2secsize) * 8 - le16_to_cpu(dr->zone_spare)) <
+ (dr->idlen + 1))
+ return 1;
+
return 0;
}

@@ -181,7 +191,8 @@ static int adfs_statfs(struct dentry *dentry, struct kstatfs *buf)
buf->f_type = ADFS_SUPER_MAGIC;
buf->f_namelen = sbi->s_namelen;
buf->f_bsize = sb->s_blocksize;
- buf->f_ffree = (long)(buf->f_bfree * buf->f_files) / (long)buf->f_blocks;
+ buf->f_ffree = buf->f_blocks ?
+ (long)(buf->f_bfree * buf->f_files) / (long)buf->f_blocks : 0;
buf->f_fsid = u64_to_fsid(id);

return 0;