Coverage Report

Created: 2025-11-24 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/plugins/uf2/fu-uf2-firmware.c
Line
Count
Source
1
/*
2
 * Copyright 2021 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
#include "config.h"
8
9
#include "fu-uf2-firmware.h"
10
#include "fu-uf2-struct.h"
11
12
struct _FuUf2Firmware {
13
  FuFirmware parent_instance;
14
};
15
16
1.55k
G_DEFINE_TYPE(FuUf2Firmware, fu_uf2_firmware, FU_TYPE_FIRMWARE)
17
1.55k
18
1.55k
static gboolean
19
1.55k
fu_uf2_firmware_parse_extensions(FuUf2Firmware *self,
20
1.55k
         const guint8 *buf,
21
1.55k
         gsize bufsz,
22
1.55k
         gsize offset,
23
1.55k
         GError **error)
24
1.55k
{
25
10.3k
  while (offset < bufsz) {
26
9.88k
    guint8 sz = 0;
27
9.88k
    FuUf2FirmwareTag tag = 0;
28
9.88k
    g_autoptr(FuStructUf2Extension) st_ext = NULL;
29
30
9.88k
    st_ext = fu_struct_uf2_extension_parse(buf, bufsz, offset, error);
31
9.88k
    if (st_ext == NULL)
32
21
      return FALSE;
33
9.86k
    sz = fu_struct_uf2_extension_get_size(st_ext);
34
9.86k
    if (sz == 0)
35
112
      break;
36
9.75k
    if (sz < 4) {
37
7
      g_set_error(error,
38
7
            FWUPD_ERROR,
39
7
            FWUPD_ERROR_INVALID_DATA,
40
7
            "invalid extension tag 0x%x [%s] size 0x%x",
41
7
            tag,
42
7
            fu_uf2_firmware_tag_to_string(tag),
43
7
            (guint)sz);
44
7
      return FALSE;
45
7
    }
46
9.74k
    tag = fu_struct_uf2_extension_get_tag(st_ext);
47
9.74k
    if (tag == 0)
48
15
      break;
49
9.73k
    if (tag == FU_UF2_FIRMWARE_TAG_VERSION) {
50
1.44k
      g_autofree gchar *str = NULL;
51
1.44k
      str = fu_memstrsafe(buf,
52
1.44k
              bufsz,
53
1.44k
              offset + st_ext->buf->len,
54
1.44k
              sz - st_ext->buf->len,
55
1.44k
              error);
56
1.44k
      if (str == NULL)
57
5
        return FALSE;
58
1.43k
      fu_firmware_set_version(FU_FIRMWARE(self), str);
59
8.29k
    } else if (tag == FU_UF2_FIRMWARE_TAG_DESCRIPTION) {
60
1.24k
      g_autofree gchar *str = NULL;
61
1.24k
      str = fu_memstrsafe(buf,
62
1.24k
              bufsz,
63
1.24k
              offset + st_ext->buf->len,
64
1.24k
              sz - st_ext->buf->len,
65
1.24k
              error);
66
1.24k
      if (str == NULL)
67
18
        return FALSE;
68
1.22k
      fu_firmware_set_id(FU_FIRMWARE(self), str);
69
7.04k
    } else {
70
7.04k
      if (g_getenv("FWUPD_FUZZER_RUNNING") == NULL) {
71
0
        g_warning("unknown tag 0x%06x [%s]",
72
0
            tag,
73
0
            fu_uf2_firmware_tag_to_string(tag));
74
0
      }
75
7.04k
    }
76
77
    /* next! */
78
9.71k
    offset += fu_common_align_up(sz, FU_FIRMWARE_ALIGNMENT_4);
79
9.71k
  }
80
81
  /* success */
82
592
  return TRUE;
83
643
}
84
85
static gboolean
86
fu_uf2_firmware_parse_chunk(FuUf2Firmware *self, FuChunk *chk, GByteArray *tmp, GError **error)
87
1.21k
{
88
1.21k
  gsize bufsz = fu_chunk_get_data_sz(chk);
89
1.21k
  const guint8 *buf = fu_chunk_get_data(chk);
90
1.21k
  guint32 flags = 0;
91
1.21k
  guint32 datasz = 0;
92
1.21k
  g_autoptr(FuStructUf2) st = NULL;
93
94
  /* parse */
95
1.21k
  st = fu_struct_uf2_parse(fu_chunk_get_data(chk),
96
1.21k
         fu_chunk_get_data_sz(chk),
97
1.21k
         0, /* offset */
98
1.21k
         error);
99
1.21k
  if (st == NULL)
100
229
    return FALSE;
101
986
  flags = fu_struct_uf2_get_flags(st);
102
986
  if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_IS_CONTAINER) {
103
1
    g_set_error_literal(error,
104
1
            FWUPD_ERROR,
105
1
            FWUPD_ERROR_NOT_SUPPORTED,
106
1
            "container U2F firmware not supported");
107
1
    return FALSE;
108
1
  }
109
985
  datasz = fu_struct_uf2_get_payload_size(st);
110
985
  if (datasz > 476) {
111
33
    g_set_error(error,
112
33
          FWUPD_ERROR,
113
33
          FWUPD_ERROR_INVALID_DATA,
114
33
          "data size impossible got 0x%08x",
115
33
          datasz);
116
33
    return FALSE;
117
33
  }
118
952
  if (fu_struct_uf2_get_block_no(st) != fu_chunk_get_idx(chk)) {
119
53
    g_set_error(error,
120
53
          FWUPD_ERROR,
121
53
          FWUPD_ERROR_INVALID_DATA,
122
53
          "block count invalid, expected 0x%04x and got 0x%04x",
123
53
          fu_chunk_get_idx(chk),
124
53
          fu_struct_uf2_get_block_no(st));
125
53
    return FALSE;
126
53
  }
127
899
  if (fu_struct_uf2_get_num_blocks(st) == 0) {
128
1
    g_set_error_literal(error,
129
1
            FWUPD_ERROR,
130
1
            FWUPD_ERROR_INVALID_DATA,
131
1
            "block count invalid, expected > 0");
132
1
    return FALSE;
133
1
  }
134
898
  if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_FAMILY) {
135
352
    if (fu_struct_uf2_get_family_id(st) == 0) {
136
2
      g_set_error_literal(error,
137
2
              FWUPD_ERROR,
138
2
              FWUPD_ERROR_INVALID_DATA,
139
2
              "family_id required but not supplied");
140
2
      return FALSE;
141
2
    }
142
352
  }
143
144
  /* assume first chunk is representative of firmware */
145
896
  if (fu_chunk_get_idx(chk) == 0) {
146
359
    fu_firmware_set_addr(FU_FIRMWARE(self), fu_struct_uf2_get_target_addr(st));
147
359
    fu_firmware_set_idx(FU_FIRMWARE(self), fu_struct_uf2_get_family_id(st));
148
359
  }
149
150
  /* just append raw data */
151
896
  g_byte_array_append(tmp, fu_struct_uf2_get_data(st, NULL), datasz);
152
896
  if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_MD5) {
153
148
    if (datasz < 24) {
154
5
      g_set_error_literal(error,
155
5
              FWUPD_ERROR,
156
5
              FWUPD_ERROR_INVALID_DATA,
157
5
              "not enough space for MD5 checksum");
158
5
      return FALSE;
159
5
    }
160
148
  }
161
891
  if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_EXTENSION_TAG) {
162
643
    if (!fu_uf2_firmware_parse_extensions(self,
163
643
                  buf,
164
643
                  bufsz,
165
643
                  datasz + FU_STRUCT_UF2_OFFSET_DATA,
166
643
                  error))
167
51
      return FALSE;
168
643
  }
169
170
  /* success */
171
840
  return TRUE;
172
891
}
173
174
static gboolean
175
fu_uf2_firmware_parse(FuFirmware *firmware,
176
          GInputStream *stream,
177
          FuFirmwareParseFlags flags,
178
          GError **error)
179
643
{
180
643
  FuUf2Firmware *self = FU_UF2_FIRMWARE(firmware);
181
643
  g_autoptr(GByteArray) tmp = g_byte_array_new();
182
643
  g_autoptr(GBytes) blob = NULL;
183
643
  g_autoptr(FuChunkArray) chunks = NULL;
184
185
  /* read in fixed sized chunks */
186
643
  chunks = fu_chunk_array_new_from_stream(stream,
187
643
            FU_CHUNK_ADDR_OFFSET_NONE,
188
643
            FU_CHUNK_PAGESZ_NONE,
189
643
            512,
190
643
            error);
191
643
  if (chunks == NULL)
192
0
    return FALSE;
193
1.48k
  for (guint i = 0; i < fu_chunk_array_length(chunks); i++) {
194
1.21k
    g_autoptr(FuChunk) chk = NULL;
195
196
    /* prepare chunk */
197
1.21k
    chk = fu_chunk_array_index(chunks, i, error);
198
1.21k
    if (chk == NULL)
199
0
      return FALSE;
200
1.21k
    if (!fu_uf2_firmware_parse_chunk(self, chk, tmp, error))
201
375
      return FALSE;
202
1.21k
  }
203
204
  /* success */
205
268
  blob = g_bytes_new(tmp->data, tmp->len);
206
268
  fu_firmware_set_bytes(firmware, blob);
207
268
  return TRUE;
208
643
}
209
210
static FuStructUf2Extension *
211
fu_uf2_firmware_build_utf8_extension(FuUf2FirmwareTag tag, const gchar *str)
212
858
{
213
858
  g_autoptr(FuStructUf2Extension) st = fu_struct_uf2_extension_new();
214
858
  fu_struct_uf2_extension_set_tag(st, tag);
215
858
  fu_struct_uf2_extension_set_size(st, st->buf->len + strlen(str));
216
858
  g_byte_array_append(st->buf, (const guint8 *)str, strlen(str));
217
858
  fu_byte_array_align_up(st->buf, FU_FIRMWARE_ALIGNMENT_4, 0x0);
218
858
  return g_steal_pointer(&st);
219
858
}
220
221
static GByteArray *
222
fu_uf2_firmware_write_chunk(FuUf2Firmware *self, FuChunk *chk, guint chk_len, GError **error)
223
836
{
224
836
  gsize offset_ext = FU_STRUCT_UF2_OFFSET_DATA + fu_chunk_get_data_sz(chk);
225
836
  guint32 addr = fu_firmware_get_addr(FU_FIRMWARE(self));
226
836
  guint32 flags = FU_UF2_FIRMWARE_BLOCK_FLAG_NONE;
227
836
  g_autoptr(FuStructUf2) st = fu_struct_uf2_new();
228
836
  g_autoptr(GPtrArray) extensions =
229
836
      g_ptr_array_new_with_free_func((GDestroyNotify)fu_struct_uf2_extension_unref);
230
231
  /* optional */
232
836
  if (fu_firmware_get_idx(FU_FIRMWARE(self)) > 0)
233
277
    flags |= FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_FAMILY;
234
235
  /* build extensions */
236
836
  if (fu_firmware_get_idx(FU_FIRMWARE(self)) == 0x0) {
237
559
    if (fu_firmware_get_id(FU_FIRMWARE(self)) != NULL) {
238
419
      g_ptr_array_add(extensions,
239
419
          fu_uf2_firmware_build_utf8_extension(
240
419
              FU_UF2_FIRMWARE_TAG_DESCRIPTION,
241
419
              fu_firmware_get_id(FU_FIRMWARE(self))));
242
419
    }
243
559
    if (fu_firmware_get_version(FU_FIRMWARE(self)) != NULL) {
244
439
      g_ptr_array_add(extensions,
245
439
          fu_uf2_firmware_build_utf8_extension(
246
439
              FU_UF2_FIRMWARE_TAG_VERSION,
247
439
              fu_firmware_get_version(FU_FIRMWARE(self))));
248
439
    }
249
559
    if (extensions->len > 0) {
250
463
      g_ptr_array_add(extensions, fu_struct_uf2_extension_new());
251
463
      flags |= FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_EXTENSION_TAG;
252
463
    }
253
559
  }
254
255
  /* offset from base address */
256
836
  addr += fu_chunk_get_idx(chk) * fu_chunk_get_data_sz(chk);
257
258
  /* build UF2 packet */
259
836
  fu_struct_uf2_set_flags(st, flags);
260
836
  fu_struct_uf2_set_target_addr(st, addr);
261
836
  fu_struct_uf2_set_payload_size(st, fu_chunk_get_data_sz(chk));
262
836
  fu_struct_uf2_set_block_no(st, fu_chunk_get_idx(chk));
263
836
  fu_struct_uf2_set_num_blocks(st, chk_len);
264
836
  fu_struct_uf2_set_family_id(st, fu_firmware_get_idx(FU_FIRMWARE(self)));
265
836
  if (!fu_struct_uf2_set_data(st, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error))
266
0
    return NULL;
267
268
  /* copy in any extensions */
269
2.15k
  for (guint i = 0; i < extensions->len; i++) {
270
1.31k
    FuStructUf2Extension *st_ext = g_ptr_array_index(extensions, i);
271
1.31k
    if (!fu_memcpy_safe(st->buf->data,
272
1.31k
            st->buf->len,
273
1.31k
            offset_ext,
274
1.31k
            st_ext->buf->data,
275
1.31k
            st_ext->buf->len,
276
1.31k
            0x0,
277
1.31k
            st_ext->buf->len,
278
1.31k
            error))
279
3
      return NULL;
280
1.31k
    offset_ext += st_ext->buf->len;
281
1.31k
  }
282
283
  /* success */
284
833
  return g_steal_pointer(&st->buf);
285
836
}
286
287
static GByteArray *
288
fu_uf2_firmware_write(FuFirmware *firmware, GError **error)
289
268
{
290
268
  FuUf2Firmware *self = FU_UF2_FIRMWARE(firmware);
291
268
  g_autoptr(GByteArray) buf = g_byte_array_new();
292
268
  g_autoptr(GInputStream) stream = NULL;
293
268
  g_autoptr(FuChunkArray) chunks = NULL;
294
295
  /* data first */
296
268
  stream = fu_firmware_get_stream(firmware, error);
297
268
  if (stream == NULL)
298
0
    return NULL;
299
300
  /* write in chunks */
301
268
  chunks = fu_chunk_array_new_from_stream(stream,
302
268
            fu_firmware_get_addr(firmware),
303
268
            FU_CHUNK_PAGESZ_NONE,
304
268
            256,
305
268
            error);
306
268
  if (chunks == NULL)
307
0
    return NULL;
308
1.10k
  for (guint i = 0; i < fu_chunk_array_length(chunks); i++) {
309
836
    g_autoptr(FuChunk) chk = NULL;
310
836
    g_autoptr(GByteArray) tmp = NULL;
311
312
    /* prepare chunk */
313
836
    chk = fu_chunk_array_index(chunks, i, error);
314
836
    if (chk == NULL)
315
0
      return NULL;
316
836
    tmp = fu_uf2_firmware_write_chunk(self, chk, fu_chunk_array_length(chunks), error);
317
836
    if (tmp == NULL)
318
3
      return NULL;
319
833
    g_byte_array_append(buf, tmp->data, tmp->len);
320
833
  }
321
322
  /* success */
323
265
  return g_steal_pointer(&buf);
324
268
}
325
326
static void
327
fu_uf2_firmware_init(FuUf2Firmware *self)
328
643
{
329
643
}
330
331
static void
332
fu_uf2_firmware_class_init(FuUf2FirmwareClass *klass)
333
1
{
334
1
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
335
1
  firmware_class->parse = fu_uf2_firmware_parse;
336
1
  firmware_class->write = fu_uf2_firmware_write;
337
1
}
338
339
FuFirmware *
340
fu_uf2_firmware_new(void)
341
0
{
342
0
  return FU_FIRMWARE(g_object_new(FU_TYPE_UF2_FIRMWARE, NULL));
343
0
}