Julius Werner | 0655f78 | 2019-12-11 16:19:48 -0800 | [diff] [blame] | 1 | /* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later */ |
| 2 | |
| 3 | #include <commonlib/bsd/cbfs_private.h> |
Thomas Heijligen | 3f9613b | 2022-11-28 11:46:48 +0100 | [diff] [blame] | 4 | #include <commonlib/bsd/helpers.h> |
Julius Werner | 0655f78 | 2019-12-11 16:19:48 -0800 | [diff] [blame] | 5 | #include <assert.h> |
| 6 | |
Julius Werner | 69cc557 | 2022-03-04 17:49:56 -0800 | [diff] [blame] | 7 | static enum cb_err read_next_header(cbfs_dev_t dev, size_t *offset, struct cbfs_file *buffer, |
| 8 | const size_t devsize) |
Julius Werner | 0655f78 | 2019-12-11 16:19:48 -0800 | [diff] [blame] | 9 | { |
Julius Werner | 0655f78 | 2019-12-11 16:19:48 -0800 | [diff] [blame] | 10 | DEBUG("Looking for next file @%#zx...\n", *offset); |
| 11 | *offset = ALIGN_UP(*offset, CBFS_ALIGNMENT); |
| 12 | while (*offset + sizeof(*buffer) < devsize) { |
| 13 | if (cbfs_dev_read(dev, buffer, *offset, sizeof(*buffer)) != sizeof(*buffer)) |
| 14 | return CB_CBFS_IO; |
| 15 | |
| 16 | if (memcmp(buffer->magic, CBFS_FILE_MAGIC, sizeof(buffer->magic)) == 0) |
| 17 | return CB_SUCCESS; |
| 18 | |
| 19 | *offset += CBFS_ALIGNMENT; |
| 20 | } |
| 21 | |
| 22 | DEBUG("End of CBFS reached\n"); |
| 23 | return CB_CBFS_NOT_FOUND; |
| 24 | } |
| 25 | |
Julius Werner | 69cc557 | 2022-03-04 17:49:56 -0800 | [diff] [blame] | 26 | enum cb_err cbfs_walk(cbfs_dev_t dev, enum cb_err (*walker)(cbfs_dev_t dev, size_t offset, |
| 27 | const union cbfs_mdata *mdata, |
| 28 | size_t already_read, void *arg), |
| 29 | void *arg, struct vb2_hash *metadata_hash, enum cbfs_walk_flags flags) |
Julius Werner | 0655f78 | 2019-12-11 16:19:48 -0800 | [diff] [blame] | 30 | { |
| 31 | const bool do_hash = CBFS_ENABLE_HASHING && metadata_hash; |
Julius Werner | 6813d56 | 2021-08-30 19:01:48 -0700 | [diff] [blame] | 32 | const size_t devsize = cbfs_dev_size(dev); |
Julius Werner | 0655f78 | 2019-12-11 16:19:48 -0800 | [diff] [blame] | 33 | struct vb2_digest_context dc; |
Julius Werner | 0655f78 | 2019-12-11 16:19:48 -0800 | [diff] [blame] | 34 | |
| 35 | assert(CBFS_ENABLE_HASHING || (!metadata_hash && !(flags & CBFS_WALK_WRITEBACK_HASH))); |
Julius Werner | d96ca24 | 2022-08-08 18:08:35 -0700 | [diff] [blame] | 36 | if (do_hash && vb2_digest_init(&dc, CBFS_HASH_HWCRYPTO, metadata_hash->algo, 0)) |
Julius Werner | 0655f78 | 2019-12-11 16:19:48 -0800 | [diff] [blame] | 37 | return CB_ERR_ARG; |
Julius Werner | 0655f78 | 2019-12-11 16:19:48 -0800 | [diff] [blame] | 38 | |
| 39 | size_t offset = 0; |
Julius Werner | 69cc557 | 2022-03-04 17:49:56 -0800 | [diff] [blame] | 40 | enum cb_err ret_header; |
| 41 | enum cb_err ret_walker = CB_CBFS_NOT_FOUND; |
Julius Werner | 0655f78 | 2019-12-11 16:19:48 -0800 | [diff] [blame] | 42 | union cbfs_mdata mdata; |
Julius Werner | 6813d56 | 2021-08-30 19:01:48 -0700 | [diff] [blame] | 43 | while ((ret_header = read_next_header(dev, &offset, &mdata.h, devsize)) == CB_SUCCESS) { |
Julius Werner | 0655f78 | 2019-12-11 16:19:48 -0800 | [diff] [blame] | 44 | const uint32_t attr_offset = be32toh(mdata.h.attributes_offset); |
| 45 | const uint32_t data_offset = be32toh(mdata.h.offset); |
| 46 | const uint32_t data_length = be32toh(mdata.h.len); |
| 47 | const uint32_t type = be32toh(mdata.h.type); |
Julius Werner | d477565 | 2020-03-13 16:43:34 -0700 | [diff] [blame] | 48 | const bool empty = (type == CBFS_TYPE_DELETED || type == CBFS_TYPE_NULL); |
Julius Werner | 0655f78 | 2019-12-11 16:19:48 -0800 | [diff] [blame] | 49 | |
| 50 | DEBUG("Found CBFS header @%#zx (type %d, attr +%#x, data +%#x, length %#x)\n", |
| 51 | offset, type, attr_offset, data_offset, data_length); |
Julius Werner | 6813d56 | 2021-08-30 19:01:48 -0700 | [diff] [blame] | 52 | if (data_offset > sizeof(mdata) || data_length > devsize || |
| 53 | offset + data_offset + data_length > devsize) { |
| 54 | ERROR("File @%#zx too large\n", offset); |
Jakub Czapiga | 8edbba4 | 2021-09-09 09:20:37 +0200 | [diff] [blame] | 55 | offset += CBFS_ALIGNMENT; |
| 56 | continue; |
Julius Werner | 0655f78 | 2019-12-11 16:19:48 -0800 | [diff] [blame] | 57 | } |
| 58 | |
| 59 | if (empty && !(flags & CBFS_WALK_INCLUDE_EMPTY)) |
| 60 | goto next_file; |
| 61 | |
| 62 | /* When hashing we need to read everything. Otherwise skip the attributes. |
| 63 | attr_offset may be 0, which means there are no attributes. */ |
| 64 | ssize_t todo; |
| 65 | if (do_hash || attr_offset == 0) |
| 66 | todo = data_offset - sizeof(mdata.h); |
| 67 | else |
| 68 | todo = attr_offset - sizeof(mdata.h); |
| 69 | if (todo <= 0 || data_offset < attr_offset) { |
| 70 | ERROR("Corrupt file header @%#zx\n", offset); |
| 71 | goto next_file; |
| 72 | } |
| 73 | |
| 74 | /* Read the rest of the metadata (filename, and possibly attributes). */ |
| 75 | assert(todo > 0 && todo <= sizeof(mdata) - sizeof(mdata.h)); |
| 76 | if (cbfs_dev_read(dev, mdata.raw + sizeof(mdata.h), |
| 77 | offset + sizeof(mdata.h), todo) != todo) |
| 78 | return CB_CBFS_IO; |
Julius Werner | a4c0e607 | 2021-09-10 17:14:41 -0700 | [diff] [blame] | 79 | /* Force filename null-termination, just in case. */ |
| 80 | mdata.raw[attr_offset ? attr_offset - 1 : data_offset - 1] = '\0'; |
Julius Werner | d477565 | 2020-03-13 16:43:34 -0700 | [diff] [blame] | 81 | DEBUG("File name: '%s'\n", mdata.h.filename); |
Julius Werner | 0655f78 | 2019-12-11 16:19:48 -0800 | [diff] [blame] | 82 | |
| 83 | if (do_hash && !empty && vb2_digest_extend(&dc, mdata.raw, data_offset)) |
| 84 | return CB_ERR; |
| 85 | |
| 86 | if (walker && ret_walker == CB_CBFS_NOT_FOUND) |
| 87 | ret_walker = walker(dev, offset, &mdata, sizeof(mdata.h) + todo, arg); |
| 88 | |
| 89 | /* Return IO errors immediately. For others, finish the hash first if needed. */ |
| 90 | if (ret_walker == CB_CBFS_IO || (ret_walker != CB_CBFS_NOT_FOUND && !do_hash)) |
| 91 | return ret_walker; |
| 92 | |
| 93 | next_file: |
| 94 | offset += data_offset + data_length; |
| 95 | } |
| 96 | |
| 97 | if (ret_header != CB_CBFS_NOT_FOUND) |
| 98 | return ret_header; |
| 99 | |
| 100 | if (do_hash) { |
| 101 | uint8_t real_hash[VB2_MAX_DIGEST_SIZE]; |
| 102 | size_t hash_size = vb2_digest_size(metadata_hash->algo); |
| 103 | if (vb2_digest_finalize(&dc, real_hash, hash_size)) |
| 104 | return CB_ERR; |
| 105 | if (flags & CBFS_WALK_WRITEBACK_HASH) |
| 106 | memcpy(metadata_hash->raw, real_hash, hash_size); |
| 107 | else if (memcmp(metadata_hash->raw, real_hash, hash_size) != 0) |
| 108 | return CB_CBFS_HASH_MISMATCH; |
| 109 | } |
| 110 | |
| 111 | return ret_walker; |
| 112 | } |
| 113 | |
Julius Werner | 69cc557 | 2022-03-04 17:49:56 -0800 | [diff] [blame] | 114 | enum cb_err cbfs_copy_fill_metadata(union cbfs_mdata *dst, const union cbfs_mdata *src, |
| 115 | size_t already_read, cbfs_dev_t dev, size_t offset) |
Julius Werner | 0655f78 | 2019-12-11 16:19:48 -0800 | [diff] [blame] | 116 | { |
| 117 | /* First, copy the stuff that cbfs_walk() already read for us. */ |
| 118 | memcpy(dst, src, already_read); |
| 119 | |
| 120 | /* Then read in whatever metadata may be left (will only happen in non-hashing case). */ |
| 121 | const size_t todo = be32toh(src->h.offset) - already_read; |
| 122 | assert(todo <= sizeof(*dst) - already_read); |
| 123 | if (todo && cbfs_dev_read(dev, dst->raw + already_read, offset + already_read, |
| 124 | todo) != todo) |
| 125 | return CB_CBFS_IO; |
| 126 | return CB_SUCCESS; |
| 127 | } |
| 128 | |
| 129 | struct cbfs_lookup_args { |
| 130 | union cbfs_mdata *mdata_out; |
| 131 | const char *name; |
| 132 | size_t namesize; |
| 133 | size_t *data_offset_out; |
| 134 | }; |
| 135 | |
Julius Werner | 69cc557 | 2022-03-04 17:49:56 -0800 | [diff] [blame] | 136 | static enum cb_err lookup_walker(cbfs_dev_t dev, size_t offset, const union cbfs_mdata *mdata, |
| 137 | size_t already_read, void *arg) |
Julius Werner | 0655f78 | 2019-12-11 16:19:48 -0800 | [diff] [blame] | 138 | { |
| 139 | struct cbfs_lookup_args *args = arg; |
Julius Werner | 0655f78 | 2019-12-11 16:19:48 -0800 | [diff] [blame] | 140 | /* Check if the name we're looking for could fit, then we can safely memcmp() it. */ |
Julius Werner | d477565 | 2020-03-13 16:43:34 -0700 | [diff] [blame] | 141 | if (args->namesize > already_read - offsetof(union cbfs_mdata, h.filename) || |
| 142 | memcmp(args->name, mdata->h.filename, args->namesize) != 0) |
Julius Werner | 0655f78 | 2019-12-11 16:19:48 -0800 | [diff] [blame] | 143 | return CB_CBFS_NOT_FOUND; |
| 144 | |
| 145 | LOG("Found '%s' @%#zx size %#x\n", args->name, offset, be32toh(mdata->h.len)); |
| 146 | if (cbfs_copy_fill_metadata(args->mdata_out, mdata, already_read, dev, offset)) |
| 147 | return CB_CBFS_IO; |
| 148 | |
| 149 | *args->data_offset_out = offset + be32toh(mdata->h.offset); |
| 150 | return CB_SUCCESS; |
| 151 | } |
| 152 | |
Julius Werner | 69cc557 | 2022-03-04 17:49:56 -0800 | [diff] [blame] | 153 | enum cb_err cbfs_lookup(cbfs_dev_t dev, const char *name, union cbfs_mdata *mdata_out, |
| 154 | size_t *data_offset_out, struct vb2_hash *metadata_hash) |
Julius Werner | 0655f78 | 2019-12-11 16:19:48 -0800 | [diff] [blame] | 155 | { |
| 156 | struct cbfs_lookup_args args = { |
| 157 | .mdata_out = mdata_out, |
| 158 | .name = name, |
| 159 | .namesize = strlen(name) + 1, /* Count trailing \0 so we can memcmp() it. */ |
| 160 | .data_offset_out = data_offset_out, |
| 161 | }; |
| 162 | return cbfs_walk(dev, lookup_walker, &args, metadata_hash, 0); |
| 163 | } |
Julius Werner | d17ce41 | 2020-10-01 18:25:49 -0700 | [diff] [blame] | 164 | |
| 165 | const void *cbfs_find_attr(const union cbfs_mdata *mdata, uint32_t attr_tag, size_t size_check) |
| 166 | { |
| 167 | uint32_t offset = be32toh(mdata->h.attributes_offset); |
| 168 | uint32_t end = be32toh(mdata->h.offset); |
| 169 | |
| 170 | if (!offset) |
| 171 | return NULL; |
| 172 | |
| 173 | while (offset + sizeof(struct cbfs_file_attribute) <= end) { |
| 174 | const struct cbfs_file_attribute *attr = (const void *)mdata->raw + offset; |
| 175 | const uint32_t tag = be32toh(attr->tag); |
| 176 | const uint32_t len = be32toh(attr->len); |
| 177 | |
Julius Werner | a4c0e607 | 2021-09-10 17:14:41 -0700 | [diff] [blame] | 178 | if (len < sizeof(struct cbfs_file_attribute) || len > end - offset) { |
| 179 | ERROR("Attribute %s[%x] invalid length: %u\n", |
| 180 | mdata->h.filename, tag, len); |
Julius Werner | d17ce41 | 2020-10-01 18:25:49 -0700 | [diff] [blame] | 181 | return NULL; |
| 182 | } |
| 183 | if (tag == attr_tag) { |
| 184 | if (size_check && len != size_check) { |
Julius Werner | 10ee7fc | 2020-12-21 17:17:02 -0800 | [diff] [blame] | 185 | ERROR("Attribute %s[%x] size mismatch: %u != %zu\n", |
Julius Werner | d477565 | 2020-03-13 16:43:34 -0700 | [diff] [blame] | 186 | mdata->h.filename, tag, len, size_check); |
Julius Werner | d17ce41 | 2020-10-01 18:25:49 -0700 | [diff] [blame] | 187 | return NULL; |
| 188 | } |
| 189 | return attr; |
| 190 | } |
| 191 | offset += len; |
| 192 | } |
| 193 | |
| 194 | return NULL; |
| 195 | } |
Julius Werner | fccf122 | 2021-04-02 15:58:05 -0700 | [diff] [blame] | 196 | |
| 197 | const struct vb2_hash *cbfs_file_hash(const union cbfs_mdata *mdata) |
| 198 | { |
| 199 | /* Hashes are variable-length attributes, so need to manually check the length. */ |
| 200 | const struct cbfs_file_attr_hash *attr = |
| 201 | cbfs_find_attr(mdata, CBFS_FILE_ATTR_TAG_HASH, 0); |
| 202 | if (!attr) |
| 203 | return NULL; /* no hash */ |
| 204 | const size_t asize = be32toh(attr->len); |
| 205 | |
| 206 | const struct vb2_hash *hash = &attr->hash; |
| 207 | const size_t hsize = vb2_digest_size(hash->algo); |
| 208 | if (!hsize) { |
| 209 | ERROR("Hash algo %u for '%s' unsupported.\n", hash->algo, mdata->h.filename); |
| 210 | return NULL; |
| 211 | } |
| 212 | if (hsize != asize - offsetof(struct cbfs_file_attr_hash, hash.raw)) { |
| 213 | ERROR("Hash attribute size for '%s' (%zu) incorrect for algo %u.\n", |
| 214 | mdata->h.filename, asize, hash->algo); |
| 215 | return NULL; |
| 216 | } |
| 217 | return hash; |
| 218 | } |