Coverage Report

Created: 2026-10-17 07:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-ifwi-cpd-firmware.c
Line
Count
Source
1
/*
2
 * Copyright 2022 Richard Hughes <richard@hughsie.com>
3
 * Copyright 2022 Intel
4
 *
5
 * SPDX-License-Identifier: LGPL-2.1-or-later
6
 */
7
8
#define G_LOG_DOMAIN "FuFirmware"
9
10
#include "config.h"
11
12
#include "fu-byte-array.h"
13
#include "fu-common.h"
14
#include "fu-ifwi-cpd-firmware.h"
15
#include "fu-ifwi-cpd-image.h"
16
#include "fu-ifwi-struct.h"
17
#include "fu-input-stream.h"
18
#include "fu-partial-input-stream.h"
19
#include "fu-string.h"
20
#include "fu-version-common.h"
21
22
/**
23
 * FuIfwiCpdFirmware:
24
 *
25
 * An Intel Code Partition Directory (aka CPD) can be found in IFWI (Integrated Firmware Image)
26
 * firmware blobs which are used in various Intel products using an IPU (Infrastructure Processing
27
 * Unit).
28
 *
29
 * This could include hardware like SmartNICs, GPUs, camera and audio devices.
30
 *
31
 * See also: [class@FuFirmware]
32
 */
33
34
typedef struct {
35
  guint8 header_version;
36
  guint8 entry_version;
37
} FuIfwiCpdFirmwarePrivate;
38
39
1.49k
G_DEFINE_TYPE_WITH_PRIVATE(FuIfwiCpdFirmware, fu_ifwi_cpd_firmware, FU_TYPE_FIRMWARE)
40
1.50k
#define GET_PRIVATE(o) (fu_ifwi_cpd_firmware_get_instance_private(o))
41
42
1.38k
#define FU_IFWI_CPD_FIRMWARE_ENTRIES_MAX 1024
43
44
static void
45
fu_ifwi_cpd_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
46
0
{
47
0
  FuIfwiCpdFirmware *self = FU_IFWI_CPD_FIRMWARE(firmware);
48
0
  FuIfwiCpdFirmwarePrivate *priv = GET_PRIVATE(self);
49
0
  fu_xmlb_builder_insert_kx(bn, "header_version", priv->header_version);
50
0
  fu_xmlb_builder_insert_kx(bn, "entry_version", priv->entry_version);
51
0
}
52
53
static gboolean
54
fu_ifwi_cpd_firmware_parse_manifest(FuIfwiCpdFirmware *self,
55
            FuFirmware *firmware,
56
            FuInputStream *stream,
57
            FuFirmwareParseFlags flags,
58
            GError **error)
59
733
{
60
733
  gsize streamsz = 0;
61
733
  guint32 size;
62
733
  gsize header_length;
63
733
  gsize offset = 0;
64
733
  guint64 version_raw = 0;
65
733
  g_autoptr(FuStructIfwiCpdManifest) st_mhd = NULL;
66
67
  /* raw version */
68
733
  st_mhd = fu_struct_ifwi_cpd_manifest_parse_stream(stream, offset, error);
69
733
  if (st_mhd == NULL)
70
0
    return FALSE;
71
72
  /* read as [u16le;4] and then build back into a native u64 */
73
733
  version_raw += (guint64)fu_struct_ifwi_cpd_manifest_get_version_major(st_mhd) << 48;
74
733
  version_raw += (guint64)fu_struct_ifwi_cpd_manifest_get_version_minor(st_mhd) << 32;
75
733
  version_raw += (guint64)fu_struct_ifwi_cpd_manifest_get_version_hotfix(st_mhd) << 16;
76
733
  version_raw += ((guint64)fu_struct_ifwi_cpd_manifest_get_version_build(st_mhd)) << 0;
77
733
  fu_firmware_set_version_raw(FU_FIRMWARE(self), version_raw);
78
79
  /* verify the size */
80
733
  if (!fu_input_stream_size(stream, &streamsz, error))
81
0
    return FALSE;
82
733
  size = fu_struct_ifwi_cpd_manifest_get_size(st_mhd);
83
733
  if ((guint64)size * 4 > G_MAXUINT32) {
84
14
    g_set_error(error,
85
14
          FWUPD_ERROR,
86
14
          FWUPD_ERROR_INVALID_DATA,
87
14
          "manifest size 0x%x dwords would overflow",
88
14
          size);
89
14
    return FALSE;
90
14
  }
91
719
  if (size * 4 != streamsz) {
92
198
    g_set_error(error,
93
198
          FWUPD_ERROR,
94
198
          FWUPD_ERROR_INVALID_DATA,
95
198
          "invalid manifest length, got 0x%x, expected 0x%x",
96
198
          size * 4,
97
198
          (guint)streamsz);
98
198
    return FALSE;
99
198
  }
100
101
  /* parse extensions */
102
521
  header_length = fu_struct_ifwi_cpd_manifest_get_header_length(st_mhd);
103
521
  if (!fu_size_checked_inc_product(&offset, header_length, 4, error)) {
104
0
    g_prefix_error_literal(error, "manifest offset overflow: ");
105
0
    return FALSE;
106
0
  }
107
4.34k
  while (offset < streamsz) {
108
4.14k
    guint32 extension_type = 0;
109
4.14k
    guint32 extension_length = 0;
110
4.14k
    g_autoptr(FuFirmware) img = fu_firmware_new();
111
4.14k
    g_autoptr(FuStructIfwiCpdManifestExt) st_mex = NULL;
112
4.14k
    g_autoptr(FuInputStream) partial_stream = NULL;
113
114
    /* set the extension type as the index */
115
4.14k
    st_mex = fu_struct_ifwi_cpd_manifest_ext_parse_stream(stream, offset, error);
116
4.14k
    if (st_mex == NULL)
117
14
      return FALSE;
118
4.13k
    extension_type = fu_struct_ifwi_cpd_manifest_ext_get_extension_type(st_mex);
119
4.13k
    if (extension_type == 0x0)
120
47
      break;
121
4.08k
    fu_firmware_set_idx(img, extension_type);
122
123
    /* add data section */
124
4.08k
    extension_length = fu_struct_ifwi_cpd_manifest_ext_get_extension_length(st_mex);
125
4.08k
    if (extension_length == 0x0)
126
95
      break;
127
3.98k
    if (extension_length < st_mex->buf->len) {
128
7
      g_set_error(error,
129
7
            FWUPD_ERROR,
130
7
            FWUPD_ERROR_INVALID_DATA,
131
7
            "invalid manifest extension header length 0x%x",
132
7
            (guint)extension_length);
133
7
      return FALSE;
134
7
    }
135
3.98k
    partial_stream = fu_partial_input_stream_new(stream,
136
3.98k
                   offset + st_mex->buf->len,
137
3.98k
                   extension_length - st_mex->buf->len,
138
3.98k
                   error);
139
3.98k
    if (partial_stream == NULL) {
140
151
      g_prefix_error_literal(error, "failed to cut CPD extension: ");
141
151
      return FALSE;
142
151
    }
143
3.83k
    if (!fu_firmware_parse_stream(img, partial_stream, 0x0, flags, error))
144
3
      return FALSE;
145
146
    /* success */
147
3.82k
    fu_firmware_set_offset(img, offset);
148
3.82k
    if (!fu_firmware_add_image(firmware, img, error))
149
0
      return FALSE;
150
3.82k
    if (!fu_size_checked_inc(&offset, extension_length, error))
151
0
      return FALSE;
152
3.82k
  }
153
154
  /* success */
155
346
  return TRUE;
156
521
}
157
158
static gboolean
159
fu_ifwi_cpd_firmware_validate(FuFirmware *firmware,
160
            FuInputStream *stream,
161
            gsize offset,
162
            GError **error)
163
1.47k
{
164
1.47k
  return fu_struct_ifwi_cpd_validate_stream(stream, offset, error);
165
1.47k
}
166
167
static gboolean
168
fu_ifwi_cpd_firmware_parse(FuFirmware *firmware,
169
         FuInputStream *stream,
170
         FuFirmwareParseFlags flags,
171
         GError **error)
172
1.32k
{
173
1.32k
  FuIfwiCpdFirmware *self = FU_IFWI_CPD_FIRMWARE(firmware);
174
1.32k
  FuIfwiCpdFirmwarePrivate *priv = GET_PRIVATE(self);
175
1.32k
  g_autoptr(FuStructIfwiCpd) st_hdr = NULL;
176
1.32k
  gsize offset = 0;
177
1.32k
  guint32 num_of_entries;
178
179
  /* other header fields */
180
1.32k
  st_hdr = fu_struct_ifwi_cpd_parse_stream(stream, offset, error);
181
1.32k
  if (st_hdr == NULL)
182
0
    return FALSE;
183
1.32k
  priv->header_version = fu_struct_ifwi_cpd_get_header_version(st_hdr);
184
1.32k
  priv->entry_version = fu_struct_ifwi_cpd_get_entry_version(st_hdr);
185
1.32k
  fu_firmware_set_idx(firmware, fu_struct_ifwi_cpd_get_partition_name(st_hdr));
186
187
  /* read out entries */
188
1.32k
  num_of_entries = fu_struct_ifwi_cpd_get_num_of_entries(st_hdr);
189
1.32k
  if (num_of_entries > FU_IFWI_CPD_FIRMWARE_ENTRIES_MAX) {
190
57
    g_set_error(error,
191
57
          FWUPD_ERROR,
192
57
          FWUPD_ERROR_INVALID_DATA,
193
57
          "too many entries 0x%x, expected <= 0x%x",
194
57
          num_of_entries,
195
57
          (guint)FU_IFWI_CPD_FIRMWARE_ENTRIES_MAX);
196
57
    return FALSE;
197
57
  }
198
1.26k
  if (!fu_size_checked_inc(&offset, fu_struct_ifwi_cpd_get_header_length(st_hdr), error))
199
0
    return FALSE;
200
36.2k
  for (guint32 i = 0; i < num_of_entries; i++) {
201
36.0k
    guint32 img_offset = 0;
202
36.0k
    g_autofree gchar *id = NULL;
203
36.0k
    g_autoptr(FuFirmware) img = fu_ifwi_cpd_image_new();
204
36.0k
    g_autoptr(FuStructIfwiCpdEntry) st_ent = NULL;
205
36.0k
    g_autoptr(FuInputStream) partial_stream = NULL;
206
207
    /* the IDX is the position in the file */
208
36.0k
    fu_firmware_set_idx(img, i);
209
210
36.0k
    st_ent = fu_struct_ifwi_cpd_entry_parse_stream(stream, offset, error);
211
36.0k
    if (st_ent == NULL)
212
262
      return FALSE;
213
214
    /* copy name as id */
215
35.7k
    id = fu_struct_ifwi_cpd_entry_get_name(st_ent);
216
35.7k
    fu_firmware_set_id(img, id);
217
218
    /* copy offset, ignoring huffman and reserved bits */
219
35.7k
    img_offset = fu_struct_ifwi_cpd_entry_get_offset(st_ent);
220
35.7k
    img_offset &= 0x1FFFFFF;
221
35.7k
    fu_firmware_set_offset(img, img_offset);
222
223
    /* copy data */
224
35.7k
    partial_stream =
225
35.7k
        fu_partial_input_stream_new(stream,
226
35.7k
            img_offset,
227
35.7k
            fu_struct_ifwi_cpd_entry_get_length(st_ent),
228
35.7k
            error);
229
35.7k
    if (partial_stream == NULL) {
230
401
      g_prefix_error_literal(error, "failed to cut IFD image: ");
231
401
      return FALSE;
232
401
    }
233
35.3k
    if (!fu_firmware_parse_stream(img, partial_stream, 0x0, flags, error))
234
39
      return FALSE;
235
236
    /* read the manifest */
237
35.3k
    if (i == FU_IFWI_CPD_FIRMWARE_IDX_MANIFEST &&
238
1.03k
        fu_struct_ifwi_cpd_entry_get_length(st_ent) >
239
1.03k
      FU_STRUCT_IFWI_CPD_MANIFEST_SIZE) {
240
733
      if (!fu_ifwi_cpd_firmware_parse_manifest(self,
241
733
                 img,
242
733
                 partial_stream,
243
733
                 flags,
244
733
                 error))
245
387
        return FALSE;
246
733
    }
247
248
    /* success */
249
34.9k
    if (!fu_firmware_add_image(firmware, img, error))
250
0
      return FALSE;
251
34.9k
    if (!fu_size_checked_inc(&offset, st_ent->buf->len, error))
252
0
      return FALSE;
253
34.9k
  }
254
255
  /* success */
256
179
  return TRUE;
257
1.26k
}
258
259
static GByteArray *
260
fu_ifwi_cpd_firmware_write(FuFirmware *firmware, GError **error)
261
179
{
262
179
  FuIfwiCpdFirmware *self = FU_IFWI_CPD_FIRMWARE(firmware);
263
179
  FuIfwiCpdFirmwarePrivate *priv = GET_PRIVATE(self);
264
179
  gsize offset = 0;
265
179
  g_autoptr(FuStructIfwiCpd) st = fu_struct_ifwi_cpd_new();
266
179
  g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware);
267
268
  /* write the header */
269
179
  fu_struct_ifwi_cpd_set_num_of_entries(st, imgs->len);
270
179
  fu_struct_ifwi_cpd_set_header_version(st, priv->header_version);
271
179
  fu_struct_ifwi_cpd_set_entry_version(st, priv->entry_version);
272
179
  fu_struct_ifwi_cpd_set_checksum(st, 0x0);
273
179
  fu_struct_ifwi_cpd_set_partition_name(st, fu_firmware_get_idx(firmware));
274
179
  fu_struct_ifwi_cpd_set_crc32(st, 0x0);
275
276
  /* fixup the image offsets */
277
179
  if (!fu_size_checked_inc(&offset, st->buf->len, error))
278
0
    return NULL;
279
179
  if (!fu_size_checked_inc_product(&offset, FU_STRUCT_IFWI_CPD_ENTRY_SIZE, imgs->len, error))
280
0
    return NULL;
281
10.6k
  for (guint i = 0; i < imgs->len; i++) {
282
10.4k
    FuFirmware *img = g_ptr_array_index(imgs, i);
283
10.4k
    g_autoptr(GBytes) blob = NULL;
284
285
10.4k
    blob = fu_firmware_get_bytes(img, error);
286
10.4k
    if (blob == NULL) {
287
0
      g_prefix_error(error, "image 0x%x: ", i);
288
0
      return NULL;
289
0
    }
290
10.4k
    fu_firmware_set_offset(img, offset);
291
10.4k
    if (!fu_size_checked_inc(&offset, g_bytes_get_size(blob), error)) {
292
0
      g_prefix_error(error, "offset overflow for entry %u: ", i);
293
0
      return NULL;
294
0
    }
295
10.4k
  }
296
297
  /* add entry headers */
298
5.08k
  for (guint i = 0; i < imgs->len; i++) {
299
4.96k
    FuFirmware *img = g_ptr_array_index(imgs, i);
300
4.96k
    g_autoptr(FuStructIfwiCpdEntry) st_ent = fu_struct_ifwi_cpd_entry_new();
301
302
    /* sanity check */
303
4.96k
    if (fu_firmware_get_id(img) == NULL) {
304
62
      g_set_error(error,
305
62
            FWUPD_ERROR,
306
62
            FWUPD_ERROR_INVALID_DATA,
307
62
            "image 0x%x must have an ID",
308
62
            (guint)fu_firmware_get_idx(img));
309
62
      return NULL;
310
62
    }
311
4.90k
    if (!fu_struct_ifwi_cpd_entry_set_name(st_ent, fu_firmware_get_id(img), error))
312
0
      return NULL;
313
4.90k
    fu_struct_ifwi_cpd_entry_set_offset(st_ent, fu_firmware_get_offset(img));
314
4.90k
    fu_struct_ifwi_cpd_entry_set_length(st_ent, fu_firmware_get_size(img));
315
4.90k
    fu_byte_array_append_array(st->buf, st_ent->buf);
316
4.90k
  }
317
318
  /* add entry data */
319
3.70k
  for (guint i = 0; i < imgs->len; i++) {
320
3.59k
    FuFirmware *img = g_ptr_array_index(imgs, i);
321
3.59k
    g_autoptr(GBytes) blob = NULL;
322
3.59k
    blob = fu_firmware_get_bytes(img, error);
323
3.59k
    if (blob == NULL)
324
0
      return NULL;
325
3.59k
    fu_byte_array_append_bytes(st->buf, blob);
326
3.59k
  }
327
328
  /* success */
329
117
  return g_steal_pointer(&st->buf);
330
117
}
331
332
static gboolean
333
fu_ifwi_cpd_firmware_build(FuFirmware *firmware, XbNode *n, GError **error)
334
0
{
335
0
  FuIfwiCpdFirmware *self = FU_IFWI_CPD_FIRMWARE(firmware);
336
0
  FuIfwiCpdFirmwarePrivate *priv = GET_PRIVATE(self);
337
0
  const gchar *tmp;
338
339
  /* simple properties */
340
0
  tmp = xb_node_query_text(n, "header_version", NULL);
341
0
  if (tmp != NULL) {
342
0
    guint64 val = 0;
343
0
    if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT8, FU_INTEGER_BASE_AUTO, error))
344
0
      return FALSE;
345
0
    priv->header_version = val;
346
0
  }
347
0
  tmp = xb_node_query_text(n, "entry_version", NULL);
348
0
  if (tmp != NULL) {
349
0
    guint64 val = 0;
350
0
    if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT8, FU_INTEGER_BASE_AUTO, error))
351
0
      return FALSE;
352
0
    priv->entry_version = val;
353
0
  }
354
355
  /* success */
356
0
  return TRUE;
357
0
}
358
359
static gchar *
360
fu_ifwi_cpd_firmware_convert_version(FuFirmware *firmware, guint64 version_raw)
361
733
{
362
733
  return fu_version_from_uint64(version_raw, fu_firmware_get_version_format(firmware));
363
733
}
364
365
static void
366
fu_ifwi_cpd_firmware_init(FuIfwiCpdFirmware *self)
367
1.49k
{
368
1.49k
  fu_firmware_set_version_format(FU_FIRMWARE(self), FWUPD_VERSION_FORMAT_QUAD);
369
1.49k
}
370
371
static void
372
fu_ifwi_cpd_firmware_class_init(FuIfwiCpdFirmwareClass *klass)
373
2
{
374
2
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
375
2
  fu_firmware_add_image_gtype(firmware_class, FU_TYPE_IFWI_CPD_IMAGE);
376
2
  fu_firmware_set_size_max(firmware_class, 128 * FU_MB);
377
2
  firmware_class->validate = fu_ifwi_cpd_firmware_validate;
378
2
  firmware_class->export = fu_ifwi_cpd_firmware_export;
379
2
  firmware_class->parse = fu_ifwi_cpd_firmware_parse;
380
2
  firmware_class->write = fu_ifwi_cpd_firmware_write;
381
2
  firmware_class->build = fu_ifwi_cpd_firmware_build;
382
2
  firmware_class->convert_version = fu_ifwi_cpd_firmware_convert_version;
383
2
  fu_firmware_set_images_max(firmware_class, FU_IFWI_CPD_FIRMWARE_ENTRIES_MAX);
384
2
}
385
386
/**
387
 * fu_ifwi_cpd_firmware_new:
388
 *
389
 * Creates a new #FuFirmware of Intel Code Partition Directory format
390
 *
391
 * Since: 1.8.2
392
 **/
393
FuFirmware *
394
fu_ifwi_cpd_firmware_new(void)
395
0
{
396
0
  return FU_FIRMWARE(g_object_new(FU_TYPE_IFWI_CPD_FIRMWARE, NULL));
397
0
}