/ src / commonlib / bsd / cbfs_private.c
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  }