// 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 #include "internal.h" static bool erofs_is_48bit(const struct erofs_mount *em) { return ((em->feature_incompat & EROFS_FEATURE_INCOMPAT_48BIT) != 0); } static uint64_t erofs_addrmask(const struct erofs_mount *em) { if (erofs_is_48bit(em)) return ((1ULL << 48) - 1); return (UINT32_MAX); } static dev_t erofs_decode_dev(uint32_t dev) { unsigned int major, minor; major = (dev & 0xfff00) >> 8; minor = (dev & 0xff) | ((dev >> 12) & 0xfff00); return (makedev(major, minor)); } static uint32_t erofs_inode_generation(const struct erofs_mount *em, uint64_t nid, const void *inode, size_t inode_size) { uint8_t encoded_nid[sizeof(nid)]; uint32_t generation; le64enc(encoded_nid, nid); generation = fnv_32_buf(encoded_nid, sizeof(encoded_nid), em->generation_seed); generation = fnv_32_buf(inode, inode_size, generation); return (generation != 0 ? generation : 1); } static int erofs_set_timestamp(struct erofs_node *en, uint64_t seconds, uint32_t nanoseconds) { if (nanoseconds >= 1000000000 || seconds > (uint64_t)INT64_MAX) return (EINTEGRITY); en->mtime = seconds; en->mtime_nsec = nanoseconds; return (0); } static int erofs_set_data_blocks(const struct erofs_mount *em, struct erofs_node *en, uint64_t compressed_blocks) { if (en->datalayout == EROFS_INODE_COMPRESSED_FULL || en->datalayout == EROFS_INODE_COMPRESSED_COMPACT) { en->data_blocks = compressed_blocks; return (0); } if (en->size == 0) { en->data_blocks = 0; return (0); } if (en->size > UINT64_MAX - (em->block_size - 1)) return (EINTEGRITY); en->data_blocks = roundup2(en->size, (uint64_t)em->block_size) >> em->block_bits; return (0); } static int erofs_validate_inline_data(const struct erofs_mount *em, const struct erofs_node *en) { uint64_t image_size, inline_end, inline_off, inline_size, tail_start; if (en->datalayout != EROFS_INODE_FLAT_INLINE || en->size == 0) return (0); tail_start = roundup2(en->size, (uint64_t)em->block_size) - em->block_size; inline_size = en->size - tail_start; if (__builtin_add_overflow(en->inode_off, en->inode_isize, &inline_off) || __builtin_add_overflow(inline_off, en->xattr_isize, &inline_off) || __builtin_add_overflow(inline_off, inline_size, &inline_end)) return (EINTEGRITY); if ((inline_off & (em->block_size - 1)) + inline_size > em->block_size) return (EINTEGRITY); if (erofs_nid_in_metabox(en->nid)) { if (em->metabox_en == NULL || inline_end > em->metabox_en->size) return (EINTEGRITY); return (0); } if (em->blocks > (UINT64_MAX >> em->block_bits)) return (EINTEGRITY); image_size = em->blocks << em->block_bits; if (inline_end > image_size || inline_end > em->dif0.mediasize) return (EINTEGRITY); return (0); } /* * Convert a logical nid to its inode-table byte offset. Normal NIDs are * relative to the primary metadata area. For metabox NIDs, bit 63 selects * the metabox backing inode and the remaining bits are relative to its data. * EROFS_NULL_ADDR is returned when the address cannot be represented. */ uint64_t erofs_iloc(struct erofs_mount *em, uint64_t nid) { uint64_t meta_offset, nid_lo, result; bool in_metabox; in_metabox = erofs_nid_in_metabox(nid); if (in_metabox && !erofs_sb_has_metabox(em)) return (EROFS_NULL_ADDR); nid_lo = nid & EROFS_DIRENT_NID_MASK; if (nid_lo > (UINT64_MAX >> 5)) return (EROFS_NULL_ADDR); result = nid_lo << 5; if (in_metabox) return (result); if (em->block_bits > 58) return (EROFS_NULL_ADDR); meta_offset = (uint64_t)em->meta_blkaddr << em->block_bits; if (result > UINT64_MAX - meta_offset) return (EROFS_NULL_ADDR); return (meta_offset + result); } /* * Check that a NID can address at least one compact inode slot without * crossing the declared primary image or metabox backing-file boundary. */ bool erofs_nid_is_valid(struct erofs_mount *em, uint64_t nid) { uint64_t image_size, off; off = erofs_iloc(em, nid); if (off == EROFS_NULL_ADDR) return (false); if (erofs_nid_in_metabox(nid)) { if (em->metabox_en == NULL || off > em->metabox_en->size) return (false); return (sizeof(struct erofs_inode_compact) <= em->metabox_en->size - off); } if (em->blocks > (UINT64_MAX >> em->block_bits)) return (false); image_size = em->blocks << em->block_bits; if (off > image_size || sizeof(struct erofs_inode_compact) > image_size - off) return (false); if (off > em->dif0.mediasize || sizeof(struct erofs_inode_compact) > em->dif0.mediasize - off) return (false); return (true); } /* * Read and decode a disk inode. * * Currently supports: * - compact / extended inode; * - plain / inline uncompressed layouts; * - basic 48-bit address parsing; * - compact inode epoch/fixed_nsec timestamp semantics; * - dot_omitted / nlink==1 i_format details. */ int erofs_read_inode(struct erofs_mount *em, uint64_t nid, struct erofs_node *en) { struct erofs_inode_compact *dic; struct erofs_inode_extended *die; struct erofs_inode_chunk_info chunk_info; union erofs_inode_i_nb inode_nb; void *buf; uint64_t addrmask, mtime, off, startblk; uint64_t compressed_blocks; uint32_t raw_rdev, startblk_lo; uint16_t ifmt, startblk_hi; int error; if (!erofs_nid_is_valid(em, nid)) return (EINTEGRITY); off = erofs_iloc(em, nid); error = erofs_read_metadata(em, nid, off, sizeof(struct erofs_inode_compact), &buf); if (error != 0) return (error); bzero(&en->size, sizeof(*en) - offsetof(struct erofs_node, size)); en->nid = nid; en->inode_off = off; ifmt = le16toh(*(uint16_t *)buf); if ((ifmt & ~EROFS_I_ALL) != 0) { erofs_brelse(buf); return (EOPNOTSUPP); } en->datalayout = erofs_inode_datalayout(ifmt); if (en->datalayout >= EROFS_INODE_DATALAYOUT_MAX) { erofs_brelse(buf); return (EOPNOTSUPP); } en->compact_inode = (erofs_inode_version(ifmt) == 0); if (!en->compact_inode) { erofs_brelse(buf); error = erofs_read_metadata(em, nid, off, sizeof(struct erofs_inode_extended), &buf); if (error != 0) return (error); } addrmask = erofs_addrmask(em); startblk = EROFS_NULL_ADDR; startblk_lo = 0; startblk_hi = 0; compressed_blocks = 0; raw_rdev = 0; bzero(&inode_nb, sizeof(inode_nb)); dic = buf; if (en->compact_inode) { en->inode_isize = sizeof(struct erofs_inode_compact); en->generation = erofs_inode_generation(em, nid, buf, en->inode_isize); en->mode = le16toh(dic->i_mode); en->size = le32toh(dic->i_size); en->ino = le32toh(dic->i_ino); en->uid = le16toh(dic->i_uid); en->gid = le16toh(dic->i_gid); en->xattr_isize = erofs_xattr_ibody_size(dic->i_xattr_icount); if (__builtin_add_overflow(em->epoch, (uint64_t)le32toh(dic->i_mtime), &mtime)) { erofs_brelse(buf); return (EINTEGRITY); } error = erofs_set_timestamp(en, mtime, em->fixed_nsec); if (error != 0) { erofs_brelse(buf); return (error); } startblk_lo = le32toh(dic->i_u.startblk_lo); compressed_blocks = le32toh(dic->i_u.blocks_lo); raw_rdev = le32toh(dic->i_u.rdev); if (!S_ISDIR(en->mode) && ((ifmt >> EROFS_I_NLINK_1_BIT) & 0x1) != 0) { en->nlink = 1; inode_nb = dic->i_nb; } else { en->nlink = le16toh(dic->i_nb.nlink); addrmask = UINT32_MAX; } } else { die = buf; en->inode_isize = sizeof(struct erofs_inode_extended); en->generation = erofs_inode_generation(em, nid, buf, en->inode_isize); en->mode = le16toh(die->i_mode); en->size = le64toh(die->i_size); en->ino = le32toh(die->i_ino); en->uid = le32toh(die->i_uid); en->gid = le32toh(die->i_gid); en->nlink = le32toh(die->i_nlink); inode_nb = die->i_nb; en->xattr_isize = erofs_xattr_ibody_size(die->i_xattr_icount); error = erofs_set_timestamp(en, le64toh(die->i_mtime), le32toh(die->i_mtime_nsec)); if (error != 0) { erofs_brelse(buf); return (error); } startblk_lo = le32toh(die->i_u.startblk_lo); compressed_blocks = le32toh(die->i_u.blocks_lo); raw_rdev = le32toh(die->i_u.rdev); } startblk_hi = le16toh(inode_nb.startblk_hi); compressed_blocks |= (uint64_t)le16toh(inode_nb.blocks_hi) << 32; if (en->size > (uint64_t)OFF_MAX) { erofs_brelse(buf); return (EINTEGRITY); } en->vtype = IFTOVT(en->mode); if (en->mode != 0 && en->vtype == VNON) { erofs_brelse(buf); return (EINTEGRITY); } en->inline_data = (en->datalayout == EROFS_INODE_FLAT_INLINE); en->dot_omitted = (en->vtype == VDIR) && (((ifmt >> EROFS_I_DOT_OMITTED_BIT) & 0x1) != 0); if (en->datalayout == EROFS_INODE_COMPRESSED_FULL || en->datalayout == EROFS_INODE_COMPRESSED_COMPACT) { error = z_erofs_fill_inode(em, en); if (error != 0) { erofs_brelse(buf); return (error); } } else if (en->datalayout == EROFS_INODE_CHUNK_BASED) { if (!erofs_sb_has_chunked_file(em) || en->vtype != VREG) { erofs_brelse(buf); return (EINTEGRITY); } if (en->compact_inode) chunk_info = dic->i_u.c; else chunk_info = die->i_u.c; if (le16toh(chunk_info.reserved) != 0) { erofs_brelse(buf); return (EINTEGRITY); } en->chunkformat = le16toh(chunk_info.format); if (en->chunkformat & ~EROFS_CHUNK_FORMAT_ALL) { erofs_brelse(buf); return (EOPNOTSUPP); } if ((en->chunkformat & EROFS_CHUNK_FORMAT_48BIT) != 0 && (en->chunkformat & EROFS_CHUNK_FORMAT_INDEXES) == 0) { erofs_brelse(buf); return (EINTEGRITY); } en->chunkbits = em->block_bits + (en->chunkformat & EROFS_CHUNK_FORMAT_BLKBITS_MASK); if (en->chunkbits >= 64) { erofs_brelse(buf); return (EINTEGRITY); } } else if (en->datalayout != EROFS_INODE_FLAT_PLAIN && en->datalayout != EROFS_INODE_FLAT_INLINE) { erofs_brelse(buf); return (EOPNOTSUPP); } switch (en->vtype) { case VREG: case VDIR: case VLNK: if (en->datalayout == EROFS_INODE_CHUNK_BASED) { en->startblk = EROFS_NULL_ADDR; en->rdev = NODEV; break; } startblk = startblk_lo | ((uint64_t)startblk_hi << 32); if (en->datalayout == EROFS_INODE_FLAT_PLAIN && ((startblk ^ EROFS_NULL_ADDR) & addrmask) == 0) startblk = EROFS_NULL_ADDR; en->startblk = startblk; en->rdev = NODEV; break; case VCHR: case VBLK: en->startblk = EROFS_NULL_ADDR; en->rdev = erofs_decode_dev(raw_rdev); break; case VFIFO: case VSOCK: en->startblk = EROFS_NULL_ADDR; en->rdev = NODEV; break; default: erofs_brelse(buf); return (EINTEGRITY); } error = erofs_set_data_blocks(em, en, compressed_blocks); if (error == 0) error = erofs_validate_inline_data(em, en); if (error != 0) { erofs_brelse(buf); return (error); } erofs_brelse(buf); return (0); } static u_int erofs_vfs_hash(uint64_t nid) { return (fnv_32_buf(&nid, sizeof(nid), FNV1_32_INIT)); } static int erofs_vfs_hash_cmp(struct vnode *vp, void *pnid) { struct erofs_node *en; en = VTOE(vp); return (en == NULL || en->nid != *(uint64_t *)pnid); } /* * Get vnode by raw on-disk nid. The raw nid is also the FreeBSD fileid and * hash identity, so the metabox selector bit remains collision-free. * Uses the standard FreeBSD vfs_hash API. * (Linux equivalent: erofs_iget in Linux's inode.c) */ int erofs_vget(struct mount *mp, ino_t ino, int flags, struct vnode **vpp) { struct erofs_mount *em; struct erofs_node *en; struct thread *td; struct vnode *vp; uint64_t nid; u_int hash; bool shared; int error; td = curthread; nid = (uint64_t)ino; shared = (flags & LK_TYPE_MASK) == LK_SHARED; hash = erofs_vfs_hash(nid); error = vfs_hash_get(mp, hash, flags, td, vpp, erofs_vfs_hash_cmp, &nid); if (error != 0 || *vpp != NULL) return (error); em = MTOE(mp); en = malloc(sizeof(*en), M_EROFS, M_WAITOK | M_ZERO); error = getnewvnode("erofs", mp, &erofs_vnodeops, &vp); if (error != 0) { free(en, M_EROFS); *vpp = NULL; return (error); } vp->v_data = en; en->vnode = vp; en->nid = nid; lockmgr(vp->v_vnlock, LK_EXCLUSIVE, NULL); error = insmntque(vp, mp); if (error != 0) { free(en, M_EROFS); vp->v_data = NULL; *vpp = NULL; return (error); } error = vfs_hash_insert(vp, hash, flags, td, vpp, erofs_vfs_hash_cmp, &nid); if (error != 0 || *vpp != NULL) return (error); error = erofs_read_inode(em, nid, en); if (error != 0) { *vpp = NULL; vgone(vp); vput(vp); return (error); } vp->v_type = en->vtype; if (vp->v_type == VFIFO) vp->v_op = &erofs_fifoops; if ((uint64_t)ino == em->root_nid) vp->v_vflag |= VV_ROOT; vn_set_state(vp, VSTATE_CONSTRUCTED); if (shared) VOP_LOCK(vp, LK_DOWNGRADE); *vpp = vp; return (0); }