// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2017-2018 HUAWEI, Inc. * https://www.huawei.com/ * Copyright (C) 2021, Alibaba Cloud */ #include #include #include #include #include #include #include #include #include #include #include "internal.h" #define EROFS_DIR_READAHEAD_BYTES (1024 * 1024) #define EROFS_DIR_READAHEAD_SLOTS (EROFS_DIR_READAHEAD_BYTES / PAGE_SIZE) /* * For flat inline files, compute the "inline tail" start offset. * Linux EROFS semantics: the last logical block may be tailpacked * into the inode metadata area. */ static erofs_off_t erofs_inline_tail_start(const struct erofs_sb_info *sbi, const struct erofs_inode *vi) { if (vi->size == 0) return (0); return (roundup2(vi->size, (uint64_t)sbi->block_size) - sbi->block_size); } static int erofs_check_device_range(const struct erofs_sb_info *sbi, const struct erofs_device_info *dif, erofs_off_t off, uint64_t len) { erofs_off_t end, limit; if (dif->blocks > (UINT64_MAX >> sbi->blkszbits) || __builtin_add_overflow(off, len, &end)) return (EINTEGRITY); limit = dif->blocks << sbi->blkszbits; return (end > limit ? EINTEGRITY : 0); } static void erofs_fill_from_devinfo(struct erofs_map_dev *map, struct erofs_device_info *dif, erofs_off_t pa) { map->m_dif = dif; map->m_pa = pa; } static int erofs_map_dev(struct erofs_sb_info *sbi, struct erofs_map_dev *map) { struct erofs_device_info *dif; uint64_t start; unsigned int id; int error; erofs_fill_from_devinfo(map, &sbi->dif0, map->m_pa); if (map->m_deviceid != 0) { if (map->m_deviceid > sbi->extra_devices || sbi->devs == NULL) return (ENODEV); dif = &sbi->devs[map->m_deviceid - 1]; error = erofs_check_device_range(sbi, dif, map->m_pa, map->m_plen); if (error != 0) return (error); if (sbi->flatdev) { if (dif->uniaddr > (UINT64_MAX >> sbi->blkszbits)) return (EINTEGRITY); start = dif->uniaddr << sbi->blkszbits; if (__builtin_add_overflow(map->m_pa, start, &map->m_pa)) return (EINTEGRITY); return (0); } if (dif->devvp == NULL || dif->cp == NULL) return (ENODEV); erofs_fill_from_devinfo(map, dif, map->m_pa); return (0); } if (sbi->extra_devices == 0) return (0); if (sbi->flatdev) { error = erofs_check_device_range(sbi, &sbi->dif0, map->m_pa, map->m_plen); if (error == 0) return (0); for (id = 0; id < sbi->extra_devices; ++id) { dif = &sbi->devs[id]; if (dif->uniaddr == 0 || dif->uniaddr > (UINT64_MAX >> sbi->blkszbits)) continue; start = dif->uniaddr << sbi->blkszbits; if (map->m_pa < start) continue; error = erofs_check_device_range(sbi, dif, map->m_pa - start, map->m_plen); if (error == 0) return (0); if (map->m_pa - start < (dif->blocks << sbi->blkszbits)) return (error); } return (EINTEGRITY); } for (id = 0; id < sbi->extra_devices; ++id) { dif = &sbi->devs[id]; if (dif->uniaddr == 0) continue; if (dif->uniaddr > (UINT64_MAX >> sbi->blkszbits)) return (EINTEGRITY); start = dif->uniaddr << sbi->blkszbits; if (map->m_pa >= start && map->m_pa - start < (dif->blocks << sbi->blkszbits)) { error = erofs_check_device_range(sbi, dif, map->m_pa - start, map->m_plen); if (error != 0) return (error); if (dif->devvp == NULL || dif->cp == NULL) return (ENODEV); erofs_fill_from_devinfo(map, dif, map->m_pa - start); break; } } return (0); } /* Map chunk-based file to physical extent */ static int erofs_map_blocks_chunk(struct erofs_sb_info *sbi, struct erofs_inode *vi, struct erofs_map_blocks *map) { struct erofs_inode_chunk_index *idx; struct erofs_buf buf = EROFS_BUF_INITIALIZER; uint64_t chunk_idx, chunk_size, entry_size; erofs_off_t loff, idx_off, chunk_off, idx_base; uint64_t image_size, addrmask; uint64_t blkaddr; uint16_t raw_device_id; int error; loff = map->m_la; chunk_size = 1ULL << vi->chunkbits; chunk_idx = loff >> vi->chunkbits; chunk_off = loff & (chunk_size - 1); if ((vi->chunkformat & EROFS_CHUNK_FORMAT_INDEXES) != 0) entry_size = sizeof(struct erofs_inode_chunk_index); else entry_size = EROFS_BLOCK_MAP_ENTRY_SIZE; if (vi->inode_off > UINT64_MAX - vi->inode_isize || vi->inode_off + vi->inode_isize > UINT64_MAX - vi->xattr_isize) return (EOVERFLOW); idx_base = vi->inode_off + vi->inode_isize + vi->xattr_isize; if (idx_base > UINT64_MAX - (entry_size - 1)) return (EOVERFLOW); idx_base = roundup2(idx_base, entry_size); if (chunk_idx > (UINT64_MAX - idx_base) / entry_size) return (EOVERFLOW); idx_off = idx_base + chunk_idx * entry_size; if (erofs_nid_in_metabox(vi->nid)) { if (sbi->metabox_en == NULL || idx_off > sbi->metabox_en->size || entry_size > sbi->metabox_en->size - idx_off) return (EINTEGRITY); } else { if (sbi->blocks > (UINT64_MAX >> sbi->blkszbits)) return (EOVERFLOW); image_size = sbi->blocks << sbi->blkszbits; if (idx_off > image_size || entry_size > image_size - idx_off) return (EINTEGRITY); } error = erofs_read_metadata(sbi, vi->nid, idx_off, entry_size, &buf); if (error != 0) return (error); idx = buf.data; if ((vi->chunkformat & EROFS_CHUNK_FORMAT_INDEXES) != 0) { blkaddr = le32toh(idx->startblk_lo); if ((vi->chunkformat & EROFS_CHUNK_FORMAT_48BIT) != 0) blkaddr |= (uint64_t)le16toh(idx->startblk_hi) << 32; raw_device_id = le16toh(idx->device_id); addrmask = (vi->chunkformat & EROFS_CHUNK_FORMAT_48BIT) != 0 ? ((1ULL << 48) - 1) : UINT32_MAX; } else { blkaddr = le32dec(idx); raw_device_id = 0; addrmask = UINT32_MAX; } erofs_put_metabuf(&buf); if (!((blkaddr ^ EROFS_NULL_ADDR) & addrmask)) { map->m_pa = 0; map->m_llen = MIN(chunk_size - chunk_off, vi->size - loff); map->m_plen = map->m_llen; return (0); } map->m_llen = MIN(chunk_size - chunk_off, vi->size - loff); map->m_plen = map->m_llen; map->m_deviceid = raw_device_id & sbi->device_id_mask; if (blkaddr > (UINT64_MAX >> sbi->blkszbits)) return (EOVERFLOW); map->m_pa = blkaddr << sbi->blkszbits; if (chunk_off > UINT64_MAX - map->m_pa) return (EOVERFLOW); map->m_pa += chunk_off; map->m_flags |= EROFS_MAP_MAPPED; return (0); } static int erofs_bread_device(struct erofs_sb_info *sbi, struct erofs_device_info *dif, erofs_blk_t blocks, erofs_off_t off, size_t len, daddr_t *rablkno, int *rabsize, int racnt, void **bufp) { struct buf *bp; erofs_off_t end, limit; off_t blkoff, current; size_t blklen, done, iosize; char *out; int error; if (bufp == NULL) return (EINVAL); *bufp = NULL; if (len == 0) { return (0); } if (dif == NULL || dif->devvp == NULL || dif->cp == NULL) return (ENODEV); if (__builtin_add_overflow(off, (uint64_t)len, &end)) return (EINTEGRITY); if (blocks != 0) { if (blocks > (UINT64_MAX >> sbi->blkszbits)) return (EINTEGRITY); limit = blocks << sbi->blkszbits; if (end > limit) return (EINTEGRITY); } if (end > dif->mediasize) return (ENXIO); if (off > INT64_MAX || end > (uint64_t)INT64_MAX + 1) return (EOVERFLOW); iosize = sbi->block_size != 0 ? sbi->block_size : dif->sectorsize; if (iosize == 0 || (iosize & (iosize - 1)) != 0) return (EINVAL); out = malloc(len, M_EROFS, M_WAITOK); done = 0; while (done < len) { current = (off_t)(off + done); blkoff = rounddown2(current, (off_t)iosize); blklen = MIN(iosize - (size_t)(current - blkoff), len - done); if (done == 0 && racnt != 0) error = breadn(dif->devvp, btodb(blkoff), iosize, rablkno, rabsize, racnt, NOCRED, &bp); else error = bread(dif->devvp, btodb(blkoff), iosize, NOCRED, &bp); if (error != 0) { free(out, M_EROFS); return (error); } if (bp->b_data == NULL) { brelse(bp); free(out, M_EROFS); return (EIO); } memcpy(out + done, (char *)bp->b_data + (current - blkoff), blklen); brelse(bp); done += blklen; } *bufp = out; return (0); } int erofs_bread(struct erofs_sb_info *sbi, erofs_off_t off, size_t len, void **bufp) { return (erofs_bread_device(sbi, &sbi->dif0, sbi->dif0.blocks, off, len, NULL, NULL, 0, bufp)); } int erofs_read_physical(struct erofs_sb_info *sbi, unsigned int device_id, erofs_off_t off, size_t len, void **bufp) { struct erofs_map_dev map; erofs_blk_t blocks; int error; map = (struct erofs_map_dev) { .m_pa = off, .m_deviceid = device_id, .m_plen = len, }; error = erofs_map_dev(sbi, &map); if (error != 0) return (error); blocks = map.m_dif->blocks; if (map.m_dif == &sbi->dif0 && sbi->flatdev) blocks = sbi->flatdev_blocks; return (erofs_bread_device(sbi, map.m_dif, blocks, map.m_pa, len, NULL, NULL, 0, bufp)); } static int erofs_read_physical_readahead(struct erofs_sb_info *sbi, unsigned int device_id, erofs_off_t off, size_t len, unsigned int rablocks, void **bufp) { struct erofs_map_dev current, future; daddr_t rablkno[EROFS_DIR_READAHEAD_SLOTS]; int rabsize[EROFS_DIR_READAHEAD_SLOTS]; erofs_off_t step; erofs_blk_t blocks; unsigned int count; int error; current = (struct erofs_map_dev) { .m_pa = off, .m_deviceid = device_id, .m_plen = len, }; error = erofs_map_dev(sbi, ¤t); if (error != 0) return (error); blocks = current.m_dif->blocks; if (current.m_dif == &sbi->dif0 && sbi->flatdev) blocks = sbi->flatdev_blocks; rablocks = MIN(rablocks, (unsigned int)nitems(rablkno)); for (count = 0; count < rablocks; count++) { step = (erofs_off_t)(count + 1) * sbi->block_size; if (off > UINT64_MAX - step || current.m_pa > UINT64_MAX - step) break; future = (struct erofs_map_dev) { .m_pa = off + step, .m_deviceid = device_id, .m_plen = sbi->block_size, }; if (erofs_map_dev(sbi, &future) != 0 || future.m_dif != current.m_dif || future.m_pa != current.m_pa + step) break; rablkno[count] = btodb(future.m_pa); rabsize[count] = sbi->block_size; } return (erofs_bread_device(sbi, current.m_dif, blocks, current.m_pa, len, rablkno, rabsize, count, bufp)); } /* Release a contiguous buffer returned by erofs_bread(). */ void erofs_brelse(void *buf) { free(buf, M_EROFS); } void erofs_put_metabuf(struct erofs_buf *buf) { void (*release)(void *); void *data; if (buf == NULL) return; data = buf->data; release = buf->release; buf->data = NULL; buf->release = NULL; if (data != NULL && release != NULL) release(data); } /* Read inode metadata from either the primary image or the metabox file. */ int erofs_read_metadata(struct erofs_sb_info *sbi, erofs_nid_t nid, erofs_off_t off, size_t len, struct erofs_buf *buf) { void *data; int error; if (!erofs_nid_in_metabox(nid)) { if (off > INT64_MAX) return (EOVERFLOW); error = erofs_bread(sbi, (off_t)off, len, &data); } else { if (!erofs_sb_has_metabox(sbi) || sbi->metabox_en == NULL) return (EINTEGRITY); error = erofs_read_data(sbi, sbi->metabox_en, off, len, &data); } if (error != 0) return (error); buf->data = data; buf->release = erofs_brelse; return (0); } static int erofs_map_blocks_flatmode(struct erofs_sb_info *sbi, struct erofs_inode *vi, struct erofs_map_blocks *map) { erofs_off_t loff, tail_start; uint64_t remain, block_rem; loff = map->m_la; if (loff >= vi->size) return (0); remain = vi->size - loff; switch (vi->datalayout) { case EROFS_INODE_CHUNK_BASED: return (erofs_map_blocks_chunk(sbi, vi, map)); case EROFS_INODE_FLAT_PLAIN: map->m_llen = remain; map->m_plen = map->m_llen; if (vi->startblk == EROFS_NULL_ADDR) return (0); if (vi->startblk > (UINT64_MAX >> sbi->blkszbits) || __builtin_add_overflow(vi->startblk << sbi->blkszbits, loff, &map->m_pa)) return (EINTEGRITY); map->m_flags |= EROFS_MAP_MAPPED; return (0); case EROFS_INODE_FLAT_INLINE: tail_start = erofs_inline_tail_start(sbi, vi); if (loff < tail_start) { map->m_llen = MIN(remain, tail_start - loff); map->m_plen = map->m_llen; if (vi->startblk == EROFS_NULL_ADDR) return (0); if (vi->startblk > (UINT64_MAX >> sbi->blkszbits) || __builtin_add_overflow(vi->startblk << sbi->blkszbits, loff, &map->m_pa)) return (EINTEGRITY); map->m_flags |= EROFS_MAP_MAPPED; return (0); } block_rem = sbi->block_size - ((loff - tail_start) & (sbi->block_size - 1)); map->m_llen = MIN(remain, block_rem); map->m_plen = map->m_llen; if (__builtin_add_overflow(vi->inode_off, vi->inode_isize, &map->m_pa) || __builtin_add_overflow(map->m_pa, vi->xattr_isize, &map->m_pa) || __builtin_add_overflow(map->m_pa, loff - tail_start, &map->m_pa)) return (EINTEGRITY); map->m_flags |= EROFS_MAP_MAPPED | EROFS_MAP_META; return (0); case EROFS_INODE_COMPRESSED_FULL: case EROFS_INODE_COMPRESSED_COMPACT: return (EOPNOTSUPP); default: return (EOPNOTSUPP); } } int erofs_map_blocks(struct erofs_sb_info *sbi, struct erofs_inode *vi, struct erofs_map_blocks *map) { struct erofs_map_blocks next = { .m_la = map->m_la }; int error; if (erofs_inode_is_data_compressed(vi->datalayout)) { error = z_erofs_map_blocks(sbi, vi, &next); } else { error = erofs_map_blocks_flatmode(sbi, vi, &next); } *map = next; return (error); } /* * Read a small range at a logical file offset into a contiguous buffer. * Primarily used for directory block reads, lookup, and symlink fragment * parsing. */ static int erofs_read_data_impl(struct erofs_sb_info *sbi, struct erofs_inode *vi, erofs_off_t loff, size_t len, unsigned int rablocks, void **bufp) { struct erofs_buf buf = EROFS_BUF_INITIALIZER; struct erofs_map_blocks map; char *out; void *blk; size_t done, want; int error; if (bufp == NULL) return (EINVAL); *bufp = NULL; if (len == 0) { return (0); } if (loff > UINT64_MAX - (uint64_t)len) return (EOVERFLOW); if (loff > vi->size || (uint64_t)len > vi->size - loff) return (EINTEGRITY); /* Compressed file path */ if (erofs_inode_is_data_compressed(vi->datalayout)) return (z_erofs_read_data(sbi, vi, loff, len, bufp)); /* Uncompressed file path */ out = malloc(len, M_EROFS, M_WAITOK); done = 0; while (done < len) { map = (struct erofs_map_blocks) { .m_la = loff + done }; error = erofs_map_blocks(sbi, vi, &map); if (error != 0) { free(out, M_EROFS); return (error); } if (map.m_llen == 0) { free(out, M_EROFS); return (EINTEGRITY); } want = MIN((size_t)MIN(map.m_llen, (uint64_t)MAXPHYS), len - done); if ((map.m_flags & EROFS_MAP_MAPPED) == 0) { bzero(out + done, want); } else { if ((map.m_flags & EROFS_MAP_META) != 0) { error = erofs_read_metadata(sbi, vi->nid, map.m_pa, want, &buf); } else if (done == 0 && rablocks != 0 && map.m_flags == EROFS_MAP_MAPPED) { error = erofs_read_physical_readahead(sbi, map.m_deviceid, map.m_pa, want, rablocks, &blk); } else { error = erofs_read_physical(sbi, map.m_deviceid, map.m_pa, want, &blk); } if (error != 0) { free(out, M_EROFS); return (error); } if ((map.m_flags & EROFS_MAP_META) != 0) { memcpy(out + done, buf.data, want); erofs_put_metabuf(&buf); } else { memcpy(out + done, blk, want); erofs_brelse(blk); } } done += want; } *bufp = out; return (0); } int erofs_read_data(struct erofs_sb_info *sbi, struct erofs_inode *vi, erofs_off_t loff, size_t len, void **bufp) { return (erofs_read_data_impl(sbi, vi, loff, len, 0, bufp)); } int erofs_read_data_readahead(struct erofs_sb_info *sbi, struct erofs_inode *vi, erofs_off_t loff, size_t len, bool sequential, void **bufp) { uint64_t remaining; unsigned int rablocks; rablocks = 0; if (sequential && vi->datalayout == EROFS_INODE_FLAT_PLAIN && sbi->block_size != 0 && (loff & (sbi->block_size - 1)) == 0 && len <= sbi->block_size && loff <= vi->size && len <= vi->size - loff) { remaining = vi->size - loff - len; rablocks = MIN(howmany(remaining, sbi->block_size), (uint64_t)EROFS_DIR_READAHEAD_SLOTS); } return (erofs_read_data_impl(sbi, vi, loff, len, rablocks, bufp)); } /* * Transfer the logical content of an inode directly into a uio. * Regular files and symlinks both use this read path. */ static int erofs_read_uio(struct erofs_sb_info *sbi, struct erofs_inode *vi, struct uio *uio) { struct erofs_buf buf = EROFS_BUF_INITIALIZER; struct erofs_map_blocks map; char zerobuf[PAGE_SIZE]; void *blk; size_t want, chunk; int error; if (uio->uio_offset < 0) return (EINVAL); if ((uint64_t)uio->uio_offset >= vi->size) return (0); /* Compressed file path */ if (erofs_inode_is_data_compressed(vi->datalayout)) return (z_erofs_read_uio(sbi, vi, uio)); /* Uncompressed file path */ bzero(zerobuf, sizeof(zerobuf)); while (uio->uio_resid > 0 && (uint64_t)uio->uio_offset < vi->size) { map = (struct erofs_map_blocks) { .m_la = uio->uio_offset }; error = erofs_map_blocks(sbi, vi, &map); if (error != 0) return (error); if (map.m_llen == 0) break; want = MIN((size_t)MIN(map.m_llen, (uint64_t)MAXPHYS), (size_t)uio->uio_resid); if ((map.m_flags & EROFS_MAP_MAPPED) == 0) { chunk = want; while (chunk > 0) { size_t zlen = MIN(chunk, sizeof(zerobuf)); error = uiomove(zerobuf, zlen, uio); if (error != 0) return (error); chunk -= zlen; } continue; } if ((map.m_flags & EROFS_MAP_META) != 0) { error = erofs_read_metadata(sbi, vi->nid, map.m_pa, want, &buf); } else { error = erofs_read_physical(sbi, map.m_deviceid, map.m_pa, want, &blk); } if (error != 0) return (error); if ((map.m_flags & EROFS_MAP_META) != 0) { error = uiomove(buf.data, want, uio); erofs_put_metabuf(&buf); } else { error = uiomove(blk, want, uio); erofs_brelse(blk); } if (error != 0) return (error); } return (0); } /* Read regular file data. */ int erofs_read_file(struct vnode *vp, struct uio *uio, int ioflag) { (void)ioflag; return (erofs_read_uio(MTOE(vp->v_mount), VTOE(vp), uio)); } int erofs_validate_symlink_target(struct erofs_sb_info *sbi, struct erofs_inode *vi) { void *target; int error; if (vi->vtype != VLNK) return (EINVAL); if (vi->size == 0) return (EINTEGRITY); if (vi->size > MAXPATHLEN) return (ENAMETOOLONG); error = erofs_read_data(sbi, vi, 0, (size_t)vi->size, &target); if (error != 0) return (error); if (memchr(target, '\0', (size_t)vi->size) != NULL) error = EINTEGRITY; erofs_brelse(target); return (error); } /* Read symlink target string. */ int erofs_readlink_target(struct vnode *vp, struct uio *uio) { struct erofs_inode *vi; vi = VTOE(vp); if (vi->size == 0) return (EINTEGRITY); if (vi->size > MAXPATHLEN) return (ENAMETOOLONG); return (erofs_read_uio(MTOE(vp->v_mount), vi, uio)); }