clamav/libclamav/aspack.c
Val S. aadf25df6a
Fix static analysis code quality issues (#1582)
`libclamav/libmspack.c`: Initialize variables before first `goto done;`
to fix unitialized variable use in an error condition.

`libclamav/others.c`: Explicitly ignore return values for calls to add
JSON values when subsequent calls don't depend on them.
If we were to add error handling here, the only thing we'd do is debug-
log it. I don't think it's worth adding the extra lines of code.

`libclamav/unarj.c`: Removed dead code.
The `status` variable is immediately set afterwards based on whether or
not any files may be extracted.

`libclamav/unzip.c`: Removed dead code.
The `ret` variable is checked immediately after being set, above. This
check after the `do`-`while()` loop is dead code.

`sigtool/sigtool.c`: Fix potential NULL deref in error handling.
This is a fix for the same issue as was fixed in a previous commit.
I somehow overlooked this one. Copy/paste bug.

`libclamav/pdfdecode.c`: Fix leaked `stream` memory when
`filter_lzwdecode()` fails.

`clamdtop/clamdtop.c`: Fix possible NULL dereference if `strchr` returns
NULL in `read_version()` and `check_stats_available()`.

`libclamav/rtf.c`: Fix memory leak in `rtf_object_process()` if
`cli_gentemp_with_prefix()` fails.
Also change empty for-loop to resolve clang-format weirdness and make it
more obvious the for-loop has no body.

`libclamav/aspack.c`: Ensure that `endoff - old` is not negative in
`build_decrypt_array()` before passing to `CLI_ISCONTAINED()` which expects
unsigned values.

`libclamav/upx.c`: Fix integer overflow checks in multiple functions.

`libclamav/vba_extract.c`: Set `entries` pointer back to NULL after free in
`word_read_macro_entry()` error condition.

`libclamav/unzip.c`: Remove logic to return `CL_EMAXFILES` from
`index_local_file_headers()`. It seems it only overwrote the status when
not `CL_SUCCESS` in which case it could be overriding a more serious failure.
Further, updates to the how the ZIP parser works has made it so this needs
to return `CL_SUCCESS` in order for the caller to at least scan the files
found so far.
Finally, the calling function has checks of its own to make sure we don't
exceeds the max-files limit.

`libclamav/unzip.c`: Fix issue where `cli_append_potentially_unwanted()` in
`index_local_file_headers()` might overwrite an error in `status` with
`CL_CLEAN`. Instead, it now checks the return value and only overwrites the
`CL_EFORMAT` status with a different value if not `CL_SUCCESS`.

`libclamav/unzip.c`: Fix a potential leak with `combined_catalogue` and
`temp_catalogue` in an error condition. We should always free them if not NULL,
not just if the function failed. And to make this safe, we must set
`combined_catalogue` to NULL when we give ownership to `*catalogue`.

`libclamav/scanners.c`: Fix a potential leak in error handling for the
`cli_ole2_tempdir_scan_vba()` function.

CLAM-2768
2025-10-02 11:46:14 -04:00

510 lines
16 KiB
C

/*
* Copyright (C) 2013-2025 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
* Copyright (C) 2007-2013 Sourcefire, Inc.
*
* Authors: Luciano Giuseppe 'Pnluck', Alberto Wu
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include <string.h>
#include "clamav.h"
#include "execs.h"
#include "others.h"
#include "rebuildpe.h"
#include "aspack.h"
#define ASPACK_BLOCKS_OFFSET_212 0x57c
#define ASPACK_BLOCKS_OFFSET_OTHER 0x5d8
#define ASPACK_BLOCKS_OFFSET_242 0x5e4
#define ASPACK_STR_INIT_MLT_OFFSET_212 0x70e
#define ASPACK_STR_INIT_MLT_OFFSET_OTHER 0x76a
#define ASPACK_STR_INIT_MLT_OFFSET_242 0x776
#define ASPACK_COMP_BLOCK_OFFSET_212 0x6d6
#define ASPACK_COMP_BLOCK_OFFSET_OTHER 0x732
#define ASPACK_COMP_BLOCK_OFFSET_242 0x73e
#define ASPACK_WRKBUF_OFFSET_212 0x148
#define ASPACK_WRKBUF_OFFSET_OTHER 0x13a
#define ASPACK_WRKBUF_OFFSET_242 0x148
#define ASPACK_OEP_OFFSET_212 0x39b
#define ASPACK_OEP_OFFSET_OTHER 0x401
#define ASPACK_OEP_OFFSET_242 0x40d
struct DICT_HELPER {
uint32_t *starts;
uint8_t *ends;
uint32_t size;
};
struct ASPK {
uint32_t bitpos;
uint32_t hash;
uint32_t init_array[58];
struct DICT_HELPER dict_helper[4];
uint8_t *input;
uint8_t *iend;
uint8_t *decrypt_dict;
uint32_t decarray3[4][24];
uint32_t decarray4[4][24];
int dict_ok;
uint8_t array2[758];
uint8_t array1[19];
};
static inline int readstream(struct ASPK *stream)
{
while (stream->bitpos >= 8) {
if (stream->input >= stream->iend) return 0;
stream->hash = (stream->hash << 8) | *stream->input;
stream->input++;
stream->bitpos -= 8;
}
return 1;
}
static uint32_t getdec(struct ASPK *stream, uint8_t which, int *err)
{
uint32_t ret;
uint8_t pos;
uint32_t *d3 = stream->decarray3[which];
uint32_t *d4 = stream->decarray4[which];
*err = 1;
if (!readstream(stream)) return 0;
ret = (stream->hash >> (8 - stream->bitpos)) & 0xfffe00;
if (ret < d3[8]) {
if ((ret >> 16) >= 0x100) return 0;
if (!(pos = stream->dict_helper[which].ends[ret >> 16]) || pos >= 24) return 0; /* 0<pos<24 */
} else {
if (ret < d3[10]) {
if (ret < d3[9])
pos = 9;
else
pos = 10;
} else {
if (ret < d3[11])
pos = 11;
else {
if (ret < d3[12])
pos = 12;
else {
if (ret < d3[13])
pos = 13;
else {
if (ret < d3[14])
pos = 14;
else
pos = 15;
}
}
}
}
}
stream->bitpos += pos;
ret = ((ret - d3[pos - 1]) >> (24 - pos)) + d4[pos];
if (ret >= stream->dict_helper[which].size) return 0;
ret = stream->dict_helper[which].starts[ret];
*err = 0;
return ret;
}
static uint8_t build_decrypt_array(struct ASPK *stream, uint8_t *array, uint8_t which)
{
uint32_t sum = 0, counter = 23, i, endoff = 0, bus[18], dict[18];
uint32_t *d3 = stream->decarray3[which];
uint32_t *d4 = stream->decarray4[which];
memset(bus, 0, sizeof(bus));
memset(dict, 0, sizeof(dict));
for (i = 0; i < stream->dict_helper[which].size; i++) {
/* within bounds - see comments in build_decrypt_dictionaries */
if (array[i] > 17) return 0;
bus[array[i]]++;
}
d3[0] = 0;
d4[0] = 0;
i = 0;
while (counter >= 9) { /* 0<=i<=14 */
sum += (bus[i + 1] << counter);
if (sum > 0x1000000) return 0;
d3[i + 1] = sum;
d4[i + 1] = dict[i + 1] = bus[i] + d4[i];
if (counter >= 0x10) {
uint32_t old = endoff;
endoff = d3[i + 1] >> 0x10;
if (endoff < old) return 0;
uint32_t remaining = endoff - old;
if (remaining > 0) {
if (!CLI_ISCONTAINED(stream->dict_helper[which].ends, 0x100, stream->dict_helper[which].ends + old, remaining)) return 0;
memset((stream->dict_helper[which].ends + old), i + 1, remaining);
}
}
i++;
counter--;
}
if (sum != 0x1000000) return 0;
i = 0;
for (i = 0; i < stream->dict_helper[which].size; i++) {
if (array[i]) { /* within bounds - see above */
if (array[i] > 17) return 0;
if (dict[array[i]] >= stream->dict_helper[which].size) return 0;
stream->dict_helper[which].starts[dict[array[i]]] = i;
dict[array[i]]++;
}
}
return 1;
}
static uint8_t getbits(struct ASPK *stream, uint32_t num, int *err)
{
uint8_t retvalue;
if (!readstream(stream)) {
*err = 1;
return 0;
}
*err = 0;
retvalue = ((stream->hash >> (8 - stream->bitpos)) & 0xffffff) >> (24 - num);
stream->bitpos += num;
return retvalue;
}
static int build_decrypt_dictionaries(struct ASPK *stream)
{
unsigned int counter;
uint32_t ret;
int oob;
if (!getbits(stream, 1, &oob)) memset(stream->decrypt_dict, 0, 0x2f5);
if (oob) return 0;
for (counter = 0; counter < 19; counter++) {
stream->array1[counter] = getbits(stream, 4, &oob);
if (oob) return 0;
}
if (!build_decrypt_array(stream, stream->array1, 3)) return 0; /* array1[19] - [3].size=19 */
counter = 0;
while (counter < 757) {
ret = getdec(stream, 3, &oob);
if (oob) return 0;
if (ret >= 16) {
if (ret != 16) {
if (ret == 17)
ret = 3 + getbits(stream, 3, &oob);
else
ret = 11 + getbits(stream, 7, &oob);
if (oob) return 0;
while (ret) {
if (counter >= 757) break;
stream->array2[1 + counter] = 0;
counter++;
ret--;
}
} else {
ret = 3 + getbits(stream, 2, &oob);
if (oob) return 0;
while (ret) {
if (counter >= 757) break;
stream->array2[1 + counter] = stream->array2[counter];
counter++;
ret--;
}
}
} else {
stream->array2[1 + counter] = (stream->decrypt_dict[counter] + ret) & 0xF;
counter++;
}
}
if (!build_decrypt_array(stream, &stream->array2[1], 0) /* array2[758-1=757] - [0].size=721 */ || !build_decrypt_array(stream, &stream->array2[722], 1) /* array2[758-722=36] - [1].size=28 */ || !build_decrypt_array(stream, &stream->array2[750], 2) /* array2[758-750=8] - [2].size=8 */) return 0;
stream->dict_ok = 0;
for (counter = 0; counter < 8; counter++) {
if (stream->array2[750 + counter] != 3) {
stream->dict_ok = 1;
break;
}
}
memcpy(stream->decrypt_dict, &stream->array2[1], 757);
return 1;
}
static int decrypt(struct ASPK *stream, uint8_t *stuff, uint32_t size, uint8_t *output)
{
/* ep+6d6 -> ep+748 = 0x72*/
uint32_t gen, backsize, backbytes, useold, counter = 0;
uint32_t hist[4] = {0, 0, 0, 0};
int oob;
cli_dbgmsg("Aspack: decrypt size:%x\n", size);
while (counter < size) {
gen = getdec(stream, 0, &oob);
if (oob) return 0;
if (gen < 256) { /* implied within bounds */
output[counter] = (uint8_t)gen;
counter++;
continue;
}
if (gen >= 720) {
if (!build_decrypt_dictionaries(stream)) return 0;
continue;
}
backbytes = (gen - 256) >> 3;
// backbytes is < 720. 719 - 256 = 463. 463 >> 3 = 57 (max).
// So backbytes cannot overrun the init_array.
backsize = ((gen - 256) & 7) + 2;
if ((backsize - 2) == 7) {
uint8_t hlp;
gen = getdec(stream, 1, &oob);
if (oob || gen >= 0x56) return 0;
hlp = stuff[gen + 0x1c];
if (!readstream(stream)) return 0;
backsize += stuff[gen] + (((stream->hash >> (8 - stream->bitpos)) & 0xffffff) >> (0x18 - hlp));
stream->bitpos += hlp;
}
useold = stream->init_array[backbytes];
gen = stuff[backbytes + 0x38];
if (!stream->dict_ok || gen < 3) {
if (!readstream(stream)) return 0;
useold += ((stream->hash >> (8 - stream->bitpos)) & 0xffffff) >> (24 - gen);
stream->bitpos += gen;
} else {
gen -= 3;
if (!readstream(stream)) return 0;
useold += ((((stream->hash >> (8 - stream->bitpos)) & 0xffffff) >> (24 - gen)) * 8);
stream->bitpos += gen;
useold += getdec(stream, 2, &oob);
if (oob) return 0;
}
if (useold < 3) {
backbytes = hist[useold];
if (useold != 0) {
hist[useold] = hist[0];
hist[0] = backbytes;
}
} else {
hist[2] = hist[1];
hist[1] = hist[0];
hist[0] = backbytes = useold - 3;
}
backbytes++;
if (!backbytes || backbytes > counter || backsize > size - counter) return 0;
while (backsize--) {
output[counter] = output[counter - backbytes];
counter++;
}
}
return 1;
}
static int decomp_block(struct ASPK *stream, uint32_t size, uint8_t *stuff, uint8_t *output)
{
memset(stream->decarray3, 0, sizeof(stream->decarray3));
memset(stream->decarray4, 0, sizeof(stream->decarray4));
memset(stream->decrypt_dict, 0, 757);
stream->bitpos = 0x20;
if (!build_decrypt_dictionaries(stream)) return 0;
return decrypt(stream, stuff, size, output);
}
#define INIT_DICT_HELPER(n, sz) \
stream.dict_helper[n].starts = (uint32_t *)wrkbuf; \
stream.dict_helper[n].ends = &wrkbuf[sz * sizeof(uint32_t)]; \
stream.dict_helper[n].size = sz; \
wrkbuf = &wrkbuf[sz * sizeof(uint32_t) + 0x100];
int unaspack(uint8_t *image, unsigned int size, struct cli_exe_section *sections, uint16_t sectcount, uint32_t ep, uint32_t base, int f, aspack_version_t version)
{
struct ASPK stream;
uint32_t i = 0, j = 0;
uint8_t *blocks = NULL, *wrkbuf;
uint32_t block_rva = 1, block_size;
struct cli_exe_section *outsects;
uint32_t blocks_offset, stream_init_multiplier_offset, comp_block_offset, wrkbuf_offset, oep_offset;
switch (version) {
case ASPACK_VER_212:
cli_dbgmsg("Aspack: Attempting to unpack Aspack 2.12.\n");
blocks_offset = ASPACK_BLOCKS_OFFSET_212;
stream_init_multiplier_offset = ASPACK_STR_INIT_MLT_OFFSET_212;
comp_block_offset = ASPACK_COMP_BLOCK_OFFSET_212;
wrkbuf_offset = ASPACK_WRKBUF_OFFSET_212;
oep_offset = ASPACK_OEP_OFFSET_212;
break;
case ASPACK_VER_OTHER:
cli_dbgmsg("Aspack: Attempting to unpack Aspack >2.12, <2.42.\n");
blocks_offset = ASPACK_BLOCKS_OFFSET_OTHER;
stream_init_multiplier_offset = ASPACK_STR_INIT_MLT_OFFSET_OTHER;
comp_block_offset = ASPACK_COMP_BLOCK_OFFSET_OTHER;
wrkbuf_offset = ASPACK_WRKBUF_OFFSET_OTHER;
oep_offset = ASPACK_OEP_OFFSET_OTHER;
break;
case ASPACK_VER_242:
cli_dbgmsg("Aspack: Attempting to unpack Aspack 2.42.\n");
blocks_offset = ASPACK_BLOCKS_OFFSET_242;
stream_init_multiplier_offset = ASPACK_STR_INIT_MLT_OFFSET_242;
comp_block_offset = ASPACK_COMP_BLOCK_OFFSET_242;
wrkbuf_offset = ASPACK_WRKBUF_OFFSET_242;
oep_offset = ASPACK_OEP_OFFSET_242;
break;
default:
cli_dbgmsg("Aspack: Unexpected/Unknown version number.\n");
return 0;
}
blocks = image + ep + blocks_offset;
if (!(wrkbuf = calloc(0x1800, sizeof(uint8_t)))) {
cli_dbgmsg("Aspack: Unable to allocate dictionary\n");
return 0;
}
INIT_DICT_HELPER(0, 721); /* dictionary -> dictionary + b44 */
INIT_DICT_HELPER(1, 28); /* dictionary + c44 -> dictionary + cb4 */
INIT_DICT_HELPER(2, 8); /* dictionary + db4 -> dictionary + dd4 */
INIT_DICT_HELPER(3, 19); /* dictionary + ed4 -> dictionary + f20 */
stream.decrypt_dict = wrkbuf;
stream.hash = 0x10000;
for (i = 0; i < 58; i++) {
stream.init_array[i] = j;
if (ep + i + stream_init_multiplier_offset < size) {
j += (1 << image[ep + i + stream_init_multiplier_offset]);
}
}
memset(stream.array1, 0, sizeof(stream.array1));
memset(stream.array2, 0, sizeof(stream.array2));
i = 0;
while (CLI_ISCONTAINED(image, size, blocks, 8) && (block_rva = cli_readint32(blocks)) && (block_size = cli_readint32(blocks + 4)) && CLI_ISCONTAINED(image, size, image + block_rva, block_size)) {
cli_dbgmsg("Aspack: unpacking block rva:%x - sz:%x\n", block_rva, block_size);
wrkbuf = (uint8_t *)cli_max_calloc(block_size + 0x10e, sizeof(uint8_t));
if (!wrkbuf) {
cli_dbgmsg("Aspack: Null work buff\n");
break;
}
stream.input = wrkbuf;
stream.iend = &wrkbuf[block_size + 0x10e];
memcpy(wrkbuf, image + block_rva, block_size);
if (!decomp_block(&stream, block_size, &image[ep + comp_block_offset], image + block_rva)) {
cli_dbgmsg("Aspack: decomp_block failed\n");
free(wrkbuf);
break;
} else
cli_dbgmsg("Aspack: decomp block succeed\n");
free(wrkbuf);
if (i == 0 && block_size > 7) { /* first sect j/c unrolling */
while (i < block_size - 6) {
uint8_t curbyte = image[block_rva + i];
if (curbyte == 0xe8 || curbyte == 0xe9) {
wrkbuf = &image[block_rva + i + 1];
if (*wrkbuf == image[ep + wrkbuf_offset]) {
uint32_t target = cli_readint32(wrkbuf) & 0xffffff00;
CLI_ROL(target, 0x18);
cli_writeint32(wrkbuf, target - i);
i += 4;
}
}
i++;
}
}
if (version == ASPACK_VER_212) {
blocks += 8;
} else {
blocks += 12;
block_size = cli_readint32(blocks + 4);
while (!((block_size + 0x10e) & 0xffffffff)) {
blocks += 12;
block_size = cli_readint32(blocks + 4);
}
}
}
cli_dbgmsg("Aspack: leaving loop all uncompressed\n");
free(stream.dict_helper[0].starts);
if (block_rva) {
cli_dbgmsg("Aspack: unpacking failure\n");
return 0;
}
if (sectcount > 2 && ep == sections[sectcount - 2].rva && !sections[sectcount - 1].rsz) {
sectcount -= 2;
}
if (!(outsects = cli_max_malloc(sizeof(struct cli_exe_section) * sectcount))) {
cli_dbgmsg("Aspack: OOM - rebuild failed\n");
cli_writen(f, image, size);
return 1; /* No whatsoheader - won't infloop in pe.c */
}
memcpy(outsects, sections, sizeof(struct cli_exe_section) * sectcount);
for (i = 0; i < sectcount; i++) {
outsects[i].raw = outsects[i].rva;
outsects[i].rsz = outsects[i].vsz;
}
if (!cli_rebuildpe((char *)image, outsects, sectcount, base, cli_readint32(image + ep + oep_offset), 0, 0, f)) {
cli_dbgmsg("Aspack: rebuild failed\n");
cli_writen(f, image, size);
} else {
cli_dbgmsg("Aspack: successfully rebuilt\n");
}
free(outsects);
return 1;
}