blob: 7ad29861df357703e5b69d73acb815f116e09c91 [file] [log] [blame]
Julius Werner0655f782019-12-11 16:19:48 -08001/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later */
2
3#include <commonlib/bsd/cbfs_private.h>
Thomas Heijligen3f9613b2022-11-28 11:46:48 +01004#include <commonlib/bsd/helpers.h>
Julius Werner0655f782019-12-11 16:19:48 -08005#include <assert.h>
6
Julius Werner69cc5572022-03-04 17:49:56 -08007static enum cb_err read_next_header(cbfs_dev_t dev, size_t *offset, struct cbfs_file *buffer,
8 const size_t devsize)
Julius Werner0655f782019-12-11 16:19:48 -08009{
Julius Werner0655f782019-12-11 16:19:48 -080010 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 Werner69cc5572022-03-04 17:49:56 -080026enum 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 Werner0655f782019-12-11 16:19:48 -080030{
31 const bool do_hash = CBFS_ENABLE_HASHING && metadata_hash;
Julius Werner6813d562021-08-30 19:01:48 -070032 const size_t devsize = cbfs_dev_size(dev);
Julius Werner0655f782019-12-11 16:19:48 -080033 struct vb2_digest_context dc;
Julius Werner0655f782019-12-11 16:19:48 -080034
35 assert(CBFS_ENABLE_HASHING || (!metadata_hash && !(flags & CBFS_WALK_WRITEBACK_HASH)));
Julius Wernerd96ca242022-08-08 18:08:35 -070036 if (do_hash && vb2_digest_init(&dc, CBFS_HASH_HWCRYPTO, metadata_hash->algo, 0))
Julius Werner0655f782019-12-11 16:19:48 -080037 return CB_ERR_ARG;
Julius Werner0655f782019-12-11 16:19:48 -080038
39 size_t offset = 0;
Julius Werner69cc5572022-03-04 17:49:56 -080040 enum cb_err ret_header;
41 enum cb_err ret_walker = CB_CBFS_NOT_FOUND;
Julius Werner0655f782019-12-11 16:19:48 -080042 union cbfs_mdata mdata;
Julius Werner6813d562021-08-30 19:01:48 -070043 while ((ret_header = read_next_header(dev, &offset, &mdata.h, devsize)) == CB_SUCCESS) {
Julius Werner0655f782019-12-11 16:19:48 -080044 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 Wernerd4775652020-03-13 16:43:34 -070048 const bool empty = (type == CBFS_TYPE_DELETED || type == CBFS_TYPE_NULL);
Julius Werner0655f782019-12-11 16:19:48 -080049
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 Werner6813d562021-08-30 19:01:48 -070052 if (data_offset > sizeof(mdata) || data_length > devsize ||
53 offset + data_offset + data_length > devsize) {
54 ERROR("File @%#zx too large\n", offset);
Jakub Czapiga8edbba42021-09-09 09:20:37 +020055 offset += CBFS_ALIGNMENT;
56 continue;
Julius Werner0655f782019-12-11 16:19:48 -080057 }
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 Wernera4c0e6072021-09-10 17:14:41 -070079 /* Force filename null-termination, just in case. */
80 mdata.raw[attr_offset ? attr_offset - 1 : data_offset - 1] = '\0';
Julius Wernerd4775652020-03-13 16:43:34 -070081 DEBUG("File name: '%s'\n", mdata.h.filename);
Julius Werner0655f782019-12-11 16:19:48 -080082
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
93next_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 Werner69cc5572022-03-04 17:49:56 -0800114enum 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 Werner0655f782019-12-11 16:19:48 -0800116{
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
129struct 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 Werner69cc5572022-03-04 17:49:56 -0800136static 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 Werner0655f782019-12-11 16:19:48 -0800138{
139 struct cbfs_lookup_args *args = arg;
Julius Werner0655f782019-12-11 16:19:48 -0800140 /* Check if the name we're looking for could fit, then we can safely memcmp() it. */
Julius Wernerd4775652020-03-13 16:43:34 -0700141 if (args->namesize > already_read - offsetof(union cbfs_mdata, h.filename) ||
142 memcmp(args->name, mdata->h.filename, args->namesize) != 0)
Julius Werner0655f782019-12-11 16:19:48 -0800143 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 Werner69cc5572022-03-04 17:49:56 -0800153enum 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 Werner0655f782019-12-11 16:19:48 -0800155{
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 Wernerd17ce412020-10-01 18:25:49 -0700164
165const 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 Wernera4c0e6072021-09-10 17:14:41 -0700178 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 Wernerd17ce412020-10-01 18:25:49 -0700181 return NULL;
182 }
183 if (tag == attr_tag) {
184 if (size_check && len != size_check) {
Julius Werner10ee7fc2020-12-21 17:17:02 -0800185 ERROR("Attribute %s[%x] size mismatch: %u != %zu\n",
Julius Wernerd4775652020-03-13 16:43:34 -0700186 mdata->h.filename, tag, len, size_check);
Julius Wernerd17ce412020-10-01 18:25:49 -0700187 return NULL;
188 }
189 return attr;
190 }
191 offset += len;
192 }
193
194 return NULL;
195}
Julius Wernerfccf1222021-04-02 15:58:05 -0700196
197const 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}