From c769805c79d5acede65d96e5786aa5ebb46c01e0 Mon Sep 17 00:00:00 2001
From: Gao Xiang <hsiangkao@linux.alibaba.com>
Date: Fri, 2 Jun 2023 11:05:19 +0800
Subject: [PATCH 1/2] erofs-utils: fsck: don't allocate/read too large extents

Since some crafted EROFS filesystem images could have insane large
extents, which causes unexpected bahaviors when extracting data.

Fix it by extracting large extents with a buffer of a reasonable
maximum size limit and reading multiple times instead.

Note that only `--extract` option is impacted.

CVE: CVE-2023-33552
Closes: https://nvd.nist.gov/vuln/detail/CVE-2023-33552
Reported-by: Chaoming Yang <lometsj@live.com>
Fixes: 412c8f908132 ("erofs-utils: fsck: add --extract=X support to extract to path X")
Signed-off-by: Gao Xiang <hsiangkao@linux.alibaba.com>
Link: https://lore.kernel.org/r/20230602030519.117071-1-hsiangkao@linux.alibaba.com

Upstream-Status: Backport
Signed-off-by: Ross Burton <ross.burton@arm.com>
---
 fsck/main.c | 63 +++++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 49 insertions(+), 14 deletions(-)

diff --git a/fsck/main.c b/fsck/main.c
index 6b42252..6689ad8 100644
--- a/fsck/main.c
+++ b/fsck/main.c
@@ -392,6 +392,8 @@ static int erofs_verify_inode_data(struct erofs_inode *inode, int outfd)
 	}
 
 	while (pos < inode->i_size) {
+		unsigned int alloc_rawsize;
+
 		map.m_la = pos;
 		if (compressed)
 			ret = z_erofs_map_blocks_iter(inode, &map,
@@ -420,10 +422,28 @@ static int erofs_verify_inode_data(struct erofs_inode *inode, int outfd)
 		if (!(map.m_flags & EROFS_MAP_MAPPED) || !fsckcfg.check_decomp)
 			continue;
 
-		if (map.m_plen > raw_size) {
-			raw_size = map.m_plen;
-			raw = realloc(raw, raw_size);
-			BUG_ON(!raw);
+		if (map.m_plen > Z_EROFS_PCLUSTER_MAX_SIZE) {
+			if (compressed) {
+				erofs_err("invalid pcluster size %" PRIu64 " @ offset %" PRIu64 " of nid %" PRIu64,
+					  map.m_plen, map.m_la,
+					  inode->nid | 0ULL);
+				ret = -EFSCORRUPTED;
+				goto out;
+			}
+			alloc_rawsize = Z_EROFS_PCLUSTER_MAX_SIZE;
+		} else {
+			alloc_rawsize = map.m_plen;
+		}
+
+		if (alloc_rawsize > raw_size) {
+			char *newraw = realloc(raw, alloc_rawsize);
+
+			if (!newraw) {
+				ret = -ENOMEM;
+				goto out;
+			}
+			raw = newraw;
+			raw_size = alloc_rawsize;
 		}
 
 		if (compressed) {
@@ -434,18 +454,27 @@ static int erofs_verify_inode_data(struct erofs_inode *inode, int outfd)
 			}
 			ret = z_erofs_read_one_data(inode, &map, raw, buffer,
 						    0, map.m_llen, false);
+			if (ret)
+				goto out;
+
+			if (outfd >= 0 && write(outfd, buffer, map.m_llen) < 0)
+				goto fail_eio;
 		} else {
-			ret = erofs_read_one_data(&map, raw, 0, map.m_plen);
-		}
-		if (ret)
-			goto out;
+			u64 p = 0;
 
-		if (outfd >= 0 && write(outfd, compressed ? buffer : raw,
-					map.m_llen) < 0) {
-			erofs_err("I/O error occurred when verifying data chunk @ nid %llu",
-				  inode->nid | 0ULL);
-			ret = -EIO;
-			goto out;
+			do {
+				u64 count = min_t(u64, alloc_rawsize,
+						  map.m_llen);
+
+				ret = erofs_read_one_data(&map, raw, p, count);
+				if (ret)
+					goto out;
+
+				if (outfd >= 0 && write(outfd, raw, count) < 0)
+					goto fail_eio;
+				map.m_llen -= count;
+				p += count;
+			} while (map.m_llen);
 		}
 	}
 
@@ -460,6 +489,12 @@ out:
 	if (buffer)
 		free(buffer);
 	return ret < 0 ? ret : 0;
+
+fail_eio:
+	erofs_err("I/O error occurred when verifying data chunk @ nid %llu",
+		  inode->nid | 0ULL);
+	ret = -EIO;
+	goto out;
 }
 
 static inline int erofs_extract_dir(struct erofs_inode *inode)
-- 
2.34.1

