/src/fwupd/libfwupdplugin/fu-zip-firmware.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2026 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | 1.83k | #define G_LOG_DOMAIN "FuZipFirmware" |
8 | | |
9 | | #include "config.h" |
10 | | |
11 | | #include "fu-byte-array.h" |
12 | | #include "fu-common.h" |
13 | | #include "fu-partial-input-stream.h" |
14 | | #include "fu-path.h" |
15 | | #include "fu-string.h" |
16 | | #include "fu-zip-file.h" |
17 | | #include "fu-zip-firmware.h" |
18 | | #include "fu-zip-struct.h" |
19 | | |
20 | 3.86k | G_DEFINE_TYPE(FuZipFirmware, fu_zip_firmware, FU_TYPE_FIRMWARE) |
21 | 3.86k | |
22 | 3.86k | #define FU_ZIP_FIRMWARE_EOCD_OFFSET_MAX (16 * FU_KB) |
23 | 35.8k | #define FU_ZIP_FIRMWARE_EXTRA_MAX (1 * FU_MB) |
24 | 9.25k | #define FU_ZIP_MAX_DECOMPRESSION_RATIO 1000 |
25 | | |
26 | | static gboolean |
27 | | fu_zip_firmware_parse_extra(GInputStream *stream, gsize offset, gsize extra_size, GError **error) |
28 | 24.6k | { |
29 | 60.4k | for (gsize i = 0; i < extra_size; i += FU_STRUCT_ZIP_EXTRA_HDR_SIZE) { |
30 | 36.0k | guint32 datasz; |
31 | 36.0k | g_autoptr(FuStructZipExtraHdr) st_ehdr = NULL; |
32 | 36.0k | st_ehdr = fu_struct_zip_extra_hdr_parse_stream(stream, offset + i, error); |
33 | 36.0k | if (st_ehdr == NULL) |
34 | 156 | return FALSE; |
35 | 35.8k | datasz = fu_struct_zip_extra_hdr_get_datasz(st_ehdr); |
36 | 35.8k | if (!fu_size_checked_inc(&i, datasz, error)) { |
37 | 0 | g_prefix_error_literal(error, "extra field size overflow: "); |
38 | 0 | return FALSE; |
39 | 0 | } |
40 | 35.8k | if (i > FU_ZIP_FIRMWARE_EXTRA_MAX) { |
41 | 0 | g_set_error(error, |
42 | 0 | FWUPD_ERROR, |
43 | 0 | FWUPD_ERROR_NOT_SUPPORTED, |
44 | 0 | "too much ZipExtraHdr data: 0x%x > 0x%x", |
45 | 0 | (guint)i, |
46 | 0 | (guint)FU_ZIP_FIRMWARE_EXTRA_MAX); |
47 | 0 | return FALSE; |
48 | 0 | } |
49 | 35.8k | } |
50 | 24.4k | return TRUE; |
51 | 24.6k | } |
52 | | |
53 | | static FuFirmware * |
54 | | fu_zip_firmware_parse_lfh(FuZipFirmware *self, |
55 | | GInputStream *stream, |
56 | | FuStructZipCdfh *st_cdfh, |
57 | | FuFirmwareParseFlags flags, |
58 | | GError **error) |
59 | 13.1k | { |
60 | 13.1k | FuZipCompression compression; |
61 | 13.1k | gsize offset = fu_struct_zip_cdfh_get_offset_lfh(st_cdfh); |
62 | 13.1k | guint16 lfh_flags; |
63 | 13.1k | guint32 actual_crc = 0xFFFFFFFF; |
64 | 13.1k | guint32 compressed_size; |
65 | 13.1k | guint32 uncompressed_size; |
66 | 13.1k | guint32 uncompressed_crc; |
67 | 13.1k | g_autofree gchar *filename = NULL; |
68 | 13.1k | g_autoptr(FuStructZipLfh) st_lfh = NULL; |
69 | 13.1k | g_autoptr(FuFirmware) zip_file = fu_zip_file_new(); |
70 | 13.1k | g_autoptr(GInputStream) stream_compressed = NULL; |
71 | | |
72 | | /* read local file header */ |
73 | 13.1k | fu_firmware_set_offset(zip_file, offset); |
74 | 13.1k | st_lfh = fu_struct_zip_lfh_parse_stream(stream, offset, error); |
75 | 13.1k | if (st_lfh == NULL) |
76 | 94 | return NULL; |
77 | 13.0k | if (!fu_size_checked_inc(&offset, FU_STRUCT_ZIP_LFH_SIZE, error)) |
78 | 0 | return NULL; |
79 | | |
80 | | /* read filename */ |
81 | 13.0k | filename = fu_input_stream_read_string(stream, |
82 | 13.0k | offset, |
83 | 13.0k | fu_struct_zip_lfh_get_filename_size(st_lfh), |
84 | 13.0k | error); |
85 | 13.0k | if (filename == NULL) { |
86 | 186 | g_prefix_error_literal(error, "failed to read filename: "); |
87 | 186 | return NULL; |
88 | 186 | } |
89 | 12.8k | if (!fu_size_checked_inc(&offset, fu_struct_zip_lfh_get_filename_size(st_lfh), error)) |
90 | 0 | return NULL; |
91 | | |
92 | | /* sanity check */ |
93 | 12.8k | if (!fu_path_verify_safe(filename, error)) |
94 | 43 | return NULL; |
95 | | |
96 | | /* parse the extra data blob just because we can */ |
97 | 12.7k | if (!fu_zip_firmware_parse_extra(stream, |
98 | 12.7k | offset, |
99 | 12.7k | fu_struct_zip_lfh_get_extra_size(st_lfh), |
100 | 12.7k | error)) |
101 | 90 | return NULL; |
102 | 12.7k | if (!fu_size_checked_inc(&offset, fu_struct_zip_lfh_get_extra_size(st_lfh), error)) |
103 | 0 | return NULL; |
104 | | |
105 | | /* read crc */ |
106 | 12.7k | lfh_flags = fu_struct_zip_lfh_get_flags(st_lfh); |
107 | 12.7k | if (lfh_flags & FU_ZIP_FLAG_DATA_DESCRIPTOR) { |
108 | 3.68k | uncompressed_crc = fu_struct_zip_cdfh_get_uncompressed_crc(st_cdfh); |
109 | 9.01k | } else { |
110 | 9.01k | uncompressed_crc = fu_struct_zip_lfh_get_uncompressed_crc(st_lfh); |
111 | 9.01k | } |
112 | | |
113 | | /* read data */ |
114 | 12.7k | compressed_size = fu_struct_zip_lfh_get_compressed_size(st_lfh); |
115 | 12.7k | if (compressed_size == 0x0) { |
116 | 4.16k | compressed_size = fu_struct_zip_cdfh_get_compressed_size(st_cdfh); |
117 | 8.53k | } else { |
118 | | /* validate LFH and CDFH agree when both are non-zero */ |
119 | 8.53k | guint32 cdfh_compressed = fu_struct_zip_cdfh_get_compressed_size(st_cdfh); |
120 | 8.53k | if (cdfh_compressed != 0x0 && cdfh_compressed != 0xFFFFFFFF && |
121 | 593 | compressed_size != cdfh_compressed) { |
122 | 121 | g_set_error(error, |
123 | 121 | FWUPD_ERROR, |
124 | 121 | FWUPD_ERROR_INVALID_DATA, |
125 | 121 | "LFH compressed size 0x%x differs from CDFH 0x%x", |
126 | 121 | compressed_size, |
127 | 121 | cdfh_compressed); |
128 | 121 | return NULL; |
129 | 121 | } |
130 | 8.53k | } |
131 | 12.5k | if (compressed_size == 0xFFFFFFFF) { |
132 | 3 | g_set_error_literal(error, |
133 | 3 | FWUPD_ERROR, |
134 | 3 | FWUPD_ERROR_NOT_SUPPORTED, |
135 | 3 | "zip64 not supported"); |
136 | 3 | return NULL; |
137 | 3 | } |
138 | 12.5k | uncompressed_size = fu_struct_zip_lfh_get_uncompressed_size(st_lfh); |
139 | 12.5k | if (uncompressed_size == 0x0) { |
140 | 4.22k | uncompressed_size = fu_struct_zip_cdfh_get_uncompressed_size(st_cdfh); |
141 | 8.34k | } else { |
142 | | /* validate LFH and CDFH agree when both are non-zero */ |
143 | 8.34k | guint32 cdfh_uncompressed = fu_struct_zip_cdfh_get_uncompressed_size(st_cdfh); |
144 | 8.34k | if (cdfh_uncompressed != 0x0 && cdfh_uncompressed != 0xFFFFFFFF && |
145 | 378 | uncompressed_size != cdfh_uncompressed) { |
146 | 159 | g_set_error(error, |
147 | 159 | FWUPD_ERROR, |
148 | 159 | FWUPD_ERROR_INVALID_DATA, |
149 | 159 | "LFH uncompressed size 0x%x differs from CDFH 0x%x", |
150 | 159 | uncompressed_size, |
151 | 159 | cdfh_uncompressed); |
152 | 159 | return NULL; |
153 | 159 | } |
154 | 8.34k | } |
155 | 12.4k | if (uncompressed_size == 0xFFFFFFFF) { |
156 | 2 | g_set_error_literal(error, |
157 | 2 | FWUPD_ERROR, |
158 | 2 | FWUPD_ERROR_NOT_SUPPORTED, |
159 | 2 | "zip64 not supported"); |
160 | 2 | return NULL; |
161 | 2 | } |
162 | | |
163 | | /* reject zero-size compressed data if uncompressed is non-zero */ |
164 | 12.4k | if (compressed_size == 0 && uncompressed_size > 0) { |
165 | 57 | g_set_error_literal(error, |
166 | 57 | FWUPD_ERROR, |
167 | 57 | FWUPD_ERROR_INVALID_DATA, |
168 | 57 | "compressed size is zero but uncompressed size is non-zero"); |
169 | 57 | return NULL; |
170 | 57 | } |
171 | | |
172 | | /* check decompression ratio to prevent bombs */ |
173 | 12.3k | if (compressed_size > 0 && |
174 | 9.22k | (uncompressed_size / compressed_size) > FU_ZIP_MAX_DECOMPRESSION_RATIO) { |
175 | 34 | g_set_error(error, |
176 | 34 | FWUPD_ERROR, |
177 | 34 | FWUPD_ERROR_INVALID_DATA, |
178 | 34 | "decompression ratio %u:1 exceeds maximum of %u:1", |
179 | 34 | uncompressed_size / compressed_size, |
180 | 34 | (guint)FU_ZIP_MAX_DECOMPRESSION_RATIO); |
181 | 34 | return NULL; |
182 | 34 | } |
183 | | |
184 | | /* prevent excessive memory allocation */ |
185 | 12.3k | if (fu_firmware_get_size_max(FU_FIRMWARE(self)) > 0 && |
186 | 12.3k | uncompressed_size > fu_firmware_get_size_max(FU_FIRMWARE(self))) { |
187 | 156 | g_autofree gchar *sz_val = g_format_size(uncompressed_size); |
188 | 156 | g_autofree gchar *sz_max = |
189 | 156 | g_format_size(fu_firmware_get_size_max(FU_FIRMWARE(self))); |
190 | 156 | g_set_error(error, |
191 | 156 | FWUPD_ERROR, |
192 | 156 | FWUPD_ERROR_INVALID_DATA, |
193 | 156 | "uncompressed file size %s exceeds maximum %s", |
194 | 156 | sz_val, |
195 | 156 | sz_max); |
196 | 156 | return NULL; |
197 | 156 | } |
198 | 12.1k | stream_compressed = fu_partial_input_stream_new(stream, offset, compressed_size, error); |
199 | 12.1k | if (stream_compressed == NULL) |
200 | 136 | return NULL; |
201 | 12.0k | compression = fu_struct_zip_lfh_get_compression(st_lfh); |
202 | 12.0k | if (compression == FU_ZIP_COMPRESSION_NONE) { |
203 | 4.26k | if (compressed_size != uncompressed_size) { |
204 | 44 | g_set_error(error, |
205 | 44 | FWUPD_ERROR, |
206 | 44 | FWUPD_ERROR_INVALID_DATA, |
207 | 44 | "no compression but compressed (0x%x) != uncompressed (0x%x)", |
208 | 44 | (guint)compressed_size, |
209 | 44 | (guint)uncompressed_size); |
210 | 44 | return NULL; |
211 | 44 | } |
212 | 4.22k | if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) { |
213 | 0 | if (!fu_input_stream_compute_crc32(stream_compressed, |
214 | 0 | FU_CRC_KIND_B32_STANDARD, |
215 | 0 | &actual_crc, |
216 | 0 | error)) |
217 | 0 | return NULL; |
218 | 0 | } |
219 | 4.22k | if (!fu_firmware_set_stream(zip_file, stream_compressed, error)) |
220 | 0 | return NULL; |
221 | 7.76k | } else if (compression == FU_ZIP_COMPRESSION_DEFLATE) { |
222 | 7.71k | g_autoptr(GBytes) blob_raw = NULL; |
223 | 7.71k | g_autoptr(GConverter) conv = NULL; |
224 | 7.71k | g_autoptr(GInputStream) stream_deflate = NULL; |
225 | | |
226 | 7.71k | conv = G_CONVERTER(g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_RAW)); |
227 | 7.71k | if (!g_seekable_seek(G_SEEKABLE(stream_compressed), 0, G_SEEK_SET, NULL, error)) |
228 | 0 | return NULL; |
229 | 7.71k | stream_deflate = g_converter_input_stream_new(stream_compressed, conv); |
230 | 7.71k | g_filter_input_stream_set_close_base_stream(G_FILTER_INPUT_STREAM(stream_deflate), |
231 | 7.71k | FALSE); |
232 | 7.71k | blob_raw = |
233 | 7.71k | fu_input_stream_read_bytes(stream_deflate, 0, uncompressed_size, NULL, error); |
234 | 7.71k | if (blob_raw == NULL) { |
235 | 29 | g_prefix_error_literal(error, "failed to read compressed stream: "); |
236 | 29 | return NULL; |
237 | 29 | } |
238 | 7.68k | if (g_bytes_get_size(blob_raw) != uncompressed_size) { |
239 | 73 | g_set_error(error, |
240 | 73 | FWUPD_ERROR, |
241 | 73 | FWUPD_ERROR_INVALID_DATA, |
242 | 73 | "invalid decompression, got 0x%x bytes but expected 0x%x", |
243 | 73 | (guint)g_bytes_get_size(blob_raw), |
244 | 73 | (guint)uncompressed_size); |
245 | 73 | return NULL; |
246 | 73 | } |
247 | 7.60k | fu_firmware_set_bytes(zip_file, blob_raw); |
248 | 7.60k | if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) |
249 | 0 | actual_crc = fu_crc32_bytes(FU_CRC_KIND_B32_STANDARD, blob_raw); |
250 | 7.60k | } else { |
251 | 57 | g_set_error(error, |
252 | 57 | FWUPD_ERROR, |
253 | 57 | FWUPD_ERROR_NOT_SUPPORTED, |
254 | 57 | "%s compression not supported", |
255 | 57 | fu_zip_compression_to_string(compression)); |
256 | 57 | return NULL; |
257 | 57 | } |
258 | 11.8k | fu_zip_file_set_compression(FU_ZIP_FILE(zip_file), compression); |
259 | | |
260 | | /* verify checksum */ |
261 | 11.8k | if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) { |
262 | 0 | if (actual_crc != uncompressed_crc) { |
263 | 0 | g_set_error(error, |
264 | 0 | FWUPD_ERROR, |
265 | 0 | FWUPD_ERROR_INVALID_DATA, |
266 | 0 | "%s CRC 0x%08x invalid, expected 0x%08x", |
267 | 0 | filename, |
268 | 0 | actual_crc, |
269 | 0 | uncompressed_crc); |
270 | 0 | return NULL; |
271 | 0 | } |
272 | 0 | } |
273 | | |
274 | | /* add as a image */ |
275 | 11.8k | if (flags & FU_FIRMWARE_PARSE_FLAG_ONLY_BASENAME) { |
276 | 0 | g_autofree gchar *filename_basename = g_path_get_basename(filename); |
277 | 0 | fu_firmware_set_id(zip_file, filename_basename); |
278 | 11.8k | } else { |
279 | 11.8k | fu_firmware_set_id(zip_file, filename); |
280 | 11.8k | } |
281 | | |
282 | | /* success */ |
283 | 11.8k | return g_steal_pointer(&zip_file); |
284 | 11.8k | } |
285 | | |
286 | | static gboolean |
287 | | fu_zip_firmware_parse(FuFirmware *firmware, |
288 | | GInputStream *stream, |
289 | | FuFirmwareParseFlags flags, |
290 | | GError **error) |
291 | 1.93k | { |
292 | 1.93k | FuZipFirmware *self = FU_ZIP_FIRMWARE(firmware); |
293 | 1.93k | gsize streamsz = 0; |
294 | 1.93k | gsize offset = 0; |
295 | 1.93k | g_autoptr(FuStructZipEocd) st_eocd = NULL; |
296 | | |
297 | 1.93k | if (!fu_input_stream_size(stream, &streamsz, error)) |
298 | 0 | return FALSE; |
299 | | |
300 | | /* look for the end of central directory record signature in the last 4K */ |
301 | 1.93k | if (streamsz > FU_ZIP_FIRMWARE_EOCD_OFFSET_MAX) |
302 | 63 | offset = streamsz - FU_ZIP_FIRMWARE_EOCD_OFFSET_MAX; |
303 | 1.93k | if (!fu_input_stream_find(stream, |
304 | 1.93k | (const guint8 *)FU_STRUCT_ZIP_EOCD_DEFAULT_MAGIC, |
305 | 1.93k | FU_STRUCT_ZIP_EOCD_N_ELEMENTS_MAGIC, |
306 | 1.93k | offset, |
307 | 1.93k | &offset, |
308 | 1.93k | error)) { |
309 | 98 | g_prefix_error_literal(error, "failed to find zip EOCD signature: "); |
310 | 98 | return FALSE; |
311 | 98 | } |
312 | 1.83k | g_debug("found ZIP EOCD magic @0x%x", (guint)offset); |
313 | 1.83k | st_eocd = fu_struct_zip_eocd_parse_stream(stream, offset, error); |
314 | 1.83k | if (st_eocd == NULL) |
315 | 19 | return FALSE; |
316 | 1.81k | if (fu_struct_zip_eocd_get_disk_number(st_eocd) != 0x0 || |
317 | 1.80k | fu_struct_zip_eocd_get_cd_disk(st_eocd) != 0x0 || |
318 | 1.78k | fu_struct_zip_eocd_get_cd_number_disk(st_eocd) != |
319 | 1.78k | fu_struct_zip_eocd_get_cd_number(st_eocd)) { |
320 | 58 | g_set_error_literal(error, |
321 | 58 | FWUPD_ERROR, |
322 | 58 | FWUPD_ERROR_NOT_SUPPORTED, |
323 | 58 | "multiple disk archives not supported"); |
324 | 58 | return FALSE; |
325 | 58 | } |
326 | | |
327 | | /* archives over 4GB do not make sense here */ |
328 | 1.75k | if (fu_struct_zip_eocd_get_cd_size(st_eocd) == 0xFFFFFFFF) { |
329 | 1 | g_set_error_literal(error, |
330 | 1 | FWUPD_ERROR, |
331 | 1 | FWUPD_ERROR_NOT_SUPPORTED, |
332 | 1 | "zip64 not supported"); |
333 | 1 | return FALSE; |
334 | 1 | } |
335 | | |
336 | | /* check file count before parsing to prevent DoS */ |
337 | 1.75k | if (fu_firmware_get_images_max(FU_FIRMWARE(self)) > 0 && |
338 | 1.75k | fu_struct_zip_eocd_get_cd_number(st_eocd) > |
339 | 1.75k | fu_firmware_get_images_max(FU_FIRMWARE(self))) { |
340 | 10 | g_set_error(error, |
341 | 10 | FWUPD_ERROR, |
342 | 10 | FWUPD_ERROR_NOT_SUPPORTED, |
343 | 10 | "too many files in ZIP: %u exceeds maximum of %u", |
344 | 10 | fu_struct_zip_eocd_get_cd_number(st_eocd), |
345 | 10 | fu_firmware_get_images_max(FU_FIRMWARE(self))); |
346 | 10 | return FALSE; |
347 | 10 | } |
348 | | |
349 | | /* parse central directory file header */ |
350 | 1.74k | offset = fu_struct_zip_eocd_get_cd_offset(st_eocd); |
351 | 13.5k | for (guint i = 0; i < fu_struct_zip_eocd_get_cd_number(st_eocd); i++) { |
352 | 13.3k | g_autoptr(FuFirmware) zip_file = NULL; |
353 | 13.3k | g_autoptr(FuStructZipCdfh) st_cdfh = NULL; |
354 | | |
355 | | /* although the filename is available in the CDFH, trust the one in the LFH */ |
356 | 13.3k | st_cdfh = fu_struct_zip_cdfh_parse_stream(stream, offset, error); |
357 | 13.3k | if (st_cdfh == NULL) |
358 | 258 | return FALSE; |
359 | 13.1k | if (fu_struct_zip_cdfh_get_flags(st_cdfh) & FU_ZIP_FLAG_ENCRYPTED) { |
360 | 2 | g_set_error_literal(error, |
361 | 2 | FWUPD_ERROR, |
362 | 2 | FWUPD_ERROR_NOT_SUPPORTED, |
363 | 2 | "encryption not supported"); |
364 | 2 | return FALSE; |
365 | 2 | } |
366 | 13.1k | zip_file = fu_zip_firmware_parse_lfh(self, stream, st_cdfh, flags, error); |
367 | 13.1k | if (zip_file == NULL) |
368 | 1.28k | return FALSE; |
369 | | |
370 | 11.8k | if (!fu_size_checked_inc(&offset, FU_STRUCT_ZIP_CDFH_SIZE, error)) |
371 | 0 | return FALSE; |
372 | 11.8k | if (!fu_size_checked_inc(&offset, |
373 | 11.8k | fu_struct_zip_cdfh_get_filename_size(st_cdfh), |
374 | 11.8k | error)) |
375 | 0 | return FALSE; |
376 | | |
377 | | /* parse the extra data blob just because we can */ |
378 | 11.8k | if (!fu_zip_firmware_parse_extra(stream, |
379 | 11.8k | offset, |
380 | 11.8k | fu_struct_zip_cdfh_get_extra_size(st_cdfh), |
381 | 11.8k | error)) |
382 | 66 | return FALSE; |
383 | 11.7k | if (!fu_size_checked_inc(&offset, |
384 | 11.7k | fu_struct_zip_cdfh_get_extra_size(st_cdfh), |
385 | 11.7k | error)) |
386 | 0 | return FALSE; |
387 | | |
388 | | /* ignore the comment */ |
389 | 11.7k | if (!fu_size_checked_inc(&offset, |
390 | 11.7k | fu_struct_zip_cdfh_get_comment_size(st_cdfh), |
391 | 11.7k | error)) |
392 | 0 | return FALSE; |
393 | | |
394 | | /* add image */ |
395 | 11.7k | if (!fu_firmware_add_image(FU_FIRMWARE(self), zip_file, error)) |
396 | 0 | return FALSE; |
397 | 11.7k | } |
398 | | |
399 | | /* success */ |
400 | 136 | return TRUE; |
401 | 1.74k | } |
402 | | |
403 | | typedef struct { |
404 | | guint32 uncompressed_crc; |
405 | | guint32 uncompressed_size; |
406 | | guint32 compressed_size; |
407 | | } FuZipFirmwareWriteItem; |
408 | | |
409 | | static GByteArray * |
410 | | fu_zip_firmware_write(FuFirmware *firmware, GError **error) |
411 | 136 | { |
412 | 136 | gsize cd_offset; |
413 | 136 | g_autoptr(GByteArray) buf = g_byte_array_new(); |
414 | 136 | g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware); |
415 | 136 | g_autoptr(FuStructZipEocd) st_eocd = fu_struct_zip_eocd_new(); |
416 | 136 | g_autofree FuZipFirmwareWriteItem *items = NULL; |
417 | | |
418 | | /* stored twice, so avoid computing */ |
419 | 136 | items = g_new0(FuZipFirmwareWriteItem, imgs->len); |
420 | | |
421 | | /* LFHs */ |
422 | 1.36k | for (guint i = 0; i < imgs->len; i++) { |
423 | 1.23k | FuZipFile *zip_file = g_ptr_array_index(imgs, i); |
424 | 1.23k | FuZipCompression compression = fu_zip_file_get_compression(zip_file); |
425 | 1.23k | const gchar *filename = fu_firmware_get_id(FU_FIRMWARE(zip_file)); |
426 | 1.23k | g_autoptr(FuStructZipLfh) st_lfh = fu_struct_zip_lfh_new(); |
427 | 1.23k | g_autoptr(GBytes) blob = NULL; |
428 | 1.23k | g_autoptr(GBytes) blob_compressed = NULL; |
429 | | |
430 | | /* check valid */ |
431 | 1.23k | if (filename == NULL) { |
432 | 0 | g_set_error_literal(error, |
433 | 0 | FWUPD_ERROR, |
434 | 0 | FWUPD_ERROR_NOT_SUPPORTED, |
435 | 0 | "filename not provided"); |
436 | 0 | return NULL; |
437 | 0 | } |
438 | | |
439 | | /* save for later */ |
440 | 1.23k | fu_firmware_set_offset(FU_FIRMWARE(zip_file), buf->len); |
441 | 1.23k | blob = fu_firmware_get_bytes(FU_FIRMWARE(zip_file), error); |
442 | 1.23k | if (blob == NULL) |
443 | 13 | return NULL; |
444 | 1.22k | if (g_bytes_get_size(blob) >= G_MAXUINT32) { |
445 | 0 | g_set_error_literal(error, |
446 | 0 | FWUPD_ERROR, |
447 | 0 | FWUPD_ERROR_INVALID_DATA, |
448 | 0 | "uncompressed size exceeds ZIP format limit"); |
449 | 0 | return NULL; |
450 | 0 | } |
451 | | |
452 | 1.22k | if (compression == FU_ZIP_COMPRESSION_NONE) { |
453 | 702 | blob_compressed = g_bytes_ref(blob); |
454 | 702 | } else if (compression == FU_ZIP_COMPRESSION_DEFLATE) { |
455 | 524 | g_autoptr(GConverter) conv = NULL; |
456 | 524 | g_autoptr(GInputStream) istream_raw = NULL; |
457 | 524 | g_autoptr(GInputStream) istream_compressed = NULL; |
458 | 524 | conv = G_CONVERTER(g_zlib_compressor_new(G_ZLIB_COMPRESSOR_FORMAT_RAW, -1)); |
459 | 524 | istream_raw = g_memory_input_stream_new_from_bytes(blob); |
460 | 524 | istream_compressed = g_converter_input_stream_new(istream_raw, conv); |
461 | 524 | blob_compressed = fu_input_stream_read_bytes(istream_compressed, |
462 | 524 | 0, |
463 | 524 | G_MAXSIZE, |
464 | 524 | NULL, |
465 | 524 | error); |
466 | 524 | if (blob_compressed == NULL) { |
467 | 0 | g_prefix_error_literal(error, "failed to read compressed stream: "); |
468 | 0 | return NULL; |
469 | 0 | } |
470 | 524 | if (g_bytes_get_size(blob_compressed) >= G_MAXUINT32) { |
471 | 0 | g_set_error_literal(error, |
472 | 0 | FWUPD_ERROR, |
473 | 0 | FWUPD_ERROR_INVALID_DATA, |
474 | 0 | "compressed size exceeds ZIP format limit"); |
475 | 0 | return NULL; |
476 | 0 | } |
477 | 524 | } else { |
478 | 0 | g_set_error(error, |
479 | 0 | FWUPD_ERROR, |
480 | 0 | FWUPD_ERROR_NOT_SUPPORTED, |
481 | 0 | "%s compression not supported", |
482 | 0 | fu_zip_compression_to_string(compression)); |
483 | 0 | return NULL; |
484 | 0 | } |
485 | | |
486 | 1.22k | items[i].uncompressed_crc = fu_crc32_bytes(FU_CRC_KIND_B32_STANDARD, blob); |
487 | 1.22k | items[i].uncompressed_size = g_bytes_get_size(blob); |
488 | 1.22k | fu_struct_zip_lfh_set_uncompressed_crc(st_lfh, items[i].uncompressed_crc); |
489 | 1.22k | fu_struct_zip_lfh_set_uncompressed_size(st_lfh, items[i].uncompressed_size); |
490 | 1.22k | fu_struct_zip_lfh_set_compression(st_lfh, compression); |
491 | 1.22k | items[i].compressed_size = g_bytes_get_size(blob_compressed); |
492 | 1.22k | fu_struct_zip_lfh_set_compressed_size(st_lfh, items[i].compressed_size); |
493 | 1.22k | if (strlen(filename) > G_MAXUINT16) { |
494 | 0 | g_set_error_literal(error, |
495 | 0 | FWUPD_ERROR, |
496 | 0 | FWUPD_ERROR_INVALID_DATA, |
497 | 0 | "filename too long for ZIP format"); |
498 | 0 | return NULL; |
499 | 0 | } |
500 | 1.22k | fu_struct_zip_lfh_set_filename_size(st_lfh, (guint16)strlen(filename)); |
501 | | |
502 | 1.22k | g_byte_array_append(buf, st_lfh->buf->data, st_lfh->buf->len); |
503 | 1.22k | g_byte_array_append(buf, (const guint8 *)filename, strlen(filename)); |
504 | 1.22k | fu_byte_array_append_bytes(buf, blob_compressed); |
505 | 1.22k | } |
506 | | |
507 | | /* CDFHs */ |
508 | 123 | cd_offset = buf->len; |
509 | 1.21k | for (guint i = 0; i < imgs->len; i++) { |
510 | 1.09k | FuZipFile *zip_file = g_ptr_array_index(imgs, i); |
511 | 1.09k | FuZipCompression compression = fu_zip_file_get_compression(zip_file); |
512 | 1.09k | const gchar *filename = fu_firmware_get_id(FU_FIRMWARE(zip_file)); |
513 | 1.09k | g_autoptr(FuStructZipCdfh) st_cdfh = fu_struct_zip_cdfh_new(); |
514 | | |
515 | 1.09k | fu_struct_zip_cdfh_set_compression(st_cdfh, compression); |
516 | 1.09k | fu_struct_zip_cdfh_set_compressed_size(st_cdfh, items[i].compressed_size); |
517 | 1.09k | fu_struct_zip_cdfh_set_uncompressed_crc(st_cdfh, items[i].uncompressed_crc); |
518 | 1.09k | fu_struct_zip_cdfh_set_uncompressed_size(st_cdfh, items[i].uncompressed_size); |
519 | 1.09k | fu_struct_zip_cdfh_set_filename_size(st_cdfh, (guint16)strlen(filename)); |
520 | 1.09k | if (fu_firmware_get_offset(FU_FIRMWARE(zip_file)) >= G_MAXUINT32) { |
521 | 0 | g_set_error_literal(error, |
522 | 0 | FWUPD_ERROR, |
523 | 0 | FWUPD_ERROR_INVALID_DATA, |
524 | 0 | "local file header offset exceeds ZIP format limit"); |
525 | 0 | return NULL; |
526 | 0 | } |
527 | 1.09k | fu_struct_zip_cdfh_set_offset_lfh(st_cdfh, |
528 | 1.09k | fu_firmware_get_offset(FU_FIRMWARE(zip_file))); |
529 | | |
530 | 1.09k | g_byte_array_append(buf, st_cdfh->buf->data, st_cdfh->buf->len); |
531 | 1.09k | g_byte_array_append(buf, (const guint8 *)filename, strlen(filename)); |
532 | 1.09k | } |
533 | | |
534 | | /* EOCD */ |
535 | 123 | if (cd_offset >= G_MAXUINT32) { |
536 | 0 | g_set_error_literal(error, |
537 | 0 | FWUPD_ERROR, |
538 | 0 | FWUPD_ERROR_INVALID_DATA, |
539 | 0 | "central directory offset exceeds ZIP format limit"); |
540 | 0 | return NULL; |
541 | 0 | } |
542 | 123 | fu_struct_zip_eocd_set_cd_offset(st_eocd, cd_offset); |
543 | 123 | fu_struct_zip_eocd_set_cd_number_disk(st_eocd, imgs->len); |
544 | 123 | fu_struct_zip_eocd_set_cd_number(st_eocd, imgs->len); |
545 | 123 | if (buf->len - cd_offset >= G_MAXUINT32) { |
546 | 0 | g_set_error_literal(error, |
547 | 0 | FWUPD_ERROR, |
548 | 0 | FWUPD_ERROR_INVALID_DATA, |
549 | 0 | "central directory size exceeds ZIP format limit"); |
550 | 0 | return NULL; |
551 | 0 | } |
552 | 123 | fu_struct_zip_eocd_set_cd_size(st_eocd, buf->len - cd_offset); |
553 | 123 | g_byte_array_append(buf, st_eocd->buf->data, st_eocd->buf->len); |
554 | | |
555 | | /* success */ |
556 | 123 | return g_steal_pointer(&buf); |
557 | 123 | } |
558 | | |
559 | | static void |
560 | | fu_zip_firmware_add_magic(FuFirmware *firmware) |
561 | 1.93k | { |
562 | 1.93k | fu_firmware_add_magic(firmware, |
563 | 1.93k | (const guint8 *)FU_STRUCT_ZIP_LFH_DEFAULT_MAGIC, |
564 | 1.93k | strlen(FU_STRUCT_ZIP_LFH_DEFAULT_MAGIC), |
565 | 1.93k | 0x0); |
566 | 1.93k | } |
567 | | |
568 | | static void |
569 | | fu_zip_firmware_class_init(FuZipFirmwareClass *klass) |
570 | 1 | { |
571 | 1 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
572 | 1 | firmware_class->parse = fu_zip_firmware_parse; |
573 | 1 | firmware_class->write = fu_zip_firmware_write; |
574 | 1 | firmware_class->add_magic = fu_zip_firmware_add_magic; |
575 | 1 | } |
576 | | |
577 | | static void |
578 | | fu_zip_firmware_init(FuZipFirmware *self) |
579 | 1.93k | { |
580 | 1.93k | fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_ZIP_FILE); |
581 | 1.93k | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_STORED_SIZE); |
582 | 1.93k | fu_firmware_set_images_max(FU_FIRMWARE(self), 1000); |
583 | 1.93k | fu_firmware_set_size_max(FU_FIRMWARE(self), 1 * FU_GB); |
584 | 1.93k | } |
585 | | |
586 | | /** |
587 | | * fu_zip_firmware_new: |
588 | | * |
589 | | * Returns: (transfer full): a #FuZipFirmware |
590 | | * |
591 | | * Since: 2.1.1 |
592 | | **/ |
593 | | FuFirmware * |
594 | | fu_zip_firmware_new(void) |
595 | 0 | { |
596 | 0 | return FU_FIRMWARE(g_object_new(FU_TYPE_ZIP_FIRMWARE, NULL)); |
597 | 0 | } |