erofs-utils: fsck: don't allocate/read too large extents
authorGao Xiang <hsiangkao@linux.alibaba.com>
Fri, 2 Jun 2023 03:05:19 +0000 (11:05 +0800)
committerGao Xiang <hsiangkao@linux.alibaba.com>
Fri, 2 Jun 2023 05:03:10 +0000 (13:03 +0800)
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
fsck/main.c

index ad4053727574f8544f3d9bdc87fac4b1f33bd05f..52fb7a0129b8614c679ba4581cf8a941b94eb6f3 100644 (file)
@@ -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)