cbfs_private.c
1 /* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later */ 2 3 #include <commonlib/bsd/cbfs_private.h> 4 #include <commonlib/bsd/helpers.h> 5 #include <assert.h> 6 7 static enum cb_err read_next_header(cbfs_dev_t dev, size_t *offset, struct cbfs_file *buffer, 8 const size_t devsize) 9 { 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 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) 30 { 31 const bool do_hash = CBFS_ENABLE_HASHING && metadata_hash; 32 const size_t devsize = cbfs_dev_size(dev); 33 struct vb2_digest_context dc; 34 35 assert(CBFS_ENABLE_HASHING || (!metadata_hash && !(flags & CBFS_WALK_WRITEBACK_HASH))); 36 if (do_hash && vb2_digest_init(&dc, CBFS_HASH_HWCRYPTO, metadata_hash->algo, 0)) 37 return CB_ERR_ARG; 38 39 size_t offset = 0; 40 enum cb_err ret_header; 41 enum cb_err ret_walker = CB_CBFS_NOT_FOUND; 42 union cbfs_mdata mdata; 43 while ((ret_header = read_next_header(dev, &offset, &mdata.h, devsize)) == CB_SUCCESS) { 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); 48 const bool empty = (type == CBFS_TYPE_DELETED || type == CBFS_TYPE_NULL); 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); 52 if (data_offset > sizeof(mdata) || data_length > devsize || 53 offset + data_offset + data_length > devsize) { 54 ERROR("File @%#zx too large\n", offset); 55 offset += CBFS_ALIGNMENT; 56 continue; 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; 79 /* Force filename null-termination, just in case. */ 80 mdata.raw[attr_offset ? attr_offset - 1 : data_offset - 1] = '\0'; 81 DEBUG("File name: '%s'\n", mdata.h.filename); 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 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) 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 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) 138 { 139 struct cbfs_lookup_args *args = arg; 140 /* Check if the name we're looking for could fit, then we can safely memcmp() it. */ 141 if (args->namesize > already_read - offsetof(union cbfs_mdata, h.filename) || 142 memcmp(args->name, mdata->h.filename, args->namesize) != 0) 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 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) 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 } 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 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); 181 return NULL; 182 } 183 if (tag == attr_tag) { 184 if (size_check && len != size_check) { 185 ERROR("Attribute %s[%x] size mismatch: %u != %zu\n", 186 mdata->h.filename, tag, len, size_check); 187 return NULL; 188 } 189 return attr; 190 } 191 offset += len; 192 } 193 194 return NULL; 195 } 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 }