Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 55 additions & 36 deletions libclamav/xlm_extract.c
Original file line number Diff line number Diff line change
Expand Up @@ -3835,17 +3835,17 @@ static const char *get_function_name(unsigned index)
}
}

static cl_error_t parse_formula(FILE *out_file, char data[], unsigned data_size)
static cl_error_t parse_formula(FILE *out_file, const uint8_t data[], size_t data_size)
{
cl_error_t status = CL_EFORMAT;
unsigned data_pos = 0;
size_t data_pos = 0;
int len;
size_t size_written;

while (data_pos < data_size) {
ptg_expr ptg = data[data_pos] & 0x7f;

if (((uint8_t)data[data_pos]) < sizeof(TOKENS) / sizeof(TOKENS[0])) {
if (ptg < sizeof(TOKENS) / sizeof(TOKENS[0]) && NULL != TOKENS[ptg]) {
len = fprintf(out_file, " %s", TOKENS[ptg]);
if (len < 0) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Error formatting token name\n");
Expand All @@ -3869,21 +3869,28 @@ static cl_error_t parse_formula(FILE *out_file, char data[], unsigned data_size)
case ptgRange:
data_pos += 1;
break;
case ptgStr:
if (data_pos + 2 >= data_size) {
case ptgStr: {
size_t char_count;
size_t str_len;

if (data_size - data_pos < 3) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Malformed ptgStr record\n");
goto done;
}

if (data[data_pos + 2] == 1 && data_pos + 2 + 2 * data[data_pos + 1] <= data_size) {
char_count = data[data_pos + 1];

if (data[data_pos + 2] == 1) {
char *utf8 = NULL;
size_t utf8_size = 0;
// TODO: Is this really times two here? Or is the string length in bytes?
size_t str_len = data[data_pos + 1] * 2;
if (str_len > data_size - data_pos) {
str_len = data_size - data_pos;

if (char_count > (data_size - data_pos - 3) / 2) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Truncated UTF16 ptgStr record\n");
goto done;
}
if (CL_SUCCESS == cli_codepage_to_utf8(&data[data_pos + 3], str_len, CODEPAGE_UTF16_LE, &utf8, &utf8_size)) {

str_len = char_count * 2;
if (CL_SUCCESS == cli_codepage_to_utf8((char *)&data[data_pos + 3], str_len, CODEPAGE_UTF16_LE, &utf8, &utf8_size)) {
if (0 < utf8_size) {
size_written = fwrite(utf8, 1, utf8_size, out_file);
free(utf8);
Expand All @@ -3901,15 +3908,17 @@ static cl_error_t parse_formula(FILE *out_file, char data[], unsigned data_size)
}
}
data_pos += 3 + str_len;
} else if (data[data_pos + 2] == 0 && data_pos + 2 + data[data_pos + 1] <= data_size) {
unsigned str_len = data[data_pos + 1];
if (str_len > data_size - data_pos) {
str_len = data_size - data_pos;
} else if (data[data_pos + 2] == 0) {
if (char_count > data_size - data_pos - 3) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Truncated ANSI ptgStr record\n");
goto done;
}

str_len = char_count;
if (0 < str_len) {
size_written = fwrite(&data[data_pos], 1, str_len, out_file);
size_written = fwrite(&data[data_pos + 3], 1, str_len, out_file);
if (size_written < str_len) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images] Error writing STRING record message with UTF16LE content\n");
cli_dbgmsg("[cli_extract_xlm_macros_and_images] Error writing ptgStr record with ANSI content\n");
goto done;
}
}
Expand All @@ -3919,22 +3928,23 @@ static cl_error_t parse_formula(FILE *out_file, char data[], unsigned data_size)
goto done;
}
break;
}
case ptgAttr:
if (data_pos + 1 >= data_size) {
if (data_size - data_pos < 4) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Malformed ptgAttr record\n");
goto done;
}

if (data[data_pos + 1] & 0x40) {
uint16_t coffset;

if (data_pos + 3 >= data_size) {
coffset = data[data_pos + 2] | (data[data_pos + 3] << 8);

if ((size_t)coffset + 1 > (data_size - data_pos - 4) / 2) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Malformed ptgAttrChoose record\n");
goto done;
}

coffset = data[data_pos + 2] | (data[data_pos + 3] << 8);

len = fprintf(out_file, " CHOOSE (%u)", (unsigned)(coffset + 1));
if (len < 0) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Error formatting ptgAttr message\n");
Expand All @@ -3947,7 +3957,7 @@ static cl_error_t parse_formula(FILE *out_file, char data[], unsigned data_size)
}
break;
case ptgBool:
if (data_pos + 1 >= data_size) {
if (data_size - data_pos < 2) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Malformed ptgBool record\n");
goto done;
}
Expand All @@ -3961,7 +3971,7 @@ static cl_error_t parse_formula(FILE *out_file, char data[], unsigned data_size)
data_pos += 2;
break;
case ptgInt:
if (data_pos + 2 >= data_size) {
if (data_size - data_pos < 3) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Malformed ptgInt record\n");
goto done;
}
Expand All @@ -3977,7 +3987,7 @@ static cl_error_t parse_formula(FILE *out_file, char data[], unsigned data_size)
case ptgFunc:
case ptgFuncV:
case ptgFuncA: {
if (data_pos + 2 >= data_size) {
if (data_size - data_pos < 3) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Malformed ptgFunc record\n");
goto done;
}
Expand All @@ -3997,7 +4007,7 @@ static cl_error_t parse_formula(FILE *out_file, char data[], unsigned data_size)
case ptgFuncVar:
case ptgFuncVarV:
case ptgFuncVarA: {
if (data_pos + 3 >= data_size) {
if (data_size - data_pos < 4) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Malformed ptgFuncVar record\n");
goto done;
}
Expand All @@ -4018,12 +4028,16 @@ static cl_error_t parse_formula(FILE *out_file, char data[], unsigned data_size)

data_pos += 4;
if (func_id == 0x806d) {
if (data_size - data_pos < 9) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Malformed extended ptgFuncVar record\n");
goto done;
}
data_pos += 9;
}
break;
}
case ptgName: {
if (data_pos + 4 >= data_size) {
if (data_size - data_pos < 5) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Malformed ptgName record\n");
goto done;
}
Expand All @@ -4040,7 +4054,7 @@ static cl_error_t parse_formula(FILE *out_file, char data[], unsigned data_size)
break;
}
case ptgNum: {
if (data_pos + 8 >= data_size) {
if (data_size - data_pos < 9) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Malformed ptgNum record\n");
goto done;
}
Expand All @@ -4059,7 +4073,7 @@ static cl_error_t parse_formula(FILE *out_file, char data[], unsigned data_size)
break;
}
case ptgMemArea: {
if (data_pos + 6 >= data_size) {
if (data_size - data_pos < 7) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Malformed ptgMemArea record\n");
goto done;
}
Expand All @@ -4074,7 +4088,7 @@ static cl_error_t parse_formula(FILE *out_file, char data[], unsigned data_size)
break;
}
case ptgExp: {
if (data_pos + 4 >= data_size) {
if (data_size - data_pos < 5) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Malformed ptgExp record\n");
goto done;
}
Expand All @@ -4092,7 +4106,7 @@ static cl_error_t parse_formula(FILE *out_file, char data[], unsigned data_size)
}
case ptgRef:
case ptgRefV: {
if (data_pos + 4 >= data_size) {
if (data_size - data_pos < 5) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Malformed ptgRef record\n");
goto done;
}
Expand All @@ -4115,7 +4129,7 @@ static cl_error_t parse_formula(FILE *out_file, char data[], unsigned data_size)
break;
}
case ptgArea: {
if (data_pos + 8 >= data_size) {
if (data_size - data_pos < 9) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Malformed ptgArea record\n");
goto done;
}
Expand Down Expand Up @@ -4146,7 +4160,7 @@ static cl_error_t parse_formula(FILE *out_file, char data[], unsigned data_size)
}
case ptgRef3d:
case ptgRef3dV: {
if (data_pos + 6 >= data_size) {
if (data_size - data_pos < 7) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Malformed ptgRef3d record\n");
goto done;
}
Expand All @@ -4170,7 +4184,7 @@ static cl_error_t parse_formula(FILE *out_file, char data[], unsigned data_size)
break;
}
case ptgNameX: {
if (data_pos + 6 >= data_size) {
if (data_size - data_pos < 7) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Malformed ptgNameX record\n");
goto done;
}
Expand All @@ -4190,7 +4204,7 @@ static cl_error_t parse_formula(FILE *out_file, char data[], unsigned data_size)
break;
}
default:
if (ptg < sizeof(TOKENS) / sizeof(char *)) {
if (ptg < sizeof(TOKENS) / sizeof(TOKENS[0]) && NULL != TOKENS[ptg]) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Encountered unexpected ptg token: %s\n", TOKENS[ptg]);
} else {
cli_dbgmsg("[cli_extract_xlm_macros_and_images:parse_formula] Encountered unknown ptg token: 0x%02x\n", ptg);
Expand Down Expand Up @@ -4709,7 +4723,7 @@ cl_error_t cli_extract_xlm_macros_and_images(const char *dir, cli_ctx *ctx, char
uint16_t length;
} formula_header;

if (biff_header.length >= 21) {
if (biff_header.length >= 22) {
formula_header.row = data[0] | (data[1] << 8);
formula_header.column = data[2] | (data[3] << 8);
formula_header.length = data[20] | (data[21] << 8);
Expand All @@ -4727,7 +4741,12 @@ cl_error_t cli_extract_xlm_macros_and_images(const char *dir, cli_ctx *ctx, char
break;
}

ret = parse_formula(out_file, &data[22], biff_header.length - 21);
if (formula_header.length > biff_header.length - 22) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images] FORMULA token data exceeds record length\n");
break;
}

ret = parse_formula(out_file, (const uint8_t *)&data[22], formula_header.length);
if (CL_SUCCESS != ret) {
cli_dbgmsg("[cli_extract_xlm_macros_and_images] Error parsing formula in FORMULA record message\n");

Expand Down
6 changes: 6 additions & 0 deletions unit_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ target_compile_definitions(check_clamav PUBLIC OBJDIR="${OBJDIR}" SRCDIR="${SRCD
ADD_CUSTOM_COMMAND(TARGET check_clamav POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/input/clamav.hdb ${CMAKE_CURRENT_BINARY_DIR}/input/.)

add_executable(check_xlm_scanmap check_xlm_scanmap.c)
target_link_libraries(check_xlm_scanmap PRIVATE ClamAV::libclamav)

#
# Paths to pass to our tests via environment variables
#
Expand Down Expand Up @@ -182,6 +185,7 @@ if(WIN32)
endif()
file(TO_NATIVE_PATH $<TARGET_FILE:${LIBMSPACK}> LIBCLAMMSPACK)
file(TO_NATIVE_PATH $<TARGET_FILE:check_clamav> CHECK_CLAMAV)
file(TO_NATIVE_PATH $<TARGET_FILE:check_xlm_scanmap> CHECK_XLM_SCANMAP)
if(ENABLE_APP)
file(TO_NATIVE_PATH $<TARGET_FILE:check_clamd> CHECK_CLAMD)
file(TO_NATIVE_PATH $<TARGET_FILE:check_fpu_endian> CHECK_FPU_ENDIAN)
Expand Down Expand Up @@ -244,6 +248,7 @@ else()
endif()
set(LIBCLAMMSPACK $<TARGET_FILE:${LIBMSPACK}>)
set(CHECK_CLAMAV $<TARGET_FILE:check_clamav>)
set(CHECK_XLM_SCANMAP $<TARGET_FILE:check_xlm_scanmap>)
if(ENABLE_APP)
set(CHECK_CLAMD $<TARGET_FILE:check_clamd>)
set(CHECK_FPU_ENDIAN $<TARGET_FILE:check_fpu_endian>)
Expand Down Expand Up @@ -297,6 +302,7 @@ set(ENVIRONMENT
LIBCLAMUNRARIFACE=${LIBCLAMUNRARIFACE}
LIBCLAMUNRAR=${LIBCLAMUNRAR}
CHECK_CLAMAV=${CHECK_CLAMAV}
CHECK_XLM_SCANMAP=${CHECK_XLM_SCANMAP}
CHECK_CLAMD=${CHECK_CLAMD}
CHECK_FPU_ENDIAN=${CHECK_FPU_ENDIAN}
CLAMBC=${CLAMBC}
Expand Down
95 changes: 95 additions & 0 deletions unit_tests/check_xlm_scanmap.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (C) 2026 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*
* Exercise the public scan-map API with XLM regression inputs.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "clamav.h"

static cl_error_t scan_file_as_map(const char *path, const struct cl_engine *engine)
{
cl_error_t status = CL_EOPEN;
cl_fmap_t *map = NULL;
FILE *file = NULL;
unsigned char *data = NULL;
long file_size;
const char *virus_name = NULL;
unsigned long scanned = 0;
struct cl_scan_options options;

file = fopen(path, "rb");
if (NULL == file || 0 != fseek(file, 0, SEEK_END) || 0 > (file_size = ftell(file)) ||
0 != fseek(file, 0, SEEK_SET)) {
goto done;
}

data = malloc((size_t)file_size);
if (NULL == data) {
status = CL_EMEM;
goto done;
}
if ((size_t)file_size != fread(data, 1, (size_t)file_size, file)) {
status = CL_EREAD;
goto done;
}

map = cl_fmap_open_memory(data, (size_t)file_size);
if (NULL == map) {
status = CL_EMEM;
goto done;
}

memset(&options, 0, sizeof(options));
options.parse = CL_SCAN_PARSE_OLE2;
status = cl_scanmap_callback(map, path, &virus_name, &scanned, engine, &options, NULL);

done:
if (NULL != map) {
cl_fmap_close(map);
}
free(data);
if (NULL != file) {
fclose(file);
}
return status;
}

int main(int argc, char **argv)
{
cl_error_t status;
struct cl_engine *engine = NULL;
int i;

if (argc < 2) {
fprintf(stderr, "Usage: %s FILE...\n", argv[0]);
return 2;
}

status = cl_init(CL_INIT_DEFAULT);
if (CL_SUCCESS != status || NULL == (engine = cl_engine_new())) {
fprintf(stderr, "Failed to initialize ClamAV: %s\n", cl_strerror(status));
return 2;
}
status = cl_engine_compile(engine);
if (CL_SUCCESS != status) {
fprintf(stderr, "Failed to compile ClamAV engine: %s\n", cl_strerror(status));
cl_engine_free(engine);
return 2;
}

for (i = 1; i < argc; i++) {
status = scan_file_as_map(argv[i], engine);
if (CL_SUCCESS != status) {
fprintf(stderr, "Scan-map failed for %s: %s\n", argv[i], cl_strerror(status));
cl_engine_free(engine);
return 1;
}
}

cl_engine_free(engine);
return 0;
}
Loading
Loading