| /* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later */ |
| |
| #include <commonlib/bsd/cbfs_private.h> |
| #include <assert.h> |
| |
| static enum cb_err read_next_header(cbfs_dev_t dev, size_t *offset, struct cbfs_file *buffer, |
| const size_t devsize) |
| { |
| DEBUG("Looking for next file @%#zx...\n", *offset); |
| *offset = ALIGN_UP(*offset, CBFS_ALIGNMENT); |
| while (*offset + sizeof(*buffer) < devsize) { |
| if (cbfs_dev_read(dev, buffer, *offset, sizeof(*buffer)) != sizeof(*buffer)) |
| return CB_CBFS_IO; |
| |
| if (memcmp(buffer->magic, CBFS_FILE_MAGIC, sizeof(buffer->magic)) == 0) |
| return CB_SUCCESS; |
| |
| *offset += CBFS_ALIGNMENT; |
| } |
| |
| DEBUG("End of CBFS reached\n"); |
| return CB_CBFS_NOT_FOUND; |
| } |
| |
| enum cb_err cbfs_walk(cbfs_dev_t dev, enum cb_err (*walker)(cbfs_dev_t dev, size_t offset, |
| const union cbfs_mdata *mdata, |
| size_t already_read, void *arg), |
| void *arg, struct vb2_hash *metadata_hash, enum cbfs_walk_flags flags) |
| { |
| const bool do_hash = CBFS_ENABLE_HASHING && metadata_hash; |
| const size_t devsize = cbfs_dev_size(dev); |
| struct vb2_digest_context dc; |
| |
| assert(CBFS_ENABLE_HASHING || (!metadata_hash && !(flags & CBFS_WALK_WRITEBACK_HASH))); |
| if (do_hash && vb2_digest_init(&dc, CBFS_HASH_HWCRYPTO, metadata_hash->algo, 0)) |
| return CB_ERR_ARG; |
| |
| size_t offset = 0; |
| enum cb_err ret_header; |
| enum cb_err ret_walker = CB_CBFS_NOT_FOUND; |
| union cbfs_mdata mdata; |
| while ((ret_header = read_next_header(dev, &offset, &mdata.h, devsize)) == CB_SUCCESS) { |
| const uint32_t attr_offset = be32toh(mdata.h.attributes_offset); |
| const uint32_t data_offset = be32toh(mdata.h.offset); |
| const uint32_t data_length = be32toh(mdata.h.len); |
| const uint32_t type = be32toh(mdata.h.type); |
| const bool empty = (type == CBFS_TYPE_DELETED || type == CBFS_TYPE_NULL); |
| |
| DEBUG("Found CBFS header @%#zx (type %d, attr +%#x, data +%#x, length %#x)\n", |
| offset, type, attr_offset, data_offset, data_length); |
| if (data_offset > sizeof(mdata) || data_length > devsize || |
| offset + data_offset + data_length > devsize) { |
| ERROR("File @%#zx too large\n", offset); |
| offset += CBFS_ALIGNMENT; |
| continue; |
| } |
| |
| if (empty && !(flags & CBFS_WALK_INCLUDE_EMPTY)) |
| goto next_file; |
| |
| /* When hashing we need to read everything. Otherwise skip the attributes. |
| attr_offset may be 0, which means there are no attributes. */ |
| ssize_t todo; |
| if (do_hash || attr_offset == 0) |
| todo = data_offset - sizeof(mdata.h); |
| else |
| todo = attr_offset - sizeof(mdata.h); |
| if (todo <= 0 || data_offset < attr_offset) { |
| ERROR("Corrupt file header @%#zx\n", offset); |
| goto next_file; |
| } |
| |
| /* Read the rest of the metadata (filename, and possibly attributes). */ |
| assert(todo > 0 && todo <= sizeof(mdata) - sizeof(mdata.h)); |
| if (cbfs_dev_read(dev, mdata.raw + sizeof(mdata.h), |
| offset + sizeof(mdata.h), todo) != todo) |
| return CB_CBFS_IO; |
| /* Force filename null-termination, just in case. */ |
| mdata.raw[attr_offset ? attr_offset - 1 : data_offset - 1] = '\0'; |
| DEBUG("File name: '%s'\n", mdata.h.filename); |
| |
| if (do_hash && !empty && vb2_digest_extend(&dc, mdata.raw, data_offset)) |
| return CB_ERR; |
| |
| if (walker && ret_walker == CB_CBFS_NOT_FOUND) |
| ret_walker = walker(dev, offset, &mdata, sizeof(mdata.h) + todo, arg); |
| |
| /* Return IO errors immediately. For others, finish the hash first if needed. */ |
| if (ret_walker == CB_CBFS_IO || (ret_walker != CB_CBFS_NOT_FOUND && !do_hash)) |
| return ret_walker; |
| |
| next_file: |
| offset += data_offset + data_length; |
| } |
| |
| if (ret_header != CB_CBFS_NOT_FOUND) |
| return ret_header; |
| |
| if (do_hash) { |
| uint8_t real_hash[VB2_MAX_DIGEST_SIZE]; |
| size_t hash_size = vb2_digest_size(metadata_hash->algo); |
| if (vb2_digest_finalize(&dc, real_hash, hash_size)) |
| return CB_ERR; |
| if (flags & CBFS_WALK_WRITEBACK_HASH) |
| memcpy(metadata_hash->raw, real_hash, hash_size); |
| else if (memcmp(metadata_hash->raw, real_hash, hash_size) != 0) |
| return CB_CBFS_HASH_MISMATCH; |
| } |
| |
| return ret_walker; |
| } |
| |
| enum cb_err cbfs_copy_fill_metadata(union cbfs_mdata *dst, const union cbfs_mdata *src, |
| size_t already_read, cbfs_dev_t dev, size_t offset) |
| { |
| /* First, copy the stuff that cbfs_walk() already read for us. */ |
| memcpy(dst, src, already_read); |
| |
| /* Then read in whatever metadata may be left (will only happen in non-hashing case). */ |
| const size_t todo = be32toh(src->h.offset) - already_read; |
| assert(todo <= sizeof(*dst) - already_read); |
| if (todo && cbfs_dev_read(dev, dst->raw + already_read, offset + already_read, |
| todo) != todo) |
| return CB_CBFS_IO; |
| return CB_SUCCESS; |
| } |
| |
| struct cbfs_lookup_args { |
| union cbfs_mdata *mdata_out; |
| const char *name; |
| size_t namesize; |
| size_t *data_offset_out; |
| }; |
| |
| static enum cb_err lookup_walker(cbfs_dev_t dev, size_t offset, const union cbfs_mdata *mdata, |
| size_t already_read, void *arg) |
| { |
| struct cbfs_lookup_args *args = arg; |
| /* Check if the name we're looking for could fit, then we can safely memcmp() it. */ |
| if (args->namesize > already_read - offsetof(union cbfs_mdata, h.filename) || |
| memcmp(args->name, mdata->h.filename, args->namesize) != 0) |
| return CB_CBFS_NOT_FOUND; |
| |
| LOG("Found '%s' @%#zx size %#x\n", args->name, offset, be32toh(mdata->h.len)); |
| if (cbfs_copy_fill_metadata(args->mdata_out, mdata, already_read, dev, offset)) |
| return CB_CBFS_IO; |
| |
| *args->data_offset_out = offset + be32toh(mdata->h.offset); |
| return CB_SUCCESS; |
| } |
| |
| enum cb_err cbfs_lookup(cbfs_dev_t dev, const char *name, union cbfs_mdata *mdata_out, |
| size_t *data_offset_out, struct vb2_hash *metadata_hash) |
| { |
| struct cbfs_lookup_args args = { |
| .mdata_out = mdata_out, |
| .name = name, |
| .namesize = strlen(name) + 1, /* Count trailing \0 so we can memcmp() it. */ |
| .data_offset_out = data_offset_out, |
| }; |
| return cbfs_walk(dev, lookup_walker, &args, metadata_hash, 0); |
| } |
| |
| const void *cbfs_find_attr(const union cbfs_mdata *mdata, uint32_t attr_tag, size_t size_check) |
| { |
| uint32_t offset = be32toh(mdata->h.attributes_offset); |
| uint32_t end = be32toh(mdata->h.offset); |
| |
| if (!offset) |
| return NULL; |
| |
| while (offset + sizeof(struct cbfs_file_attribute) <= end) { |
| const struct cbfs_file_attribute *attr = (const void *)mdata->raw + offset; |
| const uint32_t tag = be32toh(attr->tag); |
| const uint32_t len = be32toh(attr->len); |
| |
| if (len < sizeof(struct cbfs_file_attribute) || len > end - offset) { |
| ERROR("Attribute %s[%x] invalid length: %u\n", |
| mdata->h.filename, tag, len); |
| return NULL; |
| } |
| if (tag == attr_tag) { |
| if (size_check && len != size_check) { |
| ERROR("Attribute %s[%x] size mismatch: %u != %zu\n", |
| mdata->h.filename, tag, len, size_check); |
| return NULL; |
| } |
| return attr; |
| } |
| offset += len; |
| } |
| |
| return NULL; |
| } |
| |
| const struct vb2_hash *cbfs_file_hash(const union cbfs_mdata *mdata) |
| { |
| /* Hashes are variable-length attributes, so need to manually check the length. */ |
| const struct cbfs_file_attr_hash *attr = |
| cbfs_find_attr(mdata, CBFS_FILE_ATTR_TAG_HASH, 0); |
| if (!attr) |
| return NULL; /* no hash */ |
| const size_t asize = be32toh(attr->len); |
| |
| const struct vb2_hash *hash = &attr->hash; |
| const size_t hsize = vb2_digest_size(hash->algo); |
| if (!hsize) { |
| ERROR("Hash algo %u for '%s' unsupported.\n", hash->algo, mdata->h.filename); |
| return NULL; |
| } |
| if (hsize != asize - offsetof(struct cbfs_file_attr_hash, hash.raw)) { |
| ERROR("Hash attribute size for '%s' (%zu) incorrect for algo %u.\n", |
| mdata->h.filename, asize, hash->algo); |
| return NULL; |
| } |
| return hash; |
| } |