// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/ext2/xattr.c
*
* Copyright (C) 2001-2003 Andreas Gruenbacher <agruen@suse.de>
*
* Fix by Harrison Xing <harrison@mountainviewdata.com>.
* Extended attributes for symlinks and special files added per
* suggestion of Luka Renko <luka.renko@hermes.si>.
* xattr consolidation Copyright (c) 2004 James Morris <jmorris@redhat.com>,
* Red Hat Inc.
*
*/
/*
* Extended attributes are stored on disk blocks allocated outside of
* any inode. The i_file_acl field is then made to point to this allocated
* block. If all extended attributes of an inode are identical, these
* inodes may share the same extended attribute block. Such situations
* are automatically detected by keeping a cache of recent attribute block
* numbers and hashes over the block's contents in memory.
*
*
* Extended attribute block layout:
*
* +------------------+
* | header |
* | entry 1 | |
* | entry 2 | | growing downwards
* | entry 3 | v
* | four null bytes |
* | . . . |
* | value 1 | ^
* | value 3 | | growing upwards
* | value 2 | |
* +------------------+
*
* The block header is followed by multiple entry descriptors. These entry
* descriptors are variable in size, and aligned to EXT2_XATTR_PAD
* byte boundaries. The entry descriptors are sorted by attribute name,
* so that two extended attribute blocks can be compared efficiently.
*
* Attribute values are aligned to the end of the block, stored in
* no specific order. They are also padded to EXT2_XATTR_PAD byte
* boundaries. No additional gaps are left between them.
*
* Locking strategy
* ----------------
* EXT2_I(inode)->i_file_acl is protected by EXT2_I(inode)->xattr_sem.
* EA blocks are only changed if they are exclusive to an inode, so
* holding xattr_sem also means that nothing but the EA block's reference
* count will change. Multiple writers to an EA block are synchronized
* by the bh lock. No more than a single bh lock is held at any time
* to avoid deadlocks.
*/
#include <linux/buffer_head.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/mbcache.h>
#include <linux/quotaops.h>
#include <linux/rwsem.h>
#include <linux/security.h>
#include "ext2.h"
#include "xattr.h"
#include "acl.h"
#define HDR(bh) ((struct ext2_xattr_header *)((bh)->b_data))
#define ENTRY(ptr) ((struct ext2_xattr_entry *)(ptr))
#define FIRST_ENTRY(bh) ENTRY(HDR(bh)+1)
#define IS_LAST_ENTRY(entry) (*(__u32 *)(entry) == 0)
#ifdef EXT2_XATTR_DEBUG
# define ea_idebug(inode, f...) do { \
printk(KERN_DEBUG "inode %s:%ld: ", \
inode->i_sb->s_id, inode->i_ino); \
printk(f); \
printk("\n"); \
} while (0)
# define ea_bdebug(bh, f...) do { \
printk(KERN_DEBUG "block %pg:%lu: ", \
bh->b_bdev, (unsigned long) bh->b_blocknr); \
printk(f); \
printk("\n"); \
} while (0)
#else
# define ea_idebug(f...)
# define ea_bdebug(f...)
#endif
static int ext2_xattr_set2(struct inode *, struct buffer_head *,
struct ext2_xattr_header *);
static int ext2_xattr_cache_insert(struct mb_cache *, struct buffer_head *);
static struct buffer_head *ext2_xattr_cache_find(struct inode *,
struct ext2_xattr_header *);
static void ext2_xattr_rehash(struct ext2_xattr_header *,
struct ext2_xattr_entry *);
static const struct xattr_handler *ext2_xattr_handler_map[] = {
[EXT2_XATTR_INDEX_USER] = &ext2_xattr_user_handler,
#ifdef CONFIG_EXT2_FS_POSIX_ACL
[EXT2_XATTR_INDEX_POSIX_ACL_ACCESS] = &posix_acl_access_xattr_handler,
[EXT2_XATTR_INDEX_POSIX_ACL_DEFAULT] = &posix_acl_default_xattr_handler,
#endif
[EXT2_XATTR_INDEX_TRUSTED] = &ext2_xattr_trusted_handler,
#ifdef CONFIG_EXT2_FS_SECURITY
[EXT2_XATTR_INDEX_SECURITY] = &ext2_xattr_security_handler,
#endif
};
const struct xattr_handler *ext2_xattr_handlers[] = {
&ext2_xattr_user_handler,
&ext2_xattr_trusted_handler,
#ifdef CONFIG_EXT2_FS_POSIX_ACL
&posix_acl_access_xattr_handler,
&posix_acl_default_xattr_handler,
#endif
#ifdef CONFIG_EXT2_FS_SECURITY
&ext2_xattr_security_handler,
#endif
NULL
};
#define EA_BLOCK_CACHE(inode) (EXT2_SB(inode->i_sb)->s_ea_block_cache)
static inline const struct xattr_handler *
ext2_xattr_handler(int name_index)
{
const struct xattr_handler *handler = NULL;
if (name_index > 0 && name_index < ARRAY_SIZE(ext2_xattr_handler_map))
handler = ext2_xattr_handler_map[name_index];
return handler;
}
static bool
ext2_xattr_header_valid(struct ext2_xattr_header *header)
{
if (header->h_magic != cpu_to_le32(EXT2_XATTR_MAGIC) ||
header->h_blocks != cpu_to_le32(1))
return false;
return true;
}
static bool
ext2_xattr_entry_valid(struct ext2_xattr_entry *entry,
char *end, size_t end_offs)
{
struct ext2_xattr_entry *next;
size_t size;
next = EXT2_XATTR_NEXT(entry);
if ((char *)next >= end)
return false;
if (entry->e_value_block != 0)
return false;
size = le32_to_cpu(entry->e_value_size);
if (size > end_offs ||
le16_to_cpu(entry->e_value_offs) + size > end_offs)
return false;
return true;
}
static int
ext2_xattr_cmp_entry(int name_index, size_t name_len, const char *name,
struct ext2_xattr_entry *entry)
{
int cmp;
cmp = name_index - entry->e_name_index;
if (!cmp)
cmp = name_len - entry->e_name_len;
if (!cmp)
cmp = memcmp(name, entry->e_name, na