2022-10-22 18:41:00 +08:00
/*
* Byte comparison matcher support functions
*
* Copyright ( C ) 2018 - 2022 Cisco Systems , Inc . and / or its affiliates . All rights reserved .
*
* Authors : Mickey Sola
*
* 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 .
*/
# if HAVE_CONFIG_H
# include "clamav-config.h"
# endif
# include <errno.h>
# include "clamav.h"
# include "others.h"
# include "matcher.h"
# include "matcher-ac.h"
# include "matcher-byte-comp.h"
# include "mpool.h"
# include "readdb.h"
# include "str.h"
/* DEBUGGING */
//#define MATCHER_BCOMP_DEBUG
# ifdef MATCHER_BCOMP_DEBUG
# define bcm_dbgmsg(...) cli_dbgmsg(__VA_ARGS__)
# else
# define bcm_dbgmsg(...)
# endif
# undef MATCHER_BCOMP_DEBUG
/* BCOMP MATCHER FUNCTIONS */
/**
* @ brief function to add the byte compare subsig into the matcher root struct
*
* @ param root the matcher root struct in question , houses all relevant lsig and subsig info
* @ param virname virusname as given by the signature
* @ param hexsig the raw sub signature buffer itself which we will be checking / parsing
* @ param lsigid the numeric internal reference number which can be used to access this lsig in the root struct
* @ param options additional options for pattern matching , stored as a bitmask
*
*/
cl_error_t cli_bcomp_addpatt ( struct cli_matcher * root , const char * virname , const char * hexsig , const uint32_t * lsigid , unsigned int options )
{
size_t len = 0 ;
uint32_t i = 0 ;
const char * buf_start = NULL ;
const char * buf_end = NULL ;
char * buf = NULL ;
const char * tokens [ 4 ] ;
size_t toks = 0 ;
int16_t ref_subsigid = - 1 ;
int64_t offset_param = 0 ;
int64_t ret = CL_SUCCESS ;
size_t byte_length = 0 ;
int64_t comp_val = 0 ;
char * comp_buf = NULL ;
char * comp_start = NULL ;
char * comp_end = NULL ;
2023-01-14 18:28:39 +08:00
UNUSEDPARAM ( options ) ;
2022-10-22 18:41:00 +08:00
if ( ! hexsig | | ! ( * hexsig ) | | ! root | | ! virname ) {
return CL_ENULLARG ;
}
/* we'll be using these to help the root matcher struct keep track of each loaded byte compare pattern */
struct cli_bcomp_meta * * newmetatable ;
uint32_t bcomp_count = 0 ;
/* zero out our byte compare data struct and tie it to the root struct's mempool instance */
struct cli_bcomp_meta * bcomp ;
bcomp = ( struct cli_bcomp_meta * ) MPOOL_CALLOC ( root - > mempool , 1 , sizeof ( * bcomp ) ) ;
if ( ! bcomp ) {
cli_errmsg ( " cli_bcomp_addpatt: Unable to allocate memory for new byte compare meta \n " ) ;
return CL_EMEM ;
}
/* bring along the standard lsigid vector, first param marks validity of vector, 2nd is lsigid, 3rd is subsigid */
if ( lsigid ) {
bcomp - > lsigid [ 0 ] = 1 ;
bcomp - > lsigid [ 1 ] = lsigid [ 0 ] ;
bcomp - > lsigid [ 2 ] = lsigid [ 1 ] ;
} else {
/* sigtool */
bcomp - > lsigid [ 0 ] = 0 ;
}
/* first need to grab the subsig reference, we'll use this later to determine our offset */
buf_start = hexsig ;
buf_end = hexsig ;
ref_subsigid = strtol ( buf_start , ( char * * ) & buf_end , 10 ) ;
if ( buf_end & & buf_end [ 0 ] ! = ' ( ' ) {
cli_errmsg ( " cli_bcomp_addpatt: while byte compare subsig parsing, reference subsig id was invalid or included non-decimal character \n " ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
return CL_EMALFDB ;
}
if ( ref_subsigid > MAX_LDB_SUBSIGS ) {
cli_errmsg ( " cli_bcomp_addpatt: while byte compare subsig parsing, reference subigid exceeded limits on max LDB subsigs \n " ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
return CL_EMALFDB ;
}
bcomp - > ref_subsigid = ref_subsigid ;
/* use the passed hexsig buffer to find the start and ending parens and store the param length (minus starting paren) */
buf_start = buf_end ;
if ( buf_start [ 0 ] = = ' ( ' ) {
if ( ( buf_end = strchr ( buf_start , ' ) ' ) ) ) {
len = ( size_t ) ( buf_end - + + buf_start ) ;
} else {
cli_errmsg ( " cli_bcomp_addpatt: ending paren not found \n " ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
return CL_EMALFDB ;
}
} else {
cli_errmsg ( " cli_bcomp_addpatt: opening paren not found \n " ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
return CL_EMALFDB ;
}
/* make a working copy of the param buffer */
buf = CLI_STRNDUP ( buf_start , len ) ;
/* break up the new param buffer into its component strings and verify we have exactly 3 */
toks = cli_strtokenize ( buf , ' # ' , 3 + 1 , tokens ) ;
if ( 3 ! = toks ) {
cli_errmsg ( " cli_bcomp_addpatt: %zu (or more) params provided, 3 expected \n " , toks ) ;
free ( buf ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
return CL_EMALFDB ;
}
tokens [ 3 ] = NULL ;
/* since null termination is super guaranteed thanks to strndup and cli_strokenize, we can use strtol to grab the
* offset params . this has the added benefit of letting us parse hex values too */
buf_end = NULL ;
buf_start = tokens [ 0 ] ;
switch ( buf_start [ 0 ] ) {
case ' < ' :
if ( ( + + buf_start ) [ 0 ] = = ' < ' ) {
offset_param = strtol ( + + buf_start , ( char * * ) & buf_end , 0 ) ;
if ( buf_end & & buf_end + 1 ! = tokens [ 1 ] ) {
cli_errmsg ( " cli_bcomp_addpatt: while parsing (%s#%s#%s), offset parameter included invalid characters \n " , tokens [ 0 ] , tokens [ 1 ] , tokens [ 2 ] ) ;
free ( buf ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
return CL_EMALFDB ;
}
/* two's-complement for negative value */
offset_param = ( ~ offset_param ) + 1 ;
} else {
cli_errmsg ( " cli_bcomp_addpatt: while parsing (%s#%s#%s), shift operator not valid \n " , tokens [ 0 ] , tokens [ 1 ] , tokens [ 2 ] ) ;
free ( buf ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
return CL_EMALFDB ;
}
break ;
case ' > ' :
if ( ( + + buf_start ) [ 0 ] = = ' > ' ) {
offset_param = strtol ( + + buf_start , ( char * * ) & buf_end , 0 ) ;
if ( buf_end & & buf_end + 1 ! = tokens [ 1 ] ) {
cli_errmsg ( " cli_bcomp_addpatt: while parsing (%s#%s#%s), offset parameter included invalid characters \n " , tokens [ 0 ] , tokens [ 1 ] , tokens [ 2 ] ) ;
free ( buf ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
return CL_EMALFDB ;
}
break ;
} else {
cli_errmsg ( " cli_bcomp_addpatt: while parsing (%s#%s#%s), shift operator and/or offset not valid \n " , tokens [ 0 ] , tokens [ 1 ] , tokens [ 2 ] ) ;
free ( buf ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
return CL_EMALFDB ;
}
case ' 0 ' :
case ' \0 ' :
offset_param = 0 ;
break ;
default :
cli_errmsg ( " cli_bcomp_addpatt: while parsing (%s#%s#%s), shift operator included invalid characters \n " , tokens [ 0 ] , tokens [ 1 ] , tokens [ 2 ] ) ;
free ( buf ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
return CL_EMALFDB ;
}
bcomp - > offset = offset_param ;
/* the byte length indicator options are stored in a bitmask--by design each option gets its own nibble */
buf_start = tokens [ 1 ] ;
while ( ! isdigit ( * buf_start ) ) {
switch ( * buf_start ) {
case ' h ' :
/* hex, decimal, auto, and binary options are mutually exclusive parameters */
if ( bcomp - > options & CLI_BCOMP_DEC | | bcomp - > options & CLI_BCOMP_BIN | | bcomp - > options & CLI_BCOMP_AUTO ) {
ret = CL_EMALFDB ;
} else {
bcomp - > options | = CLI_BCOMP_HEX ;
}
break ;
case ' d ' :
/* hex, decimal, auto, and binary options are mutually exclusive parameters */
/* decimal may not be used with little-endian. big-endian is implied. */
if ( bcomp - > options & CLI_BCOMP_HEX | | bcomp - > options & CLI_BCOMP_BIN | | bcomp - > options & CLI_BCOMP_AUTO | | bcomp - > options & CLI_BCOMP_LE ) {
ret = CL_EMALFDB ;
} else {
bcomp - > options | = CLI_BCOMP_DEC ;
bcomp - > options | = CLI_BCOMP_BE ;
}
break ;
case ' i ' :
/* hex, decimal, auto, and binary options are mutually exclusive parameters */
if ( bcomp - > options & CLI_BCOMP_HEX | | bcomp - > options & CLI_BCOMP_DEC | | bcomp - > options & CLI_BCOMP_AUTO ) {
ret = CL_EMALFDB ;
} else {
bcomp - > options | = CLI_BCOMP_BIN ;
}
break ;
case ' a ' :
/* for automatic hex or decimal run-time detection */
/* hex, decimal, auto, and binary options are mutually exclusive parameters */
if ( bcomp - > options & CLI_BCOMP_HEX | | bcomp - > options & CLI_BCOMP_DEC | | bcomp - > options & CLI_BCOMP_BIN ) {
ret = CL_EMALFDB ;
} else {
bcomp - > options | = CLI_BCOMP_AUTO ;
}
break ;
case ' l ' :
/* little and big endian options are mutually exclusive parameters */
/* decimal may not be used with little-endian */
if ( bcomp - > options & CLI_BCOMP_BE | | bcomp - > options & CLI_BCOMP_DEC ) {
ret = CL_EMALFDB ;
} else {
bcomp - > options | = CLI_BCOMP_LE ;
}
break ;
case ' b ' :
/* little and big endian options are mutually exclusive parameters */
if ( bcomp - > options & CLI_BCOMP_LE ) {
ret = CL_EMALFDB ;
} else {
bcomp - > options | = CLI_BCOMP_BE ;
}
break ;
case ' e ' :
/* for exact byte length matches */
bcomp - > options | = CLI_BCOMP_EXACT ;
break ;
default :
ret = CL_EMALFDB ;
break ;
}
if ( CL_EMALFDB = = ret ) {
cli_errmsg ( " cli_bcomp_addpatt: while parsing (%s#%s#%s), option parameter was found invalid \n " , tokens [ 0 ] , tokens [ 1 ] , tokens [ 2 ] ) ;
free ( buf ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
return ret ;
}
buf_start + + ;
}
/* parse out the byte length parameter */
buf_end = NULL ;
byte_length = strtol ( buf_start , ( char * * ) & buf_end , 0 ) ;
2023-01-14 18:28:39 +08:00
if ( ( buf_end & & buf_end + 1 ! = tokens [ 2 ] ) | | ( 0 = = byte_length ) ) {
2022-10-22 18:41:00 +08:00
cli_errmsg ( " cli_bcomp_addpatt: while parsing (%s#%s#%s), byte length parameter included invalid characters \n " , tokens [ 0 ] , tokens [ 1 ] , tokens [ 2 ] ) ;
free ( buf ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
return CL_EMALFDB ;
}
if ( bcomp - > options & CLI_BCOMP_BIN & & ( byte_length > CLI_BCOMP_MAX_BIN_BLEN | | CLI_BCOMP_MAX_BIN_BLEN % byte_length ) ) {
cli_errmsg ( " cli_bcomp_addpatt: while parsing (%s#%s#%s), byte length was either too long or not a valid number of bytes \n " , tokens [ 0 ] , tokens [ 1 ] , tokens [ 2 ] ) ;
free ( buf ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
return CL_EMALFDB ;
}
/* same deal with hex byte lengths */
if ( bcomp - > options & CLI_BCOMP_HEX & & ( byte_length > CLI_BCOMP_MAX_HEX_BLEN ) ) {
cli_errmsg ( " cli_bcomp_addpatt: while parsing (%s#%s#%s), byte length was too long \n " , tokens [ 0 ] , tokens [ 1 ] , tokens [ 2 ] ) ;
free ( buf ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
return CL_EMALFDB ;
}
bcomp - > byte_len = byte_length ;
/* we can have up to two comparison eval statements, each sperated by a comma, let's parse them in a separate string */
comp_buf = cli_strdup ( tokens [ 2 ] ) ;
if ( ! comp_buf ) {
cli_errmsg ( " cli_bcomp_addpatt: Unable to allocate memory for comparison buffer \n " ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
return CL_EMEM ;
}
/* use different buffer start and end markers so we can keep track of what we need to free later */
buf_start = comp_buf ;
comp_start = strchr ( comp_buf , ' , ' ) ;
comp_end = strrchr ( comp_buf , ' , ' ) ;
/* check to see if we have exactly one comma, then set our count and tokenize our string apropriately */
if ( comp_start & & comp_end ) {
if ( comp_end = = comp_start ) {
comp_start [ 0 ] = ' \0 ' ;
bcomp - > comp_count = 2 ;
} else {
cli_errmsg ( " cli_bcomp_addpatt: while parsing (%s#%s#%s), too many commas found in comparison string \n " , tokens [ 0 ] , tokens [ 1 ] , tokens [ 2 ] ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
free ( buf ) ;
free ( ( void * ) buf_start ) ;
return CL_EPARSE ;
}
} else {
comp_start = comp_buf ;
bcomp - > comp_count = 1 ;
}
/* allocate comp struct list space with the root structure's mempool instance */
bcomp - > comps = ( struct cli_bcomp_comp * * ) MPOOL_CALLOC ( root - > mempool , bcomp - > comp_count , sizeof ( struct cli_bcomp_comp * ) ) ;
if ( ! bcomp - > comps ) {
cli_errmsg ( " cli_bcomp_addpatt: unable to allocate memory for comp struct pointers \n " ) ;
free ( buf ) ;
free ( ( void * ) buf_start ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
return CL_EMEM ;
}
/* loop through our new list, allocate, and parse out the needed comparison evaluation bits for this subsig */
for ( i = 0 ; i < bcomp - > comp_count ; i + + ) {
bcomp - > comps [ i ] = ( struct cli_bcomp_comp * ) MPOOL_CALLOC ( root - > mempool , 1 , sizeof ( struct cli_bcomp_comp ) ) ;
2023-01-14 18:28:39 +08:00
if ( ! bcomp - > comps [ i ] ) {
2022-10-22 18:41:00 +08:00
cli_errmsg ( " cli_bcomp_addpatt: unable to allocate memory for comp struct \n " ) ;
free ( buf ) ;
free ( ( void * ) buf_start ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
return CL_EMEM ;
}
/* currently only >, <, and = are supported comparison symbols--this makes parsing very simple */
switch ( * comp_buf ) {
case ' < ' :
case ' > ' :
case ' = ' :
bcomp - > comps [ i ] - > comp_symbol = * comp_buf ;
break ;
default :
cli_errmsg ( " cli_bcomp_addpatt: while parsing (%s#%s#%s), byte comparison symbol was invalid (>, <, = are supported operators) %s \n " , tokens [ 0 ] , tokens [ 1 ] , tokens [ 2 ] , comp_buf ) ;
free ( buf ) ;
free ( ( void * ) buf_start ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
return CL_EMALFDB ;
}
/* grab the comparison value itself */
comp_end = NULL ;
comp_buf + + ;
comp_val = strtoll ( comp_buf , ( char * * ) & comp_end , 0 ) ;
if ( * comp_end ) {
cli_errmsg ( " cli_bcomp_addpatt: while parsing (%s#%s#%s), comparison value contained invalid input \n " , tokens [ 0 ] , tokens [ 1 ] , tokens [ 2 ] ) ;
free ( buf ) ;
free ( ( void * ) buf_start ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
return CL_EMALFDB ;
}
bcomp - > comps [ i ] - > comp_value = comp_val ;
/* a bit of tricksy pointer stuffs which handles all count cases, taking advantage of where strtoll drops endptr */
if ( comp_end = = comp_start ) {
comp_buf = comp_start ;
comp_buf + + ;
}
/* manually verify successful pattern parsing */
bcm_dbgmsg ( " Matcher Byte Compare: (%s%ld#%c%c%s%zu#%c%ld) \n " ,
bcomp - > offset = = 0 ? " " : ( bcomp - > offset < 0 ? " << " : " >> " ) ,
bcomp - > offset ,
bcomp - > options & CLI_BCOMP_HEX ? ' h ' : ( bcomp - > options & CLI_BCOMP_DEC ? ' d ' : ' i ' ) ,
bcomp - > options & CLI_BCOMP_LE ? ' l ' : ' b ' ,
bcomp - > options & CLI_BCOMP_EXACT ? " e " : " " ,
bcomp - > byte_len ,
bcomp - > comps [ i ] - > comp_symbol ,
bcomp - > comps [ i ] - > comp_value ) ;
}
free ( ( void * ) buf_start ) ;
buf_start = NULL ;
/* add byte compare info to the root after reallocation */
bcomp_count = root - > bcomp_metas + 1 ;
/* allocate space for new meta table to store in root structure and increment number of byte compare patterns added */
newmetatable = ( struct cli_bcomp_meta * * ) MPOOL_REALLOC ( root - > mempool , root - > bcomp_metatable , bcomp_count * sizeof ( struct cli_bcomp_meta * ) ) ;
if ( ! newmetatable ) {
cli_errmsg ( " cli_bcomp_addpatt: Unable to allocate memory for new bcomp meta table \n " ) ;
cli_bcomp_freemeta ( root , bcomp ) ;
return CL_EMEM ;
}
newmetatable [ bcomp_count - 1 ] = bcomp ;
root - > bcomp_metatable = newmetatable ;
root - > bcomp_metas = bcomp_count ;
/* if everything went well bcomp has been totally populated, which means we can cleanup and exit */
free ( buf ) ;
return CL_SUCCESS ;
}
/**
* @ brief function to perform all byte compare matching on the file buffer
*
* @ param map the file map to perform logical byte comparison upon
* @ param res the result structure , primarily used by sigtool
* @ param root the root structure in which all byte compare lsig and subsig information is stored
* @ param mdata the ac data struct which contains offset information from recent subsig matches
* @ param ctx the clamav context struct
*
*/
cl_error_t cli_bcomp_scanbuf ( const unsigned char * buffer , size_t buffer_length , struct cli_ac_result * * res , const struct cli_matcher * root , struct cli_ac_data * mdata , cli_ctx * ctx )
{
size_t i ;
2023-01-14 18:28:39 +08:00
int val = 0 ;
2022-10-22 18:41:00 +08:00
cl_error_t ret = CL_SUCCESS ;
cl_error_t bcomp_check = CL_SUCCESS ;
uint32_t lsigid , ref_subsigid ;
uint32_t offset = 0 ;
struct cli_bcomp_meta * bcomp = NULL ;
struct cli_ac_result * newres = NULL ;
uint32_t evalcnt = 0 ;
uint64_t evalids = 0 ;
char subsigid [ 3 ] ;
if ( ! ( root ) | | ! ( root - > bcomp_metas ) | | ! ( root - > bcomp_metatable ) | | ! ( mdata ) | | ! ( mdata - > offmatrix ) | | ! ( ctx ) ) {
return CL_SUCCESS ;
}
for ( i = 0 ; i < root - > bcomp_metas ; i + + ) {
bcomp = root - > bcomp_metatable [ i ] ;
lsigid = bcomp - > lsigid [ 1 ] ;
ref_subsigid = bcomp - > ref_subsigid ;
/* check to see if we are being run in sigtool or not */
if ( bcomp - > lsigid [ 0 ] ) {
snprintf ( subsigid , 3 , " %hu " , bcomp - > ref_subsigid ) ;
/* verify the ref_subsigid */
val = cli_ac_chklsig ( subsigid , subsigid + strlen ( subsigid ) , mdata - > lsigcnt [ bcomp - > lsigid [ 1 ] ] , & evalcnt , & evalids , 0 ) ;
if ( val ! = 1 ) {
bcm_dbgmsg ( " cli_bcomp_scanbuf: could not verify a match for lsig reference subsigid (%s) \n " , subsigid ) ;
continue ;
}
/* grab the needed offset using from the last matched subsig offset matrix, i.e. the match performed above */
if ( mdata - > lsigsuboff_last [ lsigid ] ) {
offset = mdata - > lsigsuboff_last [ lsigid ] [ ref_subsigid ] ;
} else {
ret = CL_SUCCESS ;
continue ;
}
} else {
/* mdata isn't populated in sigtool so run the raw matcher stuffs */
if ( res ) {
newres = ( struct cli_ac_result * ) cli_calloc ( 1 , sizeof ( struct cli_ac_result ) ) ;
if ( ! newres ) {
cli_errmsg ( " cli_bcomp_scanbuf: can't allocate memory for new result \n " ) ;
ret = CL_EMEM ;
break ;
}
2023-01-14 18:28:39 +08:00
newres - > virname = " test " ;
2022-10-22 18:41:00 +08:00
newres - > customdata = NULL ;
newres - > next = * res ;
* res = newres ;
}
}
/* no offset available, make a best effort */
if ( offset = = CLI_OFF_NONE ) {
offset = 0 ;
}
/* now we have all the pieces of the puzzle, so lets do our byte compare check */
bcomp_check = cli_bcomp_compare_check ( buffer , buffer_length , offset , bcomp ) ;
/* Increase the lsig count for our subsig if the comparison came back positive.
* Later , the lsig - eval will evaluate the logical condition , based on these counts
* and will append the virus alert if the whole logical signature matches . */
if ( CL_VIRUS = = bcomp_check ) {
2023-01-14 18:28:39 +08:00
/* check to see if we are being run in sigtool or not */
if ( bcomp - > lsigid [ 0 ] ) {
mdata - > lsigcnt [ bcomp - > lsigid [ 1 ] ] [ bcomp - > lsigid [ 2 ] ] + + ;
} else {
/* Run by sigtool's --test-sigs feature without context of whole lsig or previous subsigs */
ret = cli_append_virus ( ctx , " test " ) ;
}
2022-10-22 18:41:00 +08:00
}
}
return ret ;
}
/**
* @ brief does a numerical , logical byte comparison on a particular offset given a filemapping and the offset
*
* @ param map the file buffer we ' ll be accessing to do our comparison check
* @ param offset the offset of the referenced subsig match from the start of the file buffer
* @ param bm the byte comparison meta data struct , contains all the other info needed to do the comparison
*
*/
cl_error_t cli_bcomp_compare_check ( const unsigned char * f_buffer , size_t buffer_length , int offset , struct cli_bcomp_meta * bm )
{
uint32_t byte_len = 0 ;
uint32_t pad_len = 0 ;
uint32_t norm_len = 0 ;
uint32_t length = 0 ;
uint32_t i = 0 ;
2023-01-14 18:28:39 +08:00
cl_error_t ret = CL_CLEAN ;
2022-10-22 18:41:00 +08:00
uint16_t opt = 0 ;
uint16_t opt_val = 0 ;
int64_t value = 0 ;
int64_t bin_value = 0 ;
int16_t compare_check = 0 ;
unsigned char * end_buf = NULL ;
2023-01-14 18:28:39 +08:00
unsigned char * buffer = NULL ; /* Used for BE, non-binary comparisons */
unsigned char * tmp_buffer = NULL ; /* Used for LE, non-binary comparisons */
2022-10-22 18:41:00 +08:00
if ( ! f_buffer | | ! bm ) {
bcm_dbgmsg ( " cli_bcomp_compare_check: a param is null \n " ) ;
2023-01-14 18:28:39 +08:00
ret = CL_ENULLARG ;
goto done ;
2022-10-22 18:41:00 +08:00
}
byte_len = bm - > byte_len ;
length = buffer_length ;
opt = bm - > options ;
/* ensure we won't run off the end of the file buffer */
if ( ! ( offset + bm - > offset + byte_len < = length ) ) {
bcm_dbgmsg ( " cli_bcomp_compare_check: %u bytes requested at offset %zu would go past file buffer of %u \n " , byte_len , ( offset + bm - > offset ) , length ) ;
2023-01-14 18:28:39 +08:00
goto done ;
2022-10-22 18:41:00 +08:00
}
if ( ! ( offset + bm - > offset > 0 ) ) {
bcm_dbgmsg ( " cli_bcomp_compare_check: negative offset would underflow buffer \n " ) ;
2023-01-14 18:28:39 +08:00
goto done ;
2022-10-22 18:41:00 +08:00
}
/* jump to byte compare offset, then store off specified bytes into a null terminated buffer */
offset + = bm - > offset ;
f_buffer + = offset ;
bcm_dbgmsg ( " cli_bcomp_compare_check: literal extracted bytes before comparison %.*s \n " , byte_len , f_buffer ) ;
/* normalize buffer for whitespace */
opt_val = opt & 0x000F ;
if ( ! ( opt_val & CLI_BCOMP_BIN ) ) {
buffer = cli_bcomp_normalize_buffer ( f_buffer , byte_len , & pad_len , opt , 1 ) ;
if ( NULL = = buffer ) {
cli_errmsg ( " cli_bcomp_compare_check: unable to whitespace normalize temp buffer, allocation failed \n " ) ;
2023-01-14 18:28:39 +08:00
ret = CL_EMEM ;
goto done ;
2022-10-22 18:41:00 +08:00
}
/* adjust byte_len accordingly */
byte_len - = pad_len ;
}
/* normalize buffer for little endian vals */
opt_val = opt & 0x00F0 ;
if ( opt_val = = CLI_BCOMP_LE ) {
opt_val = opt & 0x000F ;
if ( ! ( opt_val & CLI_BCOMP_BIN ) ) {
tmp_buffer = cli_bcomp_normalize_buffer ( buffer , byte_len , NULL , opt , 0 ) ;
if ( NULL = = tmp_buffer ) {
cli_errmsg ( " cli_bcomp_compare_check: unable to normalize temp, allocation failed \n " ) ;
2023-01-14 18:28:39 +08:00
ret = CL_EMEM ;
goto done ;
2022-10-22 18:41:00 +08:00
}
}
}
opt_val = opt ;
if ( opt_val & CLI_BCOMP_AUTO ) {
opt = cli_bcomp_chk_hex ( buffer , opt_val , byte_len , 0 ) ;
}
/* grab the first byte to handle byte length options to convert the string appropriately */
switch ( opt & 0x00FF ) {
/*hl*/
case CLI_BCOMP_HEX | CLI_BCOMP_LE :
if ( byte_len ! = 1 ) {
norm_len = ( byte_len % 2 ) = = 0 ? byte_len : byte_len + 1 ;
} else {
norm_len = 1 ;
}
errno = 0 ;
value = cli_strntol ( ( char * ) tmp_buffer , norm_len , ( char * * ) & end_buf , 16 ) ;
if ( ( ( ( value = = LONG_MAX ) | | ( value = = LONG_MIN ) ) & & errno = = ERANGE ) | | NULL = = end_buf ) {
bcm_dbgmsg ( " cli_bcomp_compare_check: little endian hex conversion unsuccessful \n " ) ;
2023-01-14 18:28:39 +08:00
goto done ;
2022-10-22 18:41:00 +08:00
}
/*hle*/
if ( opt & CLI_BCOMP_EXACT ) {
if ( tmp_buffer + byte_len ! = end_buf | | pad_len ! = 0 ) {
bcm_dbgmsg ( " cli_bcomp_compare_check: couldn't extract the exact number of requested bytes \n " ) ;
2023-01-14 18:28:39 +08:00
goto done ;
2022-10-22 18:41:00 +08:00
}
}
break ;
/*hb*/
case CLI_BCOMP_HEX | CLI_BCOMP_BE :
value = cli_strntol ( ( char * ) buffer , byte_len , ( char * * ) & end_buf , 16 ) ;
if ( ( ( ( value = = LONG_MAX ) | | ( value = = LONG_MIN ) ) & & errno = = ERANGE ) | | NULL = = end_buf ) {
bcm_dbgmsg ( " cli_bcomp_compare_check: big endian hex conversion unsuccessful \n " ) ;
2023-01-14 18:28:39 +08:00
goto done ;
2022-10-22 18:41:00 +08:00
}
/*hbe*/
if ( opt & CLI_BCOMP_EXACT ) {
if ( buffer + byte_len ! = end_buf | | pad_len ! = 0 ) {
bcm_dbgmsg ( " cli_bcomp_compare_check: couldn't extract the exact number of requested bytes \n " ) ;
2023-01-14 18:28:39 +08:00
goto done ;
2022-10-22 18:41:00 +08:00
}
}
break ;
/*dl*/
case CLI_BCOMP_DEC | CLI_BCOMP_LE :
/* it may be possible for the auto option to proc this */
bcm_dbgmsg ( " cli_bcomp_compare_check: auto detection found ascii decimal for specified little endian byte extraction, which is unsupported \n " ) ;
2023-01-14 18:28:39 +08:00
goto done ;
2022-10-22 18:41:00 +08:00
break ;
/*db*/
case CLI_BCOMP_DEC | CLI_BCOMP_BE :
value = cli_strntol ( ( char * ) buffer , byte_len , ( char * * ) & end_buf , 10 ) ;
if ( ( ( ( value = = LONG_MAX ) | | ( value = = LONG_MIN ) ) & & errno = = ERANGE ) | | NULL = = end_buf ) {
bcm_dbgmsg ( " cli_bcomp_compare_check: big endian decimal conversion unsuccessful \n " ) ;
2023-01-14 18:28:39 +08:00
goto done ;
2022-10-22 18:41:00 +08:00
}
/*dbe*/
if ( opt & CLI_BCOMP_EXACT ) {
if ( buffer + byte_len ! = end_buf | | pad_len ! = 0 ) {
bcm_dbgmsg ( " cli_bcomp_compare_check: couldn't extract the exact number of requested bytes \n " ) ;
2023-01-14 18:28:39 +08:00
goto done ;
2022-10-22 18:41:00 +08:00
}
}
break ;
/*il*/
case CLI_BCOMP_BIN | CLI_BCOMP_LE :
/* exact byte_length option is implied for binary extraction */
switch ( byte_len ) {
case 1 :
bin_value = ( int64_t ) ( * ( uint8_t * ) f_buffer ) ;
break ;
case 2 :
bin_value = ( int64_t ) le16_to_host ( * ( uint16_t * ) f_buffer ) ;
break ;
case 4 :
bin_value = ( int64_t ) le32_to_host ( * ( uint32_t * ) f_buffer ) ;
break ;
case 8 :
bin_value = ( int64_t ) le64_to_host ( * ( uint64_t * ) f_buffer ) ;
break ;
default :
bcm_dbgmsg ( " cli_bcomp_compare_check: invalid byte size for binary integer field (%u) \n " , byte_len ) ;
2023-01-14 18:28:39 +08:00
ret = CL_EARG ;
goto done ;
2022-10-22 18:41:00 +08:00
}
break ;
/*ib*/
case CLI_BCOMP_BIN | CLI_BCOMP_BE :
/* exact byte_length option is implied for binary extraction */
switch ( byte_len ) {
case 1 :
bin_value = ( int64_t ) ( * ( uint8_t * ) f_buffer ) ;
break ;
case 2 :
bin_value = ( int64_t ) be16_to_host ( * ( uint16_t * ) f_buffer ) ;
break ;
case 4 :
bin_value = ( int64_t ) be32_to_host ( * ( uint32_t * ) f_buffer ) ;
break ;
case 8 :
bin_value = ( int64_t ) be64_to_host ( * ( uint64_t * ) f_buffer ) ;
break ;
default :
bcm_dbgmsg ( " cli_bcomp_compare_check: invalid byte size for binary integer field (%u) \n " , byte_len ) ;
2023-01-14 18:28:39 +08:00
ret = CL_EARG ;
goto done ;
2022-10-22 18:41:00 +08:00
}
break ;
default :
bcm_dbgmsg ( " cli_bcomp_compare_check: options were found invalid \n " ) ;
2023-01-14 18:28:39 +08:00
ret = CL_ENULLARG ;
goto done ;
2022-10-22 18:41:00 +08:00
}
/* do the actual comparison */
for ( i = 0 ; i < bm - > comp_count ; i + + ) {
if ( bm - > comps & & bm - > comps [ i ] ) {
switch ( bm - > comps [ i ] - > comp_symbol ) {
case ' > ' :
if ( opt & CLI_BCOMP_BIN ) {
compare_check = ( bin_value > bm - > comps [ i ] - > comp_value ) ;
} else {
compare_check = ( value > bm - > comps [ i ] - > comp_value ) ;
}
if ( compare_check ) {
bcm_dbgmsg ( " cli_bcomp_compare_check: extracted value (%ld) greater than comparison value (%ld) \n " , ( opt & CLI_BCOMP_BIN ) ? bin_value : value , bm - > comps [ i ] - > comp_value ) ;
ret = CL_VIRUS ;
} else {
ret = CL_CLEAN ;
}
break ;
case ' < ' :
if ( opt & CLI_BCOMP_BIN ) {
compare_check = ( bin_value < bm - > comps [ i ] - > comp_value ) ;
} else {
compare_check = ( value < bm - > comps [ i ] - > comp_value ) ;
}
if ( compare_check ) {
bcm_dbgmsg ( " cli_bcomp_compare_check: extracted value (%ld) less than comparison value (%ld) \n " , ( opt & CLI_BCOMP_BIN ) ? bin_value : value , bm - > comps [ i ] - > comp_value ) ;
ret = CL_VIRUS ;
} else {
ret = CL_CLEAN ;
}
break ;
case ' = ' :
if ( opt & CLI_BCOMP_BIN ) {
compare_check = ( bin_value = = bm - > comps [ i ] - > comp_value ) ;
} else {
compare_check = ( value = = bm - > comps [ i ] - > comp_value ) ;
}
if ( compare_check ) {
bcm_dbgmsg ( " cli_bcomp_compare_check: extracted value (%ld) equal to comparison value (%ld) \n " , ( opt & CLI_BCOMP_BIN ) ? bin_value : value , bm - > comps [ i ] - > comp_value ) ;
ret = CL_VIRUS ;
} else {
ret = CL_CLEAN ;
}
break ;
default :
bcm_dbgmsg ( " cli_bcomp_compare_check: comparison symbol (%c) invalid \n " , bm - > comps [ i ] - > comp_symbol ) ;
2023-01-14 18:28:39 +08:00
ret = CL_ENULLARG ;
goto done ;
2022-10-22 18:41:00 +08:00
}
if ( CL_CLEAN = = ret ) {
/* comparison was not successful */
bcm_dbgmsg ( " cli_bcomp_compare_check: extracted value (%ld) was not %c %ld \n " , ( opt & CLI_BCOMP_BIN ) ? bin_value : value , bm - > comps [ i ] - > comp_symbol , bm - > comps [ i ] - > comp_value ) ;
2023-01-14 18:28:39 +08:00
goto done ;
2022-10-22 18:41:00 +08:00
}
}
}
2023-01-14 18:28:39 +08:00
done :
if ( NULL ! = tmp_buffer ) {
free ( tmp_buffer ) ;
}
if ( NULL ! = buffer ) {
free ( buffer ) ;
}
2022-10-22 18:41:00 +08:00
return ret ;
}
/**
* @ brief checks to see if an ascii buffer should be considered hex or not
*
* @ param buffer is the buffer to evaluate
* @ param opts the bcomp opts bitfield to set / evaluate during the check
* @ param len the length of the buffer , must be larger than 3 bytes
* @ param check_only specifies whether to return true / false or the modified opt value
*
* @ return if check only is set , it will return true or false , otherwise it returns a modifiied byte compare bitfield
*/
uint16_t cli_bcomp_chk_hex ( const unsigned char * buffer , uint16_t opt , uint32_t len , uint32_t check_only )
{
uint16_t check = 0 ;
if ( ! buffer | | len < 3 ) {
if ( buffer & & len < 3 ) {
if ( ( opt & 0x00F0 ) & CLI_BCOMP_AUTO ) {
opt | = CLI_BCOMP_DEC ;
opt ^ = CLI_BCOMP_AUTO ;
}
}
return check_only ? check : opt ;
}
if ( ! strncmp ( ( char * ) buffer , " 0x " , 2 ) | | ! strncmp ( ( char * ) buffer , " 0X " , 2 ) ) {
opt | = CLI_BCOMP_HEX ;
check = 1 ;
} else {
opt | = CLI_BCOMP_DEC ;
check = 0 ;
}
opt ^ = CLI_BCOMP_AUTO ;
return check_only ? check : opt ;
}
/**
* @ brief multipurpose buffer normalization support function for bytcompare
*
* Currently can be used to normalize a little endian hex buffer to big endian .
* Can also be used to trim whitespace from the front of the buffer .
*
* @ param buffer is the ascii bytes which are to be normalized
* @ param byte_len is the length of these bytes
* @ param pad_len if the address passed is non - null function will store the amount of whitespace found in bytes
* @ param opt the byte compare option bitfield
* @ param whitespace_only if true will only do whitespace normalization , will not perform whitespace
* normalization if set to no
*
* @ return returns an allocated , normalized buffer or NULL if an allocation error has occurred
*/
unsigned char * cli_bcomp_normalize_buffer ( const unsigned char * buffer , uint32_t byte_len , uint32_t * pad_len , uint16_t opt , uint16_t whitespace_only )
{
uint32_t norm_len = 0 ;
uint32_t pad = 0 ;
uint32_t i = 0 ;
uint16_t opt_val = 0 ;
unsigned char * tmp_buffer = NULL ;
if ( ! buffer ) {
cli_errmsg ( " cli_bcomp_compare_check: unable to normalize temp buffer, params null \n " ) ;
return NULL ;
}
if ( whitespace_only ) {
for ( i = 0 ; i < byte_len ; i + + ) {
if ( isspace ( buffer [ i ] ) ) {
bcm_dbgmsg ( " cli_bcomp_compare_check: buffer has whitespace \n " ) ;
pad + + ;
} else {
/* break on first non-padding whitespace */
break ;
}
}
/* keep in mind byte_len is a stack variable so this won't change byte_len in our calling functioning */
byte_len = byte_len - pad ;
tmp_buffer = cli_calloc ( byte_len + 1 , sizeof ( char ) ) ;
if ( NULL = = tmp_buffer ) {
cli_errmsg ( " cli_bcomp_compare_check: unable to allocate memory for whitespace normalized temp buffer \n " ) ;
return NULL ;
}
memset ( tmp_buffer , ' 0 ' , byte_len + 1 ) ;
memcpy ( tmp_buffer , buffer + pad , byte_len ) ;
tmp_buffer [ byte_len ] = ' \0 ' ;
if ( pad_len ) {
* pad_len = pad ;
}
return tmp_buffer ;
}
opt_val = opt & 0x000F ;
if ( opt_val & CLI_BCOMP_HEX | | opt_val & CLI_BCOMP_AUTO ) {
2023-01-14 18:28:39 +08:00
unsigned char * hex_buffer ;
2022-10-22 18:41:00 +08:00
norm_len = ( byte_len % 2 ) = = 0 ? byte_len : byte_len + 1 ;
tmp_buffer = cli_calloc ( norm_len + 1 , sizeof ( char ) ) ;
if ( NULL = = tmp_buffer ) {
cli_errmsg ( " cli_bcomp_compare_check: unable to allocate memory for normalized temp buffer \n " ) ;
return NULL ;
}
hex_buffer = cli_calloc ( norm_len + 1 , sizeof ( char ) ) ;
if ( NULL = = hex_buffer ) {
free ( tmp_buffer ) ;
cli_errmsg ( " cli_bcomp_compare_check: unable to reallocate memory for hex buffer \n " ) ;
return NULL ;
}
memset ( tmp_buffer , ' 0 ' , norm_len + 1 ) ;
memset ( hex_buffer , ' 0 ' , norm_len + 1 ) ;
if ( byte_len = = 1 ) {
tmp_buffer [ 0 ] = buffer [ 0 ] ;
} else {
if ( norm_len = = byte_len + 1 ) {
opt_val = opt ;
if ( cli_bcomp_chk_hex ( buffer , opt_val , byte_len , 1 ) ) {
memcpy ( hex_buffer + 3 , buffer + 2 , byte_len - 2 ) ;
hex_buffer [ 0 ] = ' x ' ;
} else {
memcpy ( hex_buffer + 1 , buffer , byte_len ) ;
}
} else {
opt_val = opt ;
memcpy ( hex_buffer , buffer , byte_len ) ;
if ( cli_bcomp_chk_hex ( buffer , opt_val , byte_len , 1 ) ) {
hex_buffer [ 0 ] = ' x ' ;
}
}
for ( i = 0 ; i < norm_len ; i = i + 2 ) {
if ( ( ( int32_t ) norm_len - ( int32_t ) i ) - 2 > = 0 ) {
/* 0000BA -> B0000A */
if ( isxdigit ( hex_buffer [ norm_len - i - 2 ] ) | | toupper ( hex_buffer [ norm_len - i - 2 ] ) = = ' X ' ) {
tmp_buffer [ i ] = hex_buffer [ norm_len - i - 2 ] ;
} else {
/* non-hex detected, our current buffer is invalid so zero it out and continue */
memset ( tmp_buffer , ' 0 ' , norm_len + 1 ) ;
/* nibbles after this are non-good, so skip them */
continue ;
}
}
/* 0000BA -> 0A00B0 */
if ( isxdigit ( hex_buffer [ norm_len - i - 1 ] ) | | toupper ( hex_buffer [ norm_len - i - 1 ] ) = = ' X ' ) {
tmp_buffer [ i + 1 ] = hex_buffer [ norm_len - i - 1 ] ;
} else {
/* non-hex detected, our current buffer is invalid so zero it out and continue */
memset ( tmp_buffer , ' 0 ' , norm_len + 1 ) ;
}
}
}
tmp_buffer [ norm_len ] = ' \0 ' ;
bcm_dbgmsg ( " cli_bcomp_compare_check: normalized extracted bytes before comparison %.*s \n " , norm_len , tmp_buffer ) ;
2023-01-14 18:28:39 +08:00
free ( hex_buffer ) ;
2022-10-22 18:41:00 +08:00
}
return tmp_buffer ;
}
/**
* @ brief cleans up the byte compare data struct
*
* @ param root the root matcher struct whose mempool instance the bcomp struct has been allocated with
* @ param bm the bcomp struct to be freed
*
*/
void cli_bcomp_freemeta ( struct cli_matcher * root , struct cli_bcomp_meta * bm )
{
2023-01-14 18:28:39 +08:00
uint32_t i = 0 ;
2022-10-22 18:41:00 +08:00
if ( ! root | | ! bm ) {
return ;
}
if ( bm - > comps ) {
2023-01-14 18:28:39 +08:00
for ( i = 0 ; i < bm - > comp_count ; i + + ) {
2022-10-22 18:41:00 +08:00
if ( bm - > comps [ i ] ) {
MPOOL_FREE ( root - > mempool , bm - > comps [ i ] ) ;
bm - > comps [ i ] = NULL ;
}
}
MPOOL_FREE ( root - > mempool , bm - > comps ) ;
bm - > comps = NULL ;
}
MPOOL_FREE ( root - > mempool , bm ) ;
bm = NULL ;
return ;
}