Coverage Report

Created: 2026-02-26 06:27

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