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 --- 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;