mirror of
https://github.com/linux-apfs/linux-apfs-oot.git
synced 2026-08-18 02:19:52 -07:00
325 lines
8.7 KiB
C
325 lines
8.7 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|||
|
|
/*
|
||
|
|
* linux/fs/apfs/xattr.c
|
||
|
|
*
|
||
|
|
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <linux/buffer_head.h>
|
||
|
|
#include <linux/xattr.h>
|
||
|
|
#include "apfs.h"
|
||
|
|
#include "btree.h"
|
||
|
|
#include "extents.h"
|
||
|
|
#include "key.h"
|
||
|
|
#include "super.h"
|
||
|
|
#include "node.h"
|
||
|
|
#include "message.h"
|
||
|
|
#include "xattr.h"
|
||
|
|
|
||
|
|
/**
|
||
|
|
* apfs_xattr_from_query - Read the xattr record found by a successful query
|
||
|
|
* @query: the query that found the record
|
||
|
|
* @xattr: Return parameter. The xattr record found.
|
||
|
|
*
|
||
|
|
* Reads the xattr record into @xattr and performs some basic sanity checks
|
||
|
|
* as a protection against crafted filesystems. Returns 0 on success or
|
||
|
|
* -EFSCORRUPTED otherwise.
|
||
|
|
*
|
||
|
|
* The caller must not free @query while @xattr is in use, because @xattr->name
|
||
|
|
* and @xattr->xdata point to data on disk.
|
||
|
|
*/
|
||
|
|
static int apfs_xattr_from_query(struct apfs_query *query,
|
||
|
|
struct apfs_xattr *xattr)
|
||
|
|
{
|
||
|
|
struct apfs_xattr_val *xattr_val;
|
||
|
|
struct apfs_xattr_key *xattr_key;
|
||
|
|
char *raw = query->node->object.bh->b_data;
|
||
|
|
int datalen = query->len - sizeof(*xattr_val);
|
||
|
|
int namelen = query->key_len - sizeof(*xattr_key);
|
||
|
|
|
||
|
|
if (namelen < 1 || datalen < 0)
|
||
|
|
return -EFSCORRUPTED;
|
||
|
|
|
||
|
|
xattr_val = (struct apfs_xattr_val *)(raw + query->off);
|
||
|
|
xattr_key = (struct apfs_xattr_key *)(raw + query->key_off);
|
||
|
|
|
||
|
|
if (namelen != le16_to_cpu(xattr_key->name_len))
|
||
|
|
return -EFSCORRUPTED;
|
||
|
|
|
||
|
|
/* The xattr name must be NULL-terminated */
|
||
|
|
if (xattr_key->name[namelen - 1] != 0)
|
||
|
|
return -EFSCORRUPTED;
|
||
|
|
|
||
|
|
xattr->has_dstream = le16_to_cpu(xattr_val->flags) &
|
||
|
|
APFS_XATTR_DATA_STREAM;
|
||
|
|
|
||
|
|
if (xattr->has_dstream && datalen != sizeof(struct apfs_xattr_dstream))
|
||
|
|
return -EFSCORRUPTED;
|
||
|
|
if (!xattr->has_dstream && datalen != le16_to_cpu(xattr_val->xdata_len))
|
||
|
|
return -EFSCORRUPTED;
|
||
|
|
|
||
|
|
xattr->name = xattr_key->name;
|
||
|
|
xattr->name_len = namelen - 1; /* Don't count the NULL termination */
|
||
|
|
xattr->xdata = xattr_val->xdata;
|
||
|
|
xattr->xdata_len = datalen;
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* apfs_xattr_extents_read - Read the value of a xattr from its extents
|
||
|
|
* @parent: inode the attribute belongs to
|
||
|
|
* @xattr: the xattr structure
|
||
|
|
* @buffer: where to copy the attribute value
|
||
|
|
* @size: size of @buffer
|
||
|
|
*
|
||
|
|
* Copies the value of @xattr to @buffer, if provided. If @buffer is NULL, just
|
||
|
|
* computes the size of the buffer required.
|
||
|
|
*
|
||
|
|
* Returns the number of bytes used/required, or a negative error code in case
|
||
|
|
* of failure.
|
||
|
|
*/
|
||
|
|
static int apfs_xattr_extents_read(struct inode *parent,
|
||
|
|
struct apfs_xattr *xattr,
|
||
|
|
void *buffer, size_t size)
|
||
|
|
{
|
||
|
|
struct super_block *sb = parent->i_sb;
|
||
|
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||
|
|
struct apfs_key key;
|
||
|
|
struct apfs_query *query;
|
||
|
|
struct apfs_xattr_dstream *xdata;
|
||
|
|
u64 extent_id;
|
||
|
|
int length;
|
||
|
|
int ret;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
xdata = (struct apfs_xattr_dstream *) xattr->xdata;
|
||
|
|
length = le64_to_cpu(xdata->dstream.size);
|
||
|
|
if (length < 0 || length < le64_to_cpu(xdata->dstream.size))
|
||
|
|
return -E2BIG;
|
||
|
|
|
||
|
|
if (!buffer) /* All we want is the length */
|
||
|
|
return length;
|
||
|
|
if (length > size) /* xattr won't fit in the buffer */
|
||
|
|
return -ERANGE;
|
||
|
|
|
||
|
|
extent_id = le64_to_cpu(xdata->xattr_obj_id);
|
||
|
|
/* We will read all the extents, starting with the last one */
|
||
|
|
apfs_init_file_extent_key(extent_id, 0 /* offset */, &key);
|
||
|
|
|
||
|
|
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
||
|
|
if (!query)
|
||
|
|
return -ENOMEM;
|
||
|
|
query->key = &key;
|
||
|
|
query->flags = APFS_QUERY_CAT | APFS_QUERY_MULTIPLE | APFS_QUERY_EXACT;
|
||
|
|
|
||
|
|
/*
|
||
|
|
* The logic in this loop would allow a crafted filesystem with a large
|
||
|
|
* number of redundant extents to become stuck for a long time. We use
|
||
|
|
* the xattr length to put a limit on the number of iterations.
|
||
|
|
*/
|
||
|
|
ret = -EFSCORRUPTED;
|
||
|
|
for (i = 0; i < (length >> parent->i_blkbits) + 2; i++) {
|
||
|
|
struct apfs_file_extent ext;
|
||
|
|
u64 block_count, file_off;
|
||
|
|
int err;
|
||
|
|
int j;
|
||
|
|
|
||
|
|
err = apfs_btree_query(sb, &query);
|
||
|
|
if (err == -ENODATA) { /* No more records to search */
|
||
|
|
ret = length;
|
||
|
|
goto done;
|
||
|
|
}
|
||
|
|
if (err) {
|
||
|
|
ret = err;
|
||
|
|
goto done;
|
||
|
|
}
|
||
|
|
|
||
|
|
err = apfs_extent_from_query(query, &ext);
|
||
|
|
if (err) {
|
||
|
|
apfs_alert(sb, "bad extent for xattr in inode 0x%llx",
|
||
|
|
(unsigned long long) parent->i_ino);
|
||
|
|
ret = err;
|
||
|
|
goto done;
|
||
|
|
}
|
||
|
|
|
||
|
|
block_count = ext.len >> sb->s_blocksize_bits;
|
||
|
|
file_off = ext.logical_addr;
|
||
|
|
for (j = 0; j < block_count; ++j) {
|
||
|
|
struct buffer_head *bh;
|
||
|
|
int bytes;
|
||
|
|
|
||
|
|
if (length <= file_off) /* Read the whole extent */
|
||
|
|
break;
|
||
|
|
bytes = min(sb->s_blocksize,
|
||
|
|
(unsigned long)(length - file_off));
|
||
|
|
|
||
|
|
bh = sb_bread(sb, ext.phys_block_num + j);
|
||
|
|
if (!bh) {
|
||
|
|
ret = -EIO;
|
||
|
|
goto done;
|
||
|
|
}
|
||
|
|
memcpy(buffer + file_off, bh->b_data, bytes);
|
||
|
|
brelse(bh);
|
||
|
|
file_off = file_off + bytes;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
done:
|
||
|
|
apfs_free_query(sb, query);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* apfs_xattr_inline_read - Read the value of an inline xattr
|
||
|
|
* @parent: inode the attribute belongs to
|
||
|
|
* @xattr: the xattr structure
|
||
|
|
* @buffer: where to copy the attribute value
|
||
|
|
* @size: size of @buffer
|
||
|
|
*
|
||
|
|
* Copies the inline value of @xattr to @buffer, if provided. If @buffer is
|
||
|
|
* NULL, just computes the size of the buffer required.
|
||
|
|
*
|
||
|
|
* Returns the number of bytes used/required, or a negative error code in case
|
||
|
|
* of failure.
|
||
|
|
*/
|
||
|
|
static int apfs_xattr_inline_read(struct inode *parent,
|
||
|
|
struct apfs_xattr *xattr,
|
||
|
|
void *buffer, size_t size)
|
||
|
|
{
|
||
|
|
int length = xattr->xdata_len;
|
||
|
|
|
||
|
|
if (!buffer) /* All we want is the length */
|
||
|
|
return length;
|
||
|
|
if (length > size) /* xattr won't fit in the buffer */
|
||
|
|
return -ERANGE;
|
||
|
|
memcpy(buffer, xattr->xdata, length);
|
||
|
|
return length;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* apfs_xattr_get - Find and read a named attribute
|
||
|
|
* @inode: inode the attribute belongs to
|
||
|
|
* @name: name of the attribute
|
||
|
|
* @buffer: where to copy the attribute value
|
||
|
|
* @size: size of @buffer
|
||
|
|
*
|
||
|
|
* Finds an extended attribute and copies its value to @buffer, if provided. If
|
||
|
|
* @buffer is NULL, just computes the size of the buffer required.
|
||
|
|
*
|
||
|
|
* Returns the number of bytes used/required, or a negative error code in case
|
||
|
|
* of failure.
|
||
|
|
*/
|
||
|
|
int apfs_xattr_get(struct inode *inode, const char *name, void *buffer,
|
||
|
|
size_t size)
|
||
|
|
{
|
||
|
|
struct super_block *sb = inode->i_sb;
|
||
|
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||
|
|
struct apfs_key key;
|
||
|
|
struct apfs_query *query;
|
||
|
|
struct apfs_xattr xattr;
|
||
|
|
u64 cnid = inode->i_ino;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
apfs_init_xattr_key(cnid, name, &key);
|
||
|
|
|
||
|
|
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
||
|
|
if (!query)
|
||
|
|
return -ENOMEM;
|
||
|
|
query->key = &key;
|
||
|
|
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
|
||
|
|
|
||
|
|
ret = apfs_btree_query(sb, &query);
|
||
|
|
if (ret)
|
||
|
|
goto done;
|
||
|
|
|
||
|
|
ret = apfs_xattr_from_query(query, &xattr);
|
||
|
|
if (ret) {
|
||
|
|
apfs_alert(sb, "bad xattr record in inode 0x%llx", cnid);
|
||
|
|
goto done;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (xattr.has_dstream)
|
||
|
|
ret = apfs_xattr_extents_read(inode, &xattr, buffer, size);
|
||
|
|
else
|
||
|
|
ret = apfs_xattr_inline_read(inode, &xattr, buffer, size);
|
||
|
|
|
||
|
|
done:
|
||
|
|
apfs_free_query(sb, query);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int apfs_xattr_osx_get(const struct xattr_handler *handler,
|
||
|
|
struct dentry *unused, struct inode *inode,
|
||
|
|
const char *name, void *buffer, size_t size)
|
||
|
|
{
|
||
|
|
/* Ignore the fake 'osx' prefix */
|
||
|
|
return apfs_xattr_get(inode, name, buffer, size);
|
||
|
|
}
|
||
|
|
|
||
|
|
static const struct xattr_handler apfs_xattr_osx_handler = {
|
||
|
|
.prefix = XATTR_MAC_OSX_PREFIX,
|
||
|
|
.get = apfs_xattr_osx_get,
|
||
|
|
};
|
||
|
|
|
||
|
|
/* On-disk xattrs have no namespace; use a fake 'osx' prefix in the kernel */
|
||
|
|
const struct xattr_handler *apfs_xattr_handlers[] = {
|
||
|
|
&apfs_xattr_osx_handler,
|
||
|
|
NULL
|
||
|
|
};
|
||
|
|
|
||
|
|
ssize_t apfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
|
||
|
|
{
|
||
|
|
struct inode *inode = d_inode(dentry);
|
||
|
|
struct super_block *sb = inode->i_sb;
|
||
|
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||
|
|
struct apfs_key key;
|
||
|
|
struct apfs_query *query;
|
||
|
|
u64 cnid = inode->i_ino;
|
||
|
|
size_t free = size;
|
||
|
|
ssize_t ret;
|
||
|
|
|
||
|
|
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
||
|
|
if (!query)
|
||
|
|
return -ENOMEM;
|
||
|
|
|
||
|
|
/* We want all the xattrs for the cnid, regardless of the name */
|
||
|
|
apfs_init_xattr_key(cnid, NULL /* name */, &key);
|
||
|
|
query->key = &key;
|
||
|
|
query->flags = APFS_QUERY_CAT | APFS_QUERY_MULTIPLE | APFS_QUERY_EXACT;
|
||
|
|
|
||
|
|
while (1) {
|
||
|
|
struct apfs_xattr xattr;
|
||
|
|
|
||
|
|
ret = apfs_btree_query(sb, &query);
|
||
|
|
if (ret == -ENODATA) { /* Got all the xattrs */
|
||
|
|
ret = size - free;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (ret)
|
||
|
|
break;
|
||
|
|
|
||
|
|
ret = apfs_xattr_from_query(query, &xattr);
|
||
|
|
if (ret) {
|
||
|
|
apfs_alert(sb, "bad xattr key in inode %llx", cnid);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (buffer) {
|
||
|
|
/* Prepend the fake 'osx' prefix before listing */
|
||
|
|
if (xattr.name_len + XATTR_MAC_OSX_PREFIX_LEN + 1 >
|
||
|
|
free) {
|
||
|
|
ret = -ERANGE;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
memcpy(buffer, XATTR_MAC_OSX_PREFIX,
|
||
|
|
XATTR_MAC_OSX_PREFIX_LEN);
|
||
|
|
buffer += XATTR_MAC_OSX_PREFIX_LEN;
|
||
|
|
memcpy(buffer, xattr.name, xattr.name_len + 1);
|
||
|
|
buffer += xattr.name_len + 1;
|
||
|
|
}
|
||
|
|
free -= xattr.name_len + XATTR_MAC_OSX_PREFIX_LEN + 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
apfs_free_query(sb, query);
|
||
|
|
return ret;
|
||
|
|
}
|