Coverage Report

Created: 2026-05-30 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}