Coverage Report

Created: 2025-08-29 06:48

/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
  FuFirmware 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.38k
#define DMC_FWCT_MAX_SIZE     2048
27
488
#define DMC_HASH_SIZE       32
28
1.08k
#define DMC_CUSTOM_META_LENGTH_FIELD_SIZE 2
29
30
static void
31
fu_ccgx_dmc_firmware_record_free(FuCcgxDmcFirmwareRecord *rcd)
32
2.68k
{
33
2.68k
  if (rcd->seg_records != NULL)
34
2.05k
    g_ptr_array_unref(rcd->seg_records);
35
2.68k
  g_free(rcd);
36
2.68k
}
37
38
static void
39
fu_ccgx_dmc_firmware_segment_record_free(FuCcgxDmcFirmwareSegmentRecord *rcd)
40
21.8k
{
41
21.8k
  if (rcd->data_records != NULL)
42
21.7k
    g_ptr_array_unref(rcd->data_records);
43
21.8k
  g_free(rcd);
44
21.8k
}
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
2.05k
{
96
2.05k
  FuCcgxDmcFirmware *self = FU_CCGX_DMC_FIRMWARE(firmware);
97
2.05k
  gsize row_off;
98
2.05k
  g_autoptr(GChecksum) csum = g_checksum_new(G_CHECKSUM_SHA256);
99
100
  /* set row data offset in current image */
101
2.05k
  row_off = self->row_data_offset_start + img_rcd->img_offset;
102
103
  /* parse segment in image */
104
2.05k
  img_rcd->seg_records =
105
2.05k
      g_ptr_array_new_with_free_func((GFreeFunc)fu_ccgx_dmc_firmware_segment_record_free);
106
23.4k
  for (guint32 i = 0; i < img_rcd->num_img_segments; i++) {
107
21.8k
    guint16 row_size_bytes = 0;
108
21.8k
    g_autoptr(FuCcgxDmcFirmwareSegmentRecord) seg_rcd = NULL;
109
21.8k
    g_autoptr(GByteArray) st_info = NULL;
110
111
    /* read segment info  */
112
21.8k
    seg_rcd = g_new0(FuCcgxDmcFirmwareSegmentRecord, 1);
113
21.8k
    st_info =
114
21.8k
        fu_struct_ccgx_dmc_fwct_segmentation_info_parse_stream(stream, *seg_off, error);
115
21.8k
    if (st_info == NULL)
116
163
      return FALSE;
117
21.7k
    seg_rcd->start_row =
118
21.7k
        fu_struct_ccgx_dmc_fwct_segmentation_info_get_start_row(st_info);
119
21.7k
    seg_rcd->num_rows = fu_struct_ccgx_dmc_fwct_segmentation_info_get_num_rows(st_info);
120
121
    /* calculate actual row size */
122
21.7k
    row_size_bytes = img_rcd->row_size * 64;
123
124
    /* create data record array in segment record */
125
21.7k
    seg_rcd->data_records =
126
21.7k
        g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref);
127
128
    /* read row data in segment */
129
575k
    for (int row = 0; row < seg_rcd->num_rows; row++) {
130
553k
      g_autoptr(GBytes) data_rcd = NULL;
131
132
553k
      data_rcd = fu_input_stream_read_bytes(stream,
133
553k
                    row_off,
134
553k
                    row_size_bytes,
135
553k
                    NULL,
136
553k
                    error);
137
553k
      if (data_rcd == NULL)
138
284
        return FALSE;
139
140
      /* update hash */
141
553k
      g_checksum_update(csum,
142
553k
            (guchar *)g_bytes_get_data(data_rcd, NULL),
143
553k
            g_bytes_get_size(data_rcd));
144
145
      /* add row data to data record */
146
553k
      g_ptr_array_add(seg_rcd->data_records, g_steal_pointer(&data_rcd));
147
148
      /* increment row data offset */
149
553k
      row_off += row_size_bytes;
150
553k
    }
151
152
    /* add segment record to segment array */
153
21.4k
    g_ptr_array_add(img_rcd->seg_records, g_steal_pointer(&seg_rcd));
154
155
    /* increment segment info offset */
156
21.4k
    *seg_off += st_info->len;
157
21.4k
  }
158
159
  /* check checksum */
160
1.61k
  if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) {
161
488
    guint8 csumbuf[DMC_HASH_SIZE] = {0x0};
162
488
    gsize csumbufsz = sizeof(csumbuf);
163
488
    g_checksum_get_digest(csum, csumbuf, &csumbufsz);
164
488
    if (memcmp(csumbuf, img_rcd->img_digest, DMC_HASH_SIZE) != 0) {
165
235
      g_set_error_literal(error,
166
235
              FWUPD_ERROR,
167
235
              FWUPD_ERROR_NOT_SUPPORTED,
168
235
              "invalid hash");
169
235
      return FALSE;
170
235
    }
171
488
  }
172
173
  /* success */
174
1.37k
  return TRUE;
175
1.61k
}
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.08k
{
184
1.08k
  FuCcgxDmcFirmware *self = FU_CCGX_DMC_FIRMWARE(firmware);
185
1.08k
  gsize img_off = FU_STRUCT_CCGX_DMC_FWCT_INFO_SIZE;
186
1.08k
  gsize seg_off = FU_STRUCT_CCGX_DMC_FWCT_INFO_SIZE +
187
1.08k
      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.68k
    g_autoptr(FuCcgxDmcFirmwareRecord) img_rcd = NULL;
192
2.68k
    g_autoptr(GByteArray) st_img = NULL;
193
194
    /* read image info */
195
2.68k
    img_rcd = g_new0(FuCcgxDmcFirmwareRecord, 1);
196
2.68k
    st_img = fu_struct_ccgx_dmc_fwct_image_info_parse_stream(stream, img_off, error);
197
2.68k
    if (st_img == NULL)
198
138
      return FALSE;
199
2.54k
    img_rcd->row_size = fu_struct_ccgx_dmc_fwct_image_info_get_row_size(st_img);
200
2.54k
    if (img_rcd->row_size == 0) {
201
21
      g_set_error(error,
202
21
            FWUPD_ERROR,
203
21
            FWUPD_ERROR_NOT_SUPPORTED,
204
21
            "invalid row size 0x%x",
205
21
            img_rcd->row_size);
206
21
      return FALSE;
207
21
    }
208
2.52k
    img_rcd->img_offset = fu_struct_ccgx_dmc_fwct_image_info_get_img_offset(st_img);
209
2.52k
    img_rcd->num_img_segments =
210
2.52k
        fu_struct_ccgx_dmc_fwct_image_info_get_num_img_segments(st_img);
211
212
    /* segments are optional */
213
2.52k
    if (img_rcd->num_img_segments > 0) {
214
2.05k
      gsize img_digestsz = 0;
215
2.05k
      const guint8 *img_digest;
216
217
2.05k
      img_digest =
218
2.05k
          fu_struct_ccgx_dmc_fwct_image_info_get_img_digest(st_img,
219
2.05k
                        &img_digestsz);
220
2.05k
      if (!fu_memcpy_safe((guint8 *)&img_rcd->img_digest,
221
2.05k
              sizeof(img_rcd->img_digest),
222
2.05k
              0x0, /* dst */
223
2.05k
              img_digest,
224
2.05k
              img_digestsz,
225
2.05k
              0, /* src */
226
2.05k
              img_digestsz,
227
2.05k
              error))
228
0
        return FALSE;
229
230
      /* parse segment */
231
2.05k
      if (!fu_ccgx_dmc_firmware_parse_segment(firmware,
232
2.05k
                stream,
233
2.05k
                img_rcd,
234
2.05k
                &seg_off,
235
2.05k
                flags,
236
2.05k
                error))
237
682
        return FALSE;
238
239
      /* add image record to image record array */
240
1.37k
      g_ptr_array_add(self->image_records, g_steal_pointer(&img_rcd));
241
1.37k
    }
242
243
    /* increment image offset */
244
1.84k
    img_off += FU_STRUCT_CCGX_DMC_FWCT_IMAGE_INFO_SIZE;
245
1.84k
  }
246
247
241
  return TRUE;
248
1.08k
}
249
250
static gboolean
251
fu_ccgx_dmc_firmware_validate(FuFirmware *firmware,
252
            GInputStream *stream,
253
            gsize offset,
254
            GError **error)
255
5.52M
{
256
5.52M
  return fu_struct_ccgx_dmc_fwct_info_validate_stream(stream, offset, error);
257
5.52M
}
258
259
static gboolean
260
fu_ccgx_dmc_firmware_parse(FuFirmware *firmware,
261
         GInputStream *stream,
262
         FuFirmwareParseFlags flags,
263
         GError **error)
264
1.18k
{
265
1.18k
  FuCcgxDmcFirmware *self = FU_CCGX_DMC_FIRMWARE(firmware);
266
1.18k
  gsize streamsz = 0;
267
1.18k
  guint16 hdr_size = 0;
268
1.18k
  guint16 mdbufsz = 0;
269
1.18k
  guint32 hdr_composite_version = 0;
270
1.18k
  guint8 hdr_image_count = 0;
271
1.18k
  g_autoptr(GByteArray) st_hdr = NULL;
272
273
  /* parse */
274
1.18k
  st_hdr = fu_struct_ccgx_dmc_fwct_info_parse_stream(stream, 0x0, error);
275
1.18k
  if (st_hdr == NULL)
276
0
    return FALSE;
277
278
  /* check fwct size */
279
1.18k
  hdr_size = fu_struct_ccgx_dmc_fwct_info_get_size(st_hdr);
280
1.18k
  if (hdr_size > DMC_FWCT_MAX_SIZE || hdr_size == 0) {
281
17
    g_set_error(error,
282
17
          FWUPD_ERROR,
283
17
          FWUPD_ERROR_NOT_SUPPORTED,
284
17
          "invalid dmc fwct size, expected <= 0x%x, got 0x%x",
285
17
          (guint)DMC_FWCT_MAX_SIZE,
286
17
          (guint)hdr_size);
287
17
    return FALSE;
288
17
  }
289
290
  /* set version */
291
1.16k
  hdr_composite_version = fu_struct_ccgx_dmc_fwct_info_get_composite_version(st_hdr);
292
1.16k
  if (hdr_composite_version != 0)
293
1.10k
    fu_firmware_set_version_raw(firmware, hdr_composite_version);
294
295
  /* read fwct data */
296
1.16k
  self->fwct_blob = fu_input_stream_read_bytes(stream, 0x0, hdr_size, NULL, error);
297
1.16k
  if (self->fwct_blob == NULL)
298
0
    return FALSE;
299
300
  /* create custom meta binary */
301
1.16k
  if (!fu_input_stream_read_u16(stream, hdr_size, &mdbufsz, G_LITTLE_ENDIAN, error)) {
302
78
    g_prefix_error_literal(error, "failed to read metadata size: ");
303
78
    return FALSE;
304
78
  }
305
1.08k
  if (mdbufsz > 0) {
306
647
    self->custom_meta_blob =
307
647
        fu_input_stream_read_bytes(stream, hdr_size + 2, mdbufsz, NULL, error);
308
647
    if (self->custom_meta_blob == NULL)
309
5
      return FALSE;
310
647
  }
311
312
  /* set row data start offset */
313
1.08k
  self->row_data_offset_start = hdr_size + DMC_CUSTOM_META_LENGTH_FIELD_SIZE + mdbufsz;
314
1.08k
  if (!fu_input_stream_size(stream, &streamsz, error))
315
0
    return FALSE;
316
1.08k
  self->fw_data_size = streamsz - self->row_data_offset_start;
317
318
  /* parse image */
319
1.08k
  hdr_image_count = fu_struct_ccgx_dmc_fwct_info_get_image_count(st_hdr);
320
1.08k
  if (!fu_ccgx_dmc_firmware_parse_image(firmware, hdr_image_count, stream, flags, error))
321
841
    return FALSE;
322
323
  /* success */
324
241
  return TRUE;
325
1.08k
}
326
327
static GByteArray *
328
fu_ccgx_dmc_firmware_write(FuFirmware *firmware, GError **error)
329
241
{
330
241
  g_autoptr(GByteArray) buf = g_byte_array_new();
331
241
  g_autoptr(GByteArray) st_hdr = fu_struct_ccgx_dmc_fwct_info_new();
332
241
  g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware);
333
334
  /* add header */
335
241
  fu_struct_ccgx_dmc_fwct_info_set_size(
336
241
      st_hdr,
337
241
      FU_STRUCT_CCGX_DMC_FWCT_INFO_SIZE +
338
241
    (images->len * (FU_STRUCT_CCGX_DMC_FWCT_IMAGE_INFO_SIZE +
339
241
        FU_STRUCT_CCGX_DMC_FWCT_SEGMENTATION_INFO_SIZE)));
340
241
  fu_struct_ccgx_dmc_fwct_info_set_version(st_hdr, 0x2);
341
241
  fu_struct_ccgx_dmc_fwct_info_set_custom_meta_type(st_hdr, 0x3);
342
241
  fu_struct_ccgx_dmc_fwct_info_set_cdtt_version(st_hdr, 0x1);
343
241
  fu_struct_ccgx_dmc_fwct_info_set_device_id(st_hdr, 0x1);
344
241
  fu_struct_ccgx_dmc_fwct_info_set_composite_version(st_hdr,
345
241
                 fu_firmware_get_version_raw(firmware));
346
241
  fu_struct_ccgx_dmc_fwct_info_set_image_count(st_hdr, images->len);
347
241
  g_byte_array_append(buf, st_hdr->data, st_hdr->len);
348
349
  /* add image headers */
350
241
  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
241
  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
241
  fu_byte_array_append_uint16(buf, 0x1, G_LITTLE_ENDIAN);
381
241
  fu_byte_array_append_uint8(buf, 0xff);
382
383
  /* add image headers */
384
241
  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
241
  return g_steal_pointer(&buf);
424
241
}
425
426
static gchar *
427
fu_ccgx_dmc_firmware_convert_version(FuFirmware *firmware, guint64 version_raw)
428
1.10k
{
429
1.10k
  return fu_version_from_uint32(version_raw, fu_firmware_get_version_format(firmware));
430
1.10k
}
431
432
static void
433
fu_ccgx_dmc_firmware_init(FuCcgxDmcFirmware *self)
434
1.68k
{
435
1.68k
  self->image_records =
436
1.68k
      g_ptr_array_new_with_free_func((GFreeFunc)fu_ccgx_dmc_firmware_record_free);
437
1.68k
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM);
438
1.68k
  fu_firmware_set_version_format(FU_FIRMWARE(self), FWUPD_VERSION_FORMAT_QUAD);
439
1.68k
}
440
441
static void
442
fu_ccgx_dmc_firmware_finalize(GObject *object)
443
1.68k
{
444
1.68k
  FuCcgxDmcFirmware *self = FU_CCGX_DMC_FIRMWARE(object);
445
446
1.68k
  if (self->fwct_blob != NULL)
447
1.16k
    g_bytes_unref(self->fwct_blob);
448
1.68k
  if (self->custom_meta_blob != NULL)
449
642
    g_bytes_unref(self->custom_meta_blob);
450
1.68k
  if (self->image_records != NULL)
451
1.68k
    g_ptr_array_unref(self->image_records);
452
453
1.68k
  G_OBJECT_CLASS(fu_ccgx_dmc_firmware_parent_class)->finalize(object);
454
1.68k
}
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
}