[PATCH] erofs-utils: lib: fix potential shift UB in z_erofs lclusterbits handling

Utkal Singh singhutkal015 at gmail.com
Fri Mar 6 05:13:39 AEDT 2026


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 <singhutkal015 at gmail.com>
---
 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



More information about the Linux-erofs mailing list