Coverage Report

Created: 2026-02-26 06:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupdplugin/fu-efi-lz77-decompressor.c
Line
Count
Source
1
/*
2
 * Copyright 2024 Richard Hughes <richard@hughsie.com>
3
 * Copyright 2018 LongSoft
4
 * Copyright 2008 Apple Inc
5
 * Copyright 2006 Intel Corporation
6
 *
7
 * SPDX-License-Identifier: BSD-2-Clause or LGPL-2.1-or-later
8
 */
9
10
0
#define G_LOG_DOMAIN "FuFirmware"
11
12
#include "config.h"
13
14
#include "fu-byte-array.h"
15
#include "fu-efi-lz77-decompressor.h"
16
#include "fu-input-stream.h"
17
18
struct _FuEfiLz77Decompressor {
19
  FuFirmware parent_instance;
20
};
21
22
/**
23
 * FuEfiLz77Decompressor:
24
 *
25
 * Funky LZ77 decompressor as specified by EFI. The compression design [and code] was designed for
26
 * a different era, and much better compression can be achieved using LZMA or zlib.
27
 *
28
 * My advice would be to not use this compression method in new designs.
29
 *
30
 * See also: [class@FuFirmware]
31
 */
32
33
2.17k
G_DEFINE_TYPE(FuEfiLz77Decompressor, fu_efi_lz77_decompressor, FU_TYPE_FIRMWARE)
34
2.17k
35
378M
#define BITBUFSIZ 32
36
372M
#define MAXMATCH  256
37
379M
#define THRESHOLD 3
38
3.39M
#define CODE_BIT  16
39
40
/* c: char&len set; p: position set; t: extra set */
41
372M
#define NC  (0xff + MAXMATCH + 2 - THRESHOLD)
42
165k
#define CBIT  9
43
8.81M
#define MAXPBIT 5
44
114k
#define TBIT  5
45
8.81M
#define MAXNP ((1U << MAXPBIT) - 1)
46
3.39M
#define NT  (CODE_BIT + 3)
47
#if NT > MAXNP
48
#define NPT NT
49
#else
50
1.59M
#define NPT MAXNP
51
#endif
52
53
typedef struct {
54
  GInputStream *stream; /* no-ref */
55
  GByteArray *dst;      /* no-ref */
56
57
  guint16 bit_count;
58
  guint32 bit_buf;
59
  guint32 sub_bit_buf;
60
  guint16 block_size;
61
62
  guint16 left[2 * NC - 1];
63
  guint16 right[2 * NC - 1];
64
  guint8 c_len[NC];
65
  guint8 pt_len[NPT];
66
  guint16 c_table[4096];
67
  guint16 pt_table[256];
68
69
  guint8 p_bit; /* 'position set code length array size' in block header */
70
} FuEfiLz77DecompressHelper;
71
72
static void
73
fu_efi_lz77_decompressor_memset16(guint16 *buf, gsize length, guint16 value)
74
212k
{
75
212k
  g_return_if_fail(length % 2 == 0);
76
212k
  length /= sizeof(guint16);
77
240M
  for (gsize i = 0; i < length; i++)
78
240M
    buf[i] = value;
79
212k
}
80
81
static gboolean
82
fu_efi_lz77_decompressor_read_source_bits(FuEfiLz77DecompressHelper *helper,
83
            guint16 number_of_bits,
84
            GError **error)
85
377M
{
86
  /* left shift number_of_bits of bits in advance */
87
377M
  helper->bit_buf = (guint32)(((guint64)helper->bit_buf) << number_of_bits);
88
89
  /* copy data needed in bytes into sub_bit_buf */
90
380M
  while (number_of_bits > helper->bit_count) {
91
2.70M
    gssize rc;
92
2.70M
    guint8 sub_bit_buf = 0;
93
94
2.70M
    number_of_bits = (guint16)(number_of_bits - helper->bit_count);
95
2.70M
    helper->bit_buf |= (guint32)(((guint64)helper->sub_bit_buf) << number_of_bits);
96
97
    /* get 1 byte into sub_bit_buf */
98
2.70M
    rc = g_input_stream_read(helper->stream,
99
2.70M
           &sub_bit_buf,
100
2.70M
           sizeof(sub_bit_buf),
101
2.70M
           NULL,
102
2.70M
           error);
103
2.70M
    if (rc < 0)
104
0
      return FALSE;
105
2.70M
    if (rc == 0) {
106
      /* no more bits from the source, just pad zero bit */
107
1.95M
      helper->sub_bit_buf = 0;
108
1.95M
    } else {
109
751k
      helper->sub_bit_buf = sub_bit_buf;
110
751k
    }
111
2.70M
    helper->bit_count = 8;
112
2.70M
  }
113
114
  /* calculate additional bit count read to update bit_count */
115
377M
  helper->bit_count = (guint16)(helper->bit_count - number_of_bits);
116
117
  /* copy number_of_bits of bits from sub_bit_buf into bit_buf */
118
377M
  helper->bit_buf |= helper->sub_bit_buf >> helper->bit_count;
119
377M
  return TRUE;
120
377M
}
121
122
static gboolean
123
fu_efi_lz77_decompressor_get_bits(FuEfiLz77DecompressHelper *helper,
124
          guint16 number_of_bits,
125
          guint16 *value,
126
          GError **error)
127
2.41M
{
128
  /* pop number_of_bits of bits from left */
129
2.41M
  *value = (guint16)(helper->bit_buf >> (BITBUFSIZ - number_of_bits));
130
131
  /* fill up bit_buf from source */
132
2.41M
  return fu_efi_lz77_decompressor_read_source_bits(helper, number_of_bits, error);
133
2.41M
}
134
135
/* creates huffman code mapping table for extra set, char&len set and position set according to
136
 * code length array */
137
static gboolean
138
fu_efi_lz77_decompressor_make_huffman_table(FuEfiLz77DecompressHelper *helper,
139
              guint16 number_of_symbols,
140
              guint8 *code_length_array,
141
              guint16 mapping_table_bits,
142
              guint16 *table,
143
              GError **error)
144
131k
{
145
131k
  guint16 count[17] = {0};
146
131k
  guint16 weight[17] = {0};
147
131k
  guint16 start[18] = {0};
148
131k
  guint16 *pointer;
149
131k
  guint16 index;
150
131k
  guint16 c_char;
151
131k
  guint16 ju_bits;
152
131k
  guint16 avail_symbols;
153
131k
  guint16 mask;
154
131k
  guint16 max_table_length;
155
156
  /* the maximum mapping table width supported by this internal working function is 16 */
157
131k
  if (mapping_table_bits >= (sizeof(count) / sizeof(guint16))) {
158
0
    g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "bad table");
159
0
    return FALSE;
160
0
  }
161
162
35.4M
  for (index = 0; index < number_of_symbols; index++) {
163
35.3M
    if (code_length_array[index] > 16) {
164
72
      g_set_error_literal(error,
165
72
              FWUPD_ERROR,
166
72
              FWUPD_ERROR_INVALID_DATA,
167
72
              "bad table");
168
72
      return FALSE;
169
72
    }
170
35.3M
    count[code_length_array[index]]++;
171
35.3M
  }
172
173
2.23M
  for (index = 1; index <= 16; index++) {
174
2.10M
    guint16 word_of_start = start[index];
175
2.10M
    guint16 word_of_count = count[index];
176
2.10M
    start[index + 1] = (guint16)(word_of_start + (word_of_count << (16 - index)));
177
2.10M
  }
178
179
131k
  if (start[17] != 0) {
180
    /*(1U << 16)*/
181
848
    g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "bad table");
182
848
    return FALSE;
183
848
  }
184
185
130k
  ju_bits = (guint16)(16 - mapping_table_bits);
186
1.43M
  for (index = 1; index <= mapping_table_bits; index++) {
187
1.30M
    start[index] >>= ju_bits;
188
1.30M
    weight[index] = (guint16)(1U << (mapping_table_bits - index));
189
1.30M
  }
190
191
910k
  while (index <= 16) {
192
779k
    weight[index] = (guint16)(1U << (16 - index));
193
779k
    index++;
194
779k
  }
195
196
130k
  index = (guint16)(start[mapping_table_bits + 1] >> ju_bits);
197
130k
  if (index != 0) {
198
54
    guint16 index3 = (guint16)(1U << mapping_table_bits);
199
54
    if (index < index3) {
200
54
      fu_efi_lz77_decompressor_memset16(table + index,
201
54
                (index3 - index) * sizeof(*table),
202
54
                0);
203
54
    }
204
54
  }
205
206
130k
  avail_symbols = number_of_symbols;
207
130k
  mask = (guint16)(1U << (15 - mapping_table_bits));
208
130k
  max_table_length = (guint16)(1U << mapping_table_bits);
209
210
35.3M
  for (c_char = 0; c_char < number_of_symbols; c_char++) {
211
35.1M
    guint16 len = code_length_array[c_char];
212
35.1M
    guint16 next_code;
213
214
35.1M
    if (len == 0 || len >= 17)
215
35.0M
      continue;
216
217
172k
    next_code = (guint16)(start[len] + weight[len]);
218
172k
    if (len <= mapping_table_bits) {
219
8.80M
      for (index = start[len]; index < next_code; index++) {
220
8.63M
        if (index >= max_table_length) {
221
121
          g_set_error_literal(error,
222
121
                  FWUPD_ERROR,
223
121
                  FWUPD_ERROR_INVALID_DATA,
224
121
                  "bad table");
225
121
          return FALSE;
226
121
        }
227
8.63M
        table[index] = c_char;
228
8.63M
      }
229
230
171k
    } else {
231
688
      guint16 index3 = start[len];
232
688
      pointer = &table[index3 >> ju_bits];
233
688
      index = (guint16)(len - mapping_table_bits);
234
235
2.57k
      while (index != 0) {
236
1.88k
        if (*pointer == 0 && avail_symbols < (2 * NC - 1)) {
237
567
          helper->right[avail_symbols] = helper->left[avail_symbols] =
238
567
              0;
239
567
          *pointer = avail_symbols++;
240
567
        }
241
1.88k
        if (*pointer < (2 * NC - 1)) {
242
1.88k
          if ((index3 & mask) != 0)
243
975
            pointer = &helper->right[*pointer];
244
914
          else
245
914
            pointer = &helper->left[*pointer];
246
1.88k
        }
247
1.88k
        index3 <<= 1;
248
1.88k
        index--;
249
1.88k
      }
250
688
      *pointer = c_char;
251
688
    }
252
172k
    start[len] = next_code;
253
172k
  }
254
  /* success */
255
130k
  return TRUE;
256
130k
}
257
258
/* get a position value according to Position Huffman table */
259
static gboolean
260
fu_efi_lz77_decompressor_decode_p(FuEfiLz77DecompressHelper *helper, guint32 *value, GError **error)
261
6.68M
{
262
6.68M
  guint16 val;
263
264
6.68M
  val = helper->pt_table[helper->bit_buf >> (BITBUFSIZ - 8)];
265
6.68M
  if (val >= MAXNP) {
266
415k
    guint32 mask = 1U << (BITBUFSIZ - 1 - 8);
267
415k
    do {
268
415k
      if ((helper->bit_buf & mask) != 0) {
269
251k
        val = helper->right[val];
270
251k
      } else {
271
163k
        val = helper->left[val];
272
163k
      }
273
415k
      mask >>= 1;
274
415k
    } while (val >= MAXNP);
275
415k
  }
276
277
  /* advance what we have read */
278
6.68M
  if (!fu_efi_lz77_decompressor_read_source_bits(helper, helper->pt_len[val], error))
279
0
    return FALSE;
280
281
6.68M
  if (val > 1) {
282
1.73M
    guint16 char_c = 0;
283
1.73M
    if (!fu_efi_lz77_decompressor_get_bits(helper, (guint16)(val - 1), &char_c, error))
284
0
      return FALSE;
285
1.73M
    *value = (guint32)((1U << (val - 1)) + char_c);
286
1.73M
    return TRUE;
287
1.73M
  }
288
4.94M
  *value = val;
289
4.94M
  return TRUE;
290
6.68M
}
291
292
/* read in the extra set or position set length array, then generate the code mapping for them */
293
static gboolean
294
fu_efi_lz77_decompressor_read_pt_len(FuEfiLz77DecompressHelper *helper,
295
             guint16 number_of_symbols,
296
             guint16 number_of_bits,
297
             guint16 special_symbol,
298
             GError **error)
299
229k
{
300
229k
  guint16 number = 0;
301
229k
  guint16 index = 0;
302
303
  /* read Extra Set Code Length Array size */
304
229k
  if (!fu_efi_lz77_decompressor_get_bits(helper, number_of_bits, &number, error))
305
0
    return FALSE;
306
307
  /* fail if number or number_of_symbols is greater than size of pt_len */
308
229k
  if ((number > sizeof(helper->pt_len)) || (number_of_symbols > sizeof(helper->pt_len))) {
309
0
    g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "bad table");
310
0
    return FALSE;
311
0
  }
312
229k
  if (number == 0) {
313
    /* this represents only Huffman code used */
314
163k
    guint16 char_c = 0;
315
163k
    if (!fu_efi_lz77_decompressor_get_bits(helper, number_of_bits, &char_c, error))
316
0
      return FALSE;
317
163k
    fu_efi_lz77_decompressor_memset16(&helper->pt_table[0],
318
163k
              sizeof(helper->pt_table),
319
163k
              (guint16)char_c);
320
163k
    memset(helper->pt_len, 0, number_of_symbols);
321
163k
    return TRUE;
322
163k
  }
323
324
154k
  while (index < number && index < NPT) {
325
89.1k
    guint16 char_c = helper->bit_buf >> (BITBUFSIZ - 3);
326
327
    /* if a code length is less than 7, then it is encoded as a 3-bit value.
328
     * Or it is encoded as a series of "1"s followed by a terminating "0".
329
     * The number of "1"s = Code length - 4 */
330
89.1k
    if (char_c == 7) {
331
1.18k
      guint32 mask = 1U << (BITBUFSIZ - 1 - 3);
332
7.65k
      while (mask & helper->bit_buf) {
333
6.47k
        mask >>= 1;
334
6.47k
        char_c += 1;
335
6.47k
      }
336
1.18k
    }
337
338
89.1k
    if (!fu_efi_lz77_decompressor_read_source_bits(
339
89.1k
      helper,
340
89.1k
      (guint16)((char_c < 7) ? 3 : char_c - 3),
341
89.1k
      error))
342
0
      return FALSE;
343
344
89.1k
    helper->pt_len[index++] = (guint8)char_c;
345
346
    /* for code&len set, after the third length of the code length concatenation,
347
     * a 2-bit value is used to indicated the number of consecutive zero lengths after
348
     * the third length */
349
89.1k
    if (index == special_symbol) {
350
487
      if (!fu_efi_lz77_decompressor_get_bits(helper, 2, &char_c, error))
351
0
        return FALSE;
352
487
      if (char_c == 0) {
353
231
        g_set_error_literal(error,
354
231
                FWUPD_ERROR,
355
231
                FWUPD_ERROR_INVALID_DATA,
356
231
                "bad table");
357
231
        return FALSE;
358
231
      }
359
770
      while ((gint16)(--char_c) >= 0 && index < NPT) {
360
514
        helper->pt_len[index++] = 0;
361
514
      }
362
256
    }
363
89.1k
  }
364
1.57M
  while (index < number_of_symbols && index < NPT)
365
1.50M
    helper->pt_len[index++] = 0;
366
65.2k
  return fu_efi_lz77_decompressor_make_huffman_table(helper,
367
65.2k
                 number_of_symbols,
368
65.2k
                 helper->pt_len,
369
65.2k
                 8,
370
65.2k
                 helper->pt_table,
371
65.2k
                 error);
372
65.5k
}
373
374
/* read in and decode the Char&len Set Code Length Array, then generate the Huffman Code mapping
375
 * table for the char&len set */
376
static gboolean
377
fu_efi_lz77_decompressor_read_c_len(FuEfiLz77DecompressHelper *helper, GError **error)
378
114k
{
379
114k
  guint16 number = 0;
380
114k
  guint16 index = 0;
381
382
114k
  if (!fu_efi_lz77_decompressor_get_bits(helper, CBIT, &number, error))
383
0
    return FALSE;
384
114k
  if (number == 0) {
385
    /* this represents only Huffman code used */
386
48.4k
    guint16 char_c = 0;
387
48.4k
    if (!fu_efi_lz77_decompressor_get_bits(helper, CBIT, &char_c, error))
388
0
      return FALSE;
389
48.4k
    memset(helper->c_len, 0, sizeof(helper->c_len));
390
48.4k
    fu_efi_lz77_decompressor_memset16(&helper->c_table[0],
391
48.4k
              sizeof(helper->c_table),
392
48.4k
              char_c);
393
48.4k
    return TRUE;
394
48.4k
  }
395
396
3.33M
  while (index < number && index < NC) {
397
3.26M
    guint16 char_c = helper->pt_table[helper->bit_buf >> (BITBUFSIZ - 8)];
398
3.26M
    if (char_c >= NT) {
399
7.35k
      guint32 mask = 1U << (BITBUFSIZ - 1 - 8);
400
7.67k
      do {
401
7.67k
        if (mask & helper->bit_buf) {
402
4.00k
          char_c = helper->right[char_c];
403
4.00k
        } else {
404
3.66k
          char_c = helper->left[char_c];
405
3.66k
        }
406
7.67k
        mask >>= 1;
407
408
7.67k
      } while (char_c >= NT);
409
7.35k
    }
410
411
    /* advance what we have read */
412
3.26M
    if (!fu_efi_lz77_decompressor_read_source_bits(helper,
413
3.26M
                     helper->pt_len[char_c],
414
3.26M
                     error))
415
0
      return FALSE;
416
417
3.26M
    if (char_c <= 2) {
418
3.05M
      if (char_c == 0) {
419
3.05M
        char_c = 1;
420
3.05M
      } else if (char_c == 1) {
421
6.27k
        if (!fu_efi_lz77_decompressor_get_bits(helper, 4, &char_c, error))
422
0
          return FALSE;
423
6.27k
        char_c += 3;
424
6.27k
      } else if (char_c == 2) {
425
2.27k
        if (!fu_efi_lz77_decompressor_get_bits(helper,
426
2.27k
                       CBIT,
427
2.27k
                       &char_c,
428
2.27k
                       error))
429
0
          return FALSE;
430
2.27k
        char_c += 20;
431
2.27k
      }
432
3.05M
      if (char_c == 0) {
433
0
        g_set_error_literal(error,
434
0
                FWUPD_ERROR,
435
0
                FWUPD_ERROR_INVALID_DATA,
436
0
                "bad table");
437
0
        return FALSE;
438
0
      }
439
6.21M
      while ((gint16)(--char_c) >= 0 && index < NC)
440
3.15M
        helper->c_len[index++] = 0;
441
3.05M
    } else {
442
208k
      helper->c_len[index++] = (guint8)(char_c - 2);
443
208k
    }
444
3.26M
  }
445
66.1k
  memset(helper->c_len + index, 0, sizeof(helper->c_len) - index);
446
66.1k
  return fu_efi_lz77_decompressor_make_huffman_table(helper,
447
66.1k
                 NC,
448
66.1k
                 helper->c_len,
449
66.1k
                 12,
450
66.1k
                 helper->c_table,
451
66.1k
                 error);
452
66.1k
}
453
454
/* get one code. if it is at block boundary, generate huffman code mapping table for extra set,
455
 * code&len set and position set */
456
static gboolean
457
fu_efi_lz77_decompressor_decode_c(FuEfiLz77DecompressHelper *helper, guint16 *value, GError **error)
458
365M
{
459
365M
  guint16 index2;
460
365M
  guint32 mask;
461
462
365M
  if (helper->block_size == 0) {
463
    /* starting a new block, so read blocksize from block header */
464
114k
    if (!fu_efi_lz77_decompressor_get_bits(helper, 16, &helper->block_size, error))
465
0
      return FALSE;
466
467
    /* read in the extra set code length array */
468
114k
    if (!fu_efi_lz77_decompressor_read_pt_len(helper, NT, TBIT, 3, error)) {
469
375
      g_prefix_error_literal(
470
375
          error,
471
375
          "failed to generate the Huffman code mapping table for extra set: ");
472
375
      return FALSE;
473
375
    }
474
475
    /* read in and decode the char&len set code length array */
476
114k
    if (!fu_efi_lz77_decompressor_read_c_len(helper, error)) {
477
171
      g_prefix_error_literal(
478
171
          error,
479
171
          "failed to generate the code mapping table for char&len: ");
480
171
      return FALSE;
481
171
    }
482
483
    /* read in the position set code length array */
484
114k
    if (!fu_efi_lz77_decompressor_read_pt_len(helper,
485
114k
                MAXNP,
486
114k
                helper->p_bit,
487
114k
                (guint16)(-1),
488
114k
                error)) {
489
726
      g_prefix_error_literal(
490
726
          error,
491
726
          "failed to generate the Huffman code mapping table for the "
492
726
          "position set: ");
493
726
      return FALSE;
494
726
    }
495
114k
  }
496
497
  /* get one code according to code&set huffman table */
498
365M
  if (helper->block_size == 0) {
499
577
    g_set_error_literal(error,
500
577
            FWUPD_ERROR,
501
577
            FWUPD_ERROR_INVALID_FILE,
502
577
            "no blocks remained");
503
577
    return FALSE;
504
577
  }
505
365M
  helper->block_size--;
506
365M
  index2 = helper->c_table[helper->bit_buf >> (BITBUFSIZ - 12)];
507
365M
  if (index2 >= NC) {
508
493k
    mask = 1U << (BITBUFSIZ - 1 - 12);
509
493k
    do {
510
493k
      if ((helper->bit_buf & mask) != 0) {
511
149k
        index2 = helper->right[index2];
512
343k
      } else {
513
343k
        index2 = helper->left[index2];
514
343k
      }
515
493k
      mask >>= 1;
516
493k
    } while (index2 >= NC);
517
493k
  }
518
519
  /* advance what we have read */
520
365M
  if (!fu_efi_lz77_decompressor_read_source_bits(helper, helper->c_len[index2], error))
521
0
    return FALSE;
522
365M
  *value = index2;
523
365M
  return TRUE;
524
365M
}
525
526
static gboolean
527
fu_efi_lz77_decompressor_internal(FuEfiLz77DecompressHelper *helper,
528
          FuEfiLz77DecompressorVersion version,
529
          GError **error)
530
3.23k
{
531
3.23k
  gsize dst_offset = 0;
532
533
  /* position set code length array size in the block header */
534
3.23k
  switch (version) {
535
1.83k
  case FU_EFI_LZ77_DECOMPRESSOR_VERSION_LEGACY:
536
1.83k
    helper->p_bit = 4;
537
1.83k
    break;
538
1.40k
  case FU_EFI_LZ77_DECOMPRESSOR_VERSION_TIANO:
539
1.40k
    helper->p_bit = 5;
540
1.40k
    break;
541
0
  default:
542
0
    g_set_error(error,
543
0
          FWUPD_ERROR,
544
0
          FWUPD_ERROR_INVALID_DATA,
545
0
          "unknown version 0x%x",
546
0
          version);
547
0
    return FALSE;
548
3.23k
  }
549
550
  /* fill the first BITBUFSIZ bits */
551
3.23k
  if (!fu_efi_lz77_decompressor_read_source_bits(helper, BITBUFSIZ, error))
552
0
    return FALSE;
553
554
  /* decode each char */
555
365M
  while (dst_offset < helper->dst->len) {
556
365M
    guint16 char_c = 0;
557
558
    /* get one code */
559
365M
    if (!fu_efi_lz77_decompressor_decode_c(helper, &char_c, error))
560
1.84k
      return FALSE;
561
365M
    if (char_c < 256) {
562
      /* write original character into dst_buf */
563
358M
      helper->dst->data[dst_offset++] = (guint8)char_c;
564
358M
    } else {
565
6.68M
      guint16 bytes_remaining;
566
6.68M
      guint32 data_offset;
567
6.68M
      guint32 tmp = 0;
568
569
      /* process a pointer, so get string length */
570
6.68M
      bytes_remaining = (guint16)(char_c - (0x00000100U - THRESHOLD));
571
6.68M
      if (!fu_efi_lz77_decompressor_decode_p(helper, &tmp, error))
572
0
        return FALSE;
573
6.68M
      data_offset = dst_offset - tmp - 1;
574
575
      /* write bytes_remaining of bytes into dst_buf */
576
6.68M
      bytes_remaining--;
577
1.06G
      while ((gint16)(bytes_remaining) >= 0) {
578
1.05G
        if (dst_offset >= helper->dst->len) {
579
73
          g_set_error_literal(error,
580
73
                  FWUPD_ERROR,
581
73
                  FWUPD_ERROR_INVALID_DATA,
582
73
                  "bad pointer offset");
583
73
          return FALSE;
584
73
        }
585
1.05G
        if (data_offset >= helper->dst->len) {
586
94
          g_set_error_literal(error,
587
94
                  FWUPD_ERROR,
588
94
                  FWUPD_ERROR_INVALID_DATA,
589
94
                  "bad table");
590
94
          return FALSE;
591
94
        }
592
1.05G
        helper->dst->data[dst_offset++] = helper->dst->data[data_offset++];
593
1.05G
        bytes_remaining--;
594
1.05G
      }
595
6.68M
    }
596
365M
  }
597
598
  /* success */
599
1.22k
  return TRUE;
600
3.23k
}
601
602
static gboolean
603
fu_efi_lz77_decompressor_parse(FuFirmware *firmware,
604
             GInputStream *stream,
605
             FuFirmwareParseFlags flags,
606
             GError **error)
607
2.16k
{
608
2.16k
  gsize streamsz = 0;
609
2.16k
  guint32 dst_bufsz;
610
2.16k
  guint32 src_bufsz;
611
2.16k
  g_autoptr(FuStructEfiLz77DecompressorHeader) st = NULL;
612
2.16k
  g_autoptr(GError) error_all = NULL;
613
2.16k
  g_autoptr(GByteArray) dst = g_byte_array_new();
614
2.16k
  FuEfiLz77DecompressorVersion decompressor_versions[] = {
615
2.16k
      FU_EFI_LZ77_DECOMPRESSOR_VERSION_LEGACY,
616
2.16k
      FU_EFI_LZ77_DECOMPRESSOR_VERSION_TIANO,
617
2.16k
  };
618
619
  /* parse header */
620
2.16k
  if (!fu_input_stream_size(stream, &streamsz, error))
621
0
    return FALSE;
622
2.16k
  st = fu_struct_efi_lz77_decompressor_header_parse_stream(stream, 0x0, error);
623
2.16k
  if (st == NULL)
624
5
    return FALSE;
625
2.16k
  src_bufsz = fu_struct_efi_lz77_decompressor_header_get_src_size(st);
626
2.16k
  if (streamsz < src_bufsz + st->buf->len) {
627
58
    g_set_error_literal(error,
628
58
            FWUPD_ERROR,
629
58
            FWUPD_ERROR_INVALID_DATA,
630
58
            "source buffer is truncated");
631
58
    return FALSE;
632
58
  }
633
2.10k
  dst_bufsz = fu_struct_efi_lz77_decompressor_header_get_dst_size(st);
634
2.10k
  if (dst_bufsz == 0) {
635
9
    g_set_error_literal(error,
636
9
            FWUPD_ERROR,
637
9
            FWUPD_ERROR_INVALID_DATA,
638
9
            "destination size is zero");
639
9
    return FALSE;
640
9
  }
641
2.09k
  if (dst_bufsz > fu_firmware_get_size_max(firmware)) {
642
267
    g_autofree gchar *sz_val = g_format_size(dst_bufsz);
643
267
    g_autofree gchar *sz_max = g_format_size(fu_firmware_get_size_max(firmware));
644
267
    g_set_error(error,
645
267
          FWUPD_ERROR,
646
267
          FWUPD_ERROR_INVALID_DATA,
647
267
          "destination size is too large (%s, limit %s)",
648
267
          sz_val,
649
267
          sz_max);
650
267
    return FALSE;
651
267
  }
652
1.83k
  fu_byte_array_set_size(dst, dst_bufsz, 0x0);
653
654
  /* try both position */
655
3.84k
  for (guint i = 0; i < G_N_ELEMENTS(decompressor_versions); i++) {
656
3.23k
    FuEfiLz77DecompressHelper helper = {
657
3.23k
        .dst = dst,
658
3.23k
        .stream = stream,
659
3.23k
    };
660
3.23k
    g_autoptr(GError) error_local = NULL;
661
662
3.23k
    if (!g_seekable_seek(G_SEEKABLE(stream), st->buf->len, G_SEEK_SET, NULL, error))
663
0
      return FALSE;
664
3.23k
    if (fu_efi_lz77_decompressor_internal(&helper,
665
3.23k
                  decompressor_versions[i],
666
3.23k
                  &error_local)) {
667
1.22k
      g_autoptr(GBytes) blob =
668
1.22k
          g_byte_array_free_to_bytes(g_steal_pointer(&dst)); /* nocheck:blocked */
669
1.22k
      if (!fu_firmware_set_stream(firmware, NULL, error))
670
0
        return FALSE;
671
1.22k
      fu_firmware_set_bytes(firmware, blob);
672
1.22k
      fu_firmware_set_version_raw(firmware, decompressor_versions[i]);
673
1.22k
      return TRUE;
674
1.22k
    }
675
2.01k
    if (error_all == NULL) {
676
1.40k
      g_propagate_prefixed_error(
677
1.40k
          &error_all,
678
1.40k
          g_steal_pointer(&error_local),
679
1.40k
          "failed to parse %s: ",
680
1.40k
          fu_efi_lz77_decompressor_version_to_string(decompressor_versions[i]));
681
1.40k
      continue;
682
1.40k
    }
683
607
    g_prefix_error(&error_all, /* nocheck:error */
684
607
             "failed to parse %s: %s: ",
685
607
             fu_efi_lz77_decompressor_version_to_string(decompressor_versions[i]),
686
607
             error_local->message);
687
607
  }
688
689
  /* success */
690
607
  g_propagate_error(error, g_steal_pointer(&error_all));
691
607
  return FALSE;
692
1.83k
}
693
694
static void
695
fu_efi_lz77_decompressor_init(FuEfiLz77Decompressor *self)
696
2.17k
{
697
2.17k
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_STORED_SIZE);
698
2.17k
  fu_firmware_set_size_max(FU_FIRMWARE(self), 64 * 1024 * 1024);
699
2.17k
}
700
701
static void
702
fu_efi_lz77_decompressor_class_init(FuEfiLz77DecompressorClass *klass)
703
2
{
704
2
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
705
2
  firmware_class->parse = fu_efi_lz77_decompressor_parse;
706
2
}
707
708
/**
709
 * fu_efi_lz77_decompressor_new:
710
 *
711
 * Creates a new #FuFirmware that can be used to decompress LZ77.
712
 *
713
 * Since: 2.0.0
714
 **/
715
FuFirmware *
716
fu_efi_lz77_decompressor_new(void)
717
2.17k
{
718
2.17k
  return FU_FIRMWARE(g_object_new(FU_TYPE_EFI_LZ77_DECOMPRESSOR, NULL));
719
2.17k
}