mirror of
https://github.com/linux-apfs/linux-apfs.git
synced 2026-08-18 02:22:47 -07:00
Work on the fsck tool seems to have settled an old doubt about the order of dentry keys with the same hash. The name length is not considered at all, despite sharing the same field with the hash; the order is decided by comparing the strings of the original unnormalized names. Because of our incorrect assumptions, some files are listed but don't open. This is relatively rare though, since it depends on collisions of the 22-bit hash happening inside a single directory. To fix it, split the APFS_QUERY_MULTIPLE flag into APFS_QUERY_ANY_NAME and APFS_QUERY_ANY_NUMBER, so that queries can return all records with a given hash and let apfs_inode_by_name() decide which one is correct. Also mask away the filemane length. All testing has been for ascii filenames in case-insensitive volumes, so a small degree of uncertainty remains. Signed-off-by: Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
423 lines
12 KiB
C
423 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* linux/fs/apfs/node.c
|
|
*
|
|
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
|
*/
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/buffer_head.h>
|
|
#include "apfs.h"
|
|
#include "btree.h"
|
|
#include "key.h"
|
|
#include "message.h"
|
|
#include "node.h"
|
|
#include "object.h"
|
|
#include "super.h"
|
|
|
|
/**
|
|
* apfs_node_is_valid - Check basic sanity of the node index
|
|
* @sb: filesystem superblock
|
|
* @node: node to check
|
|
*
|
|
* Verifies that the node index fits in a single block, and that the number
|
|
* of records fits in the index. Without this check a crafted filesystem could
|
|
* pretend to have too many records, and calls to apfs_node_locate_key() and
|
|
* apfs_node_locate_data() would read beyond the limits of the node.
|
|
*/
|
|
static bool apfs_node_is_valid(struct super_block *sb,
|
|
struct apfs_node *node)
|
|
{
|
|
int records = node->records;
|
|
int index_size = node->key - sizeof(struct apfs_btree_node_phys);
|
|
int entry_size;
|
|
|
|
if (!records) /* Empty nodes could keep a multiple query spinning */
|
|
return false;
|
|
|
|
if (node->key > sb->s_blocksize)
|
|
return false;
|
|
|
|
entry_size = (apfs_node_has_fixed_kv_size(node)) ?
|
|
sizeof(struct apfs_kvoff) : sizeof(struct apfs_kvloc);
|
|
|
|
return records * entry_size <= index_size;
|
|
}
|
|
|
|
static void apfs_node_release(struct kref *kref)
|
|
{
|
|
struct apfs_node *node =
|
|
container_of(kref, struct apfs_node, refcount);
|
|
|
|
brelse(node->object.bh);
|
|
kfree(node);
|
|
}
|
|
|
|
void apfs_node_get(struct apfs_node *node)
|
|
{
|
|
kref_get(&node->refcount);
|
|
}
|
|
|
|
void apfs_node_put(struct apfs_node *node)
|
|
{
|
|
kref_put(&node->refcount, apfs_node_release);
|
|
}
|
|
|
|
/**
|
|
* apfs_read_node - Read a node header from disk
|
|
* @sb: filesystem superblock
|
|
* @block: number of the block where the node is stored
|
|
*
|
|
* Returns ERR_PTR in case of failure, otherwise return a pointer to the
|
|
* resulting apfs_node structure with the initial reference taken.
|
|
*
|
|
* For now we assume the node has not been read before.
|
|
*/
|
|
struct apfs_node *apfs_read_node(struct super_block *sb, u64 block)
|
|
{
|
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
|
struct buffer_head *bh;
|
|
struct apfs_btree_node_phys *raw;
|
|
struct apfs_node *node;
|
|
|
|
bh = sb_bread(sb, block);
|
|
if (!bh) {
|
|
apfs_err(sb, "unable to read node");
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
raw = (struct apfs_btree_node_phys *) bh->b_data;
|
|
|
|
node = kmalloc(sizeof(*node), GFP_KERNEL);
|
|
if (!node) {
|
|
brelse(bh);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
node->flags = le16_to_cpu(raw->btn_flags);
|
|
node->records = le32_to_cpu(raw->btn_nkeys);
|
|
node->key = sizeof(*raw) + le16_to_cpu(raw->btn_table_space.off)
|
|
+ le16_to_cpu(raw->btn_table_space.len);
|
|
node->free = node->key + le16_to_cpu(raw->btn_free_space.off);
|
|
node->data = node->free + le16_to_cpu(raw->btn_free_space.len);
|
|
|
|
node->object.sb = sb;
|
|
node->object.block_nr = block;
|
|
node->object.oid = le64_to_cpu(raw->btn_o.o_oid);
|
|
node->object.bh = bh;
|
|
|
|
kref_init(&node->refcount);
|
|
|
|
if (sbi->s_flags & APFS_CHECK_NODES &&
|
|
!apfs_obj_verify_csum(sb, &raw->btn_o)) {
|
|
apfs_alert(sb, "bad checksum for node in block 0x%llx", block);
|
|
apfs_node_put(node);
|
|
return ERR_PTR(-EFSBADCRC);
|
|
}
|
|
if (!apfs_node_is_valid(sb, node)) {
|
|
apfs_alert(sb, "bad node in block 0x%llx", block);
|
|
apfs_node_put(node);
|
|
return ERR_PTR(-EFSCORRUPTED);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* apfs_node_locate_key - Locate the key of a node record
|
|
* @node: node to be searched
|
|
* @index: number of the entry to locate
|
|
* @off: on return will hold the offset in the block
|
|
*
|
|
* Returns the length of the key, or 0 in case of failure. The function checks
|
|
* that this length fits within the block; callers must use the returned value
|
|
* to make sure they never operate outside its bounds.
|
|
*/
|
|
static int apfs_node_locate_key(struct apfs_node *node, int index, int *off)
|
|
{
|
|
struct super_block *sb = node->object.sb;
|
|
struct apfs_btree_node_phys *raw;
|
|
int len;
|
|
|
|
if (index >= node->records)
|
|
return 0;
|
|
|
|
raw = (struct apfs_btree_node_phys *)node->object.bh->b_data;
|
|
if (apfs_node_has_fixed_kv_size(node)) {
|
|
struct apfs_kvoff *entry;
|
|
|
|
entry = (struct apfs_kvoff *)raw->btn_data + index;
|
|
len = 16;
|
|
/* Translate offset in key area to offset in block */
|
|
*off = node->key + le16_to_cpu(entry->k);
|
|
} else {
|
|
/* These node types have variable length keys and data */
|
|
struct apfs_kvloc *entry;
|
|
|
|
entry = (struct apfs_kvloc *)raw->btn_data + index;
|
|
len = le16_to_cpu(entry->k.len);
|
|
/* Translate offset in key area to offset in block */
|
|
*off = node->key + le16_to_cpu(entry->k.off);
|
|
}
|
|
|
|
if (*off + len > sb->s_blocksize) {
|
|
/* Avoid out-of-bounds read if corrupted */
|
|
return 0;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* apfs_node_locate_data - Locate the data of a node record
|
|
* @node: node to be searched
|
|
* @index: number of the entry to locate
|
|
* @off: on return will hold the offset in the block
|
|
*
|
|
* Returns the length of the data, or 0 in case of failure. The function checks
|
|
* that this length fits within the block; callers must use the returned value
|
|
* to make sure they never operate outside its bounds.
|
|
*/
|
|
static int apfs_node_locate_data(struct apfs_node *node, int index, int *off)
|
|
{
|
|
struct super_block *sb = node->object.sb;
|
|
struct apfs_btree_node_phys *raw;
|
|
int len;
|
|
|
|
if (index >= node->records)
|
|
return 0;
|
|
|
|
raw = (struct apfs_btree_node_phys *)node->object.bh->b_data;
|
|
if (apfs_node_has_fixed_kv_size(node)) {
|
|
/* These node types have fixed length keys and data */
|
|
struct apfs_kvoff *entry;
|
|
|
|
entry = (struct apfs_kvoff *)raw->btn_data + index;
|
|
/* Node type decides length */
|
|
len = apfs_node_is_leaf(node) ? 16 : 8;
|
|
/*
|
|
* Data offsets are counted backwards from the end of the
|
|
* block, or from the beginning of the footer when it exists
|
|
*/
|
|
if (apfs_node_is_root(node)) /* has footer */
|
|
*off = sb->s_blocksize - sizeof(struct apfs_btree_info)
|
|
- le16_to_cpu(entry->v);
|
|
else
|
|
*off = sb->s_blocksize - le16_to_cpu(entry->v);
|
|
} else {
|
|
/* These node types have variable length keys and data */
|
|
struct apfs_kvloc *entry;
|
|
|
|
entry = (struct apfs_kvloc *)raw->btn_data + index;
|
|
len = le16_to_cpu(entry->v.len);
|
|
/*
|
|
* Data offsets are counted backwards from the end of the
|
|
* block, or from the beginning of the footer when it exists
|
|
*/
|
|
if (apfs_node_is_root(node)) /* has footer */
|
|
*off = sb->s_blocksize - sizeof(struct apfs_btree_info)
|
|
- le16_to_cpu(entry->v.off);
|
|
else
|
|
*off = sb->s_blocksize - le16_to_cpu(entry->v.off);
|
|
}
|
|
|
|
if (*off < 0 || *off + len > sb->s_blocksize) {
|
|
/* Avoid out-of-bounds read if corrupted */
|
|
return 0;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* apfs_key_from_query - Read the current key from a query structure
|
|
* @query: the query, with @query->key_off and @query->key_len already set
|
|
* @key: return parameter for the key
|
|
*
|
|
* Reads the key into @key and performs some basic sanity checks as a
|
|
* protection against crafted filesystems. Returns 0 on success or a
|
|
* negative error code otherwise.
|
|
*/
|
|
static int apfs_key_from_query(struct apfs_query *query, struct apfs_key *key)
|
|
{
|
|
struct super_block *sb = query->node->object.sb;
|
|
char *raw = query->node->object.bh->b_data;
|
|
void *raw_key = (void *)(raw + query->key_off);
|
|
int err = 0;
|
|
|
|
switch (query->flags & APFS_QUERY_TREE_MASK) {
|
|
case APFS_QUERY_CAT:
|
|
err = apfs_read_cat_key(raw_key, query->key_len, key);
|
|
break;
|
|
case APFS_QUERY_OMAP:
|
|
err = apfs_read_omap_key(raw_key, query->key_len, key);
|
|
break;
|
|
default:
|
|
/* Not implemented yet */
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
if (err) {
|
|
apfs_alert(sb, "bad node key in block 0x%llx",
|
|
query->node->object.block_nr);
|
|
}
|
|
|
|
/* A multiple query must ignore some of these fields */
|
|
if (query->flags & APFS_QUERY_ANY_NAME)
|
|
key->name = NULL;
|
|
if (query->flags & APFS_QUERY_ANY_NUMBER)
|
|
key->number = 0;
|
|
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* apfs_node_next - Find the next matching record in the current node
|
|
* @sb: filesystem superblock
|
|
* @query: multiple query in execution
|
|
*
|
|
* Returns 0 on success, -EAGAIN if the next record is in another node,
|
|
* -ENODATA if no more matching records exist, or another negative error
|
|
* code in case of failure.
|
|
*/
|
|
static int apfs_node_next(struct super_block *sb, struct apfs_query *query)
|
|
{
|
|
struct apfs_node *node = query->node;
|
|
struct apfs_key curr_key;
|
|
int cmp, err;
|
|
|
|
if (query->flags & APFS_QUERY_DONE)
|
|
/* Nothing left to search; the query failed */
|
|
return -ENODATA;
|
|
|
|
if (!query->index) /* The next record may be in another node */
|
|
return -EAGAIN;
|
|
--query->index;
|
|
|
|
query->key_len = apfs_node_locate_key(node, query->index,
|
|
&query->key_off);
|
|
err = apfs_key_from_query(query, &curr_key);
|
|
if (err)
|
|
return err;
|
|
|
|
cmp = apfs_keycmp(sb, &curr_key, query->key);
|
|
|
|
if (cmp > 0) /* Records are out of order */
|
|
return -EFSCORRUPTED;
|
|
|
|
if (cmp != 0 && apfs_node_is_leaf(node) &&
|
|
query->flags & APFS_QUERY_EXACT)
|
|
return -ENODATA;
|
|
|
|
query->len = apfs_node_locate_data(node, query->index, &query->off);
|
|
if (query->len == 0)
|
|
return -EFSCORRUPTED;
|
|
|
|
if (cmp != 0) {
|
|
/*
|
|
* This is the last entry that can be relevant in this node.
|
|
* Keep searching the children, but don't return to this level.
|
|
*/
|
|
query->flags |= APFS_QUERY_DONE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_node_query - Execute a query on a single node
|
|
* @sb: filesystem superblock
|
|
* @query: the query to execute
|
|
*
|
|
* The search will start at index @query->index, looking for the key that comes
|
|
* right before @query->key, according to the order given by apfs_keycmp().
|
|
*
|
|
* The @query->index will be updated to the last index checked. This is
|
|
* important when searching for multiple entries, since the query may need
|
|
* to remember where it was on this level. If we are done with this node, the
|
|
* query will be flagged as APFS_QUERY_DONE, and the search will end in failure
|
|
* as soon as we return to this level. The function may also return -EAGAIN,
|
|
* to signal that the search should go on in a different branch.
|
|
*
|
|
* On success returns 0; the offset of the data within the block will be saved
|
|
* in @query->off, and its length in @query->len. The function checks that this
|
|
* length fits within the block; callers must use the returned value to make
|
|
* sure they never operate outside its bounds.
|
|
*
|
|
* -ENODATA will be returned if no appropriate entry was found, -EFSCORRUPTED
|
|
* in case of corruption.
|
|
*/
|
|
int apfs_node_query(struct super_block *sb, struct apfs_query *query)
|
|
{
|
|
struct apfs_node *node = query->node;
|
|
int left, right;
|
|
int cmp;
|
|
int err;
|
|
|
|
if (query->flags & APFS_QUERY_NEXT)
|
|
return apfs_node_next(sb, query);
|
|
|
|
/* Search by bisection */
|
|
cmp = 1;
|
|
left = 0;
|
|
do {
|
|
struct apfs_key curr_key;
|
|
if (cmp > 0) {
|
|
right = query->index - 1;
|
|
if (right < left)
|
|
return -ENODATA;
|
|
query->index = (left + right) / 2;
|
|
} else {
|
|
left = query->index;
|
|
query->index = DIV_ROUND_UP(left + right, 2);
|
|
}
|
|
|
|
query->key_len = apfs_node_locate_key(node, query->index,
|
|
&query->key_off);
|
|
err = apfs_key_from_query(query, &curr_key);
|
|
if (err)
|
|
return err;
|
|
|
|
cmp = apfs_keycmp(sb, &curr_key, query->key);
|
|
if (cmp == 0 && !(query->flags & APFS_QUERY_MULTIPLE))
|
|
break;
|
|
} while (left != right);
|
|
|
|
if (cmp > 0)
|
|
return -ENODATA;
|
|
|
|
if (cmp != 0 && apfs_node_is_leaf(query->node) &&
|
|
query->flags & APFS_QUERY_EXACT)
|
|
return -ENODATA;
|
|
|
|
if (query->flags & APFS_QUERY_MULTIPLE) {
|
|
if (cmp != 0) /* Last relevant entry in level */
|
|
query->flags |= APFS_QUERY_DONE;
|
|
query->flags |= APFS_QUERY_NEXT;
|
|
}
|
|
|
|
query->len = apfs_node_locate_data(node, query->index, &query->off);
|
|
if (query->len == 0)
|
|
return -EFSCORRUPTED;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_bno_from_query - Read the block number found by a successful omap query
|
|
* @query: the query that found the record
|
|
* @bno: Return parameter. The block number found.
|
|
*
|
|
* Reads the block number in the omap record into @bno and performs a basic
|
|
* sanity check as a protection against crafted filesystems. Returns 0 on
|
|
* success or -EFSCORRUPTED otherwise.
|
|
*/
|
|
int apfs_bno_from_query(struct apfs_query *query, u64 *bno)
|
|
{
|
|
struct apfs_omap_val *omap_val;
|
|
char *raw = query->node->object.bh->b_data;
|
|
|
|
if (query->len != sizeof(*omap_val))
|
|
return -EFSCORRUPTED;
|
|
|
|
omap_val = (struct apfs_omap_val *)(raw + query->off);
|
|
*bno = le64_to_cpu(omap_val->ov_paddr);
|
|
return 0;
|
|
}
|