Files
upx/src/compress/compress_ucl.cpp
T
Markus F.X.J. Oberhumer 47fc230893 all: welcome 2024
2024-01-03 17:47:25 +01:00

370 lines
12 KiB
C++

/* compress_ucl.cpp --
This file is part of the UPX executable compressor.
Copyright (C) 1996-2024 Markus Franz Xaver Johannes Oberhumer
All Rights Reserved.
UPX and the UCL library are free software; you can redistribute them
and/or modify them under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING.
If not, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
Markus F.X.J. Oberhumer
<markus@oberhumer.com>
*/
#include "../conf.h"
#include "compress.h"
/*************************************************************************
//
**************************************************************************/
static int convert_errno_from_ucl(int r) {
switch (r) {
case UCL_E_OK:
return UPX_E_OK;
case UCL_E_ERROR:
return UPX_E_ERROR;
case UCL_E_OUT_OF_MEMORY:
return UPX_E_OUT_OF_MEMORY;
case UCL_E_NOT_COMPRESSIBLE:
return UPX_E_NOT_COMPRESSIBLE;
case UCL_E_INPUT_OVERRUN:
return UPX_E_INPUT_OVERRUN;
case UCL_E_OUTPUT_OVERRUN:
return UPX_E_OUTPUT_OVERRUN;
case UCL_E_LOOKBEHIND_OVERRUN:
return UPX_E_LOOKBEHIND_OVERRUN;
case UCL_E_EOF_NOT_FOUND:
return UPX_E_EOF_NOT_FOUND;
case UCL_E_INPUT_NOT_CONSUMED:
return UPX_E_INPUT_NOT_CONSUMED;
case UCL_E_INVALID_ARGUMENT:
return UPX_E_INVALID_ARGUMENT;
// UCL extra:
case UCL_E_OVERLAP_OVERRUN:
return UPX_E_ERROR;
default:
break;
}
return UPX_E_ERROR;
}
extern "C" {
static void __UCL_CDECL wrap_nprogress_ucl(ucl_uint a, ucl_uint b, int state, ucl_voidp user) {
if (state != -1 && state != 3)
return;
upx_callback_t *cb = (upx_callback_t *) user;
if (cb && cb->nprogress)
cb->nprogress(cb, a, b);
}
}
/*************************************************************************
//
**************************************************************************/
int upx_ucl_compress(const upx_bytep src, unsigned src_len, upx_bytep dst, unsigned *dst_len,
upx_callback_t *cb_parm, int method, int level,
const upx_compress_config_t *cconf_parm, upx_compress_result_t *cresult) {
int r;
assert(level > 0);
assert(cresult != nullptr);
COMPILE_TIME_ASSERT(sizeof(ucl_compress_config_t) == sizeof(REAL_ucl_compress_config_t))
ucl_progress_callback_t cb;
cb.callback = nullptr;
cb.user = nullptr;
if (cb_parm && cb_parm->nprogress) {
cb.callback = wrap_nprogress_ucl;
cb.user = cb_parm;
}
ucl_compress_config_t cconf;
cconf.reset();
if (cconf_parm) {
// cconf = cconf_parm->conf_ucl; // struct copy
memcpy(&cconf, &cconf_parm->conf_ucl, sizeof(cconf));
}
ucl_uint *const res = cresult->result_ucl.result;
// assume no info available - fill in worst case results
// res[0] = 1; // min_offset_found - NOT USED
res[1] = src_len - 1; // max_offset_found
// res[2] = 2; // min_match_found - NOT USED
res[3] = src_len - 1; // max_match_found
// res[4] = 1; // min_run_found - NOT USED
res[5] = src_len; // max_run_found
res[6] = 1; // first_offset_found
// res[7] = 999999; // same_match_offsets_found - NOT USED
// prepare bit-buffer settings
cconf.bb_endian = 0;
cconf.bb_size = 0;
if (method >= M_NRV2B_LE32 && method <= M_NRV2E_LE16) {
static const upx_uint8_t sizes[3] = {32, 8, 16};
cconf.bb_size = sizes[(method - M_NRV2B_LE32) % 3];
} else {
throwInternalError("unknown compression method");
return UPX_E_ERROR;
}
// optimize compression params
if (level <= 3 && cconf.max_offset == UCL_UINT_MAX)
cconf.max_offset = 8 * 1024 - 1;
else if (level == 4 && cconf.max_offset == UCL_UINT_MAX)
cconf.max_offset = 32 * 1024 - 1;
if M_IS_NRV2B (method)
r = ucl_nrv2b_99_compress(src, src_len, dst, dst_len, &cb, level, &cconf, res);
else if M_IS_NRV2D (method)
r = ucl_nrv2d_99_compress(src, src_len, dst, dst_len, &cb, level, &cconf, res);
else if M_IS_NRV2E (method)
r = ucl_nrv2e_99_compress(src, src_len, dst, dst_len, &cb, level, &cconf, res);
else {
throwInternalError("unknown compression method");
return UPX_E_ERROR;
}
// make sure first_offset_found is set
if (res[6] == 0)
res[6] = 1;
return convert_errno_from_ucl(r);
}
/*************************************************************************
//
**************************************************************************/
int upx_ucl_decompress(const upx_bytep src, unsigned src_len, upx_bytep dst, unsigned *dst_len,
int method, const upx_compress_result_t *cresult) {
int r;
switch (method) {
case M_NRV2B_8:
r = ucl_nrv2b_decompress_safe_8(src, src_len, dst, dst_len, nullptr);
break;
case M_NRV2B_LE16:
r = ucl_nrv2b_decompress_safe_le16(src, src_len, dst, dst_len, nullptr);
break;
case M_NRV2B_LE32:
r = ucl_nrv2b_decompress_safe_le32(src, src_len, dst, dst_len, nullptr);
break;
case M_NRV2D_8:
r = ucl_nrv2d_decompress_safe_8(src, src_len, dst, dst_len, nullptr);
break;
case M_NRV2D_LE16:
r = ucl_nrv2d_decompress_safe_le16(src, src_len, dst, dst_len, nullptr);
break;
case M_NRV2D_LE32:
r = ucl_nrv2d_decompress_safe_le32(src, src_len, dst, dst_len, nullptr);
break;
case M_NRV2E_8:
r = ucl_nrv2e_decompress_safe_8(src, src_len, dst, dst_len, nullptr);
break;
case M_NRV2E_LE16:
r = ucl_nrv2e_decompress_safe_le16(src, src_len, dst, dst_len, nullptr);
break;
case M_NRV2E_LE32:
r = ucl_nrv2e_decompress_safe_le32(src, src_len, dst, dst_len, nullptr);
break;
default:
throwInternalError("unknown decompression method");
return UPX_E_ERROR;
}
UNUSED(cresult);
return convert_errno_from_ucl(r);
}
/*************************************************************************
//
**************************************************************************/
int upx_ucl_test_overlap(const upx_bytep buf, const upx_bytep tbuf, unsigned src_off,
unsigned src_len, unsigned *dst_len, int method,
const upx_compress_result_t *cresult) {
int r;
UNUSED(tbuf); // not needed for UCL
switch (method) {
case M_NRV2B_8:
r = ucl_nrv2b_test_overlap_8(buf, src_off, src_len, dst_len, nullptr);
break;
case M_NRV2B_LE16:
r = ucl_nrv2b_test_overlap_le16(buf, src_off, src_len, dst_len, nullptr);
break;
case M_NRV2B_LE32:
r = ucl_nrv2b_test_overlap_le32(buf, src_off, src_len, dst_len, nullptr);
break;
case M_NRV2D_8:
r = ucl_nrv2d_test_overlap_8(buf, src_off, src_len, dst_len, nullptr);
break;
case M_NRV2D_LE16:
r = ucl_nrv2d_test_overlap_le16(buf, src_off, src_len, dst_len, nullptr);
break;
case M_NRV2D_LE32:
r = ucl_nrv2d_test_overlap_le32(buf, src_off, src_len, dst_len, nullptr);
break;
case M_NRV2E_8:
r = ucl_nrv2e_test_overlap_8(buf, src_off, src_len, dst_len, nullptr);
break;
case M_NRV2E_LE16:
r = ucl_nrv2e_test_overlap_le16(buf, src_off, src_len, dst_len, nullptr);
break;
case M_NRV2E_LE32:
r = ucl_nrv2e_test_overlap_le32(buf, src_off, src_len, dst_len, nullptr);
break;
default:
throwInternalError("unknown decompression method");
return UPX_E_ERROR;
}
UNUSED(cresult);
return convert_errno_from_ucl(r);
}
/*************************************************************************
// misc
**************************************************************************/
extern "C" {
static ucl_voidp __UCL_CDECL my_malloc(ucl_uint n) { return upx_calloc(n, 1); }
static void __UCL_CDECL my_free(ucl_voidp p) { free(p); }
} // extern "C"
int upx_ucl_init(void) {
if (ucl_init() != UCL_E_OK)
return -1;
if (UCL_VERSION != ucl_version() || strcmp(UCL_VERSION_STRING, ucl_version_string()) != 0)
return -2;
ucl_set_malloc_hooks(my_malloc, my_free);
return 0;
}
const char *upx_ucl_version_string(void) { return ucl_version_string(); }
unsigned upx_ucl_adler32(const void *buf, unsigned len, unsigned adler) {
return ucl_adler32(adler, (const ucl_bytep) buf, len);
}
#if 0 // UNUSED
unsigned upx_ucl_crc32(const void *buf, unsigned len, unsigned crc) {
return ucl_crc32(crc, (const ucl_bytep) buf, len);
}
#endif
/*************************************************************************
// doctest checks
**************************************************************************/
#if DEBUG && !defined(DOCTEST_CONFIG_DISABLE) && 1
#include "../util/membuffer.h"
static bool check_ucl(const int method, const unsigned expected_c_len) {
const unsigned u_len = 16384;
const unsigned c_extra = 4096;
MemBuffer u_buf, c_buf, d_buf;
unsigned c_len, d_len;
upx_compress_result_t cresult;
int r;
const int level = 3; // don't waste time
u_buf.alloc(u_len);
memset(u_buf, 0, u_len);
c_buf.allocForCompression(u_len, c_extra);
d_buf.allocForDecompression(u_len);
c_len = c_buf.getSize() - c_extra;
r = upx_ucl_compress(raw_bytes(u_buf, u_len), u_len, raw_index_bytes(c_buf, c_extra, c_len),
&c_len, nullptr, method, level, NULL_cconf, &cresult);
if (r != 0 || c_len != expected_c_len)
return false;
d_len = d_buf.getSize();
r = upx_ucl_decompress(raw_index_bytes(c_buf, c_extra, c_len), c_len, raw_bytes(d_buf, d_len),
&d_len, method, nullptr);
if (r != 0 || d_len != u_len || memcmp(u_buf, d_buf, u_len) != 0)
return false;
d_len = u_len - 1;
r = upx_ucl_decompress(raw_index_bytes(c_buf, c_extra, c_len), c_len, raw_bytes(d_buf, d_len),
&d_len, method, nullptr);
if (r == 0)
return false;
// TODO: rewrite Packer::findOverlapOverhead() so that we can test it here
// unsigned x_len = d_len;
// r = upx_ucl_test_overlap(c_buf, u_buf, c_extra, c_len, &x_len, method, nullptr);
return true;
}
TEST_CASE("compress_ucl") {
CHECK(check_ucl(M_NRV2B_8, 34));
CHECK(check_ucl(M_NRV2B_LE16, 34));
CHECK(check_ucl(M_NRV2B_LE32, 34));
CHECK(check_ucl(M_NRV2D_8, 32));
CHECK(check_ucl(M_NRV2D_LE16, 32));
CHECK(check_ucl(M_NRV2D_LE32, 34));
CHECK(check_ucl(M_NRV2E_8, 32));
CHECK(check_ucl(M_NRV2E_LE16, 32));
CHECK(check_ucl(M_NRV2E_LE32, 34));
}
#endif // DEBUG
TEST_CASE("upx_ucl_decompress") {
const byte *c_data;
byte d_buf[16];
unsigned d_len;
int r;
c_data = (const byte *) "\x92\xff\x10\x00\x00\x00\x00\x00\x48\xff";
d_len = 16;
r = upx_ucl_decompress(c_data, 10, d_buf, &d_len, M_NRV2B_8, nullptr);
CHECK((r == 0 && d_len == 16));
r = upx_ucl_decompress(c_data, 9, d_buf, &d_len, M_NRV2B_8, nullptr);
CHECK(r == UPX_E_INPUT_OVERRUN);
d_len = 15;
r = upx_ucl_decompress(c_data, 10, d_buf, &d_len, M_NRV2B_8, nullptr);
CHECK(r == UPX_E_OUTPUT_OVERRUN);
c_data = (const byte *) "\x92\xff\x10\x92\x49\x24\x92\xa0\xff";
d_len = 16;
r = upx_ucl_decompress(c_data, 9, d_buf, &d_len, M_NRV2D_8, nullptr);
CHECK((r == 0 && d_len == 16));
r = upx_ucl_decompress(c_data, 8, d_buf, &d_len, M_NRV2D_8, nullptr);
CHECK(r == UPX_E_INPUT_OVERRUN);
d_len = 15;
r = upx_ucl_decompress(c_data, 9, d_buf, &d_len, M_NRV2D_8, nullptr);
CHECK(r == UPX_E_OUTPUT_OVERRUN);
c_data = (const byte *) "\x90\xff\xb0\x92\x49\x24\x92\xa0\xff";
d_len = 16;
r = upx_ucl_decompress(c_data, 9, d_buf, &d_len, M_NRV2E_8, nullptr);
CHECK((r == 0 && d_len == 16));
r = upx_ucl_decompress(c_data, 8, d_buf, &d_len, M_NRV2E_8, nullptr);
CHECK(r == UPX_E_INPUT_OVERRUN);
d_len = 15;
r = upx_ucl_decompress(c_data, 9, d_buf, &d_len, M_NRV2E_8, nullptr);
CHECK(r == UPX_E_OUTPUT_OVERRUN);
UNUSED(r);
}
/* vim:set ts=4 sw=4 et: */