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