summaryrefslogtreecommitdiffstats
path: root/src/commonlib/bsd/cbfs_private.c
blob: e77c299e7bdb53ff89521699ea1fe0e56620548a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/* 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;
	vb2_error_t vbrv;

	assert(CBFS_ENABLE_HASHING || (!metadata_hash && !(flags & CBFS_WALK_WRITEBACK_HASH)));
	if (do_hash && (vbrv = vb2_digest_init(&dc, metadata_hash->algo))) {
		ERROR("Metadata hash digest (%d) init error: %#x\n", metadata_hash->algo, vbrv);
		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;
}