/src/fwupd/libfwupdplugin/fu-efi-lz77-decompressor.c
Line | Count | Source (jump to first uncovered line) |
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 | | G_DEFINE_TYPE(FuEfiLz77Decompressor, fu_efi_lz77_decompressor, FU_TYPE_FIRMWARE) |
34 | | |
35 | 571M | #define BITBUFSIZ 32 |
36 | 565M | #define MAXMATCH 256 |
37 | 573M | #define THRESHOLD 3 |
38 | 4.47M | #define CODE_BIT 16 |
39 | | |
40 | | /* c: char&len set; p: position set; t: extra set */ |
41 | 565M | #define NC (0xff + MAXMATCH + 2 - THRESHOLD) |
42 | 193k | #define CBIT 9 |
43 | 11.7M | #define MAXPBIT 5 |
44 | 138k | #define TBIT 5 |
45 | 11.7M | #define MAXNP ((1U << MAXPBIT) - 1) |
46 | 4.47M | #define NT (CODE_BIT + 3) |
47 | | #if NT > MAXNP |
48 | | #define NPT NT |
49 | | #else |
50 | 2.60M | #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 | 222k | { |
75 | 222k | g_return_if_fail(length % 2 == 0); |
76 | 222k | length /= sizeof(guint16); |
77 | 250M | for (gsize i = 0; i < length; i++) |
78 | 250M | buf[i] = value; |
79 | 222k | } |
80 | | |
81 | | static gboolean |
82 | | fu_efi_lz77_decompressor_read_source_bits(FuEfiLz77DecompressHelper *helper, |
83 | | guint16 number_of_bits, |
84 | | GError **error) |
85 | 538M | { |
86 | | /* left shift number_of_bits of bits in advance */ |
87 | 538M | helper->bit_buf = (guint32)(((guint64)helper->bit_buf) << number_of_bits); |
88 | | |
89 | | /* copy data needed in bytes into sub_bit_buf */ |
90 | 543M | while (number_of_bits > helper->bit_count) { |
91 | 5.00M | gssize rc; |
92 | 5.00M | guint8 sub_bit_buf = 0; |
93 | | |
94 | 5.00M | number_of_bits = (guint16)(number_of_bits - helper->bit_count); |
95 | 5.00M | helper->bit_buf |= (guint32)(((guint64)helper->sub_bit_buf) << number_of_bits); |
96 | | |
97 | | /* get 1 byte into sub_bit_buf */ |
98 | 5.00M | rc = g_input_stream_read(helper->stream, |
99 | 5.00M | &sub_bit_buf, |
100 | 5.00M | sizeof(sub_bit_buf), |
101 | 5.00M | NULL, |
102 | 5.00M | error); |
103 | 5.00M | if (rc < 0) |
104 | 0 | return FALSE; |
105 | 5.00M | if (rc == 0) { |
106 | | /* no more bits from the source, just pad zero bit */ |
107 | 3.47M | helper->sub_bit_buf = 0; |
108 | 3.47M | } else { |
109 | 1.52M | helper->sub_bit_buf = sub_bit_buf; |
110 | 1.52M | } |
111 | 5.00M | helper->bit_count = 8; |
112 | 5.00M | } |
113 | | |
114 | | /* calculate additional bit count read to update bit_count */ |
115 | 538M | 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 | 538M | helper->bit_buf |= helper->sub_bit_buf >> helper->bit_count; |
119 | 538M | return TRUE; |
120 | 538M | } |
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.58M | { |
128 | | /* pop number_of_bits of bits from left */ |
129 | 2.58M | *value = (guint16)(helper->bit_buf >> (BITBUFSIZ - number_of_bits)); |
130 | | |
131 | | /* fill up bit_buf from source */ |
132 | 2.58M | return fu_efi_lz77_decompressor_read_source_bits(helper, number_of_bits, error); |
133 | 2.58M | } |
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 | 192k | { |
145 | 192k | guint16 count[17] = {0}; |
146 | 192k | guint16 weight[17] = {0}; |
147 | 192k | guint16 start[18] = {0}; |
148 | 192k | guint16 *pointer; |
149 | 192k | guint16 index; |
150 | 192k | guint16 c_char; |
151 | 192k | guint16 ju_bits; |
152 | 192k | guint16 avail_symbols; |
153 | 192k | guint16 mask; |
154 | 192k | guint16 max_table_length; |
155 | | |
156 | | /* the maximum mapping table width supported by this internal working function is 16 */ |
157 | 192k | 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 | 47.5M | for (index = 0; index < number_of_symbols; index++) { |
163 | 47.3M | if (code_length_array[index] > 16) { |
164 | 165 | g_set_error_literal(error, |
165 | 165 | FWUPD_ERROR, |
166 | 165 | FWUPD_ERROR_INVALID_DATA, |
167 | 165 | "bad table"); |
168 | 165 | return FALSE; |
169 | 165 | } |
170 | 47.3M | count[code_length_array[index]]++; |
171 | 47.3M | } |
172 | | |
173 | 3.27M | for (index = 1; index <= 16; index++) { |
174 | 3.08M | guint16 WordOfStart = start[index]; |
175 | 3.08M | guint16 WordOfCount = count[index]; |
176 | 3.08M | start[index + 1] = (guint16)(WordOfStart + (WordOfCount << (16 - index))); |
177 | 3.08M | } |
178 | | |
179 | 192k | if (start[17] != 0) { |
180 | | /*(1U << 16)*/ |
181 | 1.50k | g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "bad table"); |
182 | 1.50k | return FALSE; |
183 | 1.50k | } |
184 | | |
185 | 191k | ju_bits = (guint16)(16 - mapping_table_bits); |
186 | 2.07M | for (index = 1; index <= mapping_table_bits; index++) { |
187 | 1.87M | start[index] >>= ju_bits; |
188 | 1.87M | weight[index] = (guint16)(1U << (mapping_table_bits - index)); |
189 | 1.87M | } |
190 | | |
191 | 1.37M | while (index <= 16) { |
192 | 1.17M | weight[index] = (guint16)(1U << (16 - index)); |
193 | 1.17M | index++; |
194 | 1.17M | } |
195 | | |
196 | 191k | index = (guint16)(start[mapping_table_bits + 1] >> ju_bits); |
197 | 191k | if (index != 0) { |
198 | 425 | guint16 index3 = (guint16)(1U << mapping_table_bits); |
199 | 425 | if (index < index3) { |
200 | 425 | fu_efi_lz77_decompressor_memset16(table + index, |
201 | 425 | (index3 - index) * sizeof(*table), |
202 | 425 | 0); |
203 | 425 | } |
204 | 425 | } |
205 | | |
206 | 191k | avail_symbols = number_of_symbols; |
207 | 191k | mask = (guint16)(1U << (15 - mapping_table_bits)); |
208 | 191k | max_table_length = (guint16)(1U << mapping_table_bits); |
209 | | |
210 | 47.3M | for (c_char = 0; c_char < number_of_symbols; c_char++) { |
211 | 47.1M | guint16 len = code_length_array[c_char]; |
212 | 47.1M | guint16 next_code; |
213 | | |
214 | 47.1M | if (len == 0 || len >= 17) |
215 | 46.9M | continue; |
216 | | |
217 | 207k | next_code = (guint16)(start[len] + weight[len]); |
218 | 207k | if (len <= mapping_table_bits) { |
219 | 9.21M | for (index = start[len]; index < next_code; index++) { |
220 | 9.01M | if (index >= max_table_length) { |
221 | 106 | g_set_error_literal(error, |
222 | 106 | FWUPD_ERROR, |
223 | 106 | FWUPD_ERROR_INVALID_DATA, |
224 | 106 | "bad table"); |
225 | 106 | return FALSE; |
226 | 106 | } |
227 | 9.01M | table[index] = c_char; |
228 | 9.01M | } |
229 | | |
230 | 198k | } else { |
231 | 9.81k | guint16 index3 = start[len]; |
232 | 9.81k | pointer = &table[index3 >> ju_bits]; |
233 | 9.81k | index = (guint16)(len - mapping_table_bits); |
234 | | |
235 | 43.1k | while (index != 0) { |
236 | 33.3k | if (*pointer == 0 && avail_symbols < (2 * NC - 1)) { |
237 | 8.42k | helper->right[avail_symbols] = helper->left[avail_symbols] = |
238 | 8.42k | 0; |
239 | 8.42k | *pointer = avail_symbols++; |
240 | 8.42k | } |
241 | 33.3k | if (*pointer < (2 * NC - 1)) { |
242 | 33.3k | if ((index3 & mask) != 0) |
243 | 16.8k | pointer = &helper->right[*pointer]; |
244 | 16.4k | else |
245 | 16.4k | pointer = &helper->left[*pointer]; |
246 | 33.3k | } |
247 | 33.3k | index3 <<= 1; |
248 | 33.3k | index--; |
249 | 33.3k | } |
250 | 9.81k | *pointer = c_char; |
251 | 9.81k | } |
252 | 207k | start[len] = next_code; |
253 | 207k | } |
254 | | /* success */ |
255 | 191k | return TRUE; |
256 | 191k | } |
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 | 8.22M | { |
262 | 8.22M | guint16 val; |
263 | | |
264 | 8.22M | val = helper->pt_table[helper->bit_buf >> (BITBUFSIZ - 8)]; |
265 | 8.22M | if (val >= MAXNP) { |
266 | 811k | guint32 mask = 1U << (BITBUFSIZ - 1 - 8); |
267 | 817k | do { |
268 | 817k | if ((helper->bit_buf & mask) != 0) { |
269 | 422k | val = helper->right[val]; |
270 | 422k | } else { |
271 | 394k | val = helper->left[val]; |
272 | 394k | } |
273 | 817k | mask >>= 1; |
274 | 817k | } while (val >= MAXNP); |
275 | 811k | } |
276 | | |
277 | | /* advance what we have read */ |
278 | 8.22M | if (!fu_efi_lz77_decompressor_read_source_bits(helper, helper->pt_len[val], error)) |
279 | 0 | return FALSE; |
280 | | |
281 | 8.22M | if (val > 1) { |
282 | 1.75M | guint16 char_c = 0; |
283 | 1.75M | if (!fu_efi_lz77_decompressor_get_bits(helper, (guint16)(val - 1), &char_c, error)) |
284 | 0 | return FALSE; |
285 | 1.75M | *value = (guint32)((1U << (val - 1)) + char_c); |
286 | 1.75M | return TRUE; |
287 | 1.75M | } |
288 | 6.46M | *value = val; |
289 | 6.46M | return TRUE; |
290 | 8.22M | } |
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 | 276k | { |
300 | 276k | guint16 number = 0; |
301 | 276k | guint16 index = 0; |
302 | | |
303 | | /* read Extra Set Code Length Array size */ |
304 | 276k | 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 | 276k | 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 | 276k | if (number == 0) { |
313 | | /* this represents only Huffman code used */ |
314 | 171k | guint16 char_c = 0; |
315 | 171k | if (!fu_efi_lz77_decompressor_get_bits(helper, number_of_bits, &char_c, error)) |
316 | 0 | return FALSE; |
317 | 171k | fu_efi_lz77_decompressor_memset16(&helper->pt_table[0], |
318 | 171k | sizeof(helper->pt_table), |
319 | 171k | (guint16)char_c); |
320 | 171k | memset(helper->pt_len, 0, number_of_symbols); |
321 | 171k | return TRUE; |
322 | 171k | } |
323 | | |
324 | 375k | while (index < number && index < NPT) { |
325 | 270k | 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 | 270k | if (char_c == 7) { |
331 | 4.36k | guint32 mask = 1U << (BITBUFSIZ - 1 - 3); |
332 | 22.3k | while (mask & helper->bit_buf) { |
333 | 17.9k | mask >>= 1; |
334 | 17.9k | char_c += 1; |
335 | 17.9k | } |
336 | 4.36k | } |
337 | | |
338 | 270k | if (!fu_efi_lz77_decompressor_read_source_bits( |
339 | 270k | helper, |
340 | 270k | (guint16)((char_c < 7) ? 3 : char_c - 3), |
341 | 270k | error)) |
342 | 0 | return FALSE; |
343 | | |
344 | 270k | 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 | 270k | if (index == special_symbol) { |
350 | 5.10k | if (!fu_efi_lz77_decompressor_get_bits(helper, 2, &char_c, error)) |
351 | 0 | return FALSE; |
352 | 6.89k | while ((gint16)(--char_c) >= 0 && index < NPT) { |
353 | 1.79k | helper->pt_len[index++] = 0; |
354 | 1.79k | } |
355 | 5.10k | } |
356 | 270k | } |
357 | 2.43M | while (index < number_of_symbols && index < NPT) |
358 | 2.32M | helper->pt_len[index++] = 0; |
359 | 105k | return fu_efi_lz77_decompressor_make_huffman_table(helper, |
360 | 105k | number_of_symbols, |
361 | 105k | helper->pt_len, |
362 | 105k | 8, |
363 | 105k | helper->pt_table, |
364 | 105k | error); |
365 | 105k | } |
366 | | |
367 | | /* read in and decode the Char&len Set Code Length Array, then generate the Huffman Code mapping |
368 | | * table for the char&len set */ |
369 | | static gboolean |
370 | | fu_efi_lz77_decompressor_read_c_len(FuEfiLz77DecompressHelper *helper, GError **error) |
371 | 138k | { |
372 | 138k | guint16 number = 0; |
373 | 138k | guint16 index = 0; |
374 | | |
375 | 138k | if (!fu_efi_lz77_decompressor_get_bits(helper, CBIT, &number, error)) |
376 | 0 | return FALSE; |
377 | 138k | if (number == 0) { |
378 | | /* this represents only Huffman code used */ |
379 | 50.3k | guint16 char_c = 0; |
380 | 50.3k | if (!fu_efi_lz77_decompressor_get_bits(helper, CBIT, &char_c, error)) |
381 | 0 | return FALSE; |
382 | 50.3k | memset(helper->c_len, 0, sizeof(helper->c_len)); |
383 | 50.3k | fu_efi_lz77_decompressor_memset16(&helper->c_table[0], |
384 | 50.3k | sizeof(helper->c_table), |
385 | 50.3k | char_c); |
386 | 50.3k | return TRUE; |
387 | 50.3k | } |
388 | | |
389 | 4.39M | while (index < number && index < NC) { |
390 | 4.30M | guint16 char_c = helper->pt_table[helper->bit_buf >> (BITBUFSIZ - 8)]; |
391 | 4.30M | if (char_c >= NT) { |
392 | 30.6k | guint32 mask = 1U << (BITBUFSIZ - 1 - 8); |
393 | 31.4k | do { |
394 | 31.4k | if (mask & helper->bit_buf) { |
395 | 12.6k | char_c = helper->right[char_c]; |
396 | 18.8k | } else { |
397 | 18.8k | char_c = helper->left[char_c]; |
398 | 18.8k | } |
399 | 31.4k | mask >>= 1; |
400 | | |
401 | 31.4k | } while (char_c >= NT); |
402 | 30.6k | } |
403 | | |
404 | | /* advance what we have read */ |
405 | 4.30M | if (!fu_efi_lz77_decompressor_read_source_bits(helper, |
406 | 4.30M | helper->pt_len[char_c], |
407 | 4.30M | error)) |
408 | 0 | return FALSE; |
409 | | |
410 | 4.30M | if (char_c <= 2) { |
411 | 4.06M | if (char_c == 0) { |
412 | 4.01M | char_c = 1; |
413 | 4.01M | } else if (char_c == 1) { |
414 | 50.7k | if (!fu_efi_lz77_decompressor_get_bits(helper, 4, &char_c, error)) |
415 | 0 | return FALSE; |
416 | 50.7k | char_c += 3; |
417 | 50.7k | } else if (char_c == 2) { |
418 | 5.34k | if (!fu_efi_lz77_decompressor_get_bits(helper, |
419 | 5.34k | CBIT, |
420 | 5.34k | &char_c, |
421 | 5.34k | error)) |
422 | 0 | return FALSE; |
423 | 5.34k | char_c += 20; |
424 | 5.34k | } |
425 | 8.53M | while ((gint16)(--char_c) >= 0 && index < NC) |
426 | 4.46M | helper->c_len[index++] = 0; |
427 | 4.06M | } else { |
428 | 236k | helper->c_len[index++] = (guint8)(char_c - 2); |
429 | 236k | } |
430 | 4.30M | } |
431 | 87.7k | memset(helper->c_len + index, 0, sizeof(helper->c_len) - index); |
432 | 87.7k | return fu_efi_lz77_decompressor_make_huffman_table(helper, |
433 | 87.7k | NC, |
434 | 87.7k | helper->c_len, |
435 | 87.7k | 12, |
436 | 87.7k | helper->c_table, |
437 | 87.7k | error); |
438 | 87.7k | } |
439 | | |
440 | | /* get one code. if it is at block boundary, generate huffman code mapping table for extra set, |
441 | | * code&len set and position set */ |
442 | | static gboolean |
443 | | fu_efi_lz77_decompressor_decode_c(FuEfiLz77DecompressHelper *helper, guint16 *value, GError **error) |
444 | 522M | { |
445 | 522M | guint16 index2; |
446 | 522M | guint32 mask; |
447 | | |
448 | 522M | if (helper->block_size == 0) { |
449 | | /* starting a new block, so read blocksize from block header */ |
450 | 138k | if (!fu_efi_lz77_decompressor_get_bits(helper, 16, &helper->block_size, error)) |
451 | 0 | return FALSE; |
452 | | |
453 | | /* read in the extra set code length array */ |
454 | 138k | if (!fu_efi_lz77_decompressor_read_pt_len(helper, NT, TBIT, 3, error)) { |
455 | 658 | g_prefix_error( |
456 | 658 | error, |
457 | 658 | "failed to generate the Huffman code mapping table for extra set: "); |
458 | 658 | return FALSE; |
459 | 658 | } |
460 | | |
461 | | /* read in and decode the char&len set code length array */ |
462 | 138k | if (!fu_efi_lz77_decompressor_read_c_len(helper, error)) { |
463 | 217 | g_prefix_error(error, |
464 | 217 | "failed to generate the code mapping table for char&len: "); |
465 | 217 | return FALSE; |
466 | 217 | } |
467 | | |
468 | | /* read in the position set code length array */ |
469 | 137k | if (!fu_efi_lz77_decompressor_read_pt_len(helper, |
470 | 137k | MAXNP, |
471 | 137k | helper->p_bit, |
472 | 137k | (guint16)(-1), |
473 | 137k | error)) { |
474 | 901 | g_prefix_error(error, |
475 | 901 | "failed to generate the Huffman code mapping table for the " |
476 | 901 | "position set: "); |
477 | 901 | return FALSE; |
478 | 901 | } |
479 | 137k | } |
480 | | |
481 | | /* get one code according to code&set huffman table */ |
482 | 522M | if (helper->block_size == 0) { |
483 | 1.55k | g_set_error_literal(error, |
484 | 1.55k | FWUPD_ERROR, |
485 | 1.55k | FWUPD_ERROR_INVALID_FILE, |
486 | 1.55k | "no blocks remained"); |
487 | 1.55k | return FALSE; |
488 | 1.55k | } |
489 | 522M | helper->block_size--; |
490 | 522M | index2 = helper->c_table[helper->bit_buf >> (BITBUFSIZ - 12)]; |
491 | 522M | if (index2 >= NC) { |
492 | 32.6M | mask = 1U << (BITBUFSIZ - 1 - 12); |
493 | 34.0M | do { |
494 | 34.0M | if ((helper->bit_buf & mask) != 0) { |
495 | 4.20M | index2 = helper->right[index2]; |
496 | 29.8M | } else { |
497 | 29.8M | index2 = helper->left[index2]; |
498 | 29.8M | } |
499 | 34.0M | mask >>= 1; |
500 | 34.0M | } while (index2 >= NC); |
501 | 32.6M | } |
502 | | |
503 | | /* advance what we have read */ |
504 | 522M | if (!fu_efi_lz77_decompressor_read_source_bits(helper, helper->c_len[index2], error)) |
505 | 0 | return FALSE; |
506 | 522M | *value = index2; |
507 | 522M | return TRUE; |
508 | 522M | } |
509 | | |
510 | | static gboolean |
511 | | fu_efi_lz77_decompressor_internal(FuEfiLz77DecompressHelper *helper, |
512 | | FuEfiLz77DecompressorVersion version, |
513 | | GError **error) |
514 | 5.23k | { |
515 | 5.23k | gsize dst_offset = 0; |
516 | | |
517 | | /* position set code length array size in the block header */ |
518 | 5.23k | switch (version) { |
519 | 2.83k | case FU_EFI_LZ77_DECOMPRESSOR_VERSION_LEGACY: |
520 | 2.83k | helper->p_bit = 4; |
521 | 2.83k | break; |
522 | 2.39k | case FU_EFI_LZ77_DECOMPRESSOR_VERSION_TIANO: |
523 | 2.39k | helper->p_bit = 5; |
524 | 2.39k | break; |
525 | 0 | default: |
526 | 0 | g_set_error(error, |
527 | 0 | FWUPD_ERROR, |
528 | 0 | FWUPD_ERROR_INVALID_DATA, |
529 | 0 | "unknown version 0x%x", |
530 | 0 | version); |
531 | 0 | return FALSE; |
532 | 5.23k | } |
533 | | |
534 | | /* fill the first BITBUFSIZ bits */ |
535 | 5.23k | if (!fu_efi_lz77_decompressor_read_source_bits(helper, BITBUFSIZ, error)) |
536 | 0 | return FALSE; |
537 | | |
538 | | /* decode each char */ |
539 | 522M | while (dst_offset < helper->dst->len) { |
540 | 522M | guint16 char_c = 0; |
541 | | |
542 | | /* get one code */ |
543 | 522M | if (!fu_efi_lz77_decompressor_decode_c(helper, &char_c, error)) |
544 | 3.32k | return FALSE; |
545 | 522M | if (char_c < 256) { |
546 | | /* write original character into dst_buf */ |
547 | 514M | helper->dst->data[dst_offset++] = (guint8)char_c; |
548 | 514M | } else { |
549 | 8.22M | guint16 bytes_remaining; |
550 | 8.22M | guint32 data_offset; |
551 | 8.22M | guint32 tmp = 0; |
552 | | |
553 | | /* process a pointer, so get string length */ |
554 | 8.22M | bytes_remaining = (guint16)(char_c - (0x00000100U - THRESHOLD)); |
555 | 8.22M | if (!fu_efi_lz77_decompressor_decode_p(helper, &tmp, error)) |
556 | 0 | return FALSE; |
557 | 8.22M | data_offset = dst_offset - tmp - 1; |
558 | | |
559 | | /* write bytes_remaining of bytes into dst_buf */ |
560 | 8.22M | bytes_remaining--; |
561 | 1.10G | while ((gint16)(bytes_remaining) >= 0) { |
562 | 1.09G | if (dst_offset >= helper->dst->len) { |
563 | 140 | g_set_error_literal(error, |
564 | 140 | FWUPD_ERROR, |
565 | 140 | FWUPD_ERROR_INVALID_DATA, |
566 | 140 | "bad pointer offset"); |
567 | 140 | return FALSE; |
568 | 140 | } |
569 | 1.09G | if (data_offset >= helper->dst->len) { |
570 | 221 | g_set_error_literal(error, |
571 | 221 | FWUPD_ERROR, |
572 | 221 | FWUPD_ERROR_INVALID_DATA, |
573 | 221 | "bad table"); |
574 | 221 | return FALSE; |
575 | 221 | } |
576 | 1.09G | helper->dst->data[dst_offset++] = helper->dst->data[data_offset++]; |
577 | 1.09G | bytes_remaining--; |
578 | 1.09G | } |
579 | 8.22M | } |
580 | 522M | } |
581 | | |
582 | | /* success */ |
583 | 1.54k | return TRUE; |
584 | 5.23k | } |
585 | | |
586 | | static gboolean |
587 | | fu_efi_lz77_decompressor_parse(FuFirmware *firmware, |
588 | | GInputStream *stream, |
589 | | FuFirmwareParseFlags flags, |
590 | | GError **error) |
591 | 3.52k | { |
592 | 3.52k | gsize streamsz = 0; |
593 | 3.52k | guint32 dst_bufsz; |
594 | 3.52k | guint32 src_bufsz; |
595 | 3.52k | g_autoptr(GByteArray) st = NULL; |
596 | 3.52k | g_autoptr(GError) error_all = NULL; |
597 | 3.52k | g_autoptr(GByteArray) dst = g_byte_array_new(); |
598 | 3.52k | FuEfiLz77DecompressorVersion decompressor_versions[] = { |
599 | 3.52k | FU_EFI_LZ77_DECOMPRESSOR_VERSION_LEGACY, |
600 | 3.52k | FU_EFI_LZ77_DECOMPRESSOR_VERSION_TIANO, |
601 | 3.52k | }; |
602 | | |
603 | | /* parse header */ |
604 | 3.52k | if (!fu_input_stream_size(stream, &streamsz, error)) |
605 | 0 | return FALSE; |
606 | 3.52k | st = fu_struct_efi_lz77_decompressor_header_parse_stream(stream, 0x0, error); |
607 | 3.52k | if (st == NULL) |
608 | 33 | return FALSE; |
609 | 3.49k | src_bufsz = fu_struct_efi_lz77_decompressor_header_get_src_size(st); |
610 | 3.49k | if (streamsz < src_bufsz + st->len) { |
611 | 107 | g_set_error_literal(error, |
612 | 107 | FWUPD_ERROR, |
613 | 107 | FWUPD_ERROR_INVALID_DATA, |
614 | 107 | "source buffer is truncated"); |
615 | 107 | return FALSE; |
616 | 107 | } |
617 | 3.38k | dst_bufsz = fu_struct_efi_lz77_decompressor_header_get_dst_size(st); |
618 | 3.38k | if (dst_bufsz == 0) { |
619 | 24 | g_set_error_literal(error, |
620 | 24 | FWUPD_ERROR, |
621 | 24 | FWUPD_ERROR_INVALID_DATA, |
622 | 24 | "destination size is zero"); |
623 | 24 | return FALSE; |
624 | 24 | } |
625 | 3.36k | if (dst_bufsz > fu_firmware_get_size_max(firmware)) { |
626 | 528 | g_autofree gchar *sz_val = g_format_size(dst_bufsz); |
627 | 528 | g_autofree gchar *sz_max = g_format_size(fu_firmware_get_size_max(firmware)); |
628 | 528 | g_set_error(error, |
629 | 528 | FWUPD_ERROR, |
630 | 528 | FWUPD_ERROR_INVALID_DATA, |
631 | 528 | "destination size is too large (%s, limit %s)", |
632 | 528 | sz_val, |
633 | 528 | sz_max); |
634 | 528 | return FALSE; |
635 | 528 | } |
636 | 2.83k | fu_byte_array_set_size(dst, dst_bufsz, 0x0); |
637 | | |
638 | | /* try both position */ |
639 | 6.52k | for (guint i = 0; i < G_N_ELEMENTS(decompressor_versions); i++) { |
640 | 5.23k | FuEfiLz77DecompressHelper helper = { |
641 | 5.23k | .dst = dst, |
642 | 5.23k | .stream = stream, |
643 | 5.23k | }; |
644 | 5.23k | g_autoptr(GError) error_local = NULL; |
645 | | |
646 | 5.23k | if (!g_seekable_seek(G_SEEKABLE(stream), st->len, G_SEEK_SET, NULL, error)) |
647 | 0 | return FALSE; |
648 | 5.23k | if (fu_efi_lz77_decompressor_internal(&helper, |
649 | 5.23k | decompressor_versions[i], |
650 | 5.23k | &error_local)) { |
651 | 1.54k | g_autoptr(GBytes) blob = |
652 | 1.54k | g_byte_array_free_to_bytes(g_steal_pointer(&dst)); /* nocheck:blocked */ |
653 | 1.54k | if (!fu_firmware_set_stream(firmware, NULL, error)) |
654 | 0 | return FALSE; |
655 | 1.54k | fu_firmware_set_bytes(firmware, blob); |
656 | 1.54k | fu_firmware_set_version_raw(firmware, decompressor_versions[i]); |
657 | 1.54k | return TRUE; |
658 | 1.54k | } |
659 | 3.68k | if (error_all == NULL) { |
660 | 2.39k | g_propagate_prefixed_error( |
661 | 2.39k | &error_all, |
662 | 2.39k | g_steal_pointer(&error_local), |
663 | 2.39k | "failed to parse %s: ", |
664 | 2.39k | fu_efi_lz77_decompressor_version_to_string(decompressor_versions[i])); |
665 | 2.39k | continue; |
666 | 2.39k | } |
667 | 1.29k | g_prefix_error(&error_all, |
668 | 1.29k | "failed to parse %s: %s: ", |
669 | 1.29k | fu_efi_lz77_decompressor_version_to_string(decompressor_versions[i]), |
670 | 1.29k | error_local->message); |
671 | 1.29k | } |
672 | | |
673 | | /* success */ |
674 | 1.29k | g_propagate_error(error, g_steal_pointer(&error_all)); |
675 | 1.29k | return FALSE; |
676 | 2.83k | } |
677 | | |
678 | | static void |
679 | | fu_efi_lz77_decompressor_init(FuEfiLz77Decompressor *self) |
680 | 3.58k | { |
681 | 3.58k | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_STORED_SIZE); |
682 | 3.58k | fu_firmware_set_size_max(FU_FIRMWARE(self), 64 * 1024 * 1024); |
683 | 3.58k | } |
684 | | |
685 | | static void |
686 | | fu_efi_lz77_decompressor_class_init(FuEfiLz77DecompressorClass *klass) |
687 | 3 | { |
688 | 3 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
689 | 3 | firmware_class->parse = fu_efi_lz77_decompressor_parse; |
690 | 3 | } |
691 | | |
692 | | /** |
693 | | * fu_efi_lz77_decompressor_new: |
694 | | * |
695 | | * Creates a new #FuFirmware that can be used to decompress LZ77. |
696 | | * |
697 | | * Since: 2.0.0 |
698 | | **/ |
699 | | FuFirmware * |
700 | | fu_efi_lz77_decompressor_new(void) |
701 | 2.69k | { |
702 | 2.69k | return FU_FIRMWARE(g_object_new(FU_TYPE_EFI_LZ77_DECOMPRESSOR, NULL)); |
703 | 2.69k | } |