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


Reply via email to