/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 | } |