Coverage Report

Created: 2026-03-13 06:37

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.29k
G_DEFINE_TYPE(FuEfiLz77Decompressor, fu_efi_lz77_decompressor, FU_TYPE_FIRMWARE)
34
2.29k
35
412M
#define BITBUFSIZ 32
36
407M
#define MAXMATCH  256
37
413M
#define THRESHOLD 3
38
3.65M
#define CODE_BIT  16
39
40
/* c: char&len set; p: position set; t: extra set */
41
407M
#define NC  (0xff + MAXMATCH + 2 - THRESHOLD)
42
163k
#define CBIT  9
43
8.61M
#define MAXPBIT 5
44
116k
#define TBIT  5
45
8.61M
#define MAXNP ((1U << MAXPBIT) - 1)
46
3.65M
#define NT  (CODE_BIT + 3)
47
#if NT > MAXNP
48
#define NPT NT
49
#else
50
2.04M
#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
194k
{
75
194k
  g_return_if_fail(length % 2 == 0);
76
194k
  length /= sizeof(guint16);
77
223M
  for (gsize i = 0; i < length; i++)
78
222M
    buf[i] = value;
79
194k
}
80
81
static gboolean
82
fu_efi_lz77_decompressor_read_source_bits(FuEfiLz77DecompressHelper *helper,
83
            guint16 number_of_bits,
84
            GError **error)
85
411M
{
86
  /* left shift number_of_bits of bits in advance */
87
411M
  helper->bit_buf = (guint32)(((guint64)helper->bit_buf) << number_of_bits);
88
89
  /* copy data needed in bytes into sub_bit_buf */
90
413M
  while (number_of_bits > helper->bit_count) {
91
2.35M
    gssize rc;
92
2.35M
    guint8 sub_bit_buf = 0;
93
94
2.35M
    number_of_bits = (guint16)(number_of_bits - helper->bit_count);
95
2.35M
    helper->bit_buf |= (guint32)(((guint64)helper->sub_bit_buf) << number_of_bits);
96
97
    /* get 1 byte into sub_bit_buf */
98
2.35M
    rc = g_input_stream_read(helper->stream,
99
2.35M
           &sub_bit_buf,
100
2.35M
           sizeof(sub_bit_buf),
101
2.35M
           NULL,
102
2.35M
           error);
103
2.35M
    if (rc < 0)
104
0
      return FALSE;
105
2.35M
    if (rc == 0) {
106
      /* no more bits from the source, just pad zero bit */
107
1.60M
      helper->sub_bit_buf = 0;
108
1.60M
    } else {
109
750k
      helper->sub_bit_buf = sub_bit_buf;
110
750k
    }
111
2.35M
    helper->bit_count = 8;
112
2.35M
  }
113
114
  /* calculate additional bit count read to update bit_count */
115
411M
  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
411M
  helper->bit_buf |= helper->sub_bit_buf >> helper->bit_count;
119
411M
  return TRUE;
120
411M
}
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.18M
{
128
  /* pop number_of_bits of bits from left */
129
2.18M
  *value = (guint16)(helper->bit_buf >> (BITBUFSIZ - number_of_bits));
130
131
  /* fill up bit_buf from source */
132
2.18M
  return fu_efi_lz77_decompressor_read_source_bits(helper, number_of_bits, error);
133
2.18M
}
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
153k
{
145
153k
  guint16 count[17] = {0};
146
153k
  guint16 weight[17] = {0};
147
153k
  guint16 start[18] = {0};
148
153k
  guint16 *pointer;
149
153k
  guint16 index;
150
153k
  guint16 c_char;
151
153k
  guint16 ju_bits;
152
153k
  guint16 avail_symbols;
153
153k
  guint16 mask;
154
153k
  guint16 max_table_length;
155
156
  /* the maximum mapping table width supported by this internal working function is 16 */
157
153k
  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
38.2M
  for (index = 0; index < number_of_symbols; index++) {
163
38.1M
    if (code_length_array[index] > 16) {
164
75
      g_set_error_literal(error,
165
75
              FWUPD_ERROR,
166
75
              FWUPD_ERROR_INVALID_DATA,
167
75
              "bad table");
168
75
      return FALSE;
169
75
    }
170
38.1M
    count[code_length_array[index]]++;
171
38.1M
  }
172
173
2.61M
  for (index = 1; index <= 16; index++) {
174
2.45M
    guint16 word_of_start = start[index];
175
2.45M
    guint16 word_of_count = count[index];
176
2.45M
    start[index + 1] = (guint16)(word_of_start + (word_of_count << (16 - index)));
177
2.45M
  }
178
179
153k
  if (start[17] != 0) {
180
    /*(1U << 16)*/
181
883
    g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "bad table");
182
883
    return FALSE;
183
883
  }
184
185
152k
  ju_bits = (guint16)(16 - mapping_table_bits);
186
1.65M
  for (index = 1; index <= mapping_table_bits; index++) {
187
1.50M
    start[index] >>= ju_bits;
188
1.50M
    weight[index] = (guint16)(1U << (mapping_table_bits - index));
189
1.50M
  }
190
191
1.09M
  while (index <= 16) {
192
938k
    weight[index] = (guint16)(1U << (16 - index));
193
938k
    index++;
194
938k
  }
195
196
152k
  index = (guint16)(start[mapping_table_bits + 1] >> ju_bits);
197
152k
  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
152k
  avail_symbols = number_of_symbols;
207
152k
  mask = (guint16)(1U << (15 - mapping_table_bits));
208
152k
  max_table_length = (guint16)(1U << mapping_table_bits);
209
210
38.1M
  for (c_char = 0; c_char < number_of_symbols; c_char++) {
211
38.0M
    guint16 len = code_length_array[c_char];
212
38.0M
    guint16 next_code;
213
214
38.0M
    if (len == 0 || len >= 17)
215
37.8M
      continue;
216
217
175k
    next_code = (guint16)(start[len] + weight[len]);
218
175k
    if (len <= mapping_table_bits) {
219
8.53M
      for (index = start[len]; index < next_code; index++) {
220
8.36M
        if (index >= max_table_length) {
221
102
          g_set_error_literal(error,
222
102
                  FWUPD_ERROR,
223
102
                  FWUPD_ERROR_INVALID_DATA,
224
102
                  "bad table");
225
102
          return FALSE;
226
102
        }
227
8.36M
        table[index] = c_char;
228
8.36M
      }
229
230
174k
    } 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
565
          helper->right[avail_symbols] = helper->left[avail_symbols] =
238
565
              0;
239
565
          *pointer = avail_symbols++;
240
565
        }
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
175k
    start[len] = next_code;
253
175k
  }
254
  /* success */
255
152k
  return TRUE;
256
152k
}
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.01M
{
262
6.01M
  guint16 val;
263
264
6.01M
  val = helper->pt_table[helper->bit_buf >> (BITBUFSIZ - 8)];
265
6.01M
  if (val >= MAXNP) {
266
436k
    guint32 mask = 1U << (BITBUFSIZ - 1 - 8);
267
436k
    do {
268
436k
      if ((helper->bit_buf & mask) != 0) {
269
242k
        val = helper->right[val];
270
242k
      } else {
271
193k
        val = helper->left[val];
272
193k
      }
273
436k
      mask >>= 1;
274
436k
    } while (val >= MAXNP);
275
436k
  }
276
277
  /* advance what we have read */
278
6.01M
  if (!fu_efi_lz77_decompressor_read_source_bits(helper, helper->pt_len[val], error))
279
0
    return FALSE;
280
281
6.01M
  if (val > 1) {
282
1.51M
    guint16 char_c = 0;
283
1.51M
    if (!fu_efi_lz77_decompressor_get_bits(helper, (guint16)(val - 1), &char_c, error))
284
0
      return FALSE;
285
1.51M
    *value = (guint32)((1U << (val - 1)) + char_c);
286
1.51M
    return TRUE;
287
1.51M
  }
288
4.49M
  *value = val;
289
4.49M
  return TRUE;
290
6.01M
}
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
232k
{
300
232k
  guint16 number = 0;
301
232k
  guint16 index = 0;
302
303
  /* read Extra Set Code Length Array size */
304
232k
  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
232k
  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
232k
  if (number == 0) {
313
    /* this represents only Huffman code used */
314
148k
    guint16 char_c = 0;
315
148k
    if (!fu_efi_lz77_decompressor_get_bits(helper, number_of_bits, &char_c, error))
316
0
      return FALSE;
317
148k
    fu_efi_lz77_decompressor_memset16(&helper->pt_table[0],
318
148k
              sizeof(helper->pt_table),
319
148k
              (guint16)char_c);
320
148k
    memset(helper->pt_len, 0, number_of_symbols);
321
148k
    return TRUE;
322
148k
  }
323
324
193k
  while (index < number && index < NPT) {
325
110k
    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
110k
    if (char_c == 7) {
331
1.21k
      guint32 mask = 1U << (BITBUFSIZ - 1 - 3);
332
6.40k
      while (mask & helper->bit_buf) {
333
5.18k
        mask >>= 1;
334
5.18k
        char_c += 1;
335
5.18k
      }
336
1.21k
    }
337
338
110k
    if (!fu_efi_lz77_decompressor_read_source_bits(
339
110k
      helper,
340
110k
      (guint16)((char_c < 7) ? 3 : char_c - 3),
341
110k
      error))
342
0
      return FALSE;
343
344
110k
    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
110k
    if (index == special_symbol) {
350
535
      if (!fu_efi_lz77_decompressor_get_bits(helper, 2, &char_c, error))
351
0
        return FALSE;
352
535
      if (char_c == 0) {
353
241
        g_set_error_literal(error,
354
241
                FWUPD_ERROR,
355
241
                FWUPD_ERROR_INVALID_DATA,
356
241
                "bad table");
357
241
        return FALSE;
358
241
      }
359
899
      while ((gint16)(--char_c) >= 0 && index < NPT) {
360
605
        helper->pt_len[index++] = 0;
361
605
      }
362
294
    }
363
110k
  }
364
2.01M
  while (index < number_of_symbols && index < NPT)
365
1.92M
    helper->pt_len[index++] = 0;
366
82.8k
  return fu_efi_lz77_decompressor_make_huffman_table(helper,
367
82.8k
                 number_of_symbols,
368
82.8k
                 helper->pt_len,
369
82.8k
                 8,
370
82.8k
                 helper->pt_table,
371
82.8k
                 error);
372
83.0k
}
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
115k
{
379
115k
  guint16 number = 0;
380
115k
  guint16 index = 0;
381
382
115k
  if (!fu_efi_lz77_decompressor_get_bits(helper, CBIT, &number, error))
383
0
    return FALSE;
384
115k
  if (number == 0) {
385
    /* this represents only Huffman code used */
386
45.1k
    guint16 char_c = 0;
387
45.1k
    if (!fu_efi_lz77_decompressor_get_bits(helper, CBIT, &char_c, error))
388
0
      return FALSE;
389
45.1k
    memset(helper->c_len, 0, sizeof(helper->c_len));
390
45.1k
    fu_efi_lz77_decompressor_memset16(&helper->c_table[0],
391
45.1k
              sizeof(helper->c_table),
392
45.1k
              char_c);
393
45.1k
    return TRUE;
394
45.1k
  }
395
396
3.59M
  while (index < number && index < NC) {
397
3.52M
    guint16 char_c = helper->pt_table[helper->bit_buf >> (BITBUFSIZ - 8)];
398
3.52M
    if (char_c >= NT) {
399
6.93k
      guint32 mask = 1U << (BITBUFSIZ - 1 - 8);
400
7.27k
      do {
401
7.27k
        if (mask & helper->bit_buf) {
402
3.61k
          char_c = helper->right[char_c];
403
3.66k
        } else {
404
3.66k
          char_c = helper->left[char_c];
405
3.66k
        }
406
7.27k
        mask >>= 1;
407
408
7.27k
      } while (char_c >= NT);
409
6.93k
    }
410
411
    /* advance what we have read */
412
3.52M
    if (!fu_efi_lz77_decompressor_read_source_bits(helper,
413
3.52M
                     helper->pt_len[char_c],
414
3.52M
                     error))
415
0
      return FALSE;
416
417
3.52M
    if (char_c <= 2) {
418
3.31M
      if (char_c == 0) {
419
3.30M
        char_c = 1;
420
3.30M
      } else if (char_c == 1) {
421
6.69k
        if (!fu_efi_lz77_decompressor_get_bits(helper, 4, &char_c, error))
422
0
          return FALSE;
423
6.69k
        char_c += 3;
424
6.69k
      } else if (char_c == 2) {
425
2.25k
        if (!fu_efi_lz77_decompressor_get_bits(helper,
426
2.25k
                       CBIT,
427
2.25k
                       &char_c,
428
2.25k
                       error))
429
0
          return FALSE;
430
2.25k
        char_c += 20;
431
2.25k
      }
432
3.31M
      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.71M
      while ((gint16)(--char_c) >= 0 && index < NC)
440
3.40M
        helper->c_len[index++] = 0;
441
3.31M
    } else {
442
217k
      helper->c_len[index++] = (guint8)(char_c - 2);
443
217k
    }
444
3.52M
  }
445
70.7k
  memset(helper->c_len + index, 0, sizeof(helper->c_len) - index);
446
70.7k
  return fu_efi_lz77_decompressor_make_huffman_table(helper,
447
70.7k
                 NC,
448
70.7k
                 helper->c_len,
449
70.7k
                 12,
450
70.7k
                 helper->c_table,
451
70.7k
                 error);
452
70.7k
}
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
399M
{
459
399M
  guint16 index2;
460
399M
  guint32 mask;
461
462
399M
  if (helper->block_size == 0) {
463
    /* starting a new block, so read blocksize from block header */
464
116k
    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
116k
    if (!fu_efi_lz77_decompressor_read_pt_len(helper, NT, TBIT, 3, error)) {
469
411
      g_prefix_error_literal(
470
411
          error,
471
411
          "failed to generate the Huffman code mapping table for extra set: ");
472
411
      return FALSE;
473
411
    }
474
475
    /* read in and decode the char&len set code length array */
476
115k
    if (!fu_efi_lz77_decompressor_read_c_len(helper, error)) {
477
174
      g_prefix_error_literal(
478
174
          error,
479
174
          "failed to generate the code mapping table for char&len: ");
480
174
      return FALSE;
481
174
    }
482
483
    /* read in the position set code length array */
484
115k
    if (!fu_efi_lz77_decompressor_read_pt_len(helper,
485
115k
                MAXNP,
486
115k
                helper->p_bit,
487
115k
                (guint16)(-1),
488
115k
                error)) {
489
716
      g_prefix_error_literal(
490
716
          error,
491
716
          "failed to generate the Huffman code mapping table for the "
492
716
          "position set: ");
493
716
      return FALSE;
494
716
    }
495
115k
  }
496
497
  /* get one code according to code&set huffman table */
498
399M
  if (helper->block_size == 0) {
499
609
    g_set_error_literal(error,
500
609
            FWUPD_ERROR,
501
609
            FWUPD_ERROR_INVALID_FILE,
502
609
            "no blocks remained");
503
609
    return FALSE;
504
609
  }
505
399M
  helper->block_size--;
506
399M
  index2 = helper->c_table[helper->bit_buf >> (BITBUFSIZ - 12)];
507
399M
  if (index2 >= NC) {
508
415k
    mask = 1U << (BITBUFSIZ - 1 - 12);
509
415k
    do {
510
415k
      if ((helper->bit_buf & mask) != 0) {
511
109k
        index2 = helper->right[index2];
512
306k
      } else {
513
306k
        index2 = helper->left[index2];
514
306k
      }
515
415k
      mask >>= 1;
516
415k
    } while (index2 >= NC);
517
415k
  }
518
519
  /* advance what we have read */
520
399M
  if (!fu_efi_lz77_decompressor_read_source_bits(helper, helper->c_len[index2], error))
521
0
    return FALSE;
522
399M
  *value = index2;
523
399M
  return TRUE;
524
399M
}
525
526
static gboolean
527
fu_efi_lz77_decompressor_internal(FuEfiLz77DecompressHelper *helper,
528
          FuEfiLz77DecompressorVersion version,
529
          GError **error)
530
3.35k
{
531
3.35k
  gsize dst_offset = 0;
532
533
  /* position set code length array size in the block header */
534
3.35k
  switch (version) {
535
1.91k
  case FU_EFI_LZ77_DECOMPRESSOR_VERSION_LEGACY:
536
1.91k
    helper->p_bit = 4;
537
1.91k
    break;
538
1.44k
  case FU_EFI_LZ77_DECOMPRESSOR_VERSION_TIANO:
539
1.44k
    helper->p_bit = 5;
540
1.44k
    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.35k
  }
549
550
  /* fill the first BITBUFSIZ bits */
551
3.35k
  if (!fu_efi_lz77_decompressor_read_source_bits(helper, BITBUFSIZ, error))
552
0
    return FALSE;
553
554
  /* decode each char */
555
399M
  while (dst_offset < helper->dst->len) {
556
399M
    guint16 char_c = 0;
557
558
    /* get one code */
559
399M
    if (!fu_efi_lz77_decompressor_decode_c(helper, &char_c, error))
560
1.91k
      return FALSE;
561
399M
    if (char_c < 256) {
562
      /* write original character into dst_buf */
563
393M
      helper->dst->data[dst_offset++] = (guint8)char_c;
564
393M
    } else {
565
6.01M
      guint16 bytes_remaining;
566
6.01M
      guint32 data_offset;
567
6.01M
      guint32 tmp = 0;
568
569
      /* process a pointer, so get string length */
570
6.01M
      bytes_remaining = (guint16)(char_c - (0x00000100U - THRESHOLD));
571
6.01M
      if (!fu_efi_lz77_decompressor_decode_p(helper, &tmp, error))
572
0
        return FALSE;
573
6.01M
      data_offset = dst_offset - tmp - 1;
574
575
      /* write bytes_remaining of bytes into dst_buf */
576
6.01M
      bytes_remaining--;
577
962M
      while ((gint16)(bytes_remaining) >= 0) {
578
956M
        if (dst_offset >= helper->dst->len) {
579
75
          g_set_error_literal(error,
580
75
                  FWUPD_ERROR,
581
75
                  FWUPD_ERROR_INVALID_DATA,
582
75
                  "bad pointer offset");
583
75
          return FALSE;
584
75
        }
585
956M
        if (data_offset >= helper->dst->len) {
586
124
          g_set_error_literal(error,
587
124
                  FWUPD_ERROR,
588
124
                  FWUPD_ERROR_INVALID_DATA,
589
124
                  "bad table");
590
124
          return FALSE;
591
124
        }
592
956M
        helper->dst->data[dst_offset++] = helper->dst->data[data_offset++];
593
956M
        bytes_remaining--;
594
956M
      }
595
6.01M
    }
596
399M
  }
597
598
  /* success */
599
1.25k
  return TRUE;
600
3.35k
}
601
602
static gboolean
603
fu_efi_lz77_decompressor_parse(FuFirmware *firmware,
604
             GInputStream *stream,
605
             FuFirmwareParseFlags flags,
606
             GError **error)
607
2.29k
{
608
2.29k
  gsize streamsz = 0;
609
2.29k
  guint32 dst_bufsz;
610
2.29k
  guint32 src_bufsz;
611
2.29k
  g_autoptr(FuStructEfiLz77DecompressorHeader) st = NULL;
612
2.29k
  g_autoptr(GError) error_all = NULL;
613
2.29k
  g_autoptr(GByteArray) dst = g_byte_array_new();
614
2.29k
  FuEfiLz77DecompressorVersion decompressor_versions[] = {
615
2.29k
      FU_EFI_LZ77_DECOMPRESSOR_VERSION_LEGACY,
616
2.29k
      FU_EFI_LZ77_DECOMPRESSOR_VERSION_TIANO,
617
2.29k
  };
618
619
  /* parse header */
620
2.29k
  if (!fu_input_stream_size(stream, &streamsz, error))
621
0
    return FALSE;
622
2.29k
  st = fu_struct_efi_lz77_decompressor_header_parse_stream(stream, 0x0, error);
623
2.29k
  if (st == NULL)
624
6
    return FALSE;
625
2.28k
  src_bufsz = fu_struct_efi_lz77_decompressor_header_get_src_size(st);
626
2.28k
  if (streamsz < src_bufsz + st->buf->len) {
627
67
    g_set_error_literal(error,
628
67
            FWUPD_ERROR,
629
67
            FWUPD_ERROR_INVALID_DATA,
630
67
            "source buffer is truncated");
631
67
    return FALSE;
632
67
  }
633
2.22k
  dst_bufsz = fu_struct_efi_lz77_decompressor_header_get_dst_size(st);
634
2.22k
  if (dst_bufsz == 0) {
635
13
    g_set_error_literal(error,
636
13
            FWUPD_ERROR,
637
13
            FWUPD_ERROR_INVALID_DATA,
638
13
            "destination size is zero");
639
13
    return FALSE;
640
13
  }
641
2.20k
  if (dst_bufsz > fu_firmware_get_size_max(firmware)) {
642
297
    g_autofree gchar *sz_val = g_format_size(dst_bufsz);
643
297
    g_autofree gchar *sz_max = g_format_size(fu_firmware_get_size_max(firmware));
644
297
    g_set_error(error,
645
297
          FWUPD_ERROR,
646
297
          FWUPD_ERROR_INVALID_DATA,
647
297
          "destination size is too large (%s, limit %s)",
648
297
          sz_val,
649
297
          sz_max);
650
297
    return FALSE;
651
297
  }
652
1.91k
  fu_byte_array_set_size(dst, dst_bufsz, 0x0);
653
654
  /* try both position */
655
4.01k
  for (guint i = 0; i < G_N_ELEMENTS(decompressor_versions); i++) {
656
3.35k
    FuEfiLz77DecompressHelper helper = {
657
3.35k
        .dst = dst,
658
3.35k
        .stream = stream,
659
3.35k
    };
660
3.35k
    g_autoptr(GError) error_local = NULL;
661
662
3.35k
    if (!g_seekable_seek(G_SEEKABLE(stream), st->buf->len, G_SEEK_SET, NULL, error))
663
0
      return FALSE;
664
3.35k
    if (fu_efi_lz77_decompressor_internal(&helper,
665
3.35k
                  decompressor_versions[i],
666
3.35k
                  &error_local)) {
667
1.25k
      g_autoptr(GBytes) blob =
668
1.25k
          g_byte_array_free_to_bytes(g_steal_pointer(&dst)); /* nocheck:blocked */
669
1.25k
      if (!fu_firmware_set_stream(firmware, NULL, error))
670
0
        return FALSE;
671
1.25k
      fu_firmware_set_bytes(firmware, blob);
672
1.25k
      fu_firmware_set_version_raw(firmware, decompressor_versions[i]);
673
1.25k
      return TRUE;
674
1.25k
    }
675
2.10k
    if (error_all == NULL) {
676
1.44k
      g_propagate_prefixed_error(
677
1.44k
          &error_all,
678
1.44k
          g_steal_pointer(&error_local),
679
1.44k
          "failed to parse %s: ",
680
1.44k
          fu_efi_lz77_decompressor_version_to_string(decompressor_versions[i]));
681
1.44k
      continue;
682
1.44k
    }
683
660
    g_prefix_error(&error_all, /* nocheck:error */
684
660
             "failed to parse %s: %s: ",
685
660
             fu_efi_lz77_decompressor_version_to_string(decompressor_versions[i]),
686
660
             error_local->message);
687
660
  }
688
689
  /* success */
690
660
  g_propagate_error(error, g_steal_pointer(&error_all));
691
660
  return FALSE;
692
1.91k
}
693
694
static void
695
fu_efi_lz77_decompressor_init(FuEfiLz77Decompressor *self)
696
2.29k
{
697
2.29k
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_STORED_SIZE);
698
2.29k
  fu_firmware_set_size_max(FU_FIRMWARE(self), 64 * 1024 * 1024);
699
2.29k
}
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.29k
{
718
2.29k
  return FU_FIRMWARE(g_object_new(FU_TYPE_EFI_LZ77_DECOMPRESSOR, NULL));
719
2.29k
}