Coverage Report

Created: 2026-06-15 06:54

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