Coverage Report

Created: 2026-08-15 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-dfuse-firmware.c
Line
Count
Source
1
/*
2
 * Copyright 2015 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
#define G_LOG_DOMAIN "FuFirmware"
8
9
#include "config.h"
10
11
#include "fu-byte-array.h"
12
#include "fu-common.h"
13
#include "fu-dfu-firmware-private.h"
14
#include "fu-dfu-firmware-struct.h"
15
#include "fu-dfuse-firmware.h"
16
#include "fu-input-stream.h"
17
18
/**
19
 * FuDfuseFirmware:
20
 *
21
 * A DfuSe firmware image.
22
 *
23
 * See also: [class@FuDfuFirmware]
24
 */
25
26
1.08k
G_DEFINE_TYPE(FuDfuseFirmware, fu_dfuse_firmware, FU_TYPE_DFU_FIRMWARE)
27
1.08k
28
1.08k
#define FU_DFUSE_FIRMWARE_CHUNKS_MAX 10000
29
30
static FuChunk *
31
fu_dfuse_firmware_image_chunk_parse(FuDfuseFirmware *self,
32
            FuInputStream *stream,
33
            gsize *offset,
34
            GError **error)
35
1.29k
{
36
1.29k
  g_autoptr(FuChunk) chk = NULL;
37
1.29k
  g_autoptr(FuStructDfuseElement) st_ele = NULL;
38
1.29k
  g_autoptr(GBytes) blob = NULL;
39
40
  /* create new chunk */
41
1.29k
  st_ele = fu_struct_dfuse_element_parse_stream(stream, *offset, error);
42
1.29k
  if (st_ele == NULL)
43
60
    return NULL;
44
1.23k
  if (!fu_size_checked_inc(offset, st_ele->buf->len, error)) {
45
0
    g_prefix_error_literal(error, "DfuSe element offset overflow: ");
46
0
    return NULL;
47
0
  }
48
49
1.23k
  blob = fu_input_stream_read_bytes(stream,
50
1.23k
            *offset,
51
1.23k
            fu_struct_dfuse_element_get_size(st_ele),
52
1.23k
            NULL,
53
1.23k
            error);
54
1.23k
  if (blob == NULL)
55
10
    return NULL;
56
1.22k
  chk = fu_chunk_bytes_new(blob);
57
1.22k
  fu_chunk_set_address(chk, fu_struct_dfuse_element_get_address(st_ele));
58
1.22k
  if (!fu_size_checked_inc(offset, fu_chunk_get_data_sz(chk), error))
59
0
    return NULL;
60
61
  /* success */
62
1.22k
  return g_steal_pointer(&chk);
63
1.22k
}
64
65
static FuFirmware *
66
fu_dfuse_firmware_image_parse_stream(FuDfuseFirmware *self,
67
             FuInputStream *stream,
68
             gsize *offset,
69
             GError **error)
70
354
{
71
354
  guint chunks;
72
354
  g_autoptr(FuFirmware) image = fu_firmware_new();
73
354
  g_autoptr(FuStructDfuseImage) st_img = NULL;
74
75
  /* verify image signature */
76
354
  st_img = fu_struct_dfuse_image_parse_stream(stream, *offset, error);
77
354
  if (st_img == NULL)
78
87
    return NULL;
79
80
  /* set properties */
81
267
  fu_firmware_set_idx(image, fu_struct_dfuse_image_get_alt_setting(st_img));
82
267
  if (fu_struct_dfuse_image_get_target_named(st_img) == 0x01) {
83
102
    g_autofree gchar *target_name = fu_struct_dfuse_image_get_target_name(st_img);
84
102
    fu_firmware_set_id(image, target_name);
85
102
  }
86
87
  /* no chunks */
88
267
  chunks = fu_struct_dfuse_image_get_chunks(st_img);
89
267
  if (chunks == 0) {
90
1
    g_set_error_literal(error,
91
1
            FWUPD_ERROR,
92
1
            FWUPD_ERROR_INVALID_FILE,
93
1
            "DfuSe image has no chunks");
94
1
    return NULL;
95
1
  }
96
266
  if (chunks > FU_DFUSE_FIRMWARE_CHUNKS_MAX) {
97
70
    g_set_error(error,
98
70
          FWUPD_ERROR,
99
70
          FWUPD_ERROR_INVALID_DATA,
100
70
          "excessive chunk count: %u",
101
70
          chunks);
102
70
    return NULL;
103
70
  }
104
105
  /* parse chunks */
106
196
  if (!fu_size_checked_inc(offset, st_img->buf->len, error)) {
107
0
    g_prefix_error_literal(error, "DfuSe image offset overflow: ");
108
0
    return NULL;
109
0
  }
110
111
1.42k
  for (guint j = 0; j < chunks; j++) {
112
1.29k
    g_autoptr(FuChunk) chk = NULL;
113
1.29k
    chk = fu_dfuse_firmware_image_chunk_parse(self, stream, offset, error);
114
1.29k
    if (chk == NULL)
115
70
      return NULL;
116
1.22k
    fu_firmware_add_chunk(image, chk);
117
1.22k
  }
118
119
  /* success */
120
126
  return g_steal_pointer(&image);
121
196
}
122
123
static gboolean
124
fu_dfuse_firmware_validate(FuFirmware *firmware,
125
         FuInputStream *stream,
126
         gsize offset,
127
         GError **error)
128
600
{
129
600
  return fu_struct_dfuse_hdr_validate_stream(stream, offset, error);
130
600
}
131
132
static gboolean
133
fu_dfuse_firmware_parse(FuFirmware *firmware,
134
      FuInputStream *stream,
135
      FuFirmwareParseFlags flags,
136
      GError **error)
137
529
{
138
529
  FuDfuFirmware *dfu_firmware = FU_DFU_FIRMWARE(firmware);
139
529
  gsize offset = 0;
140
529
  gsize streamsz = 0;
141
529
  guint8 targets = 0;
142
529
  g_autoptr(FuStructDfuseHdr) st_hdr = NULL;
143
144
  /* DFU footer first */
145
529
  if (!fu_dfu_firmware_parse_footer(dfu_firmware, stream, flags, error))
146
83
    return FALSE;
147
148
  /* parse */
149
446
  st_hdr = fu_struct_dfuse_hdr_parse_stream(stream, offset, error);
150
446
  if (st_hdr == NULL)
151
0
    return FALSE;
152
153
  /* check image size */
154
446
  if (!fu_input_stream_size(stream, &streamsz, error))
155
0
    return FALSE;
156
446
  if (fu_dfu_firmware_get_footer_len(dfu_firmware) > streamsz) {
157
0
    g_set_error(error,
158
0
          FWUPD_ERROR,
159
0
          FWUPD_ERROR_INVALID_DATA,
160
0
          "footer length 0x%x exceeds stream size 0x%x",
161
0
          (guint)fu_dfu_firmware_get_footer_len(dfu_firmware),
162
0
          (guint)streamsz);
163
0
    return FALSE;
164
0
  }
165
446
  if (fu_struct_dfuse_hdr_get_image_size(st_hdr) !=
166
446
      streamsz - fu_dfu_firmware_get_footer_len(dfu_firmware)) {
167
94
    g_set_error(error,
168
94
          FWUPD_ERROR,
169
94
          FWUPD_ERROR_INTERNAL,
170
94
          "invalid DfuSe image size, "
171
94
          "got %" G_GUINT32_FORMAT ", "
172
94
          "expected %zu",
173
94
          fu_struct_dfuse_hdr_get_image_size(st_hdr),
174
94
          streamsz - fu_dfu_firmware_get_footer_len(dfu_firmware));
175
94
    return FALSE;
176
94
  }
177
178
  /* parse the image targets */
179
352
  targets = fu_struct_dfuse_hdr_get_targets(st_hdr);
180
352
  if (!fu_size_checked_inc(&offset, st_hdr->buf->len, error)) {
181
0
    g_prefix_error_literal(error, "DfuSe header offset overflow: ");
182
0
    return FALSE;
183
0
  }
184
185
478
  for (guint i = 0; i < targets; i++) {
186
354
    g_autoptr(FuFirmware) image = NULL;
187
354
    image = fu_dfuse_firmware_image_parse_stream(FU_DFUSE_FIRMWARE(firmware),
188
354
                   stream,
189
354
                   &offset,
190
354
                   error);
191
354
    if (image == NULL)
192
228
      return FALSE;
193
126
    if (!fu_firmware_add_image(firmware, image, error))
194
0
      return FALSE;
195
126
  }
196
124
  return TRUE;
197
352
}
198
199
static GBytes *
200
fu_dfuse_firmware_chunk_write(FuDfuseFirmware *self, FuChunk *chk)
201
713
{
202
713
  g_autoptr(FuStructDfuseElement) st_ele = fu_struct_dfuse_element_new();
203
713
  fu_struct_dfuse_element_set_address(st_ele, fu_chunk_get_address(chk));
204
713
  fu_struct_dfuse_element_set_size(st_ele, fu_chunk_get_data_sz(chk));
205
713
  g_byte_array_append(st_ele->buf, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk));
206
713
  return fu_struct_dfuse_element_to_bytes(st_ele);
207
713
}
208
209
static GBytes *
210
fu_dfuse_firmware_write_image(FuDfuseFirmware *self, FuFirmware *image, GError **error)
211
108
{
212
108
  gsize totalsz = 0;
213
108
  g_autoptr(FuStructDfuseImage) st_img = fu_struct_dfuse_image_new();
214
108
  g_autoptr(GPtrArray) blobs = NULL;
215
108
  g_autoptr(GPtrArray) chunks = NULL;
216
217
  /* get total size */
218
108
  blobs = g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref);
219
108
  chunks = fu_firmware_get_chunks(image, error);
220
108
  if (chunks == NULL)
221
0
    return NULL;
222
821
  for (guint i = 0; i < chunks->len; i++) {
223
713
    FuChunk *chk = g_ptr_array_index(chunks, i);
224
713
    GBytes *bytes = fu_dfuse_firmware_chunk_write(self, chk);
225
713
    g_ptr_array_add(blobs, bytes);
226
713
    if (!fu_size_checked_inc(&totalsz, g_bytes_get_size(bytes), error))
227
0
      return NULL;
228
713
  }
229
230
  /* add prefix */
231
108
  fu_struct_dfuse_image_set_alt_setting(st_img, fu_firmware_get_idx(image));
232
108
  if (fu_firmware_get_id(image) != NULL) {
233
56
    fu_struct_dfuse_image_set_target_named(st_img, 0x01);
234
56
    if (!fu_struct_dfuse_image_set_target_name(st_img,
235
56
                 fu_firmware_get_id(image),
236
56
                 error))
237
0
      return NULL;
238
56
  }
239
108
  fu_struct_dfuse_image_set_target_size(st_img, totalsz);
240
108
  fu_struct_dfuse_image_set_chunks(st_img, chunks->len);
241
242
  /* copy data */
243
821
  for (guint i = 0; i < blobs->len; i++) {
244
713
    GBytes *blob = g_ptr_array_index(blobs, i);
245
713
    fu_byte_array_append_bytes(st_img->buf, blob);
246
713
  }
247
108
  return fu_struct_dfuse_image_to_bytes(st_img);
248
108
}
249
250
static GByteArray *
251
fu_dfuse_firmware_write(FuFirmware *firmware, GError **error)
252
124
{
253
124
  FuDfuseFirmware *self = FU_DFUSE_FIRMWARE(firmware);
254
124
  gsize totalsz = 0;
255
124
  g_autoptr(FuStructDfuseHdr) st_hdr = fu_struct_dfuse_hdr_new();
256
124
  g_autoptr(GBytes) blob_noftr = NULL;
257
124
  g_autoptr(GPtrArray) blobs = NULL;
258
124
  g_autoptr(GPtrArray) images = NULL;
259
260
  /* create mutable output buffer */
261
124
  blobs = g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref);
262
124
  images = fu_firmware_get_images(FU_FIRMWARE(firmware));
263
232
  for (guint i = 0; i < images->len; i++) {
264
108
    FuFirmware *img = g_ptr_array_index(images, i);
265
108
    g_autoptr(GBytes) blob = NULL;
266
108
    blob = fu_dfuse_firmware_write_image(self, img, error);
267
108
    if (blob == NULL)
268
0
      return NULL;
269
108
    if (!fu_size_checked_inc(&totalsz, g_bytes_get_size(blob), error))
270
0
      return NULL;
271
108
    g_ptr_array_add(blobs, g_steal_pointer(&blob));
272
108
  }
273
274
  /* DfuSe header */
275
124
  fu_struct_dfuse_hdr_set_image_size(st_hdr, st_hdr->buf->len + totalsz);
276
124
  if (images->len > G_MAXUINT8) {
277
0
    g_set_error(error,
278
0
          FWUPD_ERROR,
279
0
          FWUPD_ERROR_INTERNAL,
280
0
          "too many (%u) images to write DfuSe file",
281
0
          images->len);
282
0
    return NULL;
283
0
  }
284
124
  fu_struct_dfuse_hdr_set_targets(st_hdr, (guint8)images->len);
285
286
  /* copy images */
287
232
  for (guint i = 0; i < blobs->len; i++) {
288
108
    GBytes *blob = g_ptr_array_index(blobs, i);
289
108
    fu_byte_array_append_bytes(st_hdr->buf, blob);
290
108
  }
291
292
  /* return blob */
293
124
  blob_noftr = fu_struct_dfuse_hdr_to_bytes(st_hdr);
294
124
  return fu_dfu_firmware_append_footer(FU_DFU_FIRMWARE(firmware), blob_noftr, error);
295
124
}
296
297
static void
298
fu_dfuse_firmware_init(FuDfuseFirmware *self)
299
600
{
300
600
  fu_dfu_firmware_set_version(FU_DFU_FIRMWARE(self), FU_DFU_FIRMWARE_VERSION_DFUSE);
301
600
}
302
303
static void
304
fu_dfuse_firmware_class_init(FuDfuseFirmwareClass *klass)
305
1
{
306
1
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
307
1
  fu_firmware_add_image_gtype(firmware_class, FU_TYPE_FIRMWARE);
308
1
  firmware_class->validate = fu_dfuse_firmware_validate;
309
1
  firmware_class->parse = fu_dfuse_firmware_parse;
310
1
  firmware_class->write = fu_dfuse_firmware_write;
311
1
  fu_firmware_set_images_max(firmware_class, 255);
312
1
  fu_firmware_set_size_max(firmware_class, 256 * FU_MB);
313
1
}
314
315
/**
316
 * fu_dfuse_firmware_new:
317
 *
318
 * Creates a new #FuFirmware of sub type DfuSe
319
 *
320
 * Since: 1.5.6
321
 **/
322
FuFirmware *
323
fu_dfuse_firmware_new(void)
324
0
{
325
0
  return FU_FIRMWARE(g_object_new(FU_TYPE_DFUSE_FIRMWARE, NULL));
326
0
}