Coverage Report

Created: 2026-10-17 07:48

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
680
G_DEFINE_TYPE(FuUf2Firmware, fu_uf2_firmware, FU_TYPE_FIRMWARE)
17
680
18
680
static gboolean
19
680
fu_uf2_firmware_parse_extensions(FuUf2Firmware *self,
20
680
         const guint8 *buf,
21
680
         gsize bufsz,
22
680
         gsize offset,
23
680
         GError **error)
24
680
{
25
11.4k
  while (offset < bufsz) {
26
11.0k
    guint8 sz = 0;
27
11.0k
    FuUf2FirmwareTag tag = 0;
28
11.0k
    g_autoptr(FuStructUf2Extension) st_ext = NULL;
29
30
11.0k
    st_ext = fu_struct_uf2_extension_parse(buf, bufsz, offset, error);
31
11.0k
    if (st_ext == NULL)
32
6
      return FALSE;
33
11.0k
    sz = fu_struct_uf2_extension_get_size(st_ext);
34
11.0k
    if (sz == 0)
35
144
      break;
36
10.8k
    if (sz < 4) {
37
4
      g_set_error(error,
38
4
            FWUPD_ERROR,
39
4
            FWUPD_ERROR_INVALID_DATA,
40
4
            "invalid extension tag 0x%x [%s] size 0x%x",
41
4
            tag,
42
4
            fu_uf2_firmware_tag_to_string(tag),
43
4
            (guint)sz);
44
4
      return FALSE;
45
4
    }
46
10.8k
    tag = fu_struct_uf2_extension_get_tag(st_ext);
47
10.8k
    if (tag == 0)
48
30
      break;
49
10.8k
    if (tag == FU_UF2_FIRMWARE_TAG_VERSION) {
50
1.38k
      g_autofree gchar *str = NULL;
51
1.38k
      str = fu_memstrsafe(buf,
52
1.38k
              bufsz,
53
1.38k
              offset + st_ext->buf->len,
54
1.38k
              sz - st_ext->buf->len,
55
1.38k
              error);
56
1.38k
      if (str == NULL)
57
7
        return FALSE;
58
1.38k
      fu_firmware_set_version(FU_FIRMWARE(self), str);
59
9.45k
    } else if (tag == FU_UF2_FIRMWARE_TAG_DESCRIPTION) {
60
1.27k
      g_autofree gchar *str = NULL;
61
1.27k
      str = fu_memstrsafe(buf,
62
1.27k
              bufsz,
63
1.27k
              offset + st_ext->buf->len,
64
1.27k
              sz - st_ext->buf->len,
65
1.27k
              error);
66
1.27k
      if (str == NULL)
67
6
        return FALSE;
68
1.26k
      fu_firmware_set_id(FU_FIRMWARE(self), str);
69
8.18k
    } else {
70
8.18k
      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
8.18k
    }
76
77
    /* next! */
78
10.8k
    if (!fu_size_checked_inc(&offset,
79
10.8k
           fu_common_align_up(sz, FU_FIRMWARE_ALIGNMENT_4),
80
10.8k
           error))
81
0
      return FALSE;
82
10.8k
  }
83
84
  /* success */
85
618
  return TRUE;
86
641
}
87
88
static gboolean
89
fu_uf2_firmware_parse_chunk(FuUf2Firmware *self, FuChunk *chk, GByteArray *tmp, GError **error)
90
1.24k
{
91
1.24k
  gsize bufsz = fu_chunk_get_data_sz(chk);
92
1.24k
  const guint8 *buf = fu_chunk_get_data(chk);
93
1.24k
  guint32 flags = 0;
94
1.24k
  guint32 datasz = 0;
95
1.24k
  g_autoptr(FuStructUf2) st = NULL;
96
97
  /* parse */
98
1.24k
  st = fu_struct_uf2_parse(fu_chunk_get_data(chk),
99
1.24k
         fu_chunk_get_data_sz(chk),
100
1.24k
         0, /* offset */
101
1.24k
         error);
102
1.24k
  if (st == NULL)
103
274
    return FALSE;
104
966
  flags = fu_struct_uf2_get_flags(st);
105
966
  if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_IS_CONTAINER) {
106
1
    g_set_error_literal(error,
107
1
            FWUPD_ERROR,
108
1
            FWUPD_ERROR_NOT_SUPPORTED,
109
1
            "container U2F firmware not supported");
110
1
    return FALSE;
111
1
  }
112
965
  datasz = fu_struct_uf2_get_payload_size(st);
113
965
  if (datasz > 476) {
114
31
    g_set_error(error,
115
31
          FWUPD_ERROR,
116
31
          FWUPD_ERROR_INVALID_DATA,
117
31
          "data size impossible got 0x%08x",
118
31
          datasz);
119
31
    return FALSE;
120
31
  }
121
934
  if (fu_struct_uf2_get_block_no(st) != fu_chunk_get_idx(chk)) {
122
53
    g_set_error(error,
123
53
          FWUPD_ERROR,
124
53
          FWUPD_ERROR_INVALID_DATA,
125
53
          "block count invalid, expected 0x%04x and got 0x%04x",
126
53
          fu_chunk_get_idx(chk),
127
53
          fu_struct_uf2_get_block_no(st));
128
53
    return FALSE;
129
53
  }
130
881
  if (fu_struct_uf2_get_num_blocks(st) == 0) {
131
2
    g_set_error_literal(error,
132
2
            FWUPD_ERROR,
133
2
            FWUPD_ERROR_INVALID_DATA,
134
2
            "block count invalid, expected > 0");
135
2
    return FALSE;
136
2
  }
137
879
  if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_FAMILY) {
138
390
    if (fu_struct_uf2_get_family_id(st) == 0) {
139
1
      g_set_error_literal(error,
140
1
              FWUPD_ERROR,
141
1
              FWUPD_ERROR_INVALID_DATA,
142
1
              "family_id required but not supplied");
143
1
      return FALSE;
144
1
    }
145
390
  }
146
147
  /* assume first chunk is representative of firmware */
148
878
  if (fu_chunk_get_idx(chk) == 0) {
149
356
    fu_firmware_set_addr(FU_FIRMWARE(self), fu_struct_uf2_get_target_addr(st));
150
356
    fu_firmware_set_idx(FU_FIRMWARE(self), fu_struct_uf2_get_family_id(st));
151
356
  }
152
153
  /* just append raw data */
154
878
  g_byte_array_append(tmp, fu_struct_uf2_get_data(st, NULL), datasz);
155
878
  if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_MD5) {
156
165
    if (datasz < 24) {
157
4
      g_set_error_literal(error,
158
4
              FWUPD_ERROR,
159
4
              FWUPD_ERROR_INVALID_DATA,
160
4
              "not enough space for MD5 checksum");
161
4
      return FALSE;
162
4
    }
163
165
  }
164
874
  if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_EXTENSION_TAG) {
165
641
    if (!fu_uf2_firmware_parse_extensions(self,
166
641
                  buf,
167
641
                  bufsz,
168
641
                  datasz + FU_STRUCT_UF2_OFFSET_DATA,
169
641
                  error))
170
23
      return FALSE;
171
641
  }
172
173
  /* success */
174
851
  return TRUE;
175
874
}
176
177
static gboolean
178
fu_uf2_firmware_parse(FuFirmware *firmware,
179
          FuInputStream *stream,
180
          FuFirmwareParseFlags flags,
181
          GError **error)
182
678
{
183
678
  FuUf2Firmware *self = FU_UF2_FIRMWARE(firmware);
184
678
  g_autoptr(GByteArray) tmp = g_byte_array_new();
185
678
  g_autoptr(GBytes) blob = NULL;
186
678
  g_autoptr(FuChunkArray) chunks = NULL;
187
188
  /* read in fixed sized chunks */
189
678
  chunks = fu_chunk_array_new_from_stream(stream,
190
678
            FU_CHUNK_ADDR_OFFSET_NONE,
191
678
            FU_CHUNK_PAGESZ_NONE,
192
678
            512,
193
678
            error);
194
678
  if (chunks == NULL)
195
0
    return FALSE;
196
1.52k
  for (guint i = 0; i < fu_chunk_array_length(chunks); i++) {
197
1.24k
    g_autoptr(FuChunk) chk = NULL;
198
199
    /* prepare chunk */
200
1.24k
    chk = fu_chunk_array_index(chunks, i, error);
201
1.24k
    if (chk == NULL)
202
0
      return FALSE;
203
1.24k
    if (!fu_uf2_firmware_parse_chunk(self, chk, tmp, error))
204
389
      return FALSE;
205
1.24k
  }
206
207
  /* success */
208
289
  blob = g_bytes_new(tmp->data, tmp->len);
209
289
  fu_firmware_set_bytes(firmware, blob);
210
289
  return TRUE;
211
678
}
212
213
static FuStructUf2Extension *
214
fu_uf2_firmware_build_utf8_extension(FuUf2FirmwareTag tag, const gchar *str)
215
662
{
216
662
  g_autoptr(FuStructUf2Extension) st = fu_struct_uf2_extension_new();
217
662
  fu_struct_uf2_extension_set_tag(st, tag);
218
662
  fu_struct_uf2_extension_set_size(st, st->buf->len + strlen(str));
219
662
  g_byte_array_append(st->buf, (const guint8 *)str, strlen(str));
220
662
  fu_byte_array_align_up(st->buf, FU_FIRMWARE_ALIGNMENT_4, 0x0);
221
662
  return g_steal_pointer(&st);
222
662
}
223
224
static GByteArray *
225
fu_uf2_firmware_write_chunk(FuUf2Firmware *self, FuChunk *chk, guint chk_len, GError **error)
226
778
{
227
778
  gsize offset_ext = FU_STRUCT_UF2_OFFSET_DATA + fu_chunk_get_data_sz(chk);
228
778
  guint32 addr = fu_firmware_get_addr(FU_FIRMWARE(self));
229
778
  guint32 flags = FU_UF2_FIRMWARE_BLOCK_FLAG_NONE;
230
778
  g_autoptr(FuStructUf2) st = fu_struct_uf2_new();
231
778
  g_autoptr(GPtrArray) extensions =
232
778
      g_ptr_array_new_with_free_func((GDestroyNotify)fu_struct_uf2_extension_unref);
233
234
  /* optional */
235
778
  if (fu_firmware_get_idx(FU_FIRMWARE(self)) > 0)
236
307
    flags |= FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_FAMILY;
237
238
  /* build extensions */
239
778
  if (fu_firmware_get_idx(FU_FIRMWARE(self)) == 0x0) {
240
471
    if (fu_firmware_get_id(FU_FIRMWARE(self)) != NULL) {
241
324
      g_ptr_array_add(extensions,
242
324
          fu_uf2_firmware_build_utf8_extension(
243
324
              FU_UF2_FIRMWARE_TAG_DESCRIPTION,
244
324
              fu_firmware_get_id(FU_FIRMWARE(self))));
245
324
    }
246
471
    if (fu_firmware_get_version(FU_FIRMWARE(self)) != NULL) {
247
338
      g_ptr_array_add(extensions,
248
338
          fu_uf2_firmware_build_utf8_extension(
249
338
              FU_UF2_FIRMWARE_TAG_VERSION,
250
338
              fu_firmware_get_version(FU_FIRMWARE(self))));
251
338
    }
252
471
    if (extensions->len > 0) {
253
357
      g_ptr_array_add(extensions, fu_struct_uf2_extension_new());
254
357
      flags |= FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_EXTENSION_TAG;
255
357
    }
256
471
  }
257
258
  /* offset from base address */
259
778
  addr += fu_chunk_get_idx(chk) * fu_chunk_get_data_sz(chk);
260
261
  /* build UF2 packet */
262
778
  fu_struct_uf2_set_flags(st, flags);
263
778
  fu_struct_uf2_set_target_addr(st, addr);
264
778
  fu_struct_uf2_set_payload_size(st, fu_chunk_get_data_sz(chk));
265
778
  fu_struct_uf2_set_block_no(st, fu_chunk_get_idx(chk));
266
778
  fu_struct_uf2_set_num_blocks(st, chk_len);
267
778
  fu_struct_uf2_set_family_id(st, fu_firmware_get_idx(FU_FIRMWARE(self)));
268
778
  if (!fu_struct_uf2_set_data(st, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error))
269
0
    return NULL;
270
271
  /* copy in any extensions */
272
1.77k
  for (guint i = 0; i < extensions->len; i++) {
273
1.00k
    FuStructUf2Extension *st_ext = g_ptr_array_index(extensions, i);
274
1.00k
    if (!fu_memcpy_safe(st->buf->data,
275
1.00k
            st->buf->len,
276
1.00k
            offset_ext,
277
1.00k
            st_ext->buf->data,
278
1.00k
            st_ext->buf->len,
279
1.00k
            0x0,
280
1.00k
            st_ext->buf->len,
281
1.00k
            error))
282
12
      return NULL;
283
997
    offset_ext += st_ext->buf->len;
284
997
  }
285
286
  /* success */
287
766
  return g_steal_pointer(&st->buf);
288
778
}
289
290
static GByteArray *
291
fu_uf2_firmware_write(FuFirmware *firmware, GError **error)
292
289
{
293
289
  FuUf2Firmware *self = FU_UF2_FIRMWARE(firmware);
294
289
  g_autoptr(GByteArray) buf = g_byte_array_new();
295
289
  g_autoptr(FuInputStream) stream = NULL;
296
289
  g_autoptr(FuChunkArray) chunks = NULL;
297
298
  /* data first */
299
289
  stream = fu_firmware_get_stream(firmware, error);
300
289
  if (stream == NULL)
301
0
    return NULL;
302
303
  /* write in chunks */
304
289
  chunks = fu_chunk_array_new_from_stream(stream,
305
289
            fu_firmware_get_addr(firmware),
306
289
            FU_CHUNK_PAGESZ_NONE,
307
289
            256,
308
289
            error);
309
289
  if (chunks == NULL)
310
0
    return NULL;
311
1.05k
  for (guint i = 0; i < fu_chunk_array_length(chunks); i++) {
312
778
    g_autoptr(FuChunk) chk = NULL;
313
778
    g_autoptr(GByteArray) tmp = NULL;
314
315
    /* prepare chunk */
316
778
    chk = fu_chunk_array_index(chunks, i, error);
317
778
    if (chk == NULL)
318
0
      return NULL;
319
778
    tmp = fu_uf2_firmware_write_chunk(self, chk, fu_chunk_array_length(chunks), error);
320
778
    if (tmp == NULL)
321
12
      return NULL;
322
766
    g_byte_array_append(buf, tmp->data, tmp->len);
323
766
  }
324
325
  /* success */
326
277
  return g_steal_pointer(&buf);
327
289
}
328
329
static void
330
fu_uf2_firmware_init(FuUf2Firmware *self)
331
678
{
332
678
}
333
334
static void
335
fu_uf2_firmware_class_init(FuUf2FirmwareClass *klass)
336
1
{
337
1
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
338
1
  firmware_class->parse = fu_uf2_firmware_parse;
339
1
  firmware_class->write = fu_uf2_firmware_write;
340
1
  fu_firmware_set_size_max(firmware_class, 256 * FU_MB);
341
1
}
342
343
FuFirmware *
344
fu_uf2_firmware_new(void)
345
0
{
346
0
  return FU_FIRMWARE(g_object_new(FU_TYPE_UF2_FIRMWARE, NULL));
347
0
}