Coverage Report

Created: 2025-11-11 06:44

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