/src/fwupd/libfwupdplugin/fu-fit-firmware.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2022 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | 27 | #define G_LOG_DOMAIN "FuFirmware" |
8 | | |
9 | | #include "config.h" |
10 | | |
11 | | #include "fu-bytes.h" |
12 | | #include "fu-common.h" |
13 | | #include "fu-crc.h" |
14 | | #include "fu-dump.h" |
15 | | #include "fu-fdt-image.h" |
16 | | #include "fu-fit-firmware.h" |
17 | | #include "fu-input-stream.h" |
18 | | #include "fu-mem.h" |
19 | | |
20 | | /** |
21 | | * FuFitFirmware: |
22 | | * |
23 | | * A Flat Image Tree. |
24 | | * |
25 | | * Documented: |
26 | | * https://github.com/u-boot/u-boot/blob/master/doc/uImage.FIT/source_file_format.txt |
27 | | * |
28 | | * See also: [class@FuFdtFirmware] |
29 | | */ |
30 | | |
31 | 1.79k | G_DEFINE_TYPE(FuFitFirmware, fu_fit_firmware, FU_TYPE_FDT_FIRMWARE) |
32 | 1.79k | |
33 | 1.79k | static FuFdtImage * |
34 | 1.79k | fu_fit_firmware_get_image_root(FuFitFirmware *self) |
35 | 1.79k | { |
36 | 0 | FuFirmware *img = fu_firmware_get_image_by_id(FU_FIRMWARE(self), NULL, NULL); |
37 | 0 | if (img != NULL) |
38 | 0 | return FU_FDT_IMAGE(img); |
39 | 0 | img = fu_fdt_image_new(); |
40 | 0 | fu_fdt_image_set_attr_uint32(FU_FDT_IMAGE(img), FU_FIT_FIRMWARE_ATTR_TIMESTAMP, 0x0); |
41 | 0 | fu_fdt_image_set_attr_str(FU_FDT_IMAGE(img), "description", "Firmware image"); |
42 | 0 | fu_fdt_image_set_attr_str(FU_FDT_IMAGE(img), "creator", "fwupd"); |
43 | 0 | if (!fu_firmware_add_image(FU_FIRMWARE(self), img, NULL)) |
44 | 0 | return NULL; |
45 | 0 | return FU_FDT_IMAGE(img); |
46 | 0 | } |
47 | | |
48 | | /** |
49 | | * fu_fit_firmware_get_timestamp: |
50 | | * @self: a #FuFitFirmware |
51 | | * |
52 | | * Gets the creation timestamp. |
53 | | * |
54 | | * Returns: integer |
55 | | * |
56 | | * Since: 1.8.2 |
57 | | **/ |
58 | | guint32 |
59 | | fu_fit_firmware_get_timestamp(FuFitFirmware *self) |
60 | 0 | { |
61 | 0 | guint32 tmp = 0; |
62 | 0 | g_autoptr(FuFdtImage) img_root = fu_fit_firmware_get_image_root(self); |
63 | |
|
64 | 0 | g_return_val_if_fail(FU_IS_FIT_FIRMWARE(self), 0x0); |
65 | | |
66 | 0 | if (img_root == NULL) |
67 | 0 | return 0; |
68 | | /* this has to exist */ |
69 | 0 | (void)fu_fdt_image_get_attr_u32(img_root, FU_FIT_FIRMWARE_ATTR_TIMESTAMP, &tmp, NULL); |
70 | 0 | return tmp; |
71 | 0 | } |
72 | | |
73 | | /** |
74 | | * fu_fit_firmware_set_timestamp: |
75 | | * @self: a #FuFitFirmware |
76 | | * @timestamp: integer value |
77 | | * |
78 | | * Sets the creation timestamp. |
79 | | * |
80 | | * Since: 1.8.2 |
81 | | **/ |
82 | | void |
83 | | fu_fit_firmware_set_timestamp(FuFitFirmware *self, guint32 timestamp) |
84 | 0 | { |
85 | 0 | g_autoptr(FuFdtImage) img_root = fu_fit_firmware_get_image_root(self); |
86 | 0 | g_return_if_fail(FU_IS_FIT_FIRMWARE(self)); |
87 | 0 | if (img_root == NULL) |
88 | 0 | return; |
89 | 0 | fu_fdt_image_set_attr_uint32(img_root, FU_FIT_FIRMWARE_ATTR_TIMESTAMP, timestamp); |
90 | 0 | } |
91 | | |
92 | | static gboolean |
93 | | fu_fit_firmware_verify_crc32(FuFitFirmware *self, |
94 | | FuFirmware *img, |
95 | | FuFirmware *img_hash, |
96 | | GBytes *blob, |
97 | | GError **error) |
98 | 0 | { |
99 | 0 | guint32 value = 0; |
100 | 0 | guint32 value_calc; |
101 | | |
102 | | /* get value and verify */ |
103 | 0 | if (!fu_fdt_image_get_attr_u32(FU_FDT_IMAGE(img_hash), |
104 | 0 | FU_FIT_FIRMWARE_ATTR_VALUE, |
105 | 0 | &value, |
106 | 0 | error)) |
107 | 0 | return FALSE; |
108 | 0 | value_calc = fu_crc32_bytes(FU_CRC_KIND_B32_STANDARD, blob); |
109 | 0 | if (value_calc != value) { |
110 | 0 | g_set_error(error, |
111 | 0 | FWUPD_ERROR, |
112 | 0 | FWUPD_ERROR_INVALID_DATA, |
113 | 0 | "%s CRC did not match, got 0x%x, expected 0x%x", |
114 | 0 | fu_firmware_get_id(img), |
115 | 0 | value, |
116 | 0 | value_calc); |
117 | 0 | return FALSE; |
118 | 0 | } |
119 | | |
120 | | /* success */ |
121 | 0 | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); |
122 | 0 | return TRUE; |
123 | 0 | } |
124 | | |
125 | | static gboolean |
126 | | fu_fit_firmware_verify_checksum(FuFitFirmware *self, |
127 | | FuFirmware *img, |
128 | | FuFirmware *img_hash, |
129 | | GChecksumType checksum_type, |
130 | | GBytes *blob, |
131 | | GError **error) |
132 | 0 | { |
133 | 0 | gsize digest_len = g_checksum_type_get_length(checksum_type); |
134 | 0 | g_autofree guint8 *buf = g_malloc0(digest_len); |
135 | 0 | g_autoptr(GBytes) value = NULL; |
136 | 0 | g_autoptr(GBytes) value_calc = NULL; |
137 | 0 | g_autoptr(GChecksum) checksum = g_checksum_new(checksum_type); |
138 | | |
139 | | /* get value and verify */ |
140 | 0 | value = fu_fdt_image_get_attr(FU_FDT_IMAGE(img_hash), FU_FIT_FIRMWARE_ATTR_VALUE, error); |
141 | 0 | if (value == NULL) |
142 | 0 | return FALSE; |
143 | 0 | if (g_bytes_get_size(value) != digest_len) { |
144 | 0 | g_set_error(error, |
145 | 0 | FWUPD_ERROR, |
146 | 0 | FWUPD_ERROR_INVALID_DATA, |
147 | 0 | "%s invalid hash value size, got 0x%x, expected 0x%x", |
148 | 0 | fu_firmware_get_id(img), |
149 | 0 | (guint)g_bytes_get_size(value), |
150 | 0 | (guint)digest_len); |
151 | 0 | return FALSE; |
152 | 0 | } |
153 | 0 | g_checksum_update(checksum, |
154 | 0 | (const guchar *)g_bytes_get_data(blob, NULL), |
155 | 0 | g_bytes_get_size(blob)); |
156 | 0 | g_checksum_get_digest(checksum, buf, &digest_len); |
157 | 0 | value_calc = g_bytes_new(buf, digest_len); |
158 | 0 | if (!fu_bytes_compare(value, value_calc, error)) |
159 | 0 | return FALSE; |
160 | | |
161 | | /* success */ |
162 | 0 | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); |
163 | 0 | return TRUE; |
164 | 0 | } |
165 | | |
166 | | static gboolean |
167 | | fu_fit_firmware_verify_hash(FuFitFirmware *self, |
168 | | FuFirmware *img, |
169 | | FuFirmware *img_hash, |
170 | | GBytes *blob, |
171 | | GError **error) |
172 | 0 | { |
173 | 0 | g_autofree gchar *algo = NULL; |
174 | | |
175 | | /* what is this */ |
176 | 0 | if (!fu_fdt_image_get_attr_str(FU_FDT_IMAGE(img_hash), |
177 | 0 | FU_FIT_FIRMWARE_ATTR_ALGO, |
178 | 0 | &algo, |
179 | 0 | error)) { |
180 | 0 | g_prefix_error(error, "cannot get algo for %s: ", fu_firmware_get_id(img)); |
181 | 0 | return FALSE; |
182 | 0 | } |
183 | 0 | if (g_strcmp0(algo, "crc32") == 0) |
184 | 0 | return fu_fit_firmware_verify_crc32(self, img, img_hash, blob, error); |
185 | 0 | if (g_strcmp0(algo, "md5") == 0) { |
186 | 0 | return fu_fit_firmware_verify_checksum(self, |
187 | 0 | img, |
188 | 0 | img_hash, |
189 | 0 | G_CHECKSUM_MD5, |
190 | 0 | blob, |
191 | 0 | error); |
192 | 0 | } |
193 | 0 | if (g_strcmp0(algo, "sha1") == 0) { |
194 | 0 | return fu_fit_firmware_verify_checksum(self, |
195 | 0 | img, |
196 | 0 | img_hash, |
197 | 0 | G_CHECKSUM_SHA1, |
198 | 0 | blob, |
199 | 0 | error); |
200 | 0 | } |
201 | 0 | if (g_strcmp0(algo, "sha256") == 0) { |
202 | 0 | return fu_fit_firmware_verify_checksum(self, |
203 | 0 | img, |
204 | 0 | img_hash, |
205 | 0 | G_CHECKSUM_SHA256, |
206 | 0 | blob, |
207 | 0 | error); |
208 | 0 | } |
209 | | |
210 | | /* ignore any hashes we do not support: success */ |
211 | 0 | return TRUE; |
212 | 0 | } |
213 | | |
214 | | static gboolean |
215 | | fu_fit_firmware_verify_image(FuFitFirmware *self, |
216 | | FuInputStream *stream, |
217 | | FuFirmware *img, |
218 | | FuFirmwareParseFlags flags, |
219 | | GError **error) |
220 | 56 | { |
221 | 56 | g_autoptr(GBytes) blob = NULL; |
222 | | |
223 | | /* sanity check */ |
224 | 56 | if (!fu_fdt_image_get_attr_str(FU_FDT_IMAGE(img), "type", NULL, error)) |
225 | 23 | return FALSE; |
226 | 33 | if (!fu_fdt_image_get_attr_str(FU_FDT_IMAGE(img), "description", NULL, error)) |
227 | 3 | return FALSE; |
228 | | |
229 | | /* if has data */ |
230 | 30 | blob = fu_fdt_image_get_attr(FU_FDT_IMAGE(img), FU_FIT_FIRMWARE_ATTR_DATA, NULL); |
231 | 30 | if (blob == NULL) { |
232 | 3 | guint32 data_size = 0x0; |
233 | 3 | guint32 data_position = 0x0; |
234 | 3 | gsize offset; |
235 | | |
236 | | /* extra data outside of FIT image, either at an absolute position or at an |
237 | | * offset from the end of the FIT, which is aligned to 4 bytes */ |
238 | 3 | if (fu_fdt_image_get_attr_u32(FU_FDT_IMAGE(img), |
239 | 3 | FU_FIT_FIRMWARE_ATTR_DATA_POSITION, |
240 | 3 | &data_position, |
241 | 3 | NULL)) { |
242 | 0 | offset = data_position; |
243 | 3 | } else { |
244 | 3 | guint32 data_offset = 0x0; |
245 | | |
246 | 3 | if (!fu_fdt_image_get_attr_u32(FU_FDT_IMAGE(img), |
247 | 3 | FU_FIT_FIRMWARE_ATTR_DATA_OFFSET, |
248 | 3 | &data_offset, |
249 | 3 | error)) |
250 | 2 | return FALSE; |
251 | | |
252 | | /* the size of the FIT is the totalsize from its header */ |
253 | 1 | offset = fu_size_checked_add( |
254 | 1 | fu_common_align_up(fu_firmware_get_size(FU_FIRMWARE(self)), |
255 | 1 | FU_FIRMWARE_ALIGNMENT_4), |
256 | 1 | data_offset); |
257 | 1 | } |
258 | 1 | if (!fu_fdt_image_get_attr_u32(FU_FDT_IMAGE(img), |
259 | 1 | FU_FIT_FIRMWARE_ATTR_DATA_SIZE, |
260 | 1 | &data_size, |
261 | 1 | error)) |
262 | 1 | return FALSE; |
263 | 0 | blob = fu_input_stream_read_bytes(stream, offset, data_size, NULL, error); |
264 | 0 | if (blob == NULL) |
265 | 0 | return FALSE; |
266 | 0 | if (g_bytes_get_size(blob) != data_size) { |
267 | 0 | g_set_error(error, |
268 | 0 | FWUPD_ERROR, |
269 | 0 | FWUPD_ERROR_INVALID_DATA, |
270 | 0 | "external data is truncated, got 0x%x bytes but expected 0x%x", |
271 | 0 | (guint)g_bytes_get_size(blob), |
272 | 0 | data_size); |
273 | 0 | return FALSE; |
274 | 0 | } |
275 | 0 | } |
276 | 27 | fu_dump_bytes(G_LOG_DOMAIN, "data", blob); |
277 | | |
278 | | /* keep the data, which may not be in the image */ |
279 | 27 | fu_firmware_set_bytes(img, blob); |
280 | | |
281 | | /* verify any hashes we recognize */ |
282 | 27 | if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) { |
283 | 0 | g_autoptr(GPtrArray) img_hashes = fu_firmware_get_images(img); |
284 | 0 | for (guint i = 0; i < img_hashes->len; i++) { |
285 | 0 | FuFirmware *img_hash = g_ptr_array_index(img_hashes, i); |
286 | 0 | if (fu_firmware_get_id(img_hash) == NULL) { |
287 | 0 | g_set_error_literal(error, |
288 | 0 | FWUPD_ERROR, |
289 | 0 | FWUPD_ERROR_INVALID_DATA, |
290 | 0 | "no ID for image hash"); |
291 | 0 | return FALSE; |
292 | 0 | } |
293 | 0 | if (g_str_has_prefix(fu_firmware_get_id(img_hash), "hash")) { |
294 | 0 | if (!fu_fit_firmware_verify_hash(self, img, img_hash, blob, error)) |
295 | 0 | return FALSE; |
296 | 0 | } |
297 | 0 | } |
298 | 0 | } |
299 | | |
300 | | /* success */ |
301 | 27 | return TRUE; |
302 | 27 | } |
303 | | |
304 | | static gboolean |
305 | | fu_fit_firmware_verify_configuration(FuFitFirmware *self, |
306 | | FuFirmware *img, |
307 | | FuFirmwareParseFlags flags, |
308 | | GError **error) |
309 | 339 | { |
310 | 339 | g_autoptr(GBytes) blob = NULL; |
311 | | |
312 | | /* the compatible strings are optional, but must be valid if present */ |
313 | 339 | blob = fu_fdt_image_get_attr(FU_FDT_IMAGE(img), FU_FIT_FIRMWARE_ATTR_COMPATIBLE, NULL); |
314 | 339 | if (blob != NULL && !fu_fdt_image_get_attr_strlist(FU_FDT_IMAGE(img), |
315 | 45 | FU_FIT_FIRMWARE_ATTR_COMPATIBLE, |
316 | 45 | NULL, |
317 | 45 | error)) |
318 | 19 | return FALSE; |
319 | | |
320 | | /* success */ |
321 | 320 | return TRUE; |
322 | 339 | } |
323 | | |
324 | | static gboolean |
325 | | fu_fit_firmware_parse(FuFirmware *firmware, |
326 | | FuInputStream *stream, |
327 | | FuFirmwareParseFlags flags, |
328 | | GError **error) |
329 | 1.72k | { |
330 | 1.72k | FuFitFirmware *self = FU_FIT_FIRMWARE(firmware); |
331 | 1.72k | g_autoptr(FuFirmware) img_cfgs = NULL; |
332 | 1.72k | g_autoptr(FuFirmware) img_images = NULL; |
333 | 1.72k | g_autoptr(FuFirmware) img_root = NULL; |
334 | 1.72k | g_autoptr(GPtrArray) img_images_array = NULL; |
335 | 1.72k | g_autoptr(GPtrArray) img_cfgs_array = NULL; |
336 | | |
337 | | /* FuFdtFirmware->parse */ |
338 | 1.72k | if (!FU_FIRMWARE_CLASS(fu_fit_firmware_parent_class)->parse(firmware, stream, flags, error)) |
339 | 804 | return FALSE; |
340 | | |
341 | | /* sanity check */ |
342 | 919 | img_root = fu_firmware_get_image_by_id(firmware, NULL, error); |
343 | 919 | if (img_root == NULL) |
344 | 81 | return FALSE; |
345 | 838 | if (!fu_fdt_image_get_attr_u32(FU_FDT_IMAGE(img_root), |
346 | 838 | FU_FIT_FIRMWARE_ATTR_TIMESTAMP, |
347 | 838 | NULL, |
348 | 838 | error)) |
349 | 194 | return FALSE; |
350 | | |
351 | | /* check the checksums of each image */ |
352 | 644 | img_images = fu_firmware_get_image_by_id(img_root, FU_FIT_FIRMWARE_ID_IMAGES, error); |
353 | 644 | if (img_images == NULL) |
354 | 43 | return FALSE; |
355 | 601 | img_images_array = fu_firmware_get_images(img_images); |
356 | 628 | for (guint i = 0; i < img_images_array->len; i++) { |
357 | 56 | FuFirmware *img = g_ptr_array_index(img_images_array, i); |
358 | 56 | if (!fu_fit_firmware_verify_image(self, stream, img, flags, error)) |
359 | 29 | return FALSE; |
360 | 56 | } |
361 | | |
362 | | /* check the setup of each configuration */ |
363 | 572 | img_cfgs = fu_firmware_get_image_by_id(img_root, FU_FIT_FIRMWARE_ID_CONFIGURATIONS, error); |
364 | 572 | if (img_cfgs == NULL) |
365 | 86 | return FALSE; |
366 | 486 | img_cfgs_array = fu_firmware_get_images(img_cfgs); |
367 | 806 | for (guint i = 0; i < img_cfgs_array->len; i++) { |
368 | 339 | FuFirmware *img = g_ptr_array_index(img_cfgs_array, i); |
369 | 339 | if (!fu_fit_firmware_verify_configuration(self, img, flags, error)) |
370 | 19 | return FALSE; |
371 | 339 | } |
372 | | |
373 | | /* success */ |
374 | 467 | return TRUE; |
375 | 486 | } |
376 | | |
377 | | static void |
378 | | fu_fit_firmware_init(FuFitFirmware *self) |
379 | 1.79k | { |
380 | 1.79k | } |
381 | | |
382 | | static void |
383 | | fu_fit_firmware_class_init(FuFitFirmwareClass *klass) |
384 | 1 | { |
385 | 1 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
386 | 1 | firmware_class->parse = fu_fit_firmware_parse; |
387 | 1 | fu_firmware_set_images_max(firmware_class, 1024); |
388 | 1 | } |
389 | | |
390 | | /** |
391 | | * fu_fit_firmware_new: |
392 | | * |
393 | | * Creates a new #FuFirmware of sub type FIT |
394 | | * |
395 | | * Since: 1.8.2 |
396 | | **/ |
397 | | FuFirmware * |
398 | | fu_fit_firmware_new(void) |
399 | 0 | { |
400 | 0 | return FU_FIRMWARE(g_object_new(FU_TYPE_FIT_FIRMWARE, NULL)); |
401 | 0 | } |