/* 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 "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)) static int erofs_xattr_backing_size(struct erofs_mount *em, struct erofs_node *backing_en, uint64_t *sizep) { if (backing_en != NULL) { *sizep = backing_en->size; return (0); } if (em->blocks > (UINT64_MAX >> em->block_bits)) return (EOVERFLOW); *sizep = em->blocks << em->block_bits; return (0); } static int erofs_xattr_read_backing(struct erofs_mount *em, struct erofs_node *backing_en, uint64_t off, size_t len, void **bufp) { uint64_t backing_size; int error; error = erofs_xattr_backing_size(em, 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) return (erofs_read_data(em, backing_en, off, len, bufp)); if (off > INT64_MAX) return (EOVERFLOW); return (erofs_bread(em, (off_t)off, len, bufp)); } /* * 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_mount *em, struct erofs_node *backing_en, uint64_t *offp, void **bufp, size_t *lenp) { uint16_t raw_len; void *buf, *hdrbuf; uint64_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(em, backing_en, off, sizeof(raw_len), &hdrbuf); if (error != 0) return (error); raw_len = le16toh(*(uint16_t *)hdrbuf); erofs_brelse(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(em, backing_en, off + sizeof(raw_len), len, &buf); if (error != 0) return (error); *offp = off + sizeof(raw_len) + len; *bufp = buf; *lenp = len; return (0); } void erofs_xattr_prefixes_cleanup(struct erofs_mount *em) { if (em->xattr_prefixes == NULL) return; for (uint8_t i = 0; i < em->xattr_prefix_count; i++) free(em->xattr_prefixes[i].infix, M_EROFS); free(em->xattr_prefixes, M_EROFS); em->xattr_prefixes = NULL; } int erofs_xattr_prefixes_init(struct erofs_mount *em) { struct erofs_xattr_long_prefix *prefix = NULL; struct erofs_node packed_en, *prefix_en; uint64_t off; size_t infix_len, len; int error; if ((em->feature_incompat & EROFS_FEATURE_INCOMPAT_XATTR_PREFIXES) == 0 || em->xattr_prefix_count == 0) return (0); prefix_en = NULL; if ((em->feature_compat & EROFS_FEATURE_COMPAT_PLAIN_XATTR_PFX) == 0) { if (erofs_sb_has_metabox(em)) { if (em->metabox_en == NULL) return (EINTEGRITY); prefix_en = em->metabox_en; } else if (em->packed_inode != NULL) { prefix_en = em->packed_inode; } else if (em->packed_nid != 0) { error = erofs_read_inode(em, em->packed_nid, &packed_en); if (error != 0) return (error); if (packed_en.vtype != VREG) return (EINTEGRITY); prefix_en = &packed_en; } } em->xattr_prefixes = malloc(sizeof(*em->xattr_prefixes) * em->xattr_prefix_count, M_EROFS, M_WAITOK | M_ZERO); off = (uint64_t)em->xattr_prefix_start << 2; for (uint8_t i = 0; i < em->xattr_prefix_count; i++) { error = erofs_xattr_read_metadata(em, prefix_en, &off, (void **)&prefix, &len); if (error != 0) goto fail; infix_len = len - sizeof(*prefix); em->xattr_prefixes[i].base_index = prefix->base_index; em->xattr_prefixes[i].infix_len = infix_len; em->xattr_prefixes[i].infix = malloc(infix_len + 1, M_EROFS, M_WAITOK); memcpy(em->xattr_prefixes[i].infix, prefix->infix, infix_len); em->xattr_prefixes[i].infix[infix_len] = '\0'; erofs_brelse(prefix); prefix = NULL; } return (0); fail: if (prefix != NULL) erofs_brelse(prefix); erofs_xattr_prefixes_cleanup(em); return (error); } 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(struct erofs_mount *em, struct erofs_node *en, char **bodyp, struct erofs_xattr_ibody_header **ihp, size_t *header_sizep) { struct erofs_xattr_ibody_header *ih; char *body; uint64_t body_off; size_t header_size; int error; if (en->xattr_isize < sizeof(*ih)) return (EINTEGRITY); if (en->inode_off > UINT64_MAX - en->inode_isize) return (EINTEGRITY); body_off = en->inode_off + en->inode_isize; error = erofs_xattr_read_backing(em, erofs_nid_in_metabox(en->nid) ? em->metabox_en : NULL, body_off, en->xattr_isize, (void **)&body); if (error != 0) return (error); ih = (struct erofs_xattr_ibody_header *)body; if (en->xattr_isize == sizeof(*ih)) { error = EOPNOTSUPP; goto fail; } header_size = sizeof(*ih) + sizeof(uint32_t) * ih->h_shared_count; if (header_size > en->xattr_isize) { error = EINTEGRITY; goto fail; } *bodyp = body; *ihp = ih; *header_sizep = header_size; return (0); fail: erofs_brelse(body); 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 (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_mount *em, 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 (em->xattr_prefixes == NULL) return (ENOATTR); prefix_id = entry->e_name_index & EROFS_XATTR_LONG_PREFIX_MASK; if (prefix_id >= em->xattr_prefix_count) return (ENOATTR); prefix = &em->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_mount *em, uint32_t shared_id, uint64_t *phys_offp) { uint64_t base, relative; if (em->xattr_blkaddr > (UINT64_MAX >> em->block_bits)) return (EOVERFLOW); base = (uint64_t)em->xattr_blkaddr << em->block_bits; 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_mount *em, uint32_t shared_id, struct erofs_xattr_entry **entryp, size_t *entry_sizep, size_t *value_sizep) { struct erofs_xattr_entry *entry; struct erofs_node *backing_en; void *hdrbuf; uint64_t off; size_t entry_size, value_size; int error; backing_en = erofs_sb_has_shared_ea_in_metabox(em) ? em->metabox_en : NULL; if (erofs_sb_has_shared_ea_in_metabox(em) && backing_en == NULL) return (EINTEGRITY); error = erofs_xattr_shared_entry_offset(em, shared_id, &off); if (error != 0) return (error); error = erofs_xattr_read_backing(em, backing_en, off, sizeof(*entry), &hdrbuf); if (error != 0) return (error); entry = hdrbuf; value_size = le16toh(entry->e_value_size); entry_size = erofs_xattr_entry_size(entry); erofs_brelse(hdrbuf); error = erofs_xattr_read_backing(em, backing_en, off, entry_size, (void **)entryp); if (error != 0) return (error); if (entry_sizep != NULL) *entry_sizep = entry_size; if (value_sizep != NULL) *value_sizep = value_size; return (0); } static int erofs_inode_has_noacl(struct erofs_mount *em, struct erofs_node *en, bool *noaclp) { struct erofs_xattr_ibody_header *ih; struct erofs_node *backing_en; uint64_t body_off; uint32_t name_filter; int error; *noaclp = false; if (en->xattr_isize < sizeof(*ih)) { *noaclp = true; return (0); } if (!erofs_sb_has_xattr_filter(em)) return (0); if (en->inode_off > UINT64_MAX - en->inode_isize) return (EINTEGRITY); body_off = en->inode_off + en->inode_isize; backing_en = erofs_nid_in_metabox(en->nid) ? em->metabox_en : NULL; error = erofs_xattr_read_backing(em, backing_en, body_off, sizeof(*ih), (void **)&ih); if (error != 0) return (error); name_filter = le32toh(ih->h_name_filter); erofs_brelse(ih); *noaclp = (name_filter & EROFS_XATTR_FILTER_POSIX_ACL) == EROFS_XATTR_FILTER_POSIX_ACL; return (0); } static void erofs_acl_from_mode(struct erofs_node *en, 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 = (en->mode >> 6) & ACL_PERM_BITS; aclp->acl_entry[1].ae_tag = ACL_GROUP_OBJ; aclp->acl_entry[1].ae_id = ACL_UNDEFINED_ID; aclp->acl_entry[1].ae_perm = (en->mode >> 3) & ACL_PERM_BITS; aclp->acl_entry[2].ae_tag = ACL_OTHER; aclp->acl_entry[2].ae_id = ACL_UNDEFINED_ID; aclp->acl_entry[2].ae_perm = en->mode & ACL_PERM_BITS; } struct erofs_xattr_iter { struct erofs_mount *em; struct erofs_node *en; int attrnamespace; const char *name; size_t name_len; struct uio *uio; size_t *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->em, 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_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->em, 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_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->en->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_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->em, shared_id, &entry, NULL, 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); erofs_brelse(entry); 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_mount *em; struct erofs_node *en; struct erofs_xattr_ibody_header *ih; struct erofs_xattr_iter it; char *body; size_t header_size, name_len; int error; em = MTOE(vp->v_mount); en = VTOE(vp); if (name == NULL || name[0] == '\0') return (EINVAL); name_len = strlen(name); if (name_len > EROFS_NAME_LEN) return (EINVAL); if (en->xattr_isize == 0) return (ENOATTR); error = erofs_xattr_load_body(em, en, &body, &ih, &header_size); if (error != 0) return (error); it.em = em; it.en = en; it.attrnamespace = attrnamespace; it.name = name; it.name_len = name_len; it.uio = uio; it.sizep = sizep; error = erofs_xattr_iter_inline(&it, body, header_size, true); if (error == ENOATTR) error = erofs_xattr_iter_shared(&it, ih, true); erofs_brelse(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_mount *em; struct erofs_node *en; struct erofs_xattr_ibody_header *ih; struct erofs_xattr_iter it; char *body; size_t header_size; int error; em = MTOE(vp->v_mount); en = VTOE(vp); if (sizep != NULL) *sizep = 0; if (en->xattr_isize == 0) return (0); error = erofs_xattr_load_body(em, en, &body, &ih, &header_size); if (error != 0) return (error); it.em = em; it.en = en; it.attrnamespace = attrnamespace; it.name = NULL; it.name_len = 0; it.uio = uio; it.sizep = sizep; error = erofs_xattr_iter_inline(&it, body, header_size, false); if (error == 0) error = erofs_xattr_iter_shared(&it, ih, false); erofs_brelse(body); return (error); } int erofs_get_acl(struct vnode *vp, acl_type_t type, struct acl *aclp) { struct erofs_mount *em; struct erofs_node *en; const char *xattr_name; struct uio auio; struct iovec aiov; struct posix_acl_xattr_header hdr; struct posix_acl_xattr_entry entry; uint8_t buf[sizeof(hdr) + sizeof(entry) * ACL_MAX_ENTRIES]; size_t size; uint32_t id; bool noacl; int error, count, i, j, phase; em = MTOE(vp->v_mount); if (!test_opt(&em->opt, POSIX_ACL)) return (EOPNOTSUPP); en = 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(em, en, &noacl); if (error != 0) return (error); if (noacl) { erofs_acl_from_mode(en, type, aclp); return (0); } error = erofs_getxattr(vp, EXTATTR_NAMESPACE_SYSTEM, xattr_name, NULL, &size); if (error == ENOATTR) { erofs_acl_from_mode(en, 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); 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(en, 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); }