/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 | } |