// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2019 HUAWEI, Inc. * https://www.huawei.com/ * Copyright (C) 2024 Alibaba Cloud */ #include #include #include #include #include #include "internal.h" static int z_erofs_load_lz4_config(struct erofs_mount *em, const struct erofs_super_block *dsb, const void *data, size_t size) { const struct z_erofs_lz4_cfgs *lz4; uint32_t max_pclusterblks; uint16_t distance; if (data != NULL) { if (size < sizeof(*lz4)) return (EINVAL); lz4 = data; distance = le16toh(lz4->max_distance); max_pclusterblks = le16toh(lz4->max_pclusterblks); if (max_pclusterblks == 0) max_pclusterblks = 1; else if (max_pclusterblks > (Z_EROFS_PCLUSTER_MAX_SIZE >> em->block_bits)) return (EINVAL); } else { distance = le16toh(dsb->u1.lz4_max_distance); if (distance == 0 && !erofs_sb_has_lz4_0padding(em)) return (0); max_pclusterblks = 1; em->available_compr_algs = 1U << Z_EROFS_COMPRESSION_LZ4; } em->lz4.max_pclusterblks = max_pclusterblks; em->lz4.max_distance_pages = distance != 0 ? howmany(distance, PAGE_SIZE) + 1 : howmany(UINT16_MAX, PAGE_SIZE) + 1; return (0); } static int z_erofs_read_cfg(struct erofs_mount *em, uint64_t *offset, void **bufp, size_t *sizep) { uint8_t length_buf[2]; uint64_t aligned; uint16_t length; void *buf; int error; aligned = roundup2(*offset, 4); if (aligned > UINT64_MAX - sizeof(length_buf)) return (EOVERFLOW); error = erofs_bread(em, aligned, sizeof(length_buf), &buf); if (error != 0) return (error); memcpy(length_buf, buf, sizeof(length_buf)); erofs_brelse(buf); length = le16dec(length_buf); *sizep = length != 0 ? length : UINT16_MAX + 1U; if (*sizep > 65536 || aligned + sizeof(length_buf) > UINT64_MAX - *sizep) return (EOVERFLOW); *offset = aligned + sizeof(length_buf); error = erofs_bread(em, *offset, *sizep, bufp); if (error == 0) *offset += *sizep; return (error); } int z_erofs_parse_cfgs(struct erofs_mount *em, const struct erofs_super_block *dsb) { uint64_t offset; uint16_t algorithms; void *data; size_t size; int algorithm, error; if (!erofs_sb_has_compr_cfgs(em)) return (z_erofs_load_lz4_config(em, dsb, NULL, 0)); algorithms = le16toh(dsb->u1.available_compr_algs); em->available_compr_algs = algorithms; if ((algorithms & ~Z_EROFS_ALL_COMPR_ALGS) != 0) return (EOPNOTSUPP); offset = EROFS_SUPER_OFFSET + em->sb_size; for (algorithm = 0; algorithm < Z_EROFS_COMPRESSION_MAX; ++algorithm) { if ((algorithms & (1U << algorithm)) == 0) continue; error = z_erofs_read_cfg(em, &offset, &data, &size); if (error != 0) return (error); switch (algorithm) { case Z_EROFS_COMPRESSION_LZ4: error = z_erofs_load_lz4_config(em, dsb, data, size); break; case Z_EROFS_COMPRESSION_LZMA: error = z_erofs_load_lzma_config(em, data, size); break; case Z_EROFS_COMPRESSION_DEFLATE: error = z_erofs_load_deflate_config(em, data, size); break; case Z_EROFS_COMPRESSION_ZSTD: error = z_erofs_load_zstd_config(em, data, size); break; default: error = EOPNOTSUPP; break; } erofs_brelse(data); if (error != 0) return (error); } return (0); } static int z_erofs_transform_plain(struct erofs_mount *em, const struct erofs_map_blocks *map, const uint8_t *src, size_t srclen, uint8_t *dst, size_t dstlen) { size_t first, offset; if (dstlen > srclen) return (EINTEGRITY); if (map->m_algorithmformat == Z_EROFS_COMPRESSION_SHIFTED) { memmove(dst, src, dstlen); return (0); } first = MIN((size_t)(em->block_size - (map->m_la & (em->block_size - 1))), dstlen); offset = (srclen - first) & (em->block_size - 1); if (offset > srclen || first > srclen - offset) return (EINTEGRITY); memmove(dst, src + offset, first); if (first < dstlen) memmove(dst + first, src, dstlen - first); return (0); } int z_erofs_decompress(struct erofs_mount *em, const struct erofs_map_blocks *map, const void *src0, size_t srclen, void *dst, size_t dstlen, bool partial) { const uint8_t *src; size_t padding, padding_limit; int ret; if (map->m_algorithmformat == Z_EROFS_COMPRESSION_SHIFTED || map->m_algorithmformat == Z_EROFS_COMPRESSION_INTERLACED) return (z_erofs_transform_plain(em, map, src0, srclen, dst, dstlen)); if ((unsigned char)map->m_algorithmformat >= Z_EROFS_COMPRESSION_MAX) return (EOPNOTSUPP); src = src0; if (map->m_algorithmformat != Z_EROFS_COMPRESSION_LZ4 || erofs_sb_has_lz4_0padding(em)) { padding_limit = MIN(srclen, em->block_size - (map->m_pa & (em->block_size - 1))); for (padding = 0; padding < padding_limit && src[padding] == 0; ++padding) ; if (padding == padding_limit) return (EINTEGRITY); src += padding; srclen -= padding; } switch (map->m_algorithmformat) { case Z_EROFS_COMPRESSION_LZ4: ret = lz4_decompress(__DECONST(void *, src), dst, srclen, dstlen, partial); break; case Z_EROFS_COMPRESSION_LZMA: if (em->lzma_dict_size == 0) return (EINTEGRITY); ret = lzma_decompress(src, srclen, dst, dstlen, em->lzma_dict_size, partial); break; case Z_EROFS_COMPRESSION_DEFLATE: ret = deflate_decompress(__DECONST(void *, src), srclen, dst, dstlen, em->deflate_windowbits, partial); break; case Z_EROFS_COMPRESSION_ZSTD: ret = zstd_decompress(__DECONST(void *, src), srclen, dst, dstlen, em->zstd_windowlog + 10, partial); break; default: return (EOPNOTSUPP); } return (ret == 0 ? 0 : EIO); }