/src/upx/src/compress/compress_zlib.cpp
Line | Count | Source |
1 | | /* compress_zlib.cpp -- |
2 | | |
3 | | This file is part of the UPX executable compressor. |
4 | | |
5 | | Copyright (C) Markus Franz Xaver Johannes Oberhumer |
6 | | All Rights Reserved. |
7 | | |
8 | | UPX and the UCL library are free software; you can redistribute them |
9 | | and/or modify them under the terms of the GNU General Public License as |
10 | | published by the Free Software Foundation; either version 2 of |
11 | | the License, or (at your option) any later version. |
12 | | |
13 | | This program is distributed in the hope that it will be useful, |
14 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | | GNU General Public License for more details. |
17 | | |
18 | | You should have received a copy of the GNU General Public License |
19 | | along with this program; see the file COPYING. |
20 | | If not, write to the Free Software Foundation, Inc., |
21 | | 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
22 | | |
23 | | Markus F.X.J. Oberhumer |
24 | | <markus@oberhumer.com> |
25 | | */ |
26 | | |
27 | | #include "../conf.h" |
28 | | |
29 | 446k | void zlib_compress_config_t::reset() noexcept { |
30 | 446k | mem_clear(this); |
31 | 446k | mem_level.reset(); |
32 | 446k | window_bits.reset(); |
33 | 446k | strategy.reset(); |
34 | 446k | } |
35 | | |
36 | | #if WITH_ZLIB |
37 | | #include "compress.h" |
38 | | #include "../util/membuffer.h" |
39 | | // NOLINTBEGIN(clang-analyzer-optin.performance.Padding) |
40 | | #define ZLIB_CONST 1 |
41 | | #include <zlib/zlib.h> |
42 | | #include <zlib/deflate.h> |
43 | | // NOLINTEND(clang-analyzer-optin.performance.Padding) |
44 | | |
45 | 81.9k | static int convert_errno_from_zlib(int zr) { |
46 | 81.9k | switch (zr) { |
47 | 0 | case Z_OK: |
48 | 0 | return UPX_E_OK; |
49 | | // positive values |
50 | 0 | case Z_STREAM_END: |
51 | 0 | return UPX_E_ERROR; |
52 | 0 | case Z_NEED_DICT: |
53 | 0 | return UPX_E_ERROR; |
54 | | // negative values |
55 | 0 | case Z_ERRNO: |
56 | 0 | return UPX_E_ERROR; |
57 | 0 | case Z_STREAM_ERROR: |
58 | 0 | return UPX_E_ERROR; |
59 | 501 | case Z_DATA_ERROR: |
60 | 501 | return UPX_E_ERROR; |
61 | 0 | case Z_MEM_ERROR: |
62 | 0 | return UPX_E_OUT_OF_MEMORY; |
63 | 40.6k | case Z_BUF_ERROR: |
64 | 40.6k | return UPX_E_OUTPUT_OVERRUN; |
65 | 0 | case Z_VERSION_ERROR: |
66 | 0 | return UPX_E_ERROR; |
67 | 40.7k | case -7: // UPX extra |
68 | 40.7k | return UPX_E_INPUT_OVERRUN; |
69 | 0 | default: |
70 | 0 | break; |
71 | 81.9k | } |
72 | 0 | return UPX_E_ERROR; |
73 | 81.9k | } |
74 | | |
75 | | /************************************************************************* |
76 | | // |
77 | | **************************************************************************/ |
78 | | |
79 | | int upx_zlib_compress(const upx_bytep src, unsigned src_len, upx_bytep dst, unsigned *dst_len, |
80 | | upx_callback_t *cb_parm, int method, int level, |
81 | 0 | const upx_compress_config_t *cconf_parm, upx_compress_result_t *cresult) { |
82 | 0 | assert(method == M_DEFLATE); |
83 | 0 | assert(level > 0); |
84 | 0 | assert(cresult != nullptr); |
85 | 0 | UNUSED(cb_parm); |
86 | 0 | int r = UPX_E_ERROR; |
87 | 0 | int zr; |
88 | 0 | const zlib_compress_config_t *const lcconf = cconf_parm ? &cconf_parm->conf_zlib : nullptr; |
89 | 0 | zlib_compress_result_t *const res = &cresult->result_zlib; |
90 | 0 | res->reset(); |
91 | |
|
92 | 0 | if (level == 10) |
93 | 0 | level = 9; |
94 | |
|
95 | 0 | zlib_compress_config_t::mem_level_t mem_level; |
96 | 0 | zlib_compress_config_t::window_bits_t window_bits; |
97 | 0 | zlib_compress_config_t::strategy_t strategy; |
98 | | // cconf overrides |
99 | 0 | if (lcconf) { |
100 | 0 | upx::oassign(mem_level, lcconf->mem_level); |
101 | 0 | upx::oassign(window_bits, lcconf->window_bits); |
102 | 0 | upx::oassign(strategy, lcconf->strategy); |
103 | 0 | } |
104 | |
|
105 | 0 | z_stream s; |
106 | 0 | s.zalloc = (alloc_func) nullptr; |
107 | 0 | s.zfree = (free_func) nullptr; |
108 | 0 | s.next_in = src; |
109 | 0 | s.avail_in = src_len; |
110 | 0 | s.next_out = dst; |
111 | 0 | s.avail_out = *dst_len; |
112 | 0 | s.total_in = s.total_out = 0; |
113 | |
|
114 | 0 | zr = (int) deflateInit2(&s, level, Z_DEFLATED, 0 - (int) window_bits, mem_level, strategy); |
115 | 0 | if (zr != Z_OK) |
116 | 0 | goto error; |
117 | 0 | assert(s.state->level == level); |
118 | 0 | zr = deflate(&s, Z_FINISH); |
119 | 0 | if (zr != Z_STREAM_END) |
120 | 0 | goto error; |
121 | 0 | zr = deflateEnd(&s); |
122 | 0 | if (zr != Z_OK) |
123 | 0 | goto error; |
124 | 0 | r = UPX_E_OK; |
125 | 0 | goto done; |
126 | 0 | error: |
127 | 0 | (void) deflateEnd(&s); |
128 | 0 | r = convert_errno_from_zlib(zr); |
129 | 0 | if (r == UPX_E_OK) |
130 | 0 | r = UPX_E_ERROR; |
131 | 0 | done: |
132 | 0 | if (r == UPX_E_OK) { |
133 | 0 | if (s.avail_in != 0 || s.total_in != src_len) |
134 | 0 | r = UPX_E_ERROR; |
135 | 0 | } |
136 | 0 | assert(s.total_in <= src_len); |
137 | 0 | assert(s.total_out <= *dst_len); |
138 | 0 | *dst_len = s.total_out; |
139 | 0 | return r; |
140 | 0 | } |
141 | | |
142 | | /************************************************************************* |
143 | | // |
144 | | **************************************************************************/ |
145 | | |
146 | | int upx_zlib_decompress(const upx_bytep src, unsigned src_len, upx_bytep dst, unsigned *dst_len, |
147 | 122k | int method, const upx_compress_result_t *cresult) { |
148 | 122k | assert(method == M_DEFLATE); |
149 | 122k | UNUSED(method); |
150 | 122k | UNUSED(cresult); |
151 | 122k | int r = UPX_E_ERROR; |
152 | 122k | int zr; |
153 | | |
154 | 122k | z_stream s; |
155 | 122k | s.zalloc = (alloc_func) nullptr; |
156 | 122k | s.zfree = (free_func) nullptr; |
157 | 122k | s.next_in = src; |
158 | 122k | s.avail_in = src_len; |
159 | 122k | s.next_out = dst; |
160 | 122k | s.avail_out = *dst_len; |
161 | 122k | s.total_in = s.total_out = 0; |
162 | | |
163 | 122k | zr = inflateInit2(&s, -15); |
164 | 122k | if (zr != Z_OK) |
165 | 0 | goto error; |
166 | 122k | zr = inflate(&s, Z_FINISH); |
167 | 122k | if (zr != Z_STREAM_END) { |
168 | 81.9k | if (zr == Z_BUF_ERROR && s.avail_in == 0) |
169 | 40.7k | zr = -7; // UPX extra |
170 | 81.9k | goto error; |
171 | 81.9k | } |
172 | 40.6k | zr = inflateEnd(&s); |
173 | 40.6k | if (zr != Z_OK) |
174 | 0 | goto error; |
175 | 40.6k | r = UPX_E_OK; |
176 | 40.6k | goto done; |
177 | 81.9k | error: |
178 | 81.9k | (void) inflateEnd(&s); |
179 | 81.9k | r = convert_errno_from_zlib(zr); |
180 | 81.9k | if (r == UPX_E_OK) |
181 | 0 | r = UPX_E_ERROR; |
182 | 122k | done: |
183 | 122k | if (r == UPX_E_OK) { |
184 | 40.6k | if (s.avail_in != 0 || s.total_in != src_len) |
185 | 21 | r = UPX_E_INPUT_NOT_CONSUMED; |
186 | 40.6k | } |
187 | 122k | assert(s.total_in <= src_len); |
188 | 122k | assert(s.total_out <= *dst_len); |
189 | 0 | *dst_len = s.total_out; |
190 | 122k | return r; |
191 | 81.9k | } |
192 | | |
193 | | /************************************************************************* |
194 | | // test_overlap - see <ucl/ucl.h> for semantics |
195 | | **************************************************************************/ |
196 | | |
197 | | int upx_zlib_test_overlap(const upx_bytep buf, const upx_bytep tbuf, unsigned src_off, |
198 | | unsigned src_len, unsigned *dst_len, int method, |
199 | 0 | const upx_compress_result_t *cresult) { |
200 | 0 | assert(method == M_DEFLATE); |
201 | | |
202 | 0 | MemBuffer b(src_off + src_len); |
203 | 0 | memcpy(b + src_off, buf + src_off, src_len); |
204 | 0 | unsigned saved_dst_len = *dst_len; |
205 | 0 | int r = upx_zlib_decompress(raw_index_bytes(b, src_off, src_len), src_len, |
206 | 0 | raw_bytes(b, *dst_len), dst_len, method, cresult); |
207 | 0 | if (r != UPX_E_OK) |
208 | 0 | return r; |
209 | 0 | if (*dst_len != saved_dst_len) |
210 | 0 | return UPX_E_ERROR; |
211 | | // NOTE: there is a very tiny possibility that decompression has |
212 | | // succeeded but the data is not restored correctly because of |
213 | | // in-place buffer overlapping, so we use an extra memcmp(). |
214 | 0 | if (tbuf != nullptr && memcmp(tbuf, b, *dst_len) != 0) |
215 | 0 | return UPX_E_ERROR; |
216 | 0 | return UPX_E_OK; |
217 | 0 | } |
218 | | |
219 | | /************************************************************************* |
220 | | // misc |
221 | | **************************************************************************/ |
222 | | |
223 | 40.6k | int upx_zlib_init(void) { |
224 | 40.6k | if (strcmp(ZLIB_VERSION, zlibVersion()) != 0) |
225 | 0 | return -2; |
226 | 40.6k | return 0; |
227 | 40.6k | } |
228 | | |
229 | 0 | const char *upx_zlib_version_string(void) { return zlibVersion(); } |
230 | | |
231 | | #if 0 // UNUSED |
232 | | unsigned upx_zlib_adler32(const void *buf, unsigned len, unsigned adler) { |
233 | | return adler32(adler, (const Bytef *) buf, len); |
234 | | } |
235 | | #endif |
236 | | |
237 | | #if 0 // UNUSED |
238 | | unsigned upx_zlib_crc32(const void *buf, unsigned len, unsigned crc) { |
239 | | return crc32(crc, (const Bytef *) buf, len); |
240 | | } |
241 | | #endif |
242 | | |
243 | | /************************************************************************* |
244 | | // doctest checks |
245 | | **************************************************************************/ |
246 | | |
247 | | #if DEBUG && !defined(DOCTEST_CONFIG_DISABLE) && 1 |
248 | | |
249 | | static bool check_zlib(const int method, const int level, const unsigned expected_c_len) { |
250 | | const unsigned u_len = 16384; |
251 | | const unsigned c_extra = 4096; |
252 | | MemBuffer u_buf, c_buf, d_buf; |
253 | | unsigned c_len, d_len; |
254 | | upx_compress_result_t cresult; |
255 | | int r; |
256 | | |
257 | | u_buf.alloc(u_len); |
258 | | memset(u_buf, 0, u_len); |
259 | | c_buf.allocForCompression(u_len, c_extra); |
260 | | d_buf.allocForDecompression(u_len); |
261 | | |
262 | | c_len = c_buf.getSize() - c_extra; |
263 | | r = upx_zlib_compress(raw_bytes(u_buf, u_len), u_len, raw_index_bytes(c_buf, c_extra, c_len), |
264 | | &c_len, nullptr, method, level, NULL_cconf, &cresult); |
265 | | if (r != 0 || c_len != expected_c_len) |
266 | | return false; |
267 | | |
268 | | d_len = d_buf.getSize(); |
269 | | r = upx_zlib_decompress(raw_index_bytes(c_buf, c_extra, c_len), c_len, raw_bytes(d_buf, d_len), |
270 | | &d_len, method, nullptr); |
271 | | if (r != 0 || d_len != u_len || memcmp(u_buf, d_buf, u_len) != 0) |
272 | | return false; |
273 | | |
274 | | d_len = u_len - 1; |
275 | | r = upx_zlib_decompress(raw_index_bytes(c_buf, c_extra, c_len), c_len, raw_bytes(d_buf, d_len), |
276 | | &d_len, method, nullptr); |
277 | | if (r == 0) |
278 | | return false; |
279 | | |
280 | | // TODO: rewrite Packer::findOverlapOverhead() so that we can test it here |
281 | | // unsigned x_len = d_len; |
282 | | // r = upx_zlib_test_overlap(c_buf, u_buf, c_extra, c_len, &x_len, method, nullptr); |
283 | | return true; |
284 | | } |
285 | | |
286 | | TEST_CASE("compress_zlib") { |
287 | | CHECK(check_zlib(M_DEFLATE, 1, 89)); |
288 | | CHECK(check_zlib(M_DEFLATE, 3, 89)); |
289 | | CHECK(check_zlib(M_DEFLATE, 5, 33)); |
290 | | } |
291 | | |
292 | | #endif // DEBUG |
293 | | |
294 | 40.6k | TEST_CASE("upx_zlib_decompress") { |
295 | 40.6k | const byte *c_data; |
296 | 40.6k | byte d_buf[16]; |
297 | 40.6k | unsigned d_len; |
298 | 40.6k | int r; |
299 | | |
300 | 40.6k | c_data = (const byte *) "\xfb\xff\x1f\x15\x00\x00"; |
301 | 40.6k | d_len = 16; |
302 | 40.6k | r = upx_zlib_decompress(c_data, 6, d_buf, &d_len, M_DEFLATE, nullptr); |
303 | 40.6k | CHECK((r == 0 && d_len == 16)); |
304 | 40.6k | r = upx_zlib_decompress(c_data, 5, d_buf, &d_len, M_DEFLATE, nullptr); |
305 | 40.6k | CHECK(r == UPX_E_INPUT_OVERRUN); |
306 | 40.6k | d_len = 15; |
307 | 40.6k | r = upx_zlib_decompress(c_data, 6, d_buf, &d_len, M_DEFLATE, nullptr); |
308 | 40.6k | CHECK(r == UPX_E_OUTPUT_OVERRUN); |
309 | 40.6k | UNUSED(r); |
310 | 40.6k | } |
311 | | |
312 | | #endif // WITH_ZLIB |
313 | | |
314 | | /* vim:set ts=4 sw=4 et: */ |