Coverage Report

Created: 2025-07-01 07:09

/src/fwupd/plugins/ccgx-dmc/fu-ccgx-dmc-firmware.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright 2020 Cypress Semiconductor Corporation.
3
 * Copyright 2020 Richard Hughes <richard@hughsie.com>
4
 *
5
 * SPDX-License-Identifier: LGPL-2.1-or-later
6
 */
7
8
#include "config.h"
9
10
#include <string.h>
11
12
#include "fu-ccgx-dmc-firmware.h"
13
#include "fu-ccgx-dmc-struct.h"
14
15
struct _FuCcgxDmcFirmware {
16
  FuFirmwareClass parent_instance;
17
  GPtrArray *image_records;
18
  GBytes *fwct_blob;
19
  GBytes *custom_meta_blob;
20
  guint32 row_data_offset_start;
21
  guint32 fw_data_size;
22
};
23
24
G_DEFINE_TYPE(FuCcgxDmcFirmware, fu_ccgx_dmc_firmware, FU_TYPE_FIRMWARE)
25
26
2.80k
#define DMC_FWCT_MAX_SIZE     2048
27
485
#define DMC_HASH_SIZE       32
28
1.20k
#define DMC_CUSTOM_META_LENGTH_FIELD_SIZE 2
29
30
static void
31
fu_ccgx_dmc_firmware_record_free(FuCcgxDmcFirmwareRecord *rcd)
32
2.66k
{
33
2.66k
  if (rcd->seg_records != NULL)
34
1.99k
    g_ptr_array_unref(rcd->seg_records);
35
2.66k
  g_free(rcd);
36
2.66k
}
37
38
static void
39
fu_ccgx_dmc_firmware_segment_record_free(FuCcgxDmcFirmwareSegmentRecord *rcd)
40
24.2k
{
41
24.2k
  if (rcd->data_records != NULL)
42
24.0k
    g_ptr_array_unref(rcd->data_records);
43
24.2k
  g_free(rcd);
44
24.2k
}
45
46
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCcgxDmcFirmwareRecord, fu_ccgx_dmc_firmware_record_free)
47
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCcgxDmcFirmwareSegmentRecord,
48
            fu_ccgx_dmc_firmware_segment_record_free)
49
50
GPtrArray *
51
fu_ccgx_dmc_firmware_get_image_records(FuCcgxDmcFirmware *self)
52
0
{
53
0
  g_return_val_if_fail(FU_IS_CCGX_DMC_FIRMWARE(self), NULL);
54
0
  return self->image_records;
55
0
}
56
57
GBytes *
58
fu_ccgx_dmc_firmware_get_fwct_record(FuCcgxDmcFirmware *self)
59
0
{
60
0
  g_return_val_if_fail(FU_IS_CCGX_DMC_FIRMWARE(self), NULL);
61
0
  return self->fwct_blob;
62
0
}
63
64
GBytes *
65
fu_ccgx_dmc_firmware_get_custom_meta_record(FuCcgxDmcFirmware *self)
66
0
{
67
0
  g_return_val_if_fail(FU_IS_CCGX_DMC_FIRMWARE(self), NULL);
68
0
  return self->custom_meta_blob;
69
0
}
70
71
guint32
72
fu_ccgx_dmc_firmware_get_fw_data_size(FuCcgxDmcFirmware *self)
73
0
{
74
0
  g_return_val_if_fail(FU_IS_CCGX_DMC_FIRMWARE(self), 0);
75
0
  return self->fw_data_size;
76
0
}
77
78
static void
79
fu_ccgx_dmc_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
80
0
{
81
0
  FuCcgxDmcFirmware *self = FU_CCGX_DMC_FIRMWARE(firmware);
82
0
  if (flags & FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG) {
83
0
    fu_xmlb_builder_insert_kx(bn, "fw_data_size", self->fw_data_size);
84
0
    fu_xmlb_builder_insert_kx(bn, "image_records", self->image_records->len);
85
0
  }
86
0
}
87
88
static gboolean
89
fu_ccgx_dmc_firmware_parse_segment(FuFirmware *firmware,
90
           GInputStream *stream,
91
           FuCcgxDmcFirmwareRecord *img_rcd,
92
           gsize *seg_off,
93
           FuFirmwareParseFlags flags,
94
           GError **error)
95
1.99k
{
96
1.99k
  FuCcgxDmcFirmware *self = FU_CCGX_DMC_FIRMWARE(firmware);
97
1.99k
  gsize row_off;
98
1.99k
  g_autoptr(GChecksum) csum = g_checksum_new(G_CHECKSUM_SHA256);
99
100
  /* set row data offset in current image */
101
1.99k
  row_off = self->row_data_offset_start + img_rcd->img_offset;
102
103
  /* parse segment in image */
104
1.99k
  img_rcd->seg_records =
105
1.99k
      g_ptr_array_new_with_free_func((GFreeFunc)fu_ccgx_dmc_firmware_segment_record_free);
106
25.7k
  for (guint32 i = 0; i < img_rcd->num_img_segments; i++) {
107
24.2k
    guint16 row_size_bytes = 0;
108
24.2k
    g_autoptr(FuCcgxDmcFirmwareSegmentRecord) seg_rcd = NULL;
109
24.2k
    g_autoptr(GByteArray) st_info = NULL;
110
111
    /* read segment info  */
112
24.2k
    seg_rcd = g_new0(FuCcgxDmcFirmwareSegmentRecord, 1);
113
24.2k
    st_info =
114
24.2k
        fu_struct_ccgx_dmc_fwct_segmentation_info_parse_stream(stream, *seg_off, error);
115
24.2k
    if (st_info == NULL)
116
193
      return FALSE;
117
24.0k
    seg_rcd->start_row =
118
24.0k
        fu_struct_ccgx_dmc_fwct_segmentation_info_get_start_row(st_info);
119
24.0k
    seg_rcd->num_rows = fu_struct_ccgx_dmc_fwct_segmentation_info_get_num_rows(st_info);
120
121
    /* calculate actual row size */
122
24.0k
    row_size_bytes = img_rcd->row_size * 64;
123
124
    /* create data record array in segment record */
125
24.0k
    seg_rcd->data_records =
126
24.0k
        g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref);
127
128
    /* read row data in segment */
129
592k
    for (int row = 0; row < seg_rcd->num_rows; row++) {
130
569k
      g_autoptr(GBytes) data_rcd = NULL;
131
132
569k
      data_rcd = fu_input_stream_read_bytes(stream,
133
569k
                    row_off,
134
569k
                    row_size_bytes,
135
569k
                    NULL,
136
569k
                    error);
137
569k
      if (data_rcd == NULL)
138
308
        return FALSE;
139
140
      /* update hash */
141
568k
      g_checksum_update(csum,
142
568k
            (guchar *)g_bytes_get_data(data_rcd, NULL),
143
568k
            g_bytes_get_size(data_rcd));
144
145
      /* add row data to data record */
146
568k
      g_ptr_array_add(seg_rcd->data_records, g_steal_pointer(&data_rcd));
147
148
      /* increment row data offset */
149
568k
      row_off += row_size_bytes;
150
568k
    }
151
152
    /* add segment record to segment array */
153
23.7k
    g_ptr_array_add(img_rcd->seg_records, g_steal_pointer(&seg_rcd));
154
155
    /* increment segment info offset */
156
23.7k
    *seg_off += st_info->len;
157
23.7k
  }
158
159
  /* check checksum */
160
1.48k
  if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) {
161
485
    guint8 csumbuf[DMC_HASH_SIZE] = {0x0};
162
485
    gsize csumbufsz = sizeof(csumbuf);
163
485
    g_checksum_get_digest(csum, csumbuf, &csumbufsz);
164
485
    if (memcmp(csumbuf, img_rcd->img_digest, DMC_HASH_SIZE) != 0) {
165
257
      g_set_error_literal(error,
166
257
              FWUPD_ERROR,
167
257
              FWUPD_ERROR_NOT_SUPPORTED,
168
257
              "invalid hash");
169
257
      return FALSE;
170
257
    }
171
485
  }
172
173
  /* success */
174
1.23k
  return TRUE;
175
1.48k
}
176
177
static gboolean
178
fu_ccgx_dmc_firmware_parse_image(FuFirmware *firmware,
179
         guint8 image_count,
180
         GInputStream *stream,
181
         FuFirmwareParseFlags flags,
182
         GError **error)
183
1.20k
{
184
1.20k
  FuCcgxDmcFirmware *self = FU_CCGX_DMC_FIRMWARE(firmware);
185
1.20k
  gsize img_off = FU_STRUCT_CCGX_DMC_FWCT_INFO_SIZE;
186
1.20k
  gsize seg_off = FU_STRUCT_CCGX_DMC_FWCT_INFO_SIZE +
187
1.20k
      image_count * FU_STRUCT_CCGX_DMC_FWCT_IMAGE_INFO_SIZE;
188
189
  /* set initial segment info offset */
190
2.92k
  for (guint32 i = 0; i < image_count; i++) {
191
2.66k
    g_autoptr(FuCcgxDmcFirmwareRecord) img_rcd = NULL;
192
2.66k
    g_autoptr(GByteArray) st_img = NULL;
193
194
    /* read image info */
195
2.66k
    img_rcd = g_new0(FuCcgxDmcFirmwareRecord, 1);
196
2.66k
    st_img = fu_struct_ccgx_dmc_fwct_image_info_parse_stream(stream, img_off, error);
197
2.66k
    if (st_img == NULL)
198
171
      return FALSE;
199
2.49k
    img_rcd->row_size = fu_struct_ccgx_dmc_fwct_image_info_get_row_size(st_img);
200
2.49k
    if (img_rcd->row_size == 0) {
201
20
      g_set_error(error,
202
20
            FWUPD_ERROR,
203
20
            FWUPD_ERROR_NOT_SUPPORTED,
204
20
            "invalid row size 0x%x",
205
20
            img_rcd->row_size);
206
20
      return FALSE;
207
20
    }
208
2.47k
    img_rcd->img_offset = fu_struct_ccgx_dmc_fwct_image_info_get_img_offset(st_img);
209
2.47k
    img_rcd->num_img_segments =
210
2.47k
        fu_struct_ccgx_dmc_fwct_image_info_get_num_img_segments(st_img);
211
212
    /* segments are optional */
213
2.47k
    if (img_rcd->num_img_segments > 0) {
214
1.99k
      gsize img_digestsz = 0;
215
1.99k
      const guint8 *img_digest;
216
217
1.99k
      img_digest =
218
1.99k
          fu_struct_ccgx_dmc_fwct_image_info_get_img_digest(st_img,
219
1.99k
                        &img_digestsz);
220
1.99k
      if (!fu_memcpy_safe((guint8 *)&img_rcd->img_digest,
221
1.99k
              sizeof(img_rcd->img_digest),
222
1.99k
              0x0, /* dst */
223
1.99k
              img_digest,
224
1.99k
              img_digestsz,
225
1.99k
              0, /* src */
226
1.99k
              img_digestsz,
227
1.99k
              error))
228
0
        return FALSE;
229
230
      /* parse segment */
231
1.99k
      if (!fu_ccgx_dmc_firmware_parse_segment(firmware,
232
1.99k
                stream,
233
1.99k
                img_rcd,
234
1.99k
                &seg_off,
235
1.99k
                flags,
236
1.99k
                error))
237
758
        return FALSE;
238
239
      /* add image record to image record array */
240
1.23k
      g_ptr_array_add(self->image_records, g_steal_pointer(&img_rcd));
241
1.23k
    }
242
243
    /* increment image offset */
244
1.72k
    img_off += FU_STRUCT_CCGX_DMC_FWCT_IMAGE_INFO_SIZE;
245
1.72k
  }
246
247
256
  return TRUE;
248
1.20k
}
249
250
static gboolean
251
fu_ccgx_dmc_firmware_validate(FuFirmware *firmware,
252
            GInputStream *stream,
253
            gsize offset,
254
            GError **error)
255
7.11M
{
256
7.11M
  return fu_struct_ccgx_dmc_fwct_info_validate_stream(stream, offset, error);
257
7.11M
}
258
259
static gboolean
260
fu_ccgx_dmc_firmware_parse(FuFirmware *firmware,
261
         GInputStream *stream,
262
         FuFirmwareParseFlags flags,
263
         GError **error)
264
1.39k
{
265
1.39k
  FuCcgxDmcFirmware *self = FU_CCGX_DMC_FIRMWARE(firmware);
266
1.39k
  gsize streamsz = 0;
267
1.39k
  guint16 hdr_size = 0;
268
1.39k
  guint16 mdbufsz = 0;
269
1.39k
  guint32 hdr_composite_version = 0;
270
1.39k
  guint8 hdr_image_count = 0;
271
1.39k
  g_autoptr(GByteArray) st_hdr = NULL;
272
273
  /* parse */
274
1.39k
  st_hdr = fu_struct_ccgx_dmc_fwct_info_parse_stream(stream, 0x0, error);
275
1.39k
  if (st_hdr == NULL)
276
0
    return FALSE;
277
278
  /* check fwct size */
279
1.39k
  hdr_size = fu_struct_ccgx_dmc_fwct_info_get_size(st_hdr);
280
1.39k
  if (hdr_size > DMC_FWCT_MAX_SIZE || hdr_size == 0) {
281
21
    g_set_error(error,
282
21
          FWUPD_ERROR,
283
21
          FWUPD_ERROR_NOT_SUPPORTED,
284
21
          "invalid dmc fwct size, expected <= 0x%x, got 0x%x",
285
21
          (guint)DMC_FWCT_MAX_SIZE,
286
21
          (guint)hdr_size);
287
21
    return FALSE;
288
21
  }
289
290
  /* set version */
291
1.37k
  hdr_composite_version = fu_struct_ccgx_dmc_fwct_info_get_composite_version(st_hdr);
292
1.37k
  if (hdr_composite_version != 0)
293
1.26k
    fu_firmware_set_version_raw(firmware, hdr_composite_version);
294
295
  /* read fwct data */
296
1.37k
  self->fwct_blob = fu_input_stream_read_bytes(stream, 0x0, hdr_size, NULL, error);
297
1.37k
  if (self->fwct_blob == NULL)
298
0
    return FALSE;
299
300
  /* create custom meta binary */
301
1.37k
  if (!fu_input_stream_read_u16(stream, hdr_size, &mdbufsz, G_LITTLE_ENDIAN, error)) {
302
160
    g_prefix_error(error, "failed to read metadata size: ");
303
160
    return FALSE;
304
160
  }
305
1.21k
  if (mdbufsz > 0) {
306
707
    self->custom_meta_blob =
307
707
        fu_input_stream_read_bytes(stream, hdr_size + 2, mdbufsz, NULL, error);
308
707
    if (self->custom_meta_blob == NULL)
309
6
      return FALSE;
310
707
  }
311
312
  /* set row data start offset */
313
1.20k
  self->row_data_offset_start = hdr_size + DMC_CUSTOM_META_LENGTH_FIELD_SIZE + mdbufsz;
314
1.20k
  if (!fu_input_stream_size(stream, &streamsz, error))
315
0
    return FALSE;
316
1.20k
  self->fw_data_size = streamsz - self->row_data_offset_start;
317
318
  /* parse image */
319
1.20k
  hdr_image_count = fu_struct_ccgx_dmc_fwct_info_get_image_count(st_hdr);
320
1.20k
  if (!fu_ccgx_dmc_firmware_parse_image(firmware, hdr_image_count, stream, flags, error))
321
949
    return FALSE;
322
323
  /* success */
324
256
  return TRUE;
325
1.20k
}
326
327
static GByteArray *
328
fu_ccgx_dmc_firmware_write(FuFirmware *firmware, GError **error)
329
256
{
330
256
  g_autoptr(GByteArray) buf = g_byte_array_new();
331
256
  g_autoptr(GByteArray) st_hdr = fu_struct_ccgx_dmc_fwct_info_new();
332
256
  g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware);
333
334
  /* add header */
335
256
  fu_struct_ccgx_dmc_fwct_info_set_size(
336
256
      st_hdr,
337
256
      FU_STRUCT_CCGX_DMC_FWCT_INFO_SIZE +
338
256
    (images->len * (FU_STRUCT_CCGX_DMC_FWCT_IMAGE_INFO_SIZE +
339
256
        FU_STRUCT_CCGX_DMC_FWCT_SEGMENTATION_INFO_SIZE)));
340
256
  fu_struct_ccgx_dmc_fwct_info_set_version(st_hdr, 0x2);
341
256
  fu_struct_ccgx_dmc_fwct_info_set_custom_meta_type(st_hdr, 0x3);
342
256
  fu_struct_ccgx_dmc_fwct_info_set_cdtt_version(st_hdr, 0x1);
343
256
  fu_struct_ccgx_dmc_fwct_info_set_device_id(st_hdr, 0x1);
344
256
  fu_struct_ccgx_dmc_fwct_info_set_composite_version(st_hdr,
345
256
                 fu_firmware_get_version_raw(firmware));
346
256
  fu_struct_ccgx_dmc_fwct_info_set_image_count(st_hdr, images->len);
347
256
  g_byte_array_append(buf, st_hdr->data, st_hdr->len);
348
349
  /* add image headers */
350
256
  for (guint i = 0; i < images->len; i++) {
351
0
    g_autoptr(GByteArray) st_img = fu_struct_ccgx_dmc_fwct_image_info_new();
352
0
    fu_struct_ccgx_dmc_fwct_image_info_set_device_type(st_img, 0x2);
353
0
    fu_struct_ccgx_dmc_fwct_image_info_set_img_type(st_img, 0x1);
354
0
    fu_struct_ccgx_dmc_fwct_image_info_set_row_size(st_img, 0x1);
355
0
    fu_struct_ccgx_dmc_fwct_image_info_set_fw_version(st_img, 0x330006d2);
356
0
    fu_struct_ccgx_dmc_fwct_image_info_set_app_version(st_img, 0x14136161);
357
0
    fu_struct_ccgx_dmc_fwct_image_info_set_num_img_segments(st_img, 0x1);
358
0
    g_byte_array_append(buf, st_img->data, st_img->len);
359
0
  }
360
361
  /* add segments */
362
256
  for (guint i = 0; i < images->len; i++) {
363
0
    FuFirmware *img = g_ptr_array_index(images, i);
364
0
    g_autoptr(GByteArray) st_info = fu_struct_ccgx_dmc_fwct_segmentation_info_new();
365
0
    g_autoptr(FuChunkArray) chunks = NULL;
366
0
    g_autoptr(GBytes) img_bytes = fu_firmware_get_bytes(img, error);
367
0
    if (img_bytes == NULL)
368
0
      return NULL;
369
0
    chunks = fu_chunk_array_new_from_bytes(img_bytes,
370
0
                   FU_CHUNK_ADDR_OFFSET_NONE,
371
0
                   FU_CHUNK_PAGESZ_NONE,
372
0
                   64);
373
0
    fu_struct_ccgx_dmc_fwct_segmentation_info_set_num_rows(
374
0
        st_info,
375
0
        MAX(fu_chunk_array_length(chunks), 1));
376
0
    g_byte_array_append(buf, st_info->data, st_info->len);
377
0
  }
378
379
  /* metadata */
380
256
  fu_byte_array_append_uint16(buf, 0x1, G_LITTLE_ENDIAN);
381
256
  fu_byte_array_append_uint8(buf, 0xff);
382
383
  /* add image headers */
384
256
  for (guint i = 0; i < images->len; i++) {
385
0
    FuFirmware *img = g_ptr_array_index(images, i);
386
0
    gsize csumbufsz = DMC_HASH_SIZE;
387
0
    gsize img_offset = FU_STRUCT_CCGX_DMC_FWCT_INFO_SIZE +
388
0
           (i * FU_STRUCT_CCGX_DMC_FWCT_IMAGE_INFO_SIZE);
389
0
    guint8 csumbuf[DMC_HASH_SIZE] = {0x0};
390
0
    g_autoptr(GChecksum) csum = g_checksum_new(G_CHECKSUM_SHA256);
391
0
    g_autoptr(GBytes) img_bytes = NULL;
392
0
    g_autoptr(GBytes) img_padded = NULL;
393
0
    g_autoptr(FuChunkArray) chunks = NULL;
394
395
0
    img_bytes = fu_firmware_get_bytes(img, error);
396
0
    if (img_bytes == NULL)
397
0
      return NULL;
398
0
    chunks = fu_chunk_array_new_from_bytes(img_bytes,
399
0
                   FU_CHUNK_ADDR_OFFSET_NONE,
400
0
                   FU_CHUNK_PAGESZ_NONE,
401
0
                   64);
402
0
    img_padded =
403
0
        fu_bytes_pad(img_bytes, MAX(fu_chunk_array_length(chunks), 1) * 64, 0xFF);
404
0
    fu_byte_array_append_bytes(buf, img_padded);
405
0
    g_checksum_update(csum,
406
0
          (const guchar *)g_bytes_get_data(img_padded, NULL),
407
0
          g_bytes_get_size(img_padded));
408
0
    g_checksum_get_digest(csum, csumbuf, &csumbufsz);
409
410
    /* update checksum */
411
0
    if (!fu_memcpy_safe(buf->data,
412
0
            buf->len, /* dst */
413
0
            img_offset +
414
0
          FU_STRUCT_CCGX_DMC_FWCT_IMAGE_INFO_OFFSET_IMG_DIGEST,
415
0
            csumbuf,
416
0
            sizeof(csumbuf),
417
0
            0x0, /* src */
418
0
            sizeof(csumbuf),
419
0
            error))
420
0
      return NULL;
421
0
  }
422
423
256
  return g_steal_pointer(&buf);
424
256
}
425
426
static gchar *
427
fu_ccgx_dmc_firmware_convert_version(FuFirmware *firmware, guint64 version_raw)
428
1.26k
{
429
1.26k
  return fu_version_from_uint32(version_raw, fu_firmware_get_version_format(firmware));
430
1.26k
}
431
432
static void
433
fu_ccgx_dmc_firmware_init(FuCcgxDmcFirmware *self)
434
1.97k
{
435
1.97k
  self->image_records =
436
1.97k
      g_ptr_array_new_with_free_func((GFreeFunc)fu_ccgx_dmc_firmware_record_free);
437
1.97k
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM);
438
1.97k
  fu_firmware_set_version_format(FU_FIRMWARE(self), FWUPD_VERSION_FORMAT_QUAD);
439
1.97k
}
440
441
static void
442
fu_ccgx_dmc_firmware_finalize(GObject *object)
443
1.97k
{
444
1.97k
  FuCcgxDmcFirmware *self = FU_CCGX_DMC_FIRMWARE(object);
445
446
1.97k
  if (self->fwct_blob != NULL)
447
1.37k
    g_bytes_unref(self->fwct_blob);
448
1.97k
  if (self->custom_meta_blob != NULL)
449
701
    g_bytes_unref(self->custom_meta_blob);
450
1.97k
  if (self->image_records != NULL)
451
1.97k
    g_ptr_array_unref(self->image_records);
452
453
1.97k
  G_OBJECT_CLASS(fu_ccgx_dmc_firmware_parent_class)->finalize(object);
454
1.97k
}
455
456
static void
457
fu_ccgx_dmc_firmware_class_init(FuCcgxDmcFirmwareClass *klass)
458
1
{
459
1
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
460
1
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
461
1
  firmware_class->convert_version = fu_ccgx_dmc_firmware_convert_version;
462
1
  object_class->finalize = fu_ccgx_dmc_firmware_finalize;
463
1
  firmware_class->validate = fu_ccgx_dmc_firmware_validate;
464
1
  firmware_class->parse = fu_ccgx_dmc_firmware_parse;
465
1
  firmware_class->write = fu_ccgx_dmc_firmware_write;
466
1
  firmware_class->export = fu_ccgx_dmc_firmware_export;
467
1
}
468
469
FuFirmware *
470
fu_ccgx_dmc_firmware_new(void)
471
0
{
472
0
  return FU_FIRMWARE(g_object_new(FU_TYPE_CCGX_DMC_FIRMWARE, NULL));
473
0
}