z_lclusterbits is computed from on-disk h_clusterbits without an upper bound check: blkszbits + (h_clusterbits & 15). While blkszbits is validated in erofs_read_superblock(), EROFS_MAX_BLOCK_SIZE can be overridden at compile time (e.g. 64K pages on ARM64), making blkszbits up to 16 and z_lclusterbits up to 31.
Several places in z_erofs_map_blocks_ext() and related functions use '1 << lclusterbits' where the literal 1 has type int (32-bit signed). Per C11 6.5.7p4, left-shifting a signed positive value such that the result is not representable is undefined behavior. Fix this by: - Adding a validation check rejecting z_lclusterbits > 30 as filesystem corruption, since no valid EROFS image uses logical clusters larger than 1 GiB. - Changing all '1 << lclusterbits' to '1U << lclusterbits' to use unsigned arithmetic, matching the kernel EROFS driver style. This hardens the z_erofs metadata parsing path against crafted images. Signed-off-by: Utkal Singh <[email protected]> --- lib/zmap.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/zmap.c b/lib/zmap.c index 0e7af4e..42e982d 100644 --- a/lib/zmap.c +++ b/lib/zmap.c @@ -45,7 +45,7 @@ static int z_erofs_load_full_lcluster(struct z_erofs_maprecorder *m, advise = le16_to_cpu(di->di_advise); m->type = advise & Z_EROFS_LI_LCLUSTER_TYPE_MASK; if (m->type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) { - m->clusterofs = 1 << vi->z_lclusterbits; + m->clusterofs = 1U << vi->z_lclusterbits; m->delta[0] = le16_to_cpu(di->di_u.delta[0]); if (m->delta[0] & Z_EROFS_LI_D0_CBLKCNT) { if (!(vi->z_advise & (Z_EROFS_ADVISE_BIG_PCLUSTER_1 | @@ -60,7 +60,7 @@ static int z_erofs_load_full_lcluster(struct z_erofs_maprecorder *m, } else { m->partialref = !!(advise & Z_EROFS_LI_PARTIAL_REF); m->clusterofs = le16_to_cpu(di->di_clusterofs); - if (m->clusterofs >= 1 << vi->z_lclusterbits) { + if (m->clusterofs >= 1U << vi->z_lclusterbits) { DBG_BUGON(1); return -EFSCORRUPTED; } @@ -168,7 +168,7 @@ static int z_erofs_load_compact_lcluster(struct z_erofs_maprecorder *m, lo = decode_compactedbits(lobits, in, encodebits * i, &type); m->type = type; if (type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) { - m->clusterofs = 1 << lclusterbits; + m->clusterofs = 1U << lclusterbits; /* figure out lookahead_distance: delta[1] if needed */ if (lookahead) @@ -423,7 +423,7 @@ static int z_erofs_map_blocks_fo(struct erofs_inode *vi, return 0; } initial_lcn = ofs >> lclusterbits; - endoff = ofs & ((1 << lclusterbits) - 1); + endoff = ofs & ((1U << lclusterbits) - 1); err = z_erofs_load_lcluster_from_disk(&m, initial_lcn, false); if (err) @@ -561,12 +561,12 @@ static int z_erofs_map_blocks_ext(struct erofs_inode *vi, pos += sizeof(__le64); lstart = 0; } else { - lstart = round_down(map->m_la, 1 << vi->z_lclusterbits); + lstart = round_down(map->m_la, 1U << vi->z_lclusterbits); pos += (lstart >> vi->z_lclusterbits) * recsz; pa = EROFS_NULL_ADDR; } - for (; lstart <= map->m_la; lstart += 1 << vi->z_lclusterbits) { + for (; lstart <= map->m_la; lstart += 1U << vi->z_lclusterbits) { ext = erofs_read_metabuf(&map->buf, sbi, pos, in_mbox); if (IS_ERR(ext)) return PTR_ERR(ext); @@ -579,9 +579,9 @@ static int z_erofs_map_blocks_ext(struct erofs_inode *vi, } pos += recsz; } - last = (lstart >= round_up(lend, 1 << vi->z_lclusterbits)); + last = (lstart >= round_up(lend, 1U << vi->z_lclusterbits)); lend = min(lstart, lend); - lstart -= 1 << vi->z_lclusterbits; + lstart -= 1U << vi->z_lclusterbits; } else { lstart = lend; for (l = 0, r = vi->z_extents; l < r; ) { @@ -673,6 +673,13 @@ static int z_erofs_fill_inode_lazy(struct erofs_inode *vi) vi->z_advise = le16_to_cpu(h->h_advise); vi->z_lclusterbits = sbi->blkszbits + (h->h_clusterbits & 15); + + if (vi->z_lclusterbits > 30) { + erofs_err("invalid lclusterbits %u of nid %llu", + vi->z_lclusterbits, vi->nid | 0ULL); + err = -EFSCORRUPTED; + goto out_put_metabuf; + } if (vi->datalayout == EROFS_INODE_COMPRESSED_FULL && (vi->z_advise & Z_EROFS_ADVISE_EXTENTS)) { vi->z_extents = le32_to_cpu(h->h_extents_lo) | -- 2.43.0
