// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2018-2019 HUAWEI, Inc. * https://www.huawei.com/ */ #include #include #include #include #include #include "internal.h" #define Z_EROFS_EXTENT_VALIDATE_CHUNK_SIZE (64 * 1024) struct z_erofs_maprecorder { struct erofs_sb_info *sbi; struct erofs_inode *vi; struct erofs_map_blocks *map; uint64_t lcn; uint8_t type; uint8_t headtype; unsigned int clusterofs; uint16_t delta[2]; erofs_blk_t pblk; erofs_blk_t compressedblks; erofs_off_t nextpackoff; bool partialref; }; _Static_assert((EROFS_MAP_MAPPED | EROFS_MAP_META | EROFS_MAP_PARTIAL_MAPPED | EROFS_MAP_PARTIAL_REF | EROFS_MAP_FRAGMENT) == 0x1f, "map adapter must preserve every map flag"); static int z_erofs_index_base(const struct erofs_inode *vi, erofs_off_t *base) { erofs_off_t end; if (__builtin_add_overflow(vi->inode_off, (erofs_off_t)vi->inode_isize, &end) || __builtin_add_overflow(end, (erofs_off_t)vi->xattr_isize, &end) || __builtin_add_overflow(end, (erofs_off_t)7, &end)) return (EINTEGRITY); end = rounddown2(end, 8); if (__builtin_add_overflow(end, (erofs_off_t)sizeof(struct z_erofs_map_header), base)) return (EINTEGRITY); return (0); } static int z_erofs_index_advance(erofs_off_t *pos, uint64_t count, uint64_t unit) { erofs_off_t delta, next; if (__builtin_mul_overflow(count, unit, &delta) || __builtin_add_overflow(*pos, delta, &next)) return (EINTEGRITY); *pos = next; return (0); } static int z_erofs_lcluster_count(uint64_t size, unsigned int lclusterbits, uint64_t *count) { uint64_t mask; if (lclusterbits >= 64) return (EINTEGRITY); mask = (1ULL << lclusterbits) - 1; *count = size >> lclusterbits; if ((size & mask) != 0 && __builtin_add_overflow(*count, 1, count)) return (EINTEGRITY); return (0); } static int z_erofs_lcluster_pos(uint64_t lcn, unsigned int lclusterbits, uint64_t clusterofs, uint64_t *pos) { uint64_t base, clustersize; if (lclusterbits >= 64) return (EINTEGRITY); clustersize = 1ULL << lclusterbits; if (__builtin_mul_overflow(lcn, clustersize, &base) || __builtin_add_overflow(base, clusterofs, pos)) return (EINTEGRITY); return (0); } static int z_erofs_lcn_advance(uint64_t *lcn, uint64_t delta) { uint64_t next; if (__builtin_add_overflow(*lcn, delta, &next)) return (EINTEGRITY); *lcn = next; return (0); } static int z_erofs_compact_pblk(uint32_t base, unsigned int nblk, erofs_blk_t *pblk) { if (__builtin_add_overflow((erofs_blk_t)base, (erofs_blk_t)nblk, pblk)) return (EINTEGRITY); return (0); } static int z_erofs_fragment_offset(uint64_t low, erofs_blk_t high, erofs_off_t *offset) { if (low > UINT32_MAX || high > UINT32_MAX) return (EINTEGRITY); *offset = low | (high << 32); return (0); } static int z_erofs_post_eof_len(uint64_t la, uint64_t size, uint64_t *len) { if (la < size) return (EINTEGRITY); *len = la - size; if (__builtin_add_overflow(*len, 1, len)) *len = UINT64_MAX; return (0); } static int z_erofs_physical_end(uint64_t pa, uint64_t plen, uint64_t block_size) { uint64_t limit, pend; if (__builtin_add_overflow(pa, plen, &pend)) return (EINTEGRITY); if (__builtin_mul_overflow(1ULL << 48, block_size, &limit)) return (0); if (pend > limit) return (EINTEGRITY); return (0); } static int z_erofs_read_index(struct z_erofs_maprecorder *m, erofs_off_t pos, size_t len, struct erofs_buf *buf) { return (erofs_read_metadata(m->sbi, m->vi->nid, pos, len, buf)); } static int z_erofs_load_full_lcluster(struct z_erofs_maprecorder *m, uint64_t lcn) { struct erofs_buf buf = EROFS_BUF_INITIALIZER; struct erofs_inode *vi; struct z_erofs_lcluster_index *di; erofs_off_t base, pos; unsigned int advise; int error; vi = m->vi; if (z_erofs_index_base(vi, &base) != 0 || z_erofs_index_advance(&base, 1, 8) != 0) return (EINTEGRITY); pos = base; if (z_erofs_index_advance(&pos, lcn, sizeof(*di)) != 0) return (EINTEGRITY); m->nextpackoff = pos; if (z_erofs_index_advance(&m->nextpackoff, 1, sizeof(*di)) != 0) return (EINTEGRITY); error = z_erofs_read_index(m, pos, sizeof(*di), &buf); if (error != 0) return (error); di = buf.data; m->lcn = lcn; advise = le16toh(di->di_advise); m->type = advise & Z_EROFS_LI_LCLUSTER_TYPE_MASK; if (m->type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) { m->clusterofs = 1U << vi->z_lclusterbits; m->delta[0] = le16toh(di->di_u.delta[0]); if ((m->delta[0] & Z_EROFS_LI_D0_CBLKCNT) != 0) { if ((vi->z_advise & (Z_EROFS_ADVISE_BIG_PCLUSTER_1 | Z_EROFS_ADVISE_BIG_PCLUSTER_2)) == 0) { erofs_put_metabuf(&buf); return (EINTEGRITY); } m->compressedblks = m->delta[0] & ~Z_EROFS_LI_D0_CBLKCNT; m->delta[0] = 1; } m->delta[1] = le16toh(di->di_u.delta[1]); } else { m->partialref = (advise & Z_EROFS_LI_PARTIAL_REF) != 0; m->clusterofs = le16toh(di->di_clusterofs); m->pblk = le32toh(di->di_u.blkaddr); } erofs_put_metabuf(&buf); return (0); } static unsigned int decode_compactedbits(unsigned int lobits, const uint8_t *in, unsigned int pos, uint8_t *type) { uint32_t value; unsigned int lo; value = le32dec(in + pos / 8) >> (pos & 7); lo = value & ((1U << lobits) - 1); *type = (value >> lobits) & 3; return (lo); } static int get_compacted_la_distance(unsigned int lobits, unsigned int encodebits, unsigned int vcnt, const uint8_t *in, int i) { unsigned int lo, distance; uint8_t type; distance = 0; do { lo = decode_compactedbits(lobits, in, encodebits * i, &type); if (type != Z_EROFS_LCLUSTER_TYPE_NONHEAD) return (distance); ++distance; } while (++i < (int)vcnt); if ((lo & Z_EROFS_LI_D0_CBLKCNT) == 0) { if (lo == 0) return (-1); distance += lo - 1; } return ((int)distance); } static int z_erofs_load_compact_lcluster(struct z_erofs_maprecorder *m, uint64_t lcn, bool lookahead) { struct erofs_buf buf = EROFS_BUF_INITIALIZER; struct erofs_inode *vi; erofs_off_t ebase, pos; uint64_t compacted_2b, totalidx, original_lcn; unsigned int compacted_4b_initial, amortizedshift; unsigned int vcnt, lo, lobits, encodebits, nblk, bytes, packsize; bool big_pcluster; uint8_t *in, type; int distance, error, i; vi = m->vi; if (vi->z_lclusterbits > 14 || z_erofs_index_base(vi, &ebase) != 0 || z_erofs_lcluster_count(vi->size, vi->z_lclusterbits, &totalidx) != 0) return (EINTEGRITY); if (lcn >= totalidx) return (EINVAL); original_lcn = lcn; m->lcn = lcn; compacted_4b_initial = ((32 - ebase % 32) / 4) & 7; compacted_2b = 0; if ((vi->z_advise & Z_EROFS_ADVISE_COMPACTED_2B) != 0 && compacted_4b_initial < totalidx) compacted_2b = rounddown2(totalidx - compacted_4b_initial, 16); pos = ebase; amortizedshift = 2; if (lcn >= compacted_4b_initial) { if (z_erofs_index_advance(&pos, compacted_4b_initial, 4) != 0) return (EINTEGRITY); lcn -= compacted_4b_initial; if (lcn < compacted_2b) { amortizedshift = 1; } else { if (z_erofs_index_advance(&pos, compacted_2b, 2) != 0) return (EINTEGRITY); lcn -= compacted_2b; } } if (z_erofs_index_advance(&pos, lcn, 1U << amortizedshift) != 0) return (EINTEGRITY); if (amortizedshift == 2 && vi->z_lclusterbits <= 14) vcnt = 2; else if (amortizedshift == 1 && vi->z_lclusterbits <= 12) vcnt = 16; else return (EOPNOTSUPP); packsize = vcnt << amortizedshift; bytes = pos & (packsize - 1); pos -= bytes; m->nextpackoff = pos; if (z_erofs_index_advance(&m->nextpackoff, 1, packsize) != 0) return (EINTEGRITY); error = z_erofs_read_index(m, pos, packsize, &buf); if (error != 0) return (error); in = buf.data; lobits = MAX(vi->z_lclusterbits, fls(Z_EROFS_LI_D0_CBLKCNT)); encodebits = (packsize - sizeof(uint32_t)) * 8 / vcnt; i = bytes >> amortizedshift; lo = decode_compactedbits(lobits, in, encodebits * i, &type); m->type = type; if (type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) { m->clusterofs = 1U << vi->z_lclusterbits; if (lookahead) { distance = get_compacted_la_distance(lobits, encodebits, vcnt, in, i); if (distance < 0) { erofs_put_metabuf(&buf); return (EINTEGRITY); } m->delta[1] = distance; } big_pcluster = (vi->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_1) != 0; if ((lo & Z_EROFS_LI_D0_CBLKCNT) != 0) { if (!big_pcluster) { erofs_put_metabuf(&buf); return (EINTEGRITY); } m->compressedblks = lo & ~Z_EROFS_LI_D0_CBLKCNT; m->delta[0] = 1; } else if (i + 1 != (int)vcnt) { m->delta[0] = lo; } else { if (i == 0) { erofs_put_metabuf(&buf); return (EINTEGRITY); } lo = decode_compactedbits(lobits, in, encodebits * (i - 1), &type); if (type != Z_EROFS_LCLUSTER_TYPE_NONHEAD) lo = 0; else if ((lo & Z_EROFS_LI_D0_CBLKCNT) != 0) lo = 1; m->delta[0] = lo + 1; } erofs_put_metabuf(&buf); return (m->delta[0] == 0 ? EINTEGRITY : 0); } m->clusterofs = lo; m->delta[0] = 0; big_pcluster = (vi->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_1) != 0; if (!big_pcluster) { nblk = 1; while (i > 0) { --i; lo = decode_compactedbits(lobits, in, encodebits * i, &type); if (type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) i -= lo; if (i >= 0) ++nblk; } } else { nblk = 0; while (i > 0) { --i; lo = decode_compactedbits(lobits, in, encodebits * i, &type); if (type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) { if ((lo & Z_EROFS_LI_D0_CBLKCNT) != 0) { --i; nblk += lo & ~Z_EROFS_LI_D0_CBLKCNT; continue; } if (lo <= 1) { erofs_put_metabuf(&buf); return (EINTEGRITY); } i -= lo - 2; continue; } ++nblk; } } error = z_erofs_compact_pblk( le32dec(in + packsize - sizeof(uint32_t)), nblk, &m->pblk); erofs_put_metabuf(&buf); if (error != 0) return (error); m->lcn = original_lcn; return (0); } static int z_erofs_load_lcluster_from_disk(struct z_erofs_maprecorder *m, uint64_t lcn, bool lookahead) { int error; if (m->vi->datalayout == EROFS_INODE_COMPRESSED_COMPACT) error = z_erofs_load_compact_lcluster(m, lcn, lookahead); else if (m->vi->datalayout == EROFS_INODE_COMPRESSED_FULL) error = z_erofs_load_full_lcluster(m, lcn); else return (EINTEGRITY); if (error != 0) return (error); if (m->type >= Z_EROFS_LCLUSTER_TYPE_MAX) return (EOPNOTSUPP); if (m->type != Z_EROFS_LCLUSTER_TYPE_NONHEAD && m->clusterofs >= (1U << m->vi->z_lclusterbits)) return (EINTEGRITY); return (0); } static int z_erofs_extent_lookback(struct z_erofs_maprecorder *m, unsigned int lookback_distance) { uint64_t lcn; int error; while (lookback_distance != 0 && m->lcn >= lookback_distance) { lcn = m->lcn - lookback_distance; error = z_erofs_load_lcluster_from_disk(m, lcn, false); if (error != 0) return (error); if (m->type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) { lookback_distance = m->delta[0]; continue; } m->headtype = m->type; error = z_erofs_lcluster_pos(lcn, m->vi->z_lclusterbits, m->clusterofs, &m->map->m_la); if (error != 0) return (error); return (0); } return (EINTEGRITY); } static int z_erofs_get_extent_compressedlen(struct z_erofs_maprecorder *m, uint64_t initial_lcn) { struct erofs_inode *vi; bool bigpcl1, bigpcl2; uint64_t lcn, lcnpos; int error; vi = m->vi; bigpcl1 = (vi->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_1) != 0; bigpcl2 = (vi->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_2) != 0; lcn = m->lcn; if (z_erofs_lcn_advance(&lcn, 1) != 0 || z_erofs_lcluster_pos(lcn, vi->z_lclusterbits, 0, &lcnpos) != 0) return (EINTEGRITY); if ((m->headtype == Z_EROFS_LCLUSTER_TYPE_HEAD1 && !bigpcl1) || ((m->headtype == Z_EROFS_LCLUSTER_TYPE_PLAIN || m->headtype == Z_EROFS_LCLUSTER_TYPE_HEAD2) && !bigpcl2) || lcnpos >= vi->size) m->compressedblks = 1; if (m->compressedblks == 0) { error = z_erofs_load_lcluster_from_disk(m, lcn, false); if (error != 0) return (error); if (m->type == Z_EROFS_LCLUSTER_TYPE_NONHEAD && m->delta[0] != 1) return (EINTEGRITY); if (m->type != Z_EROFS_LCLUSTER_TYPE_NONHEAD || m->compressedblks == 0) m->compressedblks = 1; } if (__builtin_mul_overflow(m->compressedblks, (uint64_t)m->sbi->block_size, &m->map->m_plen)) return (EINTEGRITY); (void)initial_lcn; return (0); } static int z_erofs_get_extent_decompressedlen(struct z_erofs_maprecorder *m) { struct erofs_inode *vi; struct erofs_map_blocks *map; uint64_t lcn, headlcn, lend, lcnpos; int error; vi = m->vi; map = m->map; lcn = m->lcn; headlcn = map->m_la >> vi->z_lclusterbits; for (;;) { if (z_erofs_lcluster_pos(lcn, vi->z_lclusterbits, 0, &lcnpos) != 0) return (EINTEGRITY); if (lcnpos >= vi->size) { if (map->m_la > vi->size) return (EINTEGRITY); map->m_llen = vi->size - map->m_la; return (0); } error = z_erofs_load_lcluster_from_disk(m, lcn, true); if (error != 0) return (error); if (m->type == Z_EROFS_LCLUSTER_TYPE_NONHEAD) { if (m->delta[1] == 0) m->delta[1] = 1; } else { if (lcn != headlcn) break; m->delta[1] = 1; } if (z_erofs_lcn_advance(&lcn, m->delta[1]) != 0) return (EINTEGRITY); } if (z_erofs_lcluster_pos(lcn, vi->z_lclusterbits, m->clusterofs, &lend) != 0 || lend < map->m_la) return (EINTEGRITY); map->m_llen = lend - map->m_la; return (0); } static int z_erofs_extent_add(uint64_t left, uint64_t right, uint64_t *result) { if (__builtin_add_overflow(left, right, result)) return (EINTEGRITY); return (0); } static int z_erofs_extent_roundup(uint64_t value, unsigned int alignment, uint64_t *result) { uint64_t rounded; if (z_erofs_extent_add(value, alignment - 1, &rounded) != 0) return (EINTEGRITY); *result = rounddown2(rounded, alignment); return (0); } static int z_erofs_extent_table_pos(const struct erofs_inode *vi, unsigned int recsz, erofs_off_t *result) { erofs_off_t pos; if (z_erofs_extent_add(vi->inode_off, vi->inode_isize, &pos) != 0 || z_erofs_extent_add(pos, vi->xattr_isize, &pos) != 0 || z_erofs_extent_roundup(pos, 8, &pos) != 0 || z_erofs_extent_add(pos, sizeof(struct z_erofs_map_header), &pos) != 0 || z_erofs_extent_roundup(pos, recsz, result) != 0) return (EINTEGRITY); return (0); } static int z_erofs_extent_record_pos(const struct erofs_inode *vi, erofs_off_t table_pos, unsigned int recsz, uint64_t index, erofs_off_t *result) { uint64_t offset; if (index >= vi->z_extents || __builtin_mul_overflow(index, recsz, &offset) || z_erofs_extent_add(table_pos, offset, result) != 0) return (EINTEGRITY); return (0); } static int z_erofs_read_extent(struct erofs_sb_info *sbi, struct erofs_inode *vi, erofs_off_t pos, unsigned int recsz, struct z_erofs_extent *ext) { struct erofs_buf buf = EROFS_BUF_INITIALIZER; int error; bzero(ext, sizeof(*ext)); error = erofs_read_metadata(sbi, vi->nid, pos, recsz, &buf); if (error != 0) return (error); memcpy(ext, buf.data, recsz); erofs_put_metabuf(&buf); return (0); } static uint64_t z_erofs_extent_lstart(const struct z_erofs_extent *ext, unsigned int recsz) { uint64_t lstart; lstart = le32toh(ext->lstart_lo); if (recsz > offsetof(struct z_erofs_extent, lstart_hi)) lstart |= (uint64_t)le32toh(ext->lstart_hi) << 32; return (lstart); } static int z_erofs_validate_extent_table(struct erofs_sb_info *sbi, struct erofs_inode *vi, unsigned int recsz) { struct erofs_buf buf = EROFS_BUF_INITIALIZER; struct z_erofs_extent ext; erofs_off_t extent_pos, record_pos, scan_pos, table_end; uint64_t index, lstart, previous; size_t chunk_len, offset; int error; if (vi->z_extents == 0) return (vi->size == 0 ? 0 : EINTEGRITY); error = z_erofs_extent_table_pos(vi, recsz, &extent_pos); if (error != 0) return (error); if (recsz <= offsetof(struct z_erofs_extent, pstart_lo) && z_erofs_extent_add(extent_pos, sizeof(uint64_t), &extent_pos) != 0) return (EINTEGRITY); error = z_erofs_extent_record_pos(vi, extent_pos, recsz, vi->z_extents - 1, &record_pos); if (error != 0 || z_erofs_extent_add(record_pos, recsz, &table_end) != 0) return (EINTEGRITY); if (recsz <= offsetof(struct z_erofs_extent, pstart_hi)) return (z_erofs_read_extent(sbi, vi, record_pos, recsz, &ext)); if (vi->size == 0) return (EINTEGRITY); scan_pos = extent_pos; index = 0; previous = 0; while (scan_pos < table_end) { chunk_len = (size_t)MIN(table_end - scan_pos, (uint64_t)Z_EROFS_EXTENT_VALIDATE_CHUNK_SIZE); error = erofs_read_metadata(sbi, vi->nid, scan_pos, chunk_len, &buf); if (error != 0) return (error); for (offset = 0; offset < chunk_len; offset += recsz, index++) { bzero(&ext, sizeof(ext)); memcpy(&ext, (const char *)buf.data + offset, recsz); lstart = z_erofs_extent_lstart(&ext, recsz); if (lstart >= vi->size || (index != 0 && lstart <= previous)) { error = EINTEGRITY; goto out; } previous = lstart; } erofs_put_metabuf(&buf); scan_pos += chunk_len; } return (index == vi->z_extents ? 0 : EINTEGRITY); out: erofs_put_metabuf(&buf); return (error); } static int z_erofs_map_blocks_fo(struct erofs_sb_info *sbi, struct erofs_inode *vi, struct erofs_map_blocks *map, int flags) { bool fragment, ztailpacking; struct z_erofs_maprecorder m; uint64_t initial_lcn, ofs, end, end_lcn; erofs_off_t fragmentoff; unsigned int endoff; int error; fragment = (vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0; ztailpacking = vi->z_idata_size != 0; bzero(&m, sizeof(m)); m.sbi = sbi; m.vi = vi; m.map = map; if (vi->size == 0) { map->m_la = 0; map->m_llen = 0; map->m_flags = 0; return (0); } ofs = (flags & EROFS_GET_BLOCKS_FINDTAIL) != 0 ? vi->size - 1 : map->m_la; if (fragment && (flags & EROFS_GET_BLOCKS_FINDTAIL) == 0 && vi->z_tailextent_headlcn == 0) { map->m_la = 0; map->m_llen = vi->size; map->m_flags = EROFS_MAP_FRAGMENT; return (0); } initial_lcn = ofs >> vi->z_lclusterbits; endoff = ofs & ((1U << vi->z_lclusterbits) - 1); error = z_erofs_load_lcluster_from_disk(&m, initial_lcn, false); if (error != 0) return (error); if ((flags & EROFS_GET_BLOCKS_FINDTAIL) != 0 && ztailpacking) vi->z_fragmentoff = m.nextpackoff; map->m_flags = EROFS_MAP_MAPPED | EROFS_MAP_PARTIAL_MAPPED; end_lcn = m.lcn; if (z_erofs_lcn_advance(&end_lcn, 1) != 0 || z_erofs_lcluster_pos(end_lcn, vi->z_lclusterbits, 0, &end) != 0) return (EINTEGRITY); if (m.type != Z_EROFS_LCLUSTER_TYPE_NONHEAD && endoff >= m.clusterofs) { m.headtype = m.type; if (z_erofs_lcluster_pos(m.lcn, vi->z_lclusterbits, m.clusterofs, &map->m_la) != 0) return (EINTEGRITY); if (ztailpacking && end > vi->size) end = vi->size; } else { if (m.type != Z_EROFS_LCLUSTER_TYPE_NONHEAD) { if (z_erofs_lcluster_pos(m.lcn, vi->z_lclusterbits, m.clusterofs, &end) != 0) return (EINTEGRITY); map->m_flags &= ~EROFS_MAP_PARTIAL_MAPPED; m.delta[0] = 1; } error = z_erofs_extent_lookback(&m, m.delta[0]); if (error != 0) return (error); } if (m.partialref) map->m_flags |= EROFS_MAP_PARTIAL_REF; if (end < map->m_la) return (EINTEGRITY); map->m_llen = end - map->m_la; if ((flags & EROFS_GET_BLOCKS_FINDTAIL) != 0) { vi->z_tailextent_headlcn = m.lcn; if (fragment && vi->datalayout == EROFS_INODE_COMPRESSED_FULL) { error = z_erofs_fragment_offset(vi->z_fragmentoff, m.pblk, &fragmentoff); if (error != 0) return (error); vi->z_fragmentoff = fragmentoff; } } if (ztailpacking && m.lcn == vi->z_tailextent_headlcn) { map->m_flags |= EROFS_MAP_META; map->m_pa = vi->z_fragmentoff; map->m_plen = vi->z_idata_size; if ((map->m_pa & (sbi->block_size - 1)) + map->m_plen > sbi->block_size) return (EINTEGRITY); } else if (fragment && m.lcn == vi->z_tailextent_headlcn) { map->m_flags = EROFS_MAP_FRAGMENT; } else { if (__builtin_mul_overflow(m.pblk, (erofs_blk_t)sbi->block_size, &map->m_pa)) return (EINTEGRITY); error = z_erofs_get_extent_compressedlen(&m, initial_lcn); if (error != 0) return (error); } if (m.headtype == Z_EROFS_LCLUSTER_TYPE_PLAIN) { map->m_algorithmformat = (vi->z_advise & Z_EROFS_ADVISE_INTERLACED_PCLUSTER) != 0 ? Z_EROFS_COMPRESSION_INTERLACED : Z_EROFS_COMPRESSION_SHIFTED; } else if (m.headtype == Z_EROFS_LCLUSTER_TYPE_HEAD2) { map->m_algorithmformat = vi->z_algorithmtype[1]; } else { map->m_algorithmformat = vi->z_algorithmtype[0]; } if ((flags & EROFS_GET_BLOCKS_FIEMAP) != 0 || ((flags & EROFS_GET_BLOCKS_READMORE) != 0 && (map->m_algorithmformat == Z_EROFS_COMPRESSION_LZMA || map->m_algorithmformat == Z_EROFS_COMPRESSION_DEFLATE || map->m_algorithmformat == Z_EROFS_COMPRESSION_ZSTD) && map->m_llen >= sbi->block_size)) { error = z_erofs_get_extent_decompressedlen(&m); if (error == 0) map->m_flags &= ~EROFS_MAP_PARTIAL_MAPPED; return (error); } return (0); } static int z_erofs_map_blocks_ext(struct erofs_sb_info *sbi, struct erofs_inode *vi, struct erofs_map_blocks *map, int flags) { struct erofs_buf buf = EROFS_BUF_INITIALIZER; struct z_erofs_extent ext; unsigned int recsz, bmask; uint8_t fmt; uint64_t cluster_size, extent_idx, next, rounded_lend; erofs_off_t extent_pos, pos, table_pos; uint64_t fragmentoff, lend, l, r, mid, pa, la, lstart; bool interlaced, last; int error; (void)flags; interlaced = (vi->z_advise & Z_EROFS_ADVISE_INTERLACED_PCLUSTER) != 0; recsz = z_erofs_extent_recsize(vi->z_advise); error = z_erofs_extent_table_pos(vi, recsz, &table_pos); if (error != 0) return (error); pos = table_pos; bmask = sbi->block_size - 1; lend = vi->size; cluster_size = 1ULL << vi->z_lclusterbits; map->m_flags = 0; if (recsz <= offsetof(struct z_erofs_extent, pstart_hi)) { if (recsz <= offsetof(struct z_erofs_extent, pstart_lo)) { error = erofs_read_metadata(sbi, vi->nid, pos, sizeof(uint64_t), &buf); if (error != 0) return (error); pa = le64dec(buf.data); erofs_put_metabuf(&buf); if (z_erofs_extent_add(pos, sizeof(uint64_t), &pos) != 0) return (EINTEGRITY); lstart = 0; extent_idx = 0; } else { lstart = rounddown2(map->m_la, cluster_size); extent_idx = lstart >> vi->z_lclusterbits; pa = EROFS_NULL_ADDR; } for (;;) { error = z_erofs_extent_record_pos(vi, pos, recsz, extent_idx, &extent_pos); if (error != 0) return (error); error = z_erofs_read_extent(sbi, vi, extent_pos, recsz, &ext); if (error != 0) return (error); map->m_plen = le32toh(ext.plen); if (pa != EROFS_NULL_ADDR) { map->m_pa = pa; if (z_erofs_extent_add(pa, map->m_plen & Z_EROFS_EXTENT_PLEN_MASK, &next) != 0) return (EINTEGRITY); pa = next; } else { map->m_pa = le32toh(ext.pstart_lo); } if (extent_idx == UINT64_MAX) return (EINTEGRITY); extent_idx++; if (z_erofs_extent_add(lstart, cluster_size, &next) != 0) return (EINTEGRITY); lstart = next; if (lstart > map->m_la) break; } if (z_erofs_extent_roundup(lend, cluster_size, &rounded_lend) != 0) return (EINTEGRITY); last = lstart >= rounded_lend; lend = MIN(lstart, lend); lstart -= cluster_size; } else { lstart = lend; for (l = 0, r = vi->z_extents; l < r;) { mid = l + (r - l) / 2; error = z_erofs_extent_record_pos(vi, table_pos, recsz, mid, &extent_pos); if (error != 0) return (error); error = z_erofs_read_extent(sbi, vi, extent_pos, recsz, &ext); if (error != 0) return (error); la = z_erofs_extent_lstart(&ext, recsz); pa = le32toh(ext.pstart_lo) | ((uint64_t)le32toh(ext.pstart_hi) << 32); if (la > map->m_la) { r = mid; if (la > lend) return (EINTEGRITY); lend = la; } else { l = mid + 1; if (map->m_la == la) r = MIN(l + 1, r); lstart = la; map->m_plen = le32toh(ext.plen); map->m_pa = pa; } } last = l >= vi->z_extents; } if (lstart < lend) { map->m_la = lstart; if (last && (vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0) { map->m_flags = EROFS_MAP_FRAGMENT; if (recsz > offsetof(struct z_erofs_extent, pstart_lo)) { error = z_erofs_fragment_offset(map->m_plen, map->m_pa, &fragmentoff); if (error != 0) return (error); vi->z_fragmentoff = fragmentoff; } else { vi->z_fragmentoff = map->m_plen; } } else if ((map->m_plen & Z_EROFS_EXTENT_PLEN_MASK) != 0) { map->m_flags = EROFS_MAP_MAPPED; if ((map->m_plen >> Z_EROFS_EXTENT_PLEN_FMT_BIT) > UINT8_MAX) return (EINTEGRITY); fmt = (uint8_t)(map->m_plen >> Z_EROFS_EXTENT_PLEN_FMT_BIT); if ((map->m_plen & Z_EROFS_EXTENT_PLEN_PARTIAL) != 0) map->m_flags |= EROFS_MAP_PARTIAL_REF; map->m_plen &= Z_EROFS_EXTENT_PLEN_MASK; if (fmt != 0) map->m_algorithmformat = (uint8_t)(fmt - 1); else if (interlaced && ((map->m_pa | map->m_plen) & bmask) == 0) map->m_algorithmformat = Z_EROFS_COMPRESSION_INTERLACED; else map->m_algorithmformat = Z_EROFS_COMPRESSION_SHIFTED; } } map->m_llen = lend - map->m_la; return (0); } int z_erofs_fill_inode(struct erofs_sb_info *sbi, struct erofs_inode *vi) { struct erofs_buf buf = EROFS_BUF_INITIALIZER; struct z_erofs_map_header *h; struct erofs_map_blocks map; uint64_t cluster_size, raw, rounded_size; erofs_off_t pos; unsigned int recsz; int error; if (vi->z_initialized) return (0); if (z_erofs_extent_add(vi->inode_off, vi->inode_isize, &pos) != 0 || z_erofs_extent_add(pos, vi->xattr_isize, &pos) != 0 || z_erofs_extent_roundup(pos, 8, &pos) != 0) return (EINTEGRITY); error = erofs_read_metadata(sbi, vi->nid, pos, sizeof(*h), &buf); if (error != 0) return (error); h = buf.data; if ((h->h_clusterbits & (1U << Z_EROFS_FRAGMENT_INODE_BIT)) != 0) { if (!erofs_sb_has_fragments(sbi) || sbi->packed_nid == 0) { erofs_put_metabuf(&buf); return (EINTEGRITY); } raw = le64dec(h); vi->z_advise = Z_EROFS_ADVISE_FRAGMENT_PCLUSTER; vi->z_fragmentoff = raw ^ (1ULL << 63); vi->z_tailextent_headlcn = 0; vi->fragment = true; erofs_put_metabuf(&buf); vi->z_initialized = true; return (0); } vi->z_advise = le16toh(h->h_advise); vi->z_lclusterbits = sbi->blkszbits + (h->h_clusterbits & 15); if (vi->z_lclusterbits >= 31) { erofs_put_metabuf(&buf); return (EINTEGRITY); } if (vi->datalayout == EROFS_INODE_COMPRESSED_FULL && (vi->z_advise & Z_EROFS_ADVISE_EXTENTS) != 0) { recsz = z_erofs_extent_recsize(vi->z_advise); if (recsz <= offsetof(struct z_erofs_extent, pstart_hi)) { cluster_size = 1ULL << vi->z_lclusterbits; if (z_erofs_extent_roundup(vi->size, cluster_size, &rounded_size) != 0) { erofs_put_metabuf(&buf); return (EINTEGRITY); } vi->z_extents = rounded_size >> vi->z_lclusterbits; } else { vi->z_extents = le32toh(h->h_extents_lo) | ((uint64_t)le16toh(h->h_extents_hi) << 32); } vi->fragment = (vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0; erofs_put_metabuf(&buf); if (vi->fragment && (!erofs_sb_has_fragments(sbi) || sbi->packed_nid == 0)) return (EINTEGRITY); if (recsz > offsetof(struct z_erofs_extent, pstart_hi) && vi->z_extents == 0 && vi->size != 0) return (EINTEGRITY); error = z_erofs_validate_extent_table(sbi, vi, recsz); if (error != 0) return (error); vi->z_initialized = true; return (0); } vi->z_algorithmtype[0] = (uint8_t)(h->h_algorithmtype & 15); vi->z_algorithmtype[1] = (uint8_t)(h->h_algorithmtype >> 4); if ((vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0) vi->z_fragmentoff = le32toh(h->h_fragmentoff); else if ((vi->z_advise & Z_EROFS_ADVISE_INLINE_PCLUSTER) != 0) vi->z_idata_size = le16toh(h->h_idata_size); erofs_put_metabuf(&buf); if (!erofs_sb_has_big_pcluster(sbi) && (vi->z_advise & (Z_EROFS_ADVISE_BIG_PCLUSTER_1 | Z_EROFS_ADVISE_BIG_PCLUSTER_2)) != 0) return (EINTEGRITY); if (vi->datalayout == EROFS_INODE_COMPRESSED_COMPACT && (((vi->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_1) != 0) != ((vi->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_2) != 0))) return (EINTEGRITY); if (vi->z_idata_size != 0 || (vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0) { if ((vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0 && (!erofs_sb_has_fragments(sbi) || sbi->packed_nid == 0)) return (EINTEGRITY); bzero(&map, sizeof(map)); error = z_erofs_map_blocks_fo(sbi, vi, &map, EROFS_GET_BLOCKS_FINDTAIL); if (error != 0) return (error); } vi->fragment = (vi->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0; vi->z_initialized = true; return (0); } static int z_erofs_map_sanity_check(struct erofs_sb_info *sbi, struct erofs_inode *vi, struct erofs_map_blocks *map) { if ((map->m_flags & EROFS_MAP_FRAGMENT) != 0) { if ((map->m_flags & (EROFS_MAP_MAPPED | EROFS_MAP_META)) != 0 || sbi->packed_inode == NULL || sbi->packed_inode->nid == vi->nid || vi->z_fragmentoff > sbi->packed_inode->size || map->m_llen > sbi->packed_inode->size - vi->z_fragmentoff) return (EINTEGRITY); return (0); } if ((map->m_flags & EROFS_MAP_MAPPED) == 0) return (0); if (map->m_algorithmformat >= Z_EROFS_COMPRESSION_RUNTIME_MAX) return (EOPNOTSUPP); if (map->m_algorithmformat < Z_EROFS_COMPRESSION_MAX) { if ((sbi->available_compr_algs & (1U << map->m_algorithmformat)) == 0) return (EINTEGRITY); if (EROFS_MAP_FULL(map->m_flags) && map->m_llen < map->m_plen) return (EINTEGRITY); } else if (map->m_llen > map->m_plen) { return (EINTEGRITY); } if (map->m_plen > Z_EROFS_PCLUSTER_MAX_SIZE || map->m_llen > Z_EROFS_PCLUSTER_MAX_DSIZE) return (EOPNOTSUPP); if ((map->m_flags & EROFS_MAP_META) != 0) return (0); if (z_erofs_physical_end(map->m_pa, map->m_plen, sbi->block_size) != 0) return (EINTEGRITY); (void)vi; return (0); } int z_erofs_map_blocks(struct erofs_sb_info *sbi, struct erofs_inode *vi, struct erofs_map_blocks *map) { int error; if (map->m_la >= vi->size) { error = z_erofs_post_eof_len(map->m_la, vi->size, &map->m_llen); if (error != 0) return (error); map->m_la = vi->size; map->m_flags = 0; return (0); } error = z_erofs_fill_inode(sbi, vi); if (error == 0) { if (vi->datalayout == EROFS_INODE_COMPRESSED_FULL && (vi->z_advise & Z_EROFS_ADVISE_EXTENTS) != 0) error = z_erofs_map_blocks_ext(sbi, vi, map, EROFS_GET_BLOCKS_FIEMAP); else error = z_erofs_map_blocks_fo(sbi, vi, map, EROFS_GET_BLOCKS_FIEMAP); } if (error == 0) error = z_erofs_map_sanity_check(sbi, vi, map); if (error != 0) map->m_llen = 0; return (error); }