diff options
author | David Oberhollenzer <david.oberhollenzer@sigma-star.at> | 2019-08-01 13:50:05 +0200 |
---|---|---|
committer | David Oberhollenzer <david.oberhollenzer@sigma-star.at> | 2019-08-01 15:48:42 +0200 |
commit | 23a3f10ab62abadf5adcfb9540f2180275ab5b0c (patch) | |
tree | cfe537d4359de7dd3486898dad70c40761947680 /lib/sqfs/xattr_reader.c | |
parent | 7073f002b54c690ffa17d9f736a4417485d3c6d6 (diff) |
Add xattr reader implementation to recover xattrs from squashfs
Signed-off-by: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
Diffstat (limited to 'lib/sqfs/xattr_reader.c')
-rw-r--r-- | lib/sqfs/xattr_reader.c | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/lib/sqfs/xattr_reader.c b/lib/sqfs/xattr_reader.c new file mode 100644 index 0000000..4383e89 --- /dev/null +++ b/lib/sqfs/xattr_reader.c @@ -0,0 +1,372 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * xattr_reader.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "config.h" + +#include "xattr_reader.h" +#include "meta_reader.h" +#include "util.h" + +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +struct xattr_reader_t { + uint64_t xattr_start; + + size_t num_id_blocks; + size_t num_ids; + + uint64_t *id_block_starts; + + meta_reader_t *idrd; + meta_reader_t *kvrd; + sqfs_super_t *super; +}; + +static int get_id_block_locations(xattr_reader_t *xr, int sqfsfd, + sqfs_super_t *super) +{ + sqfs_xattr_id_table_t idtbl; + size_t i; + + if (super->xattr_id_table_start >= super->bytes_used) { + fputs("xattr ID location table is after end of filesystem\n", + stderr); + return -1; + } + + if (read_data_at("reading xattr ID location table", + super->xattr_id_table_start, + sqfsfd, &idtbl, sizeof(idtbl))) { + return -1; + } + + xr->xattr_start = le64toh(idtbl.xattr_table_start); + xr->num_ids = le32toh(idtbl.xattr_ids); + xr->num_id_blocks = + (xr->num_ids * sizeof(sqfs_xattr_id_t)) / SQFS_META_BLOCK_SIZE; + + if ((xr->num_ids * sizeof(sqfs_xattr_id_t)) % SQFS_META_BLOCK_SIZE) + xr->num_id_blocks += 1; + + xr->id_block_starts = calloc(sizeof(uint64_t), xr->num_id_blocks); + if (xr->id_block_starts == NULL) { + perror("allocating xattr ID location table"); + return -1; + } + + if (read_data_at("reading xattr ID block locations", + super->xattr_id_table_start + sizeof(idtbl), + sqfsfd, xr->id_block_starts, + sizeof(xr->id_block_starts[0]) * xr->num_id_blocks)) { + free(xr->id_block_starts); + return -1; + } + + for (i = 0; i < xr->num_id_blocks; ++i) { + xr->id_block_starts[i] = le64toh(xr->id_block_starts[i]); + + if (xr->id_block_starts[i] > super->bytes_used) { + fputs("found xattr ID block that is past " + "end of filesystem\n", stderr); + free(xr->id_block_starts); + return -1; + } + } + + return 0; +} + +static int get_xattr_desc(xattr_reader_t *xr, uint32_t idx, + sqfs_xattr_id_t *desc) +{ + size_t block, offset; + + if (idx >= xr->num_ids) { + fputs("Tried to access out of bounds xattr index\n", stderr); + return -1; + } + + offset = (idx * sizeof(*desc)) % SQFS_META_BLOCK_SIZE; + block = (idx * sizeof(*desc)) / SQFS_META_BLOCK_SIZE; + + if (meta_reader_seek(xr->idrd, xr->id_block_starts[block], offset)) + return -1; + + if (meta_reader_read(xr->idrd, desc, sizeof(*desc))) + return -1; + + desc->xattr = le64toh(desc->xattr); + desc->count = le32toh(desc->count); + desc->size = le32toh(desc->size); + + if ((desc->xattr & 0xFFFF) >= SQFS_META_BLOCK_SIZE) { + fputs("Found xattr ID record pointing outside " + "metadata block\n", stderr); + return -1; + } + + if ((xr->xattr_start + (desc->xattr >> 16)) >= xr->super->bytes_used) { + fputs("Found xattr ID record pointing past " + "end of filesystem\n", stderr); + return -1; + } + + return 0; +} + +static sqfs_xattr_entry_t *read_key(xattr_reader_t *xr) +{ + sqfs_xattr_entry_t key, *out; + const char *prefix; + size_t plen; + + if (meta_reader_read(xr->kvrd, &key, sizeof(key))) + return NULL; + + key.type = le16toh(key.type); + key.size = le16toh(key.size); + + prefix = sqfs_get_xattr_prefix(key.type & SQUASHFS_XATTR_PREFIX_MASK); + if (prefix == NULL) { + fprintf(stderr, "found unknown xattr type %u\n", + key.type & SQUASHFS_XATTR_PREFIX_MASK); + return NULL; + } + + plen = strlen(prefix); + out = calloc(1, sizeof(*out) + plen + key.size + 1); + if (out == NULL) { + perror("restoring xattr key"); + return NULL; + } + + *out = key; + memcpy(out->key, prefix, plen); + + if (meta_reader_read(xr->kvrd, out->key + plen, key.size)) { + free(out); + return NULL; + } + + return out; +} + +static sqfs_xattr_value_t *read_value(xattr_reader_t *xr, + const sqfs_xattr_entry_t *key) +{ + sqfs_xattr_value_t value, *out; + uint64_t ref, start, new_start; + size_t offset, new_offset; + + if (meta_reader_read(xr->kvrd, &value, sizeof(value))) + return NULL; + + if (key->type & SQUASHFS_XATTR_FLAG_OOL) { + if (meta_reader_read(xr->kvrd, &ref, sizeof(ref))) + return NULL; + + meta_reader_get_position(xr->kvrd, &start, &offset); + + new_start = xr->xattr_start + (ref >> 16); + new_offset = ref & 0xFFFF; + + if (new_start > xr->super->bytes_used) { + fputs("OOL xattr reference points past end of " + "filesystem\n", stderr); + return NULL; + } + + if (new_offset >= SQFS_META_BLOCK_SIZE) { + fputs("OOL xattr reference points outside " + "metadata block\n", stderr); + return NULL; + } + + if (meta_reader_seek(xr->kvrd, new_start, new_offset)) + return NULL; + } + + value.size = le32toh(value.size); + + out = calloc(1, sizeof(*out) + value.size); + if (out == NULL) { + perror("reading xattr value"); + return NULL; + } + + *out = value; + + if (meta_reader_read(xr->kvrd, out->value, value.size)) + goto fail; + + if (key->type & SQUASHFS_XATTR_FLAG_OOL) { + if (meta_reader_seek(xr->kvrd, start, offset)) + goto fail; + } + + return out; +fail: + free(out); + return NULL; +} + +static int restore_kv_pairs(xattr_reader_t *xr, fstree_t *fs, + tree_node_t *node) +{ + size_t i, key_idx, val_idx; + sqfs_xattr_entry_t *key; + sqfs_xattr_value_t *val; + int ret; + + if (meta_reader_seek(xr->kvrd, node->xattr->block, + node->xattr->offset)) { + return -1; + } + + for (i = 0; i < node->xattr->num_attr; ++i) { + key = read_key(xr); + if (key == NULL) + return -1; + + val = read_value(xr, key); + if (val == NULL) { + free(key); + return -1; + } + + ret = str_table_get_index(&fs->xattr_keys, + (const char *)key->key, &key_idx); + if (ret) { + free(val); + free(key); + return -1; + } + + ret = str_table_get_index(&fs->xattr_values, + (const char *)val->value, &val_idx); + if (ret) { + free(val); + free(key); + return -1; + } + + if (sizeof(size_t) > sizeof(uint32_t)) { + if (key_idx > 0xFFFFFFFFUL) { + fputs("too many unique xattr keys\n", stderr); + return -1; + } + + if (val_idx > 0xFFFFFFFFUL) { + fputs("too many unique xattr values\n", stderr); + return -1; + } + } + + node->xattr->attr[i].key_index = key_idx; + node->xattr->attr[i].value_index = val_idx; + + free(key); + free(val); + } + + return 0; +} + +int xattr_reader_restore_node(xattr_reader_t *xr, fstree_t *fs, + tree_node_t *node, uint32_t xattr) +{ + sqfs_xattr_id_t desc; + tree_xattr_t *it; + size_t size; + + if (xr->kvrd == NULL || xr->idrd == NULL) + return 0; + + for (it = fs->xattr; it != NULL; it = it->next) { + if (it->index == xattr) { + node->xattr = it; + return 0; + } + } + + if (get_xattr_desc(xr, xattr, &desc)) + return -1; + + size = sizeof(*node->xattr); + size += sizeof(node->xattr->attr[0]) * desc.count; + + node->xattr = calloc(1, size); + if (node->xattr == NULL) { + perror("creating xattr structure"); + return -1; + } + + node->xattr->num_attr = desc.count; + node->xattr->max_attr = desc.count; + node->xattr->block = xr->xattr_start + (desc.xattr >> 16); + node->xattr->offset = desc.xattr & 0xFFFF; + node->xattr->size = desc.size; + node->xattr->index = xattr; + node->xattr->owner = node; + + if (restore_kv_pairs(xr, fs, node)) { + free(node->xattr); + return -1; + } + + node->xattr->next = fs->xattr; + fs->xattr = node->xattr; + return 0; +} + +void xattr_reader_destroy(xattr_reader_t *xr) +{ + if (xr->kvrd != NULL) + meta_reader_destroy(xr->kvrd); + + if (xr->idrd != NULL) + meta_reader_destroy(xr->idrd); + + free(xr->id_block_starts); + free(xr); +} + +xattr_reader_t *xattr_reader_create(int sqfsfd, sqfs_super_t *super, + compressor_t *cmp) +{ + xattr_reader_t *xr = calloc(1, sizeof(*xr)); + + if (xr == NULL) { + perror("creating xattr reader"); + return NULL; + } + + if (super->flags & SQFS_FLAG_NO_XATTRS) + return xr; + + if (super->xattr_id_table_start == 0xFFFFFFFFFFFFFFFF) + return xr; + + if (get_id_block_locations(xr, sqfsfd, super)) + goto fail; + + xr->idrd = meta_reader_create(sqfsfd, cmp); + if (xr->idrd == NULL) + goto fail; + + xr->kvrd = meta_reader_create(sqfsfd, cmp); + if (xr->kvrd == NULL) + goto fail; + + xr->super = super; + return xr; +fail: + xattr_reader_destroy(xr); + return NULL; +} |