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