Coverage Report

Created: 2026-10-28 07:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}