Files
2026-08-18 09:38:06 +02:00

1273 lines
34 KiB
C

/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2017-2018 HUAWEI, Inc.
* https://www.huawei.com/
* Copyright (C) 2021-2022, Alibaba Cloud
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/dirent.h>
#include <sys/extattr.h>
#include <sys/kernel.h>
#include <sys/libkern.h>
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/acl.h>
#include <machine/atomic.h>
#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));
}