// 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" struct z_erofs_maprecorder { struct erofs_mount *em; struct erofs_node *en; 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 int z_erofs_read_index(struct z_erofs_maprecorder *m, uint64_t pos, size_t len, void **bufp) { return (erofs_read_metadata(m->em, m->en->nid, pos, len, bufp)); } static int z_erofs_load_full_lcluster(struct z_erofs_maprecorder *m, uint64_t lcn) { struct erofs_node *en; struct z_erofs_lcluster_index *di; uint64_t base, pos; unsigned int advise; void *buf; int error; en = m->en; base = en->inode_off + en->inode_isize + en->xattr_isize; if (base < en->inode_off) return (EOVERFLOW); base = Z_EROFS_FULL_INDEX_START(base); if (lcn > (UINT64_MAX - base) / sizeof(*di)) return (EOVERFLOW); pos = base + lcn * sizeof(*di); error = z_erofs_read_index(m, pos, sizeof(*di), &buf); if (error != 0) return (error); di = buf; m->lcn = lcn; m->nextpackoff = pos + sizeof(*di); 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 << en->z_lclusterbits; m->delta[0] = le16toh(di->di_u.delta[0]); if ((m->delta[0] & Z_EROFS_LI_D0_CBLKCNT) != 0) { if ((en->z_advise & (Z_EROFS_ADVISE_BIG_PCLUSTER_1 | Z_EROFS_ADVISE_BIG_PCLUSTER_2)) == 0) { erofs_brelse(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_brelse(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_node *en; uint64_t ebase, pos, totalidx, original_lcn; unsigned int compacted_4b_initial, compacted_2b, amortizedshift; unsigned int vcnt, lo, lobits, encodebits, nblk, bytes, packsize; bool big_pcluster; uint8_t *in, type; void *buf; int distance, error, i; en = m->en; ebase = Z_EROFS_MAP_HEADER_END(en->inode_off + en->inode_isize + en->xattr_isize); totalidx = roundup2(en->size, 1ULL << en->z_lclusterbits) >> en->z_lclusterbits; if (lcn >= totalidx || en->z_lclusterbits > 14) return (EINVAL); original_lcn = lcn; m->lcn = lcn; compacted_4b_initial = ((32 - ebase % 32) / 4) & 7; compacted_2b = 0; if ((en->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) { pos += compacted_4b_initial * 4; lcn -= compacted_4b_initial; if (lcn < compacted_2b) { amortizedshift = 1; } else { pos += compacted_2b * 2; lcn -= compacted_2b; } } pos += lcn << amortizedshift; if (amortizedshift == 2 && en->z_lclusterbits <= 14) vcnt = 2; else if (amortizedshift == 1 && en->z_lclusterbits <= 12) vcnt = 16; else return (EOPNOTSUPP); packsize = vcnt << amortizedshift; bytes = pos & (packsize - 1); pos -= bytes; error = z_erofs_read_index(m, pos, packsize, &buf); if (error != 0) return (error); in = buf; m->nextpackoff = pos + packsize; lobits = MAX(en->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 << en->z_lclusterbits; if (lookahead) { distance = get_compacted_la_distance(lobits, encodebits, vcnt, in, i); if (distance < 0) { erofs_brelse(buf); return (EINTEGRITY); } m->delta[1] = distance; } big_pcluster = (en->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_1) != 0; if ((lo & Z_EROFS_LI_D0_CBLKCNT) != 0) { if (!big_pcluster) { erofs_brelse(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_brelse(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_brelse(buf); return (m->delta[0] == 0 ? EINTEGRITY : 0); } m->clusterofs = lo; m->delta[0] = 0; big_pcluster = (en->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) { if (i == 0) { erofs_brelse(buf); return (EINTEGRITY); } --i; nblk += lo & ~Z_EROFS_LI_D0_CBLKCNT; continue; } if (lo <= 1) { erofs_brelse(buf); return (EINTEGRITY); } i -= lo - 2; continue; } ++nblk; } } m->pblk = le32dec(in + packsize - sizeof(uint32_t)) + nblk; erofs_brelse(buf); 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->en->datalayout == EROFS_INODE_COMPRESSED_COMPACT) error = z_erofs_load_compact_lcluster(m, lcn, lookahead); else if (m->en->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->en->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; m->map->m_la = (lcn << m->en->z_lclusterbits) | m->clusterofs; return (0); } return (EINTEGRITY); } static int z_erofs_get_extent_compressedlen(struct z_erofs_maprecorder *m, uint64_t initial_lcn) { struct erofs_node *en; bool bigpcl1, bigpcl2; uint64_t lcn; int error; en = m->en; bigpcl1 = (en->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_1) != 0; bigpcl2 = (en->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_2) != 0; lcn = m->lcn + 1; if ((m->headtype == Z_EROFS_LCLUSTER_TYPE_HEAD1 && !bigpcl1) || ((m->headtype == Z_EROFS_LCLUSTER_TYPE_PLAIN || m->headtype == Z_EROFS_LCLUSTER_TYPE_HEAD2) && !bigpcl2) || (lcn << en->z_lclusterbits) >= en->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 (m->compressedblks > (UINT64_MAX >> m->em->block_bits)) return (EOVERFLOW); m->map->m_plen = m->compressedblks << m->em->block_bits; (void)initial_lcn; return (0); } static int z_erofs_get_extent_decompressedlen(struct z_erofs_maprecorder *m) { struct erofs_node *en; struct erofs_map_blocks *map; uint64_t lcn, headlcn; int error; en = m->en; map = m->map; lcn = m->lcn; headlcn = map->m_la >> en->z_lclusterbits; for (;;) { if ((lcn << en->z_lclusterbits) >= en->size) { map->m_llen = en->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 (lcn > UINT64_MAX - m->delta[1]) return (EOVERFLOW); lcn += m->delta[1]; } map->m_llen = (lcn << en->z_lclusterbits) + m->clusterofs - map->m_la; return (0); } static int z_erofs_map_blocks_fo(struct erofs_mount *em, struct erofs_node *en, struct erofs_map_blocks *map, int flags) { bool fragment, ztailpacking; struct z_erofs_maprecorder m; uint64_t initial_lcn, ofs, end; unsigned int endoff; int error; fragment = (en->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0; ztailpacking = en->z_idata_size != 0; bzero(&m, sizeof(m)); m.em = em; m.en = en; m.map = map; if (en->size == 0) { map->m_la = 0; map->m_llen = 0; map->m_flags = 0; return (0); } ofs = (flags & EROFS_GET_BLOCKS_FINDTAIL) != 0 ? en->size - 1 : map->m_la; if (fragment && (flags & EROFS_GET_BLOCKS_FINDTAIL) == 0 && en->z_tailextent_headlcn == 0) { map->m_la = 0; map->m_llen = en->size; map->m_flags = EROFS_MAP_FRAGMENT; return (0); } initial_lcn = ofs >> en->z_lclusterbits; endoff = ofs & ((1U << en->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) en->z_fragmentoff = m.nextpackoff; map->m_flags = EROFS_MAP_MAPPED | EROFS_MAP_PARTIAL_MAPPED; end = (m.lcn + 1) << en->z_lclusterbits; if (m.type != Z_EROFS_LCLUSTER_TYPE_NONHEAD && endoff >= m.clusterofs) { m.headtype = m.type; map->m_la = (m.lcn << en->z_lclusterbits) | m.clusterofs; if (ztailpacking && end > en->size) end = en->size; } else { if (m.type != Z_EROFS_LCLUSTER_TYPE_NONHEAD) { end = (m.lcn << en->z_lclusterbits) | m.clusterofs; 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) { en->z_tailextent_headlcn = m.lcn; if (fragment && en->datalayout == EROFS_INODE_COMPRESSED_FULL) en->z_fragmentoff |= m.pblk << 32; } if (ztailpacking && m.lcn == en->z_tailextent_headlcn) { map->m_flags |= EROFS_MAP_META; map->m_pa = en->z_fragmentoff; map->m_plen = en->z_idata_size; if ((map->m_pa & (em->block_size - 1)) + map->m_plen > em->block_size) return (EINTEGRITY); } else if (fragment && m.lcn == en->z_tailextent_headlcn) { map->m_flags = EROFS_MAP_FRAGMENT; } else { if (m.pblk > (UINT64_MAX >> em->block_bits)) return (EOVERFLOW); map->m_pa = m.pblk << em->block_bits; 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 = (en->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 = en->z_algorithmtype[1]; } else { map->m_algorithmformat = en->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 >= em->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_read_extent(struct erofs_mount *em, struct erofs_node *en, uint64_t pos, unsigned int recsz, struct z_erofs_extent *ext) { void *buf; int error; bzero(ext, sizeof(*ext)); error = erofs_read_metadata(em, en->nid, pos, recsz, &buf); if (error != 0) return (error); memcpy(ext, buf, recsz); erofs_brelse(buf); 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_node *en, unsigned int recsz, uint64_t *result) { uint64_t pos; if (z_erofs_extent_add(en->inode_off, en->inode_isize, &pos) != 0 || z_erofs_extent_add(pos, en->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_node *en, uint64_t table_pos, unsigned int recsz, uint64_t index, uint64_t *result) { uint64_t offset; if (index >= en->z_extents || __builtin_mul_overflow(index, recsz, &offset) || z_erofs_extent_add(table_pos, offset, result) != 0) return (EINTEGRITY); 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_mount *em, struct erofs_node *en, unsigned int recsz) { struct z_erofs_extent ext; uint64_t extent_pos, index, last_pos, lstart, previous; int error; if (en->z_extents == 0) return (en->size == 0 ? 0 : EINTEGRITY); error = z_erofs_extent_table_pos(en, 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(en, extent_pos, recsz, en->z_extents - 1, &last_pos); if (error != 0 || z_erofs_extent_add(last_pos, recsz, &last_pos) != 0) return (EINTEGRITY); if (recsz <= offsetof(struct z_erofs_extent, pstart_hi)) return (0); if (en->size == 0) return (EINTEGRITY); previous = 0; for (index = 0; index < en->z_extents; index++) { error = z_erofs_extent_record_pos(en, extent_pos, recsz, index, &last_pos); if (error != 0) return (error); error = z_erofs_read_extent(em, en, last_pos, recsz, &ext); if (error != 0) return (error); lstart = z_erofs_extent_lstart(&ext, recsz); if (lstart >= en->size || (index != 0 && lstart <= previous)) return (EINTEGRITY); previous = lstart; } return (0); } static int z_erofs_map_blocks_ext(struct erofs_mount *em, struct erofs_node *en, struct erofs_map_blocks *map, int flags) { struct z_erofs_extent ext; unsigned int recsz, bmask, fmt; uint64_t cluster_size, extent_idx, extent_pos, next, pos, rounded_lend; uint64_t lend, l, r, mid, pa, la, lstart, table_pos; bool interlaced, last; void *buf; int error; (void)flags; interlaced = (en->z_advise & Z_EROFS_ADVISE_INTERLACED_PCLUSTER) != 0; recsz = z_erofs_extent_recsize(en->z_advise); error = z_erofs_extent_table_pos(en, recsz, &table_pos); if (error != 0) return (error); pos = table_pos; bmask = em->block_size - 1; lend = en->size; cluster_size = 1ULL << en->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(em, en->nid, pos, sizeof(uint64_t), &buf); if (error != 0) return (error); pa = le64dec(buf); erofs_brelse(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 >> en->z_lclusterbits; pa = EROFS_NULL_ADDR; } for (;;) { error = z_erofs_extent_record_pos(en, pos, recsz, extent_idx, &extent_pos); if (error != 0) return (error); error = z_erofs_read_extent(em, en, 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 = en->z_extents; l < r;) { mid = l + (r - l) / 2; error = z_erofs_extent_record_pos(en, table_pos, recsz, mid, &extent_pos); if (error != 0) return (error); error = z_erofs_read_extent(em, en, 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 >= en->z_extents; } if (lstart < lend) { map->m_la = lstart; if (last && (en->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0) { map->m_flags = EROFS_MAP_FRAGMENT; en->z_fragmentoff = map->m_plen; if (recsz > offsetof(struct z_erofs_extent, pstart_lo)) en->z_fragmentoff |= map->m_pa << 32; } else if ((map->m_plen & Z_EROFS_EXTENT_PLEN_MASK) != 0) { map->m_flags = EROFS_MAP_MAPPED; fmt = 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 = 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_mount *em, struct erofs_node *en) { struct z_erofs_map_header *h; struct erofs_map_blocks map; uint64_t cluster_size, raw, pos, rounded_size; unsigned int recsz; void *buf; int error; if (en->z_initialized) return (0); if (z_erofs_extent_add(en->inode_off, en->inode_isize, &pos) != 0 || z_erofs_extent_add(pos, en->xattr_isize, &pos) != 0 || z_erofs_extent_roundup(pos, 8, &pos) != 0) return (EINTEGRITY); error = erofs_read_metadata(em, en->nid, pos, sizeof(*h), &buf); if (error != 0) return (error); h = buf; if ((h->h_clusterbits & (1U << Z_EROFS_FRAGMENT_INODE_BIT)) != 0) { if (!erofs_sb_has_fragments(em) || em->packed_nid == 0) { erofs_brelse(buf); return (EINTEGRITY); } raw = le64dec(h); en->z_advise = Z_EROFS_ADVISE_FRAGMENT_PCLUSTER; en->z_fragmentoff = raw ^ (1ULL << 63); en->z_tailextent_headlcn = 0; en->fragment = true; erofs_brelse(buf); en->z_initialized = true; return (0); } en->z_advise = le16toh(h->h_advise); en->z_lclusterbits = em->block_bits + (h->h_clusterbits & 15); if (en->z_lclusterbits >= 31) { erofs_brelse(buf); return (EINTEGRITY); } if (en->datalayout == EROFS_INODE_COMPRESSED_FULL && (en->z_advise & Z_EROFS_ADVISE_EXTENTS) != 0) { recsz = z_erofs_extent_recsize(en->z_advise); if (recsz <= offsetof(struct z_erofs_extent, pstart_hi)) { cluster_size = 1ULL << en->z_lclusterbits; if (z_erofs_extent_roundup(en->size, cluster_size, &rounded_size) != 0) { erofs_brelse(buf); return (EINTEGRITY); } en->z_extents = rounded_size >> en->z_lclusterbits; } else { en->z_extents = le32toh(h->h_extents_lo) | ((uint64_t)le16toh(h->h_extents_hi) << 32); } en->fragment = (en->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0; erofs_brelse(buf); if (en->fragment && (!erofs_sb_has_fragments(em) || em->packed_nid == 0)) return (EINTEGRITY); if (recsz > offsetof(struct z_erofs_extent, pstart_hi) && en->z_extents == 0 && en->size != 0) return (EINTEGRITY); error = z_erofs_validate_extent_table(em, en, recsz); if (error != 0) return (error); en->z_initialized = true; return (0); } en->z_algorithmtype[0] = h->h_algorithmtype & 15; en->z_algorithmtype[1] = h->h_algorithmtype >> 4; if ((en->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0) en->z_fragmentoff = le32toh(h->h_fragmentoff); else if ((en->z_advise & Z_EROFS_ADVISE_INLINE_PCLUSTER) != 0) en->z_idata_size = le16toh(h->h_idata_size); erofs_brelse(buf); if (!erofs_sb_has_big_pcluster(em) && (en->z_advise & (Z_EROFS_ADVISE_BIG_PCLUSTER_1 | Z_EROFS_ADVISE_BIG_PCLUSTER_2)) != 0) return (EINTEGRITY); if (en->datalayout == EROFS_INODE_COMPRESSED_COMPACT && (((en->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_1) != 0) != ((en->z_advise & Z_EROFS_ADVISE_BIG_PCLUSTER_2) != 0))) return (EINTEGRITY); if (en->z_idata_size != 0 || (en->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0) { if ((en->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0 && (!erofs_sb_has_fragments(em) || em->packed_nid == 0)) return (EINTEGRITY); bzero(&map, sizeof(map)); error = z_erofs_map_blocks_fo(em, en, &map, EROFS_GET_BLOCKS_FINDTAIL); if (error != 0) return (error); } en->fragment = (en->z_advise & Z_EROFS_ADVISE_FRAGMENT_PCLUSTER) != 0; en->z_initialized = true; return (0); } static int z_erofs_map_sanity_check(struct erofs_mount *em, struct erofs_node *en, struct erofs_map_blocks *map) { uint64_t pend; if ((map->m_flags & EROFS_MAP_FRAGMENT) != 0) { if ((map->m_flags & (EROFS_MAP_MAPPED | EROFS_MAP_META)) != 0 || em->packed_inode == NULL || em->packed_inode->nid == en->nid || en->z_fragmentoff > em->packed_inode->size || map->m_llen > em->packed_inode->size - en->z_fragmentoff) return (EINTEGRITY); return (0); } if ((map->m_flags & EROFS_MAP_MAPPED) == 0) return (0); if ((unsigned char)map->m_algorithmformat >= Z_EROFS_COMPRESSION_RUNTIME_MAX) return (EOPNOTSUPP); if (map->m_algorithmformat < Z_EROFS_COMPRESSION_MAX) { if ((em->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 (__builtin_add_overflow(map->m_pa, map->m_plen, &pend)) return (EINTEGRITY); if ((pend >> em->block_bits) >= (1ULL << 48)) return (EINTEGRITY); (void)en; return (0); } int z_erofs_map_blocks_iter(struct erofs_mount *em, struct erofs_node *en, struct erofs_map_blocks *map, int flags) { int error; if (map->m_la >= en->size) { map->m_llen = map->m_la + 1 - en->size; map->m_la = en->size; map->m_flags = 0; return (0); } error = z_erofs_fill_inode(em, en); if (error == 0) { if (en->datalayout == EROFS_INODE_COMPRESSED_FULL && (en->z_advise & Z_EROFS_ADVISE_EXTENTS) != 0) error = z_erofs_map_blocks_ext(em, en, map, flags); else error = z_erofs_map_blocks_fo(em, en, map, flags); } if (error == 0) error = z_erofs_map_sanity_check(em, en, map); if (error != 0) map->m_llen = 0; return (error); }