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