libclamav/matcher-byte-comp.c
18ff5029
 /*
  *  Byte comparison matcher support functions
  *
c442ca9c
  *  Copyright (C) 2018-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
18ff5029
  *
  *  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
 
b7001d68
 #include <errno.h>
 
18ff5029
 #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 */
821b1f51
 //#define MATCHER_BCOMP_DEBUG
18ff5029
 #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
  *
  */
2b6c456a
 cl_error_t cli_bcomp_addpatt(struct cli_matcher *root, const char *virname, const char *hexsig, const uint32_t *lsigid, unsigned int options) {
18ff5029
 
     size_t len = 0;
dc3b273f
     uint32_t i = 0;
18ff5029
     const char *buf_start = NULL;
     const char *buf_end = NULL;
     char *buf = NULL;
2b6c456a
     const char *tokens[4];
18ff5029
     size_t toks = 0;
     int16_t ref_subsigid = -1;
     int64_t offset_param = 0;
b7001d68
     int64_t ret = CL_SUCCESS;
18ff5029
     size_t byte_length = 0;
178d0303
     int64_t comp_val = 0;
dc3b273f
     char *comp_buf = NULL;
     char *comp_start = NULL;
     char *comp_end = NULL;
18ff5029
     char *hexcpy = NULL;
 
2b6c456a
     if (!hexsig || !(*hexsig) || !root || !virname) {
         return CL_ENULLARG;
     }
 
18ff5029
     /* 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;
     }
 
     /* allocate virname space with the root structure's mempool instance */
     bcomp->virname = (char *) cli_mpool_virname(root->mempool, virname, options & CL_DB_OFFICIAL);
     if(!bcomp->virname) {
         cli_errmsg("cli_bcomp_addpatt: Unable to allocate memory for virname or NULL virname\n");
         cli_bcomp_freemeta(root, bcomp);
         return CL_EMEM;
     }
 
     /* bring along the standard lsigid vector, first param marks validity of vector, 2nd is lsigid, 3rd is subsigid */
     if (lsigid) {
         root->ac_lsigtable[lsigid[0]]->virname = bcomp->virname;
 
         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;
     }
 
     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] == '(') {
6ad41ab2
         if (( buf_end = strchr(buf_start, ')') )) {
18ff5029
             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;
     }
2b6c456a
     tokens[3] = NULL;
18ff5029
 
     /* 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 '<':
dfa92896
             if ((++buf_start)[0] == '<') {
18ff5029
                 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 '>':
dfa92896
             if ((++buf_start)[0] == '>') {
                 offset_param = strtol(++buf_start, (char**) &buf_end, 0);
18ff5029
                 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];
 
b7001d68
     while (!isdigit(*buf_start)) {
 
         switch (*buf_start) {
             case 'h':
85f528e8
                 /* 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) {
b7001d68
                     ret = CL_EMALFDB;
                 } else {
                     bcomp->options |= CLI_BCOMP_HEX;
                 } break;
85f528e8
 
b7001d68
             case 'd':
85f528e8
                 /* hex, decimal, auto, and binary options are mutually exclusive parameters */
6ad41ab2
                 /* 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) {
b7001d68
                     ret = CL_EMALFDB;
                 } else {
                     bcomp->options |= CLI_BCOMP_DEC;
6ad41ab2
                     bcomp->options |= CLI_BCOMP_BE;
b7001d68
                 } break;
85f528e8
 
b7001d68
             case 'i':
85f528e8
                 /* 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) {
b7001d68
                     ret = CL_EMALFDB;
                 } else {
                     bcomp->options |= CLI_BCOMP_BIN;
                 } break;
85f528e8
 
             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;
 
b7001d68
             case 'l':
                 /* little and big endian options are mutually exclusive parameters */
6ad41ab2
                 /* decimal may not be used with little-endian */
                 if (bcomp->options & CLI_BCOMP_BE || bcomp->options & CLI_BCOMP_DEC) {
b7001d68
                     ret = CL_EMALFDB;
                 } else {
                     bcomp->options |= CLI_BCOMP_LE;
                 } break;
85f528e8
 
b7001d68
             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;
85f528e8
 
b7001d68
             case 'e':
                 /* for exact byte length matches */
                 bcomp->options |= CLI_BCOMP_EXACT;
                 break;
18ff5029
 
b7001d68
             default:
                 ret = CL_EMALFDB;
                 break;
         }
18ff5029
 
b7001d68
         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]);
18ff5029
             free(buf);
             cli_bcomp_freemeta(root, bcomp);
b7001d68
             return ret;
         }
         buf_start++;
18ff5029
     }
 
     /* parse out the byte length parameter */
     buf_end = NULL;
     byte_length = strtol(buf_start, (char **) &buf_end, 0);
     if (buf_end && buf_end+1 != tokens[2]) {
178d0303
         cli_errmsg("cli_bcomp_addpatt: while parsing (%s#%s#%s), byte length parameter included invalid characters\n", tokens[0], tokens[1], tokens[2]);
18ff5029
         free(buf);
         cli_bcomp_freemeta(root, bcomp);
         return CL_EMALFDB;
     }
 
178d0303
     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]);
b7001d68
         free(buf);
         cli_bcomp_freemeta(root, bcomp);
         return CL_EMALFDB;
     }
 
85f528e8
     /* 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;
     }
 
18ff5029
     bcomp->byte_len = byte_length;
 
dc3b273f
     /* 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, ',');
18ff5029
 
dc3b273f
     /* 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]);
18ff5029
             cli_bcomp_freemeta(root, bcomp);
dc3b273f
             free(buf);
             free((void*)buf_start);
             return CL_EPARSE;
         }
     } else {
         comp_start = comp_buf;
         bcomp->comp_count = 1;
18ff5029
     }
 
dc3b273f
     /* 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");
18ff5029
         free(buf);
dc3b273f
         free((void*)buf_start);
18ff5029
         cli_bcomp_freemeta(root, bcomp);
dc3b273f
         return CL_EMEM;
18ff5029
     }
 
dc3b273f
     /* 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));
         if(!bcomp->virname) {
             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++;
         }
18ff5029
 
dc3b273f
         /* 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);
     }
18ff5029
 
dc3b273f
     free((void*)buf_start);
     buf_start = NULL;
18ff5029
     /* 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
  *
  */
d7d58a58
 cl_error_t cli_bcomp_scanbuf(const unsigned char *buffer, size_t buffer_length, const char **virname, struct cli_ac_result **res, const struct cli_matcher *root, struct cli_ac_data *mdata, cli_ctx *ctx) {
18ff5029
 
     int64_t i = 0, rc = 0, ret = CL_SUCCESS;
     uint32_t lsigid, ref_subsigid;
     uint32_t offset = 0;
     uint8_t viruses_found = 0;
     struct cli_bcomp_meta *bcomp = NULL;
f662034b
     struct cli_ac_result *newres = NULL;
18ff5029
 
4617e707
     uint32_t evalcnt = 0;
     uint64_t evalids = 0;
     char *subsigid = NULL;
 
18ff5029
     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;
 
f662034b
         /* check to see if we are being run in sigtool or not */
         if (bcomp->lsigid[0]) {
4617e707
 
             subsigid = cli_calloc(3, sizeof(char));
             sprintf(subsigid, "%hu", bcomp->ref_subsigid);
 
             /* verify the ref_subsigid */
             if (cli_ac_chklsig(subsigid, subsigid + strlen(subsigid),
                         mdata->lsigcnt[bcomp->lsigid[1]], &evalcnt, &evalids, 0) != 1) {
                 bcm_dbgmsg("cli_bcomp_scanbuf: could not verify a match for lsig reference subsigid (%s)\n", subsigid);
                 continue;
             }
 
f662034b
             /* ensures the referenced subsig matches as expected, and also ensures mdata has the needed offset */
6ad41ab2
             if (( ret = lsig_sub_matched(root, mdata, lsigid, ref_subsigid, CLI_OFF_NONE, 0) )) {
4617e707
                 break;
f662034b
             }
18ff5029
 
f662034b
             /* 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;
             }
18ff5029
         } else {
4617e707
             /* can't run lsig_sub_matched in sigtool, and mdata isn't populated so run the raw matcher stuffs */
f662034b
             if(res) {
                 newres = (struct cli_ac_result *)cli_calloc(1, sizeof(struct cli_ac_result));
                 if(!newres) {
4617e707
                     cli_errmsg("cli_bcomp_scanbuf: can't allocate memory for new result\n");
f662034b
                     ret = CL_EMEM;
                     break;
                 }
                 newres->virname = bcomp->virname;
                 newres->customdata = NULL;
                 newres->next = *res;
                 *res = newres;
             }
18ff5029
         }
 
88567a42
         /* no offset available, make a best effort */
         if (offset == CLI_OFF_NONE) {
             offset = 0;
         }
 
18ff5029
         /* now we have all the pieces of the puzzle, so lets do our byte compare check */
d7d58a58
         ret = cli_bcomp_compare_check(buffer, buffer_length, offset, bcomp);
18ff5029
 
         /* set and append our lsig's virus name if the comparison came back positive */
         if (CL_VIRUS == ret) {
             viruses_found = 1;
 
             if (virname) {
                 *virname = bcomp->virname;
             }
             /* if we aren't scanning all, let's just exit here */
d2f48a2c
             if (!SCAN_ALLMATCHES) {
18ff5029
                 break;
             } else {
                 ret = cli_append_virus(ctx, (const char *)bcomp->virname);
             }
         }
     }
 
     if (ret == CL_SUCCESS && viruses_found) {
         return CL_VIRUS;
     }
     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
  *
  */
371d4308
 cl_error_t cli_bcomp_compare_check(const unsigned char* f_buffer, size_t buffer_length, int offset, struct cli_bcomp_meta *bm)
18ff5029
 {
 
2b6c456a
     uint32_t byte_len = 0;
371d4308
     uint32_t pad_len = 0;
     uint32_t norm_len = 0;
2b6c456a
     uint32_t length = 0;
dc3b273f
     uint32_t i = 0;
     cl_error_t ret = 0;
b7001d68
     uint16_t opt = 0;
65a68422
     uint16_t opt_val = 0;
178d0303
     int64_t value = 0;
dbb60dc9
     uint64_t bin_value = 0;
     int16_t compare_check = 0;
29267a88
     unsigned char* end_buf = NULL;
     unsigned char* buffer = NULL;
65a68422
     unsigned char* tmp_buffer = NULL;
18ff5029
 
371d4308
     if (!f_buffer || !bm) {
70170a66
         bcm_dbgmsg("cli_bcomp_compare_check: a param is null\n");
2b6c456a
         return CL_ENULLARG;
     }
 
     byte_len = bm->byte_len;
d7d58a58
     length = buffer_length;
b7001d68
     opt = bm->options;
2b6c456a
 
18ff5029
     /* ensure we won't run off the end of the file buffer */
     if (bm->offset > 0) {
         if (!((offset + bm->offset + byte_len <= length))) {
70170a66
             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);
18ff5029
             return CL_CLEAN; 
         }
     } else {
         if (!(offset + bm->offset > 0)) {
70170a66
             bcm_dbgmsg("cli_bcomp_compare_check: negative offset would underflow buffer\n");
18ff5029
             return CL_CLEAN; 
         }
     }
 
     /* jump to byte compare offset, then store off specified bytes into a null terminated buffer */
     offset += bm->offset;
371d4308
     f_buffer += offset;
d7d58a58
 
371d4308
     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");
             return CL_EMEM;
         }
 
         /* adjust byte_len accordingly */
         byte_len -= pad_len;
     }
65a68422
 
     /* normalize buffer for little endian vals */
     opt_val = opt & 0x00F0;
     if (opt_val == CLI_BCOMP_LE) {
         opt_val = opt & 0x000F;
371d4308
         if ( !(opt_val & CLI_BCOMP_BIN) ) {
821b1f51
             tmp_buffer = cli_bcomp_normalize_buffer(buffer, byte_len, NULL, opt, 0);
65a68422
             if (NULL == tmp_buffer) {
371d4308
                 cli_errmsg("cli_bcomp_compare_check: unable to normalize temp, allocation failed\n");
65a68422
                 return CL_EMEM;
85f528e8
             }
65a68422
         }
     }
85f528e8
 
65a68422
     opt_val = opt;
     if (opt_val & CLI_BCOMP_AUTO) {
821b1f51
         opt = cli_bcomp_chk_hex(buffer, opt_val, byte_len, 0);
65a68422
     }
85f528e8
 
65a68422
     /* grab the first byte to handle byte length options to convert the string appropriately */
     switch(opt & 0x00FF) {
18ff5029
         /*hl*/
         case CLI_BCOMP_HEX | CLI_BCOMP_LE:
4bc3b6c3
             if (byte_len != 1) {
                 norm_len = (byte_len % 2) == 0 ? byte_len : byte_len + 1;
             } else {
                 norm_len = 1;
             }
b7001d68
             errno = 0;
4bc3b6c3
             value = cli_strntol((char*) tmp_buffer, norm_len, (char**) &end_buf, 16);
b7001d68
             if ((((value == LONG_MAX) || (value == LONG_MIN)) && errno == ERANGE) || NULL == end_buf) {
 
65a68422
                 free(tmp_buffer);
70170a66
                 bcm_dbgmsg("cli_bcomp_compare_check: little endian hex conversion unsuccessful\n");
18ff5029
                 return CL_CLEAN;
             }
b7001d68
             /*hle*/
             if (opt & CLI_BCOMP_EXACT) {
371d4308
                 if (tmp_buffer+byte_len != end_buf || pad_len != 0) {
b7001d68
 
65a68422
                     free(tmp_buffer);
821b1f51
                     free(buffer);
70170a66
                     bcm_dbgmsg("cli_bcomp_compare_check: couldn't extract the exact number of requested bytes\n");
b7001d68
                     return CL_CLEAN;
                 }
             }
18ff5029
 
             break;
 
         /*hb*/  
         case CLI_BCOMP_HEX | CLI_BCOMP_BE:
178d0303
             value = cli_strntol((char*) buffer, byte_len, (char**) &end_buf, 16);
b7001d68
             if ((((value == LONG_MAX) || (value == LONG_MIN)) && errno == ERANGE) || NULL == end_buf) {
18ff5029
 
70170a66
                 bcm_dbgmsg("cli_bcomp_compare_check: big endian hex conversion unsuccessful\n");
18ff5029
                 return CL_CLEAN;
             }
b7001d68
             /*hbe*/
             if (opt & CLI_BCOMP_EXACT) {
371d4308
                 if (buffer+byte_len != end_buf || pad_len != 0) {
18ff5029
 
821b1f51
                     free(buffer);
70170a66
                     bcm_dbgmsg("cli_bcomp_compare_check: couldn't extract the exact number of requested bytes\n");
b7001d68
                     return CL_CLEAN;
                 }
             }
 
18ff5029
             break;
 
         /*dl*/
         case CLI_BCOMP_DEC | CLI_BCOMP_LE:
6ad41ab2
             /* it may be possible for the auto option to proc this */
821b1f51
 
             if (buffer) {
                 free(buffer);
             }
6ad41ab2
             bcm_dbgmsg("cli_bcomp_compare_check: auto detection found ascii decimal for specified little endian byte extraction, which is unsupported\n");
             return CL_CLEAN;
18ff5029
             break;
 
         /*db*/
         case CLI_BCOMP_DEC | CLI_BCOMP_BE:
178d0303
             value = cli_strntol((char*) buffer, byte_len, (char**) &end_buf, 10);
b7001d68
             if ((((value == LONG_MAX) || (value == LONG_MIN)) && errno == ERANGE) || NULL == end_buf) {
18ff5029
 
821b1f51
                 free(buffer);
70170a66
                 bcm_dbgmsg("cli_bcomp_compare_check: big endian decimal conversion unsuccessful\n");
18ff5029
                 return CL_CLEAN;
             }
b7001d68
             /*dbe*/
             if (opt & CLI_BCOMP_EXACT) {
371d4308
                 if (buffer+byte_len != end_buf || pad_len != 0) {
b7001d68
 
821b1f51
                     free(buffer);
70170a66
                     bcm_dbgmsg("cli_bcomp_compare_check: couldn't extract the exact number of requested bytes\n");
b7001d68
                     return CL_CLEAN;
                 }
             }
18ff5029
 
             break;
4617e707
 
178d0303
         /*il*/
b7001d68
         case CLI_BCOMP_BIN | CLI_BCOMP_LE:
178d0303
             /* exact byte_length option is implied for binary extraction */
             switch (byte_len) {
dbb60dc9
                 case 1: bin_value = (*(uint8_t*) f_buffer);                           break;
                 case 2: bin_value =   (uint16_t) le16_to_host( *(uint16_t*) f_buffer); break;
                 case 4: bin_value =   (uint32_t) le32_to_host( *(uint32_t*) f_buffer); break;
                 case 8: bin_value =   (uint64_t) le64_to_host( *(uint64_t*) f_buffer); break;
178d0303
 
                 default:
70170a66
                     bcm_dbgmsg("cli_bcomp_compare_check: invalid byte size for binary integer field (%u)\n", byte_len);
821b1f51
                     free(buffer);
178d0303
                     return CL_EARG;
             }
b7001d68
             break;
4617e707
 
178d0303
         /*ib*/
b7001d68
         case CLI_BCOMP_BIN | CLI_BCOMP_BE:
178d0303
             /* exact byte_length option is implied for binary extraction */
             switch (byte_len) {
dbb60dc9
                 case 1: bin_value = ( *(uint8_t*) f_buffer);                           break;
                 case 2: bin_value =    (uint16_t) be16_to_host( *(uint16_t*) f_buffer); break;
                 case 4: bin_value =    (uint32_t) be32_to_host( *(uint32_t*) f_buffer); break;
                 case 8: bin_value =    (uint64_t) be64_to_host( *(uint64_t*) f_buffer); break;
178d0303
 
                 default:
70170a66
                     bcm_dbgmsg("cli_bcomp_compare_check: invalid byte size for binary integer field (%u)\n", byte_len);
821b1f51
                     free(buffer);
178d0303
                     return CL_EARG;
             }
b7001d68
             break;
4617e707
 
18ff5029
         default:
65a68422
             bcm_dbgmsg("cli_bcomp_compare_check: options were found invalid\n");
             if (tmp_buffer) {
                 free(tmp_buffer);
             }
821b1f51
 
             if(buffer) {
                 free(buffer);
             }
18ff5029
             return CL_ENULLARG;
     }
 
65a68422
     if (tmp_buffer) {
         free(tmp_buffer);
     }
 
821b1f51
     if (buffer) {
         free(buffer);
     }
 
18ff5029
     /* do the actual comparison */
dc3b273f
     ret = CL_CLEAN;
     for (i = 0; i < bm->comp_count; i++) {
         if (bm->comps && bm->comps[i]) {
             switch (bm->comps[i]->comp_symbol) {
 
                 case '>':
dbb60dc9
                     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);
dc3b273f
                         ret = CL_VIRUS;
                     } else {
                         ret = CL_CLEAN;
                     }
                     break;
18ff5029
 
dc3b273f
                 case '<':
dbb60dc9
                     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);
dc3b273f
                         ret = CL_VIRUS;
                     } else {
                         ret = CL_CLEAN;
                     }
                     break;
18ff5029
 
dc3b273f
                 case '=':
dbb60dc9
                     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);
dc3b273f
                         ret = CL_VIRUS;
                     } else {
                         ret = CL_CLEAN;
                     }
                     break;
18ff5029
 
dc3b273f
                 default:
                     bcm_dbgmsg("cli_bcomp_compare_check: comparison symbol (%c) invalid\n", bm->comps[i]->comp_symbol);
                     return CL_ENULLARG;
18ff5029
             }
 
dc3b273f
             if (CL_CLEAN == ret) {
                 /* comparison was not successful */
dbb60dc9
                 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);
dc3b273f
                 return CL_CLEAN;
             }
         }
18ff5029
     }
dbb60dc9
 
dc3b273f
     return ret;
18ff5029
 }
 
 /**
371d4308
  * @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) {
ad94912c
         if (buffer && len < 3) {
             if ((opt & 0x00F0) & CLI_BCOMP_AUTO) {
                 opt |= CLI_BCOMP_DEC;
                 opt ^= CLI_BCOMP_AUTO;
             }
         }
371d4308
         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;
     uint16_t hex = 0;
     unsigned char* tmp_buffer = NULL;
     unsigned char* hex_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) {
         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' ) {
                         if ( isxdigit(hex_buffer[norm_len-i-2]) ) {
                             hex = 1;
                         }
                         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);
                         hex = 0;
ad94912c
                         /* nibbles after this are non-good, so skip them */
                         continue;
371d4308
                     }
                 }
 
                 /* 0000BA -> 0A00B0 */
                 if ( isxdigit(hex_buffer[norm_len-i-1]) || toupper(hex_buffer[norm_len-i-1]) == 'X' ) {
                         if ( isxdigit(hex_buffer[norm_len-i-2]) ) {
                             hex = 1;
                         }
                         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);
                     hex = 0;
                 }
             }
         }
         tmp_buffer[norm_len+1] = '\0';
4bc3b6c3
         bcm_dbgmsg("cli_bcomp_compare_check: normalized extracted bytes before comparison %.*s\n", norm_len, tmp_buffer);
371d4308
     }
 
     return tmp_buffer;
 }
 
 /**
18ff5029
  * @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) {
 
dc3b273f
     int i = 0;
 
2b6c456a
     if(!root || !bm) {
18ff5029
         return;
     }
     
     if (bm->virname) {
         mpool_free(root->mempool, bm->virname);
         bm->virname = NULL;
     }
dc3b273f
 
     /* can never have more than 2 */
     if (bm->comps) {
         for (i = 0; i < 2; i++) {
             if (bm->comps[i]) {
                 mpool_free(root->mempool, bm->comps[i]);
                 bm->comps[i] = NULL;
             }
         }
 
         mpool_free(root->mempool, bm->comps);
         bm->comps = NULL;
     }
 
18ff5029
     mpool_free(root->mempool, bm);
     bm = NULL;
 
     return;
 }