/* SPDX-License-Identifier: GPL-2.0-only */ /* * Copyright (C) 2017-2018 HUAWEI, Inc. * https://www.huawei.com/ * Copyright (C) 2021-2022, Alibaba Cloud */ #include #include #include #include #include #include #include #include #include #include #include #include "internal.h" #include "xattr.h" struct posix_acl_xattr_entry { uint16_t e_tag; uint16_t e_perm; uint32_t e_id; }; struct posix_acl_xattr_header { uint32_t a_version; }; #define POSIX_ACL_XATTR_VERSION 0x0002 #define EROFS_XATTR_FILTER_POSIX_ACL \ ((1U << 21) | (1U << 30)) #define EROFS_XATTR_CACHE_BODY_LIMIT (64UL * 1024) #define EROFS_XATTR_CACHE_MOUNT_BUDGET (1024UL * 1024) struct erofs_xattr_iter { struct erofs_sb_info *sbi; struct erofs_inode *vi; int attrnamespace; const char *name; size_t name_len; struct uio *uio; size_t *sizep; }; static int erofs_xattr_load_body(struct erofs_sb_info *sbi, struct erofs_inode *vi, struct erofs_buf *bodybuf, struct erofs_xattr_ibody_header **ihp, size_t *header_sizep); static uint32_t erofs_xxh32_rotl(uint32_t value, unsigned int count) { return ((value << count) | (value >> (32 - count))); } static uint32_t erofs_xxh32_round(uint32_t seed, uint32_t input) { seed += input * UINT32_C(2246822519); seed = erofs_xxh32_rotl(seed, 13); return (seed * UINT32_C(2654435761)); } static uint32_t erofs_xxh32(const void *input, size_t length, uint32_t seed) { const uint8_t *cursor, *end; uint32_t hash; cursor = input; end = cursor + length; if (length >= 16) { const uint8_t *limit; uint32_t v1, v2, v3, v4; limit = end - 16; v1 = seed + UINT32_C(2654435761) + UINT32_C(2246822519); v2 = seed + UINT32_C(2246822519); v3 = seed; v4 = seed - UINT32_C(2654435761); do { v1 = erofs_xxh32_round(v1, le32dec(cursor)); cursor += 4; v2 = erofs_xxh32_round(v2, le32dec(cursor)); cursor += 4; v3 = erofs_xxh32_round(v3, le32dec(cursor)); cursor += 4; v4 = erofs_xxh32_round(v4, le32dec(cursor)); cursor += 4; } while (cursor <= limit); hash = erofs_xxh32_rotl(v1, 1) + erofs_xxh32_rotl(v2, 7) + erofs_xxh32_rotl(v3, 12) + erofs_xxh32_rotl(v4, 18); } else { hash = seed + UINT32_C(374761393); } hash += (uint32_t)length; while (cursor + 4 <= end) { hash += le32dec(cursor) * UINT32_C(3266489917); hash = erofs_xxh32_rotl(hash, 17) * UINT32_C(668265263); cursor += 4; } while (cursor < end) { hash += *cursor++ * UINT32_C(374761393); hash = erofs_xxh32_rotl(hash, 11) * UINT32_C(2654435761); } hash ^= hash >> 15; hash *= UINT32_C(2246822519); hash ^= hash >> 13; hash *= UINT32_C(3266489917); hash ^= hash >> 16; return (hash); } static bool erofs_xattr_filter_name(int attrnamespace, const char *name, size_t name_len, uint8_t *indexp, const char **filter_namep, size_t *filter_name_lenp) { static const char acl_access[] = "posix_acl_access"; static const char acl_default[] = "posix_acl_default"; static const char security[] = "security."; static const char trusted[] = "trusted."; if (attrnamespace == EXTATTR_NAMESPACE_USER) { *indexp = EROFS_XATTR_INDEX_USER; *filter_namep = name; *filter_name_lenp = name_len; return (true); } if (attrnamespace != EXTATTR_NAMESPACE_SYSTEM) return (false); if (name_len == sizeof(acl_access) - 1 && memcmp(name, acl_access, sizeof(acl_access) - 1) == 0) { *indexp = EROFS_XATTR_INDEX_POSIX_ACL_ACCESS; *filter_namep = name + name_len; *filter_name_lenp = 0; return (true); } if (name_len == sizeof(acl_default) - 1 && memcmp(name, acl_default, sizeof(acl_default) - 1) == 0) { *indexp = EROFS_XATTR_INDEX_POSIX_ACL_DEFAULT; *filter_namep = name + name_len; *filter_name_lenp = 0; return (true); } if (name_len >= sizeof(trusted) - 1 && memcmp(name, trusted, sizeof(trusted) - 1) == 0) { *indexp = EROFS_XATTR_INDEX_TRUSTED; *filter_namep = name + sizeof(trusted) - 1; *filter_name_lenp = name_len - (sizeof(trusted) - 1); return (true); } if (name_len >= sizeof(security) - 1 && memcmp(name, security, sizeof(security) - 1) == 0) { *indexp = EROFS_XATTR_INDEX_SECURITY; *filter_namep = name + sizeof(security) - 1; *filter_name_lenp = name_len - (sizeof(security) - 1); return (true); } return (false); } static int erofs_xattr_backing_size(struct erofs_sb_info *sbi, struct erofs_inode *backing_en, erofs_off_t *sizep) { if (backing_en != NULL) { *sizep = backing_en->size; return (0); } if (sbi->blocks > (UINT64_MAX >> sbi->blkszbits)) return (EOVERFLOW); *sizep = sbi->blocks << sbi->blkszbits; return (0); } static int erofs_xattr_read_backing(struct erofs_sb_info *sbi, struct erofs_inode *backing_en, erofs_off_t off, size_t len, struct erofs_buf *buf) { void *data; erofs_off_t backing_size; int error; error = erofs_xattr_backing_size(sbi, backing_en, &backing_size); if (error != 0) return (error); if (off > backing_size || (uint64_t)len > backing_size - off) return (EINTEGRITY); if (backing_en != NULL) error = erofs_read_data(sbi, backing_en, off, len, &data); else { if (off > INT64_MAX) return (EOVERFLOW); error = erofs_bread(sbi, (off_t)off, len, &data); } if (error != 0) return (error); buf->data = data; buf->release = erofs_brelse; return (0); } static int erofs_xattr_filter_negative(struct erofs_sb_info *sbi, struct erofs_inode *vi, int attrnamespace, const char *name, size_t name_len, bool *negativep) { struct erofs_buf body = EROFS_BUF_INITIALIZER; struct erofs_xattr_ibody_header *ih; const char *filter_name; size_t filter_name_len, header_size; uint32_t hashbit, name_filter; uint8_t index; int error; *negativep = false; if (!erofs_sb_has_xattr_filter_v1(sbi) || !erofs_xattr_filter_name(attrnamespace, name, name_len, &index, &filter_name, &filter_name_len)) return (0); if (vi->xattr_isize < sizeof(*ih)) return (EINTEGRITY); if (vi->xattr_isize == sizeof(*ih)) return (EOPNOTSUPP); error = erofs_xattr_load_body(sbi, vi, &body, &ih, &header_size); if (error != 0) return (error); name_filter = le32toh(ih->h_name_filter); hashbit = erofs_xxh32(filter_name, filter_name_len, EROFS_XATTR_FILTER_SEED + index); hashbit &= EROFS_XATTR_FILTER_BITS - 1; *negativep = (name_filter & (1U << hashbit)) != 0; error = 0; (void)header_size; erofs_put_metabuf(&body); return (error); } /* * Read one prefix table metadata record. * * When backing_en == NULL the record is in the physical metadata area; * otherwise it lives in the selected metadata carrier's logical data stream. */ static int erofs_xattr_read_metadata(struct erofs_sb_info *sbi, struct erofs_inode *backing_en, erofs_off_t *offp, struct erofs_buf *buf, size_t *lenp) { struct erofs_buf hdrbuf = EROFS_BUF_INITIALIZER; uint16_t raw_len; erofs_off_t off; size_t len; int error; if (*offp > UINT64_MAX - (sizeof(struct erofs_xattr_entry) - 1)) return (EOVERFLOW); off = roundup2(*offp, sizeof(struct erofs_xattr_entry)); error = erofs_xattr_read_backing(sbi, backing_en, off, sizeof(raw_len), &hdrbuf); if (error != 0) return (error); raw_len = le16dec(hdrbuf.data); erofs_put_metabuf(&hdrbuf); len = (raw_len == 0) ? (size_t)UINT16_MAX + 1 : raw_len; if (len < sizeof(struct erofs_xattr_long_prefix) || len > EROFS_NAME_LEN + sizeof(struct erofs_xattr_long_prefix)) return (EINTEGRITY); if (off > UINT64_MAX - sizeof(raw_len)) return (EOVERFLOW); error = erofs_xattr_read_backing(sbi, backing_en, off + sizeof(raw_len), len, buf); if (error != 0) return (error); *offp = off + sizeof(raw_len) + len; *lenp = len; return (0); } static int erofs_xattr_move(void *value, size_t value_size, struct uio *uio, size_t *sizep) { if (sizep != NULL) *sizep = value_size; if (uio == NULL || value_size == 0) return (0); return (uiomove(value, value_size, uio)); } static int erofs_xattr_load_body_uncached(struct erofs_sb_info *sbi, struct erofs_inode *vi, struct erofs_buf *bodybuf, struct erofs_xattr_ibody_header **ihp, size_t *header_sizep) { struct erofs_buf buf = EROFS_BUF_INITIALIZER; struct erofs_xattr_ibody_header *ih; char *body; uint64_t body_off; size_t header_size; int error; if (vi->xattr_isize < sizeof(*ih)) return (EINTEGRITY); if (vi->inode_off > UINT64_MAX - vi->inode_isize) return (EINTEGRITY); body_off = vi->inode_off + vi->inode_isize; error = erofs_xattr_read_backing(sbi, erofs_nid_in_metabox(vi->nid) ? sbi->metabox_en : NULL, body_off, vi->xattr_isize, &buf); if (error != 0) return (error); body = buf.data; ih = (struct erofs_xattr_ibody_header *)body; if (vi->xattr_isize == sizeof(*ih)) { error = EOPNOTSUPP; goto fail; } header_size = sizeof(*ih) + sizeof(uint32_t) * ih->h_shared_count; if (header_size > vi->xattr_isize) { error = EINTEGRITY; goto fail; } *bodybuf = buf; *ihp = ih; *header_sizep = header_size; return (0); fail: erofs_put_metabuf(&buf); return (error); } static int erofs_xattr_validate_entry(struct erofs_xattr_entry *entry, size_t remaining, size_t *entry_sizep, size_t *value_sizep) { size_t entry_size, min_size, value_size; if (remaining < sizeof(*entry)) return (EINTEGRITY); value_size = le16toh(entry->e_value_size); min_size = sizeof(*entry) + entry->e_name_len + value_size; if (min_size > remaining) return (EINTEGRITY); entry_size = erofs_xattr_entry_size(entry); if (entry_size > remaining) return (EINTEGRITY); if (memchr(entry->e_name, '\0', entry->e_name_len) != NULL) return (EINTEGRITY); if (entry_sizep != NULL) *entry_sizep = entry_size; if (value_sizep != NULL) *value_sizep = value_size; return (0); } static bool erofs_xattr_prefix(uint8_t base_index, int *namespacep, const char **prefixp, size_t *prefix_lenp) { switch (base_index) { case EROFS_XATTR_INDEX_USER: *namespacep = EXTATTR_NAMESPACE_USER; *prefixp = NULL; *prefix_lenp = 0; return (true); case EROFS_XATTR_INDEX_POSIX_ACL_ACCESS: *namespacep = EXTATTR_NAMESPACE_SYSTEM; *prefixp = "posix_acl_access"; *prefix_lenp = sizeof("posix_acl_access") - 1; return (true); case EROFS_XATTR_INDEX_POSIX_ACL_DEFAULT: *namespacep = EXTATTR_NAMESPACE_SYSTEM; *prefixp = "posix_acl_default"; *prefix_lenp = sizeof("posix_acl_default") - 1; return (true); case EROFS_XATTR_INDEX_TRUSTED: *namespacep = EXTATTR_NAMESPACE_SYSTEM; *prefixp = "trusted."; *prefix_lenp = sizeof("trusted.") - 1; return (true); case EROFS_XATTR_INDEX_SECURITY: *namespacep = EXTATTR_NAMESPACE_SYSTEM; *prefixp = "security."; *prefix_lenp = sizeof("security.") - 1; return (true); case EROFS_XATTR_INDEX_LUSTRE: default: return (false); } } static int erofs_xattr_namespace_prefix(int attrnamespace, uint8_t base_index, const char **prefixp, size_t *prefix_lenp) { int mapped_namespace; if (attrnamespace != EXTATTR_NAMESPACE_USER && attrnamespace != EXTATTR_NAMESPACE_SYSTEM) return (EOPNOTSUPP); if (!erofs_xattr_prefix(base_index, &mapped_namespace, prefixp, prefix_lenp)) return (ENOATTR); if (mapped_namespace != attrnamespace) return (ENOATTR); return (0); } static int erofs_xattr_list_move(const char *namespace_prefix, size_t namespace_prefix_len, const char *infix, size_t infix_len, const char *name, uint8_t name_len, struct uio *uio, size_t *sizep) { uint8_t total_name_len; int error; if (namespace_prefix_len + infix_len + name_len > EROFS_NAME_LEN) return (EINTEGRITY); total_name_len = namespace_prefix_len + infix_len + name_len; if (sizep != NULL) { *sizep += total_name_len + 1; return (0); } if (uio == NULL) return (0); error = uiomove(__DECONST(void *, &total_name_len), 1, uio); if (error != 0) return (error); if (namespace_prefix_len != 0) { error = uiomove(__DECONST(void *, namespace_prefix), namespace_prefix_len, uio); if (error != 0) return (error); } if (infix_len != 0) { error = uiomove(__DECONST(void *, infix), infix_len, uio); if (error != 0) return (error); } return (uiomove(__DECONST(void *, name), name_len, uio)); } static int erofs_xattr_resolve_name(struct erofs_sb_info *sbi, const struct erofs_xattr_entry *entry, uint8_t *base_indexp, const char **infixp, size_t *infix_lenp) { struct erofs_xattr_prefix_item *prefix; uint8_t prefix_id; if ((entry->e_name_index & EROFS_XATTR_LONG_PREFIX) == 0) { *base_indexp = entry->e_name_index; *infixp = NULL; *infix_lenp = 0; return (0); } if (sbi->xattr_prefixes == NULL) return (ENOATTR); prefix_id = entry->e_name_index & EROFS_XATTR_LONG_PREFIX_MASK; if (prefix_id >= sbi->xattr_prefix_count) return (ENOATTR); prefix = &sbi->xattr_prefixes[prefix_id]; *base_indexp = prefix->base_index; *infixp = prefix->infix; *infix_lenp = prefix->infix_len; return (0); } static bool erofs_xattr_name_match(const char *namespace_prefix, size_t namespace_prefix_len, const char *infix, size_t infix_len, const struct erofs_xattr_entry *entry, const char *name, size_t name_len) { if (name_len != namespace_prefix_len + infix_len + entry->e_name_len) return (false); if (namespace_prefix_len != 0 && memcmp(name, namespace_prefix, namespace_prefix_len) != 0) return (false); if (infix_len != 0 && memcmp(name + namespace_prefix_len, infix, infix_len) != 0) return (false); return (memcmp(name + namespace_prefix_len + infix_len, entry->e_name, entry->e_name_len) == 0); } static int erofs_xattr_shared_entry_offset(struct erofs_sb_info *sbi, uint32_t shared_id, erofs_off_t *phys_offp) { erofs_off_t base, relative; if (sbi->xattr_blkaddr > (UINT64_MAX >> sbi->blkszbits)) return (EOVERFLOW); base = (uint64_t)sbi->xattr_blkaddr << sbi->blkszbits; relative = (uint64_t)shared_id * sizeof(uint32_t); if (relative > UINT64_MAX - base) return (EOVERFLOW); *phys_offp = base + relative; return (0); } static int erofs_xattr_load_shared_entry(struct erofs_sb_info *sbi, uint32_t shared_id, struct erofs_buf *entrybuf, size_t *entry_sizep, size_t *value_sizep) { struct erofs_buf hdrbuf = EROFS_BUF_INITIALIZER; struct erofs_xattr_entry *entry; struct erofs_inode *backing_en; erofs_off_t off; size_t entry_size, value_size; int error; backing_en = erofs_sb_has_shared_ea_in_metabox(sbi) ? sbi->metabox_en : NULL; if (erofs_sb_has_shared_ea_in_metabox(sbi) && backing_en == NULL) return (EINTEGRITY); error = erofs_xattr_shared_entry_offset(sbi, shared_id, &off); if (error != 0) return (error); error = erofs_xattr_read_backing(sbi, backing_en, off, sizeof(*entry), &hdrbuf); if (error != 0) return (error); entry = hdrbuf.data; value_size = le16toh(entry->e_value_size); entry_size = erofs_xattr_entry_size(entry); erofs_put_metabuf(&hdrbuf); error = erofs_xattr_read_backing(sbi, backing_en, off, entry_size, entrybuf); if (error != 0) return (error); error = erofs_xattr_validate_entry(entrybuf->data, entry_size, NULL, value_sizep); if (error != 0) { erofs_put_metabuf(entrybuf); return (error); } if (entry_sizep != NULL) *entry_sizep = entry_size; if (value_sizep != NULL) *value_sizep = value_size; return (0); } static int erofs_xattr_validate_body(struct erofs_sb_info *sbi, struct erofs_inode *vi, void *body, struct erofs_xattr_ibody_header *ih, size_t header_size) { struct erofs_buf entrybuf = EROFS_BUF_INITIALIZER; struct erofs_xattr_entry *entry; char *cursor; uint32_t shared_id; size_t entry_size, remaining; int error; if (header_size > vi->xattr_isize || memchr(ih->h_reserved2, '\0', sizeof(ih->h_reserved2)) == NULL) return (EINTEGRITY); remaining = vi->xattr_isize - header_size; cursor = (char *)body + header_size; while (remaining != 0) { entry = (struct erofs_xattr_entry *)cursor; error = erofs_xattr_validate_entry(entry, remaining, &entry_size, NULL); if (error != 0) return (error); cursor += entry_size; remaining -= entry_size; } for (uint8_t i = 0; i < ih->h_shared_count; i++) { shared_id = le32toh(ih->h_shared_xattrs[i]); error = erofs_xattr_load_shared_entry(sbi, shared_id, &entrybuf, NULL, NULL); if (error != 0) return (error); erofs_put_metabuf(&entrybuf); } return (0); } static int erofs_xattr_load_body_checked(struct erofs_sb_info *sbi, struct erofs_inode *vi, struct erofs_buf *bodybuf, struct erofs_xattr_ibody_header **ihp, size_t *header_sizep) { int error; error = erofs_xattr_load_body_uncached(sbi, vi, bodybuf, ihp, header_sizep); if (error != 0) return (error); error = erofs_xattr_validate_body(sbi, vi, bodybuf->data, *ihp, *header_sizep); if (error != 0) erofs_put_metabuf(bodybuf); return (error); } static bool erofs_xattr_cache_reserve(struct erofs_sb_info *sbi, size_t size) { u_long resident; for (;;) { resident = atomic_load_acq_long(&sbi->xattr_cache_resident); if (resident > EROFS_XATTR_CACHE_MOUNT_BUDGET || size > EROFS_XATTR_CACHE_MOUNT_BUDGET - resident) return (false); if (atomic_cmpset_acq_long(&sbi->xattr_cache_resident, resident, resident + size)) return (true); } } static void erofs_xattr_cache_release(struct erofs_sb_info *sbi, size_t size) { KASSERT(size <= EROFS_XATTR_CACHE_MOUNT_BUDGET, ("erofs xattr cache release exceeds mount budget")); atomic_subtract_rel_long(&sbi->xattr_cache_resident, size); } static void * erofs_xattr_cache_invalidate_locked(struct erofs_sb_info *sbi, struct erofs_xattr_cache *cache) { void *data; data = cache->data; if (cache->charged_bytes != 0) { erofs_xattr_cache_release(sbi, cache->charged_bytes); cache->charged_bytes = 0; } cache->data = NULL; cache->size = 0; cache->error = 0; cache->state = EROFS_XATTR_CACHE_EMPTY; return (data); } void erofs_xattr_cache_init(struct erofs_inode *vi) { struct erofs_xattr_cache *cache; cache = &vi->xattr_cache; bzero(cache, sizeof(*cache)); mtx_init(&cache->lock, "erofs xattr", NULL, MTX_DEF); cv_init(&cache->cv, "erofs xattr"); cache->state = EROFS_XATTR_CACHE_EMPTY; cache->initialized = true; } void erofs_xattr_cache_fini(struct erofs_sb_info *sbi, struct erofs_inode *vi) { struct erofs_xattr_cache *cache; void *data; cache = &vi->xattr_cache; if (!cache->initialized) return; mtx_lock(&cache->lock); cache->closing = true; while (cache->state == EROFS_XATTR_CACHE_INFLIGHT || cache->waiters != 0) cv_wait(&cache->cv, &cache->lock); data = erofs_xattr_cache_invalidate_locked(sbi, cache); cache->initialized = false; mtx_unlock(&cache->lock); free(data, M_EROFS); cv_destroy(&cache->cv); mtx_destroy(&cache->lock); } static int erofs_xattr_load_body(struct erofs_sb_info *sbi, struct erofs_inode *vi, struct erofs_buf *bodybuf, struct erofs_xattr_ibody_header **ihp, size_t *header_sizep) { struct erofs_xattr_cache *cache; bool reserved; int error; cache = &vi->xattr_cache; if (!cache->initialized || vi->xattr_isize > EROFS_XATTR_CACHE_BODY_LIMIT) return (erofs_xattr_load_body_checked(sbi, vi, bodybuf, ihp, header_sizep)); mtx_lock(&cache->lock); if (cache->closing) { mtx_unlock(&cache->lock); return (ENXIO); } if (cache->state == EROFS_XATTR_CACHE_READY) { KASSERT(cache->data != NULL, ("erofs xattr ready cache has no body")); bodybuf->data = cache->data; *ihp = bodybuf->data; *header_sizep = sizeof(**ihp) + sizeof(uint32_t) * (*ihp)->h_shared_count; mtx_unlock(&cache->lock); return (0); } if (cache->state == EROFS_XATTR_CACHE_INFLIGHT) { cache->waiters++; do { cv_wait(&cache->cv, &cache->lock); } while (cache->state == EROFS_XATTR_CACHE_INFLIGHT); if (cache->state == EROFS_XATTR_CACHE_READY) { bodybuf->data = cache->data; *ihp = bodybuf->data; *header_sizep = sizeof(**ihp) + sizeof(uint32_t) * (*ihp)->h_shared_count; error = 0; } else { KASSERT(cache->state == EROFS_XATTR_CACHE_FAILED && cache->error > 0, ("erofs xattr failed cache has no typed error")); error = cache->error; } cache->waiters--; if (cache->state == EROFS_XATTR_CACHE_FAILED && cache->waiters == 0) (void)erofs_xattr_cache_invalidate_locked(sbi, cache); if (cache->waiters == 0) cv_broadcast(&cache->cv); mtx_unlock(&cache->lock); return (error); } if (cache->state == EROFS_XATTR_CACHE_FAILED) { if (cache->waiters != 0) { mtx_unlock(&cache->lock); return (erofs_xattr_load_body_checked(sbi, vi, bodybuf, ihp, header_sizep)); } (void)erofs_xattr_cache_invalidate_locked(sbi, cache); } reserved = erofs_xattr_cache_reserve(sbi, vi->xattr_isize); if (!reserved) { mtx_unlock(&cache->lock); return (erofs_xattr_load_body_checked(sbi, vi, bodybuf, ihp, header_sizep)); } cache->state = EROFS_XATTR_CACHE_INFLIGHT; cache->error = 0; mtx_unlock(&cache->lock); error = erofs_xattr_load_body_checked(sbi, vi, bodybuf, ihp, header_sizep); mtx_lock(&cache->lock); if (error == 0 && !cache->closing) { cache->data = bodybuf->data; cache->size = vi->xattr_isize; cache->charged_bytes = vi->xattr_isize; cache->state = EROFS_XATTR_CACHE_READY; bodybuf->release = NULL; cv_broadcast(&cache->cv); mtx_unlock(&cache->lock); return (0); } if (error == 0) error = ENXIO; erofs_xattr_cache_release(sbi, vi->xattr_isize); cache->error = error; cache->state = EROFS_XATTR_CACHE_FAILED; if (cache->waiters == 0) (void)erofs_xattr_cache_invalidate_locked(sbi, cache); cv_broadcast(&cache->cv); mtx_unlock(&cache->lock); erofs_put_metabuf(bodybuf); return (error); } static int erofs_listxattr_foreach(struct erofs_xattr_iter *it, struct erofs_xattr_entry *entry) { const char *infix, *namespace_prefix; size_t infix_len, namespace_prefix_len; uint8_t base_index; int error; error = erofs_xattr_resolve_name(it->sbi, entry, &base_index, &infix, &infix_len); if (error == ENOATTR) return (0); if (error != 0) return (error); error = erofs_xattr_namespace_prefix(it->attrnamespace, base_index, &namespace_prefix, &namespace_prefix_len); if (error == ENOATTR) return (0); if (error != 0) return (error); return (erofs_xattr_list_move(namespace_prefix, namespace_prefix_len, infix, infix_len, entry->e_name, entry->e_name_len, it->uio, it->sizep)); } static int erofs_getxattr_foreach(struct erofs_xattr_iter *it, struct erofs_xattr_entry *entry, size_t value_size) { const char *infix, *namespace_prefix; size_t infix_len, namespace_prefix_len; uint8_t base_index; int error; error = erofs_xattr_resolve_name(it->sbi, entry, &base_index, &infix, &infix_len); if (error != 0) return (error); error = erofs_xattr_namespace_prefix(it->attrnamespace, base_index, &namespace_prefix, &namespace_prefix_len); if (error != 0) return (error); if (!erofs_xattr_name_match(namespace_prefix, namespace_prefix_len, infix, infix_len, entry, it->name, it->name_len)) return (ENOATTR); return (erofs_xattr_move(entry->e_name + entry->e_name_len, value_size, it->uio, it->sizep)); } static int erofs_xattr_iter_inline(struct erofs_xattr_iter *it, char *body, size_t header_size, bool get) { struct erofs_xattr_entry *entry; char *cursor; size_t entry_size, remaining, value_size; int error; remaining = it->vi->xattr_isize - header_size; cursor = body + header_size; while (remaining != 0) { entry = (struct erofs_xattr_entry *)cursor; error = erofs_xattr_validate_entry(entry, remaining, &entry_size, get ? &value_size : NULL); if (error != 0) return (error); if (get) error = erofs_getxattr_foreach(it, entry, value_size); else error = erofs_listxattr_foreach(it, entry); if (get) { if (error != ENOATTR) return (error); } else if (error != 0) { return (error); } cursor += entry_size; remaining -= entry_size; } return (get ? ENOATTR : 0); } static int erofs_xattr_iter_shared(struct erofs_xattr_iter *it, struct erofs_xattr_ibody_header *ih, bool get) { struct erofs_buf buf = EROFS_BUF_INITIALIZER; struct erofs_xattr_entry *entry; uint32_t shared_id; size_t value_size; int error; for (uint8_t i = 0; i < ih->h_shared_count; i++) { shared_id = le32toh(ih->h_shared_xattrs[i]); error = erofs_xattr_load_shared_entry(it->sbi, shared_id, &buf, NULL, get ? &value_size : NULL); if (error != 0) return (error); entry = buf.data; if (get) error = erofs_getxattr_foreach(it, entry, value_size); else error = erofs_listxattr_foreach(it, entry); erofs_put_metabuf(&buf); if (get) { if (error != ENOATTR) return (error); } else if (error != 0) { return (error); } } return (get ? ENOATTR : 0); } /* * Look up one inline/shared xattr by name. * * Name exposure rules: * - user namespace: bare name, no "user." prefix; * - system namespace: exposes full "trusted.*" / "security.*" names. */ int erofs_getxattr(struct vnode *vp, int attrnamespace, const char *name, struct uio *uio, size_t *sizep) { struct erofs_buf body = EROFS_BUF_INITIALIZER; struct erofs_sb_info *sbi; struct erofs_inode *vi; struct erofs_xattr_ibody_header *ih; struct erofs_xattr_iter it; size_t header_size, name_len; bool filter_negative; int error; sbi = MTOE(vp->v_mount); vi = VTOE(vp); if (name == NULL || name[0] == '\0') return (EINVAL); name_len = strlen(name); if (name_len > EROFS_NAME_LEN) return (EINVAL); if (vi->xattr_isize == 0) return (ENOATTR); error = erofs_xattr_filter_negative(sbi, vi, attrnamespace, name, name_len, &filter_negative); if (error != 0) return (error); if (filter_negative) return (ENOATTR); error = erofs_xattr_load_body(sbi, vi, &body, &ih, &header_size); if (error != 0) return (error); it.sbi = sbi; it.vi = vi; it.attrnamespace = attrnamespace; it.name = name; it.name_len = name_len; it.uio = uio; it.sizep = sizep; error = erofs_xattr_iter_inline(&it, body.data, header_size, true); if (error == ENOATTR) error = erofs_xattr_iter_shared(&it, ih, true); erofs_put_metabuf(&body); return (error); } /* * Enumerate inline/shared xattr names for a given namespace. * * Return format: 1-byte name length followed by non-NUL-terminated name bytes. */ int erofs_listxattr(struct vnode *vp, int attrnamespace, struct uio *uio, size_t *sizep) { struct erofs_buf body = EROFS_BUF_INITIALIZER; struct erofs_sb_info *sbi; struct erofs_inode *vi; struct erofs_xattr_ibody_header *ih; struct erofs_xattr_iter it; size_t header_size; int error; sbi = MTOE(vp->v_mount); vi = VTOE(vp); if (sizep != NULL) *sizep = 0; if (vi->xattr_isize == 0) return (0); error = erofs_xattr_load_body(sbi, vi, &body, &ih, &header_size); if (error != 0) return (error); it.sbi = sbi; it.vi = vi; it.attrnamespace = attrnamespace; it.name = NULL; it.name_len = 0; it.uio = uio; it.sizep = sizep; error = erofs_xattr_iter_inline(&it, body.data, header_size, false); if (error == 0) error = erofs_xattr_iter_shared(&it, ih, false); erofs_put_metabuf(&body); return (error); } void erofs_xattr_prefixes_cleanup(struct erofs_sb_info *sbi) { if (sbi->xattr_prefixes == NULL) return; for (uint8_t i = 0; i < sbi->xattr_prefix_count; i++) free(sbi->xattr_prefixes[i].infix, M_EROFS); free(sbi->xattr_prefixes, M_EROFS); sbi->xattr_prefixes = NULL; } int erofs_xattr_prefixes_init(struct erofs_sb_info *sbi) { struct erofs_buf buf = EROFS_BUF_INITIALIZER; struct erofs_xattr_long_prefix *prefix = NULL; struct erofs_inode packed_en, *prefix_en; erofs_off_t off; size_t infix_len, len; int error; if (!erofs_sb_has_xattr_prefixes(sbi) || sbi->xattr_prefix_count == 0) return (0); prefix_en = NULL; if (!erofs_sb_has_plain_xattr_pfx(sbi)) { if (erofs_sb_has_metabox(sbi)) { if (sbi->metabox_en == NULL) return (EINTEGRITY); prefix_en = sbi->metabox_en; } else if (sbi->packed_inode != NULL) { prefix_en = sbi->packed_inode; } else if (sbi->packed_nid != 0) { error = erofs_read_inode(sbi, sbi->packed_nid, &packed_en); if (error != 0) return (error); if (packed_en.vtype != VREG) return (EINTEGRITY); prefix_en = &packed_en; } } sbi->xattr_prefixes = malloc(sizeof(*sbi->xattr_prefixes) * sbi->xattr_prefix_count, M_EROFS, M_WAITOK | M_ZERO); off = (uint64_t)sbi->xattr_prefix_start << 2; for (uint8_t i = 0; i < sbi->xattr_prefix_count; i++) { error = erofs_xattr_read_metadata(sbi, prefix_en, &off, &buf, &len); if (error != 0) goto fail; prefix = buf.data; infix_len = len - sizeof(*prefix); if (memchr(prefix->infix, '\0', infix_len) != NULL) { error = EINTEGRITY; goto fail; } sbi->xattr_prefixes[i].base_index = prefix->base_index; sbi->xattr_prefixes[i].infix_len = infix_len; sbi->xattr_prefixes[i].infix = malloc(infix_len + 1, M_EROFS, M_WAITOK); memcpy(sbi->xattr_prefixes[i].infix, prefix->infix, infix_len); sbi->xattr_prefixes[i].infix[infix_len] = '\0'; erofs_put_metabuf(&buf); } return (0); fail: erofs_put_metabuf(&buf); erofs_xattr_prefixes_cleanup(sbi); return (error); } static int erofs_inode_has_noacl(struct erofs_sb_info *sbi, struct erofs_inode *vi, bool *noaclp) { struct erofs_buf buf = EROFS_BUF_INITIALIZER; struct erofs_xattr_ibody_header *ih; struct erofs_inode *backing_en; erofs_off_t body_off; uint32_t name_filter; int error; *noaclp = false; if (vi->xattr_isize < sizeof(*ih)) { *noaclp = true; return (0); } if (!erofs_sb_has_xattr_filter(sbi) || sbi->xattr_filter_reserved != 0) return (0); if (vi->inode_off > UINT64_MAX - vi->inode_isize) return (EINTEGRITY); body_off = vi->inode_off + vi->inode_isize; backing_en = erofs_nid_in_metabox(vi->nid) ? sbi->metabox_en : NULL; error = erofs_xattr_read_backing(sbi, backing_en, body_off, sizeof(*ih), &buf); if (error != 0) return (error); ih = buf.data; name_filter = le32toh(ih->h_name_filter); erofs_put_metabuf(&buf); *noaclp = (name_filter & EROFS_XATTR_FILTER_POSIX_ACL) == EROFS_XATTR_FILTER_POSIX_ACL; return (0); } static void erofs_acl_from_mode(mode_t mode, acl_type_t type, struct acl *aclp) { if (type == ACL_TYPE_DEFAULT) { aclp->acl_cnt = 0; return; } aclp->acl_cnt = 3; aclp->acl_entry[0].ae_tag = ACL_USER_OBJ; aclp->acl_entry[0].ae_id = ACL_UNDEFINED_ID; aclp->acl_entry[0].ae_perm = acl_posix1e_mode_to_perm(ACL_USER_OBJ, mode); aclp->acl_entry[1].ae_tag = ACL_GROUP_OBJ; aclp->acl_entry[1].ae_id = ACL_UNDEFINED_ID; aclp->acl_entry[1].ae_perm = acl_posix1e_mode_to_perm(ACL_GROUP_OBJ, mode); aclp->acl_entry[2].ae_tag = ACL_OTHER; aclp->acl_entry[2].ae_id = ACL_UNDEFINED_ID; aclp->acl_entry[2].ae_perm = acl_posix1e_mode_to_perm(ACL_OTHER, mode); } static int erofs_posix_acl_from_xattr(const void *value, size_t size, mode_t mode, acl_type_t type, struct acl *aclp) { const uint8_t *buf; struct posix_acl_xattr_header hdr; struct posix_acl_xattr_entry entry; uint32_t id; int count, i, j, phase; buf = value; if (size < sizeof(hdr) || (size - sizeof(hdr)) % sizeof(entry) != 0) return (EINTEGRITY); memcpy(&hdr, buf, sizeof(hdr)); if (le32toh(hdr.a_version) != POSIX_ACL_XATTR_VERSION) return (EINTEGRITY); count = (size - sizeof(hdr)) / sizeof(entry); if (count > ACL_MAX_ENTRIES) return (EINTEGRITY); if (count == 0) { erofs_acl_from_mode(mode, type, aclp); return (0); } aclp->acl_cnt = count; phase = 0; for (i = 0; i < count; i++) { uint16_t tag, perm; memcpy(&entry, buf + sizeof(hdr) + i * sizeof(entry), sizeof(entry)); tag = le16toh(entry.e_tag); perm = le16toh(entry.e_perm); id = le32toh(entry.e_id); if ((perm & ~ACL_PERM_BITS) != 0) return (EINTEGRITY); switch (tag) { case ACL_USER_OBJ: if (phase != 0 || id != UINT32_MAX) return (EINTEGRITY); phase = 1; break; case ACL_USER: if ((phase != 1 && phase != 2) || id == UINT32_MAX) return (EINTEGRITY); phase = 2; break; case ACL_GROUP_OBJ: if ((phase != 1 && phase != 2) || id != UINT32_MAX) return (EINTEGRITY); phase = 3; break; case ACL_GROUP: if ((phase != 3 && phase != 4) || id == UINT32_MAX) return (EINTEGRITY); phase = 4; break; case ACL_MASK: if ((phase != 3 && phase != 4) || id != UINT32_MAX) return (EINTEGRITY); phase = 5; break; case ACL_OTHER: if ((phase != 3 && phase != 4 && phase != 5) || id != UINT32_MAX) return (EINTEGRITY); phase = 6; break; default: return (EINTEGRITY); } if (tag == ACL_USER || tag == ACL_GROUP) { for (j = 0; j < i; j++) { if (aclp->acl_entry[j].ae_tag == tag && aclp->acl_entry[j].ae_id == id) return (EINTEGRITY); } } aclp->acl_entry[i].ae_tag = tag; aclp->acl_entry[i].ae_perm = perm; aclp->acl_entry[i].ae_id = (id == UINT32_MAX) ? ACL_UNDEFINED_ID : id; } if (phase != 6 || acl_posix1e_check(aclp) != 0) return (EINTEGRITY); return (0); } int erofs_get_acl(struct vnode *vp, acl_type_t type, struct acl *aclp) { struct erofs_sb_info *sbi; struct erofs_inode *vi; const char *xattr_name; struct uio auio; struct iovec aiov; uint8_t buf[sizeof(struct posix_acl_xattr_header) + sizeof(struct posix_acl_xattr_entry) * ACL_MAX_ENTRIES]; size_t size; bool noacl; int error; sbi = MTOE(vp->v_mount); if (!test_opt(&sbi->opt, POSIX_ACL)) return (EOPNOTSUPP); vi = VTOE(vp); switch (type) { case ACL_TYPE_ACCESS: xattr_name = "posix_acl_access"; break; case ACL_TYPE_DEFAULT: if (vp->v_type != VDIR) return (EINVAL); xattr_name = "posix_acl_default"; break; default: return (EINVAL); } error = erofs_inode_has_noacl(sbi, vi, &noacl); if (error != 0) return (error); if (noacl) { erofs_acl_from_mode(vi->mode, type, aclp); return (0); } error = erofs_getxattr(vp, EXTATTR_NAMESPACE_SYSTEM, xattr_name, NULL, &size); if (error == ENOATTR) { erofs_acl_from_mode(vi->mode, type, aclp); return (0); } if (error != 0) return (error); if (size > sizeof(buf)) return (EINTEGRITY); aiov.iov_base = buf; aiov.iov_len = size; auio.uio_iov = &aiov; auio.uio_iovcnt = 1; auio.uio_offset = 0; auio.uio_resid = size; auio.uio_segflg = UIO_SYSSPACE; auio.uio_rw = UIO_READ; auio.uio_td = curthread; error = erofs_getxattr(vp, EXTATTR_NAMESPACE_SYSTEM, xattr_name, &auio, NULL); if (error != 0) return (error); if (auio.uio_resid != 0) return (EINTEGRITY); return (erofs_posix_acl_from_xattr(buf, size, vi->mode, type, aclp)); }