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