Coverage Report

Created: 2026-06-10 07:04

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