Coverage Report

Created: 2026-10-17 07:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-uswid-firmware.c
Line
Count
Source
1
/*
2
 * Copyright 2022 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-bytes.h"
13
#include "fu-common.h"
14
#include "fu-compressor-stream.h"
15
#include "fu-coswid-firmware.h"
16
#include "fu-input-stream.h"
17
#include "fu-lzma-common.h"
18
#include "fu-memory-input-stream.h"
19
#include "fu-partial-input-stream.h"
20
#include "fu-uswid-firmware.h"
21
#include "fu-uswid-struct.h"
22
23
/**
24
 * FuUswidFirmware:
25
 *
26
 * A uSWID header with multiple optionally-compressed SBOM sections.
27
 *
28
 * See also: [class@FuCoswidFirmware]
29
 */
30
31
typedef struct {
32
  guint8 hdrver;
33
  FuUswidPayloadCompression compression;
34
  FuUswidPayloadFormat format;
35
} FuUswidFirmwarePrivate;
36
37
69.7k
G_DEFINE_TYPE_WITH_PRIVATE(FuUswidFirmware, fu_uswid_firmware, FU_TYPE_FIRMWARE)
38
134k
#define GET_PRIVATE(o) (fu_uswid_firmware_get_instance_private(o))
39
40
134k
#define FU_USWID_FIRMWARE_MINIMUM_HDRVER 1
41
42
static void
43
fu_uswid_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
44
0
{
45
0
  FuUswidFirmware *self = FU_USWID_FIRMWARE(firmware);
46
0
  FuUswidFirmwarePrivate *priv = GET_PRIVATE(self);
47
0
  fu_xmlb_builder_insert_kx(bn, "hdrver", priv->hdrver);
48
0
  if (priv->compression != FU_USWID_PAYLOAD_COMPRESSION_NONE) {
49
0
    fu_xmlb_builder_insert_kv(
50
0
        bn,
51
0
        "compression",
52
0
        fu_uswid_payload_compression_to_string(priv->compression));
53
0
  }
54
0
  fu_xmlb_builder_insert_kv(bn, "format", fu_uswid_payload_format_to_string(priv->format));
55
0
}
56
57
static gboolean
58
fu_uswid_firmware_validate(FuFirmware *firmware,
59
         FuInputStream *stream,
60
         gsize offset,
61
         GError **error)
62
65.5k
{
63
65.5k
  return fu_struct_uswid_validate_stream(stream, offset, error);
64
65.5k
}
65
66
static GType
67
fu_uswid_firmware_format_to_gtype(FuUswidPayloadFormat format, GError **error)
68
17.8k
{
69
17.8k
  if (format == FU_USWID_PAYLOAD_FORMAT_COSWID)
70
13.1k
    return FU_TYPE_COSWID_FIRMWARE;
71
4.62k
  if (format == FU_USWID_PAYLOAD_FORMAT_CYCLONEDX)
72
2.51k
    return FU_TYPE_FIRMWARE;
73
2.11k
  if (format == FU_USWID_PAYLOAD_FORMAT_SPDX)
74
800
    return FU_TYPE_FIRMWARE;
75
1.31k
  g_set_error(error,
76
1.31k
        FWUPD_ERROR,
77
1.31k
        FWUPD_ERROR_NOT_SUPPORTED,
78
1.31k
        "format 0x%x is not supported",
79
1.31k
        format);
80
1.31k
  return G_TYPE_INVALID;
81
2.11k
}
82
83
static gboolean
84
fu_uswid_firmware_parse(FuFirmware *firmware,
85
      FuInputStream *stream,
86
      FuFirmwareParseFlags flags,
87
      GError **error)
88
65.2k
{
89
65.2k
  FuUswidFirmware *self = FU_USWID_FIRMWARE(firmware);
90
65.2k
  FuUswidFirmwarePrivate *priv = GET_PRIVATE(self);
91
65.2k
  GType img_gtype;
92
65.2k
  gsize payloadsz_max;
93
65.2k
  guint16 hdrsz;
94
65.2k
  guint32 payloadsz;
95
65.2k
  g_autoptr(FuStructUswid) st = NULL;
96
65.2k
  g_autoptr(GBytes) payload = NULL;
97
98
  /* unpack */
99
65.2k
  st = fu_struct_uswid_parse_stream(stream, 0x0, error);
100
65.2k
  if (st == NULL)
101
0
    return FALSE;
102
103
  /* hdrver */
104
65.2k
  priv->hdrver = fu_struct_uswid_get_hdrver(st);
105
65.2k
  if (priv->hdrver < FU_USWID_FIRMWARE_MINIMUM_HDRVER) {
106
391
    g_set_error_literal(error,
107
391
            FWUPD_ERROR,
108
391
            FWUPD_ERROR_NOT_SUPPORTED,
109
391
            "header version was unsupported");
110
391
    return FALSE;
111
391
  }
112
113
  /* hdrsz+payloadsz */
114
64.8k
  hdrsz = fu_struct_uswid_get_hdrsz(st);
115
64.8k
  payloadsz = fu_struct_uswid_get_payloadsz(st);
116
64.8k
  if (payloadsz == 0x0) {
117
524
    g_set_error_literal(error,
118
524
            FWUPD_ERROR,
119
524
            FWUPD_ERROR_NOT_SUPPORTED,
120
524
            "payload size is invalid");
121
524
    return FALSE;
122
524
  }
123
64.3k
  fu_firmware_set_size(firmware, hdrsz + payloadsz);
124
125
  /* payload compression */
126
64.3k
  if (priv->hdrver >= 0x03) {
127
17.9k
    if (fu_struct_uswid_get_flags(st) & FU_USWID_HEADER_FLAG_COMPRESSED) {
128
4.19k
      priv->compression = fu_struct_uswid_get_compression(st);
129
13.7k
    } else {
130
13.7k
      priv->compression = FU_USWID_PAYLOAD_COMPRESSION_NONE;
131
13.7k
    }
132
46.3k
  } else if (priv->hdrver >= 0x02) {
133
44.4k
    priv->compression = fu_struct_uswid_get_flags(st) & FU_USWID_HEADER_FLAG_COMPRESSED
134
44.4k
          ? FU_USWID_PAYLOAD_COMPRESSION_ZLIB
135
44.4k
          : FU_USWID_PAYLOAD_COMPRESSION_NONE;
136
44.4k
  } else {
137
1.95k
    priv->compression = FU_USWID_PAYLOAD_COMPRESSION_NONE;
138
1.95k
  }
139
140
  /* payload format */
141
64.3k
  if (priv->hdrver >= 0x04) {
142
17.8k
    priv->format = fu_struct_uswid_get_format(st);
143
17.8k
    img_gtype = fu_uswid_firmware_format_to_gtype(priv->format, error);
144
17.8k
    if (img_gtype == G_TYPE_INVALID)
145
1.31k
      return FALSE;
146
46.4k
  } else {
147
46.4k
    priv->format = FU_USWID_PAYLOAD_FORMAT_COSWID;
148
46.4k
    img_gtype = FU_TYPE_COSWID_FIRMWARE;
149
46.4k
  }
150
151
  /* limit inflated size to prevent decompression bombs */
152
62.9k
  payloadsz_max = MIN((gsize)payloadsz * 64, 32 * FU_MB);
153
154
  /* zlib stream */
155
62.9k
  if (priv->compression == FU_USWID_PAYLOAD_COMPRESSION_ZLIB) {
156
1.87k
    g_autoptr(FuInputStream) istream1 = NULL;
157
1.87k
    g_autoptr(FuInputStream) istream2 = NULL;
158
1.87k
    istream1 = fu_partial_input_stream_new(stream, hdrsz, payloadsz, error);
159
1.87k
    if (istream1 == NULL) {
160
742
      g_prefix_error_literal(error, "failed to cut uSWID payload: ");
161
742
      return FALSE;
162
742
    }
163
1.13k
    if (!g_seekable_seek(G_SEEKABLE(istream1), 0, G_SEEK_SET, NULL, error))
164
0
      return FALSE;
165
1.13k
    istream2 = fu_compressor_stream_new_decompress(istream1,
166
1.13k
                     FWUPD_COMPRESSOR_FORMAT_ZLIB,
167
1.13k
                     error);
168
1.13k
    if (istream2 == NULL)
169
0
      return FALSE;
170
1.13k
    payload = fu_input_stream_read_bytes(istream2, 0, payloadsz_max + 1, NULL, error);
171
1.13k
    if (payload == NULL)
172
1.13k
      return FALSE;
173
61.1k
  } else if (priv->compression == FU_USWID_PAYLOAD_COMPRESSION_LZMA) {
174
1.46k
    g_autoptr(GBytes) payload_tmp = NULL;
175
1.46k
    payload_tmp = fu_input_stream_read_bytes(stream, hdrsz, payloadsz, NULL, error);
176
1.46k
    if (payload_tmp == NULL)
177
490
      return FALSE;
178
974
    payload = fu_lzma_decompress_bytes(payload_tmp, payloadsz_max + 1, error);
179
974
    if (payload == NULL)
180
596
      return FALSE;
181
59.6k
  } else if (priv->compression == FU_USWID_PAYLOAD_COMPRESSION_NONE) {
182
59.2k
    payload = fu_input_stream_read_bytes(stream, hdrsz, payloadsz, NULL, error);
183
59.2k
    if (payload == NULL)
184
2.05k
      return FALSE;
185
59.2k
  } else {
186
405
    g_set_error(error,
187
405
          FWUPD_ERROR,
188
405
          FWUPD_ERROR_NOT_SUPPORTED,
189
405
          "compression format 0x%x is not supported",
190
405
          priv->compression);
191
405
    return FALSE;
192
405
  }
193
194
  /* payload */
195
57.5k
  payloadsz = g_bytes_get_size(payload);
196
57.5k
  if (payloadsz > payloadsz_max) {
197
0
    g_set_error(error,
198
0
          FWUPD_ERROR,
199
0
          FWUPD_ERROR_NOT_SUPPORTED,
200
0
          "inflated size 0x%x larger than allowed 0x%x",
201
0
          payloadsz,
202
0
          (guint)payloadsz_max);
203
0
    return FALSE;
204
0
  }
205
68.6k
  for (gsize offset_tmp = 0; offset_tmp < payloadsz;) {
206
66.2k
    g_autoptr(FuFirmware) img = g_object_new(img_gtype, NULL);
207
66.2k
    g_autoptr(GBytes) img_blob = NULL;
208
209
    /* parse SBOM component */
210
66.2k
    img_blob = fu_bytes_new_offset(payload, offset_tmp, payloadsz - offset_tmp, error);
211
66.2k
    if (img_blob == NULL)
212
0
      return FALSE;
213
66.2k
    if (!fu_firmware_parse_bytes(img,
214
66.2k
               img_blob,
215
66.2k
               0x0,
216
66.2k
               flags | FU_FIRMWARE_PARSE_FLAG_NO_SEARCH,
217
66.2k
               error))
218
55.1k
      return FALSE;
219
11.0k
    if (!fu_firmware_add_image(firmware, img, error))
220
0
      return FALSE;
221
11.0k
    if (fu_firmware_get_size(img) == 0) {
222
0
      g_set_error_literal(error,
223
0
              FWUPD_ERROR,
224
0
              FWUPD_ERROR_NOT_SUPPORTED,
225
0
              "read no bytes from uSWID child");
226
0
      return FALSE;
227
0
    }
228
11.0k
    offset_tmp += fu_firmware_get_size(img);
229
11.0k
  }
230
231
  /* success */
232
2.37k
  return TRUE;
233
57.5k
}
234
235
static GByteArray *
236
fu_uswid_firmware_write(FuFirmware *firmware, GError **error)
237
2
{
238
2
  FuUswidFirmware *self = FU_USWID_FIRMWARE(firmware);
239
2
  FuUswidFirmwarePrivate *priv = GET_PRIVATE(self);
240
2
  FuUswidHeaderFlags flags = FU_USWID_HEADER_FLAG_NONE;
241
2
  g_autoptr(FuStructUswid) st = fu_struct_uswid_new();
242
2
  g_autoptr(GByteArray) payload = g_byte_array_new();
243
2
  g_autoptr(GBytes) payload_blob = NULL;
244
2
  g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware);
245
246
  /* sanity check */
247
2
  if (priv->hdrver > FU_STRUCT_USWID_DEFAULT_HDRVER) {
248
0
    g_set_error(error,
249
0
          FWUPD_ERROR,
250
0
          FWUPD_ERROR_NOT_SUPPORTED,
251
0
          "no idea how to write header format 0x%02x",
252
0
          priv->hdrver);
253
0
    return NULL;
254
0
  }
255
256
  /* generate early so we know the size */
257
4
  for (guint i = 0; i < images->len; i++) {
258
2
    FuFirmware *img = g_ptr_array_index(images, i);
259
2
    g_autoptr(GByteArray) buf_tmp = fu_firmware_write_array(img, error);
260
2
    if (buf_tmp == NULL)
261
0
      return NULL;
262
2
    fu_byte_array_append_array(payload, buf_tmp);
263
2
  }
264
265
  /* compression flag */
266
2
  if (priv->compression != FU_USWID_PAYLOAD_COMPRESSION_NONE)
267
0
    flags |= FU_USWID_HEADER_FLAG_COMPRESSED;
268
269
  /* compression format */
270
2
  if (priv->compression == FU_USWID_PAYLOAD_COMPRESSION_ZLIB) {
271
0
    g_autoptr(FuInputStream) istream1 = NULL;
272
0
    g_autoptr(FuInputStream) istream2 = NULL;
273
274
0
    istream1 = fu_memory_input_stream_new_from_data(payload->data, payload->len, NULL);
275
0
    istream2 = fu_compressor_stream_new_compress(istream1,
276
0
                   FWUPD_COMPRESSOR_FORMAT_ZLIB,
277
0
                   error);
278
0
    if (istream2 == NULL)
279
0
      return NULL;
280
0
    payload_blob = fu_input_stream_read_bytes(istream2, 0, G_MAXSIZE, NULL, error);
281
0
    if (payload_blob == NULL)
282
0
      return NULL;
283
2
  } else if (priv->compression == FU_USWID_PAYLOAD_COMPRESSION_LZMA) {
284
0
    g_autoptr(GBytes) payload_tmp = g_bytes_new(payload->data, payload->len);
285
0
    payload_blob = fu_lzma_compress_bytes(payload_tmp, error);
286
0
    if (payload_blob == NULL)
287
0
      return NULL;
288
2
  } else {
289
2
    payload_blob = g_bytes_new(payload->data, payload->len);
290
2
  }
291
292
  /* pack */
293
2
  fu_struct_uswid_set_hdrver(st, priv->hdrver);
294
2
  fu_struct_uswid_set_payloadsz(st, g_bytes_get_size(payload_blob));
295
2
  fu_struct_uswid_set_flags(st, flags);
296
2
  fu_struct_uswid_set_compression(st, priv->compression);
297
2
  fu_struct_uswid_set_format(st, priv->format);
298
299
  /* previous headers were smaller in size */
300
2
  if (priv->hdrver == 3) {
301
0
    g_byte_array_set_size(st->buf, st->buf->len - 1);
302
2
  } else if (priv->hdrver == 2) {
303
0
    if (priv->compression != FU_USWID_PAYLOAD_COMPRESSION_NONE &&
304
0
        priv->compression != FU_USWID_PAYLOAD_COMPRESSION_ZLIB) {
305
0
      g_set_error_literal(error,
306
0
              FWUPD_ERROR,
307
0
              FWUPD_ERROR_NOT_SUPPORTED,
308
0
              "hdrver 0x02 only supports zlib compression");
309
0
      return NULL;
310
0
    }
311
0
    g_byte_array_set_size(st->buf, st->buf->len - 2);
312
2
  } else if (priv->hdrver == 1) {
313
0
    g_byte_array_set_size(st->buf, st->buf->len - 3);
314
0
  }
315
2
  fu_struct_uswid_set_hdrsz(st, st->buf->len);
316
317
  /* success */
318
2
  fu_byte_array_append_bytes(st->buf, payload_blob);
319
2
  return g_steal_pointer(&st->buf);
320
2
}
321
322
static gboolean
323
fu_uswid_firmware_build(FuFirmware *firmware, XbNode *n, GError **error)
324
0
{
325
0
  FuUswidFirmware *self = FU_USWID_FIRMWARE(firmware);
326
0
  FuUswidFirmwarePrivate *priv = GET_PRIVATE(self);
327
0
  const gchar *str;
328
0
  guint64 tmp;
329
330
  /* simple properties */
331
0
  tmp = xb_node_query_text_as_uint(n, "hdrver", NULL);
332
0
  if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8)
333
0
    priv->hdrver = tmp;
334
335
  /* simple properties */
336
0
  str = xb_node_query_text(n, "compression", NULL);
337
0
  if (str != NULL) {
338
0
    priv->compression = fu_uswid_payload_compression_from_string(str);
339
0
    if (priv->compression == FU_USWID_PAYLOAD_COMPRESSION_NONE) {
340
0
      g_set_error(error,
341
0
            FWUPD_ERROR,
342
0
            FWUPD_ERROR_INVALID_DATA,
343
0
            "invalid compression type %s",
344
0
            str);
345
0
      return FALSE;
346
0
    }
347
0
  } else {
348
0
    priv->compression = FU_USWID_PAYLOAD_COMPRESSION_NONE;
349
0
  }
350
351
  /* success */
352
0
  return TRUE;
353
0
}
354
355
static void
356
fu_uswid_firmware_add_magic(FuFirmware *firmware)
357
69.7k
{
358
69.7k
  fu_firmware_add_magic(firmware,
359
69.7k
            (const guint8 *)FU_STRUCT_USWID_DEFAULT_MAGIC,
360
69.7k
            sizeof(fwupd_guid_t),
361
69.7k
            0x0);
362
69.7k
}
363
364
static void
365
fu_uswid_firmware_init(FuUswidFirmware *self)
366
69.7k
{
367
69.7k
  FuUswidFirmwarePrivate *priv = GET_PRIVATE(self);
368
69.7k
  priv->hdrver = FU_USWID_FIRMWARE_MINIMUM_HDRVER;
369
69.7k
  priv->compression = FU_USWID_PAYLOAD_COMPRESSION_NONE;
370
69.7k
  priv->format = FU_USWID_PAYLOAD_FORMAT_COSWID;
371
69.7k
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_STORED_SIZE);
372
69.7k
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_ALWAYS_SEARCH);
373
69.7k
}
374
375
static void
376
fu_uswid_firmware_class_init(FuUswidFirmwareClass *klass)
377
3
{
378
3
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
379
3
  fu_firmware_add_image_gtype(firmware_class, FU_TYPE_COSWID_FIRMWARE);
380
3
  fu_firmware_add_image_gtype(firmware_class, FU_TYPE_FIRMWARE);
381
3
  firmware_class->validate = fu_uswid_firmware_validate;
382
3
  firmware_class->parse = fu_uswid_firmware_parse;
383
3
  firmware_class->write = fu_uswid_firmware_write;
384
3
  firmware_class->build = fu_uswid_firmware_build;
385
3
  firmware_class->export = fu_uswid_firmware_export;
386
3
  firmware_class->add_magic = fu_uswid_firmware_add_magic;
387
3
  fu_firmware_set_size_max(firmware_class, 16 * FU_MB);
388
3
  fu_firmware_set_images_max(firmware_class, 2000);
389
3
}
390
391
/**
392
 * fu_uswid_firmware_new:
393
 *
394
 * Creates a new #FuFirmware of sub type uSWID
395
 *
396
 * Since: 1.8.0
397
 **/
398
FuFirmware *
399
fu_uswid_firmware_new(void)
400
0
{
401
0
  return FU_FIRMWARE(g_object_new(FU_TYPE_USWID_FIRMWARE, NULL));
402
0
}