1 /*
2  *  Byte comparison matcher support functions
3  *
4  *  Copyright (C) 2018-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
5  *
6  *  Authors: Mickey Sola
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License version 2 as
10  *  published by the Free Software Foundation.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20  *  MA 02110-1301, USA.
21  */
22 
23 #if HAVE_CONFIG_H
24 #include "clamav-config.h"
25 #endif
26 
27 #include <errno.h>
28 
29 #include "clamav.h"
30 #include "others.h"
31 #include "matcher.h"
32 #include "matcher-ac.h"
33 #include "matcher-byte-comp.h"
34 #include "mpool.h"
35 #include "readdb.h"
36 #include "str.h"
37 
38 /* DEBUGGING */
39 //#define MATCHER_BCOMP_DEBUG
40 #ifdef MATCHER_BCOMP_DEBUG
41 #define bcm_dbgmsg(...) cli_dbgmsg(__VA_ARGS__)
42 #else
43 #define bcm_dbgmsg(...)
44 #endif
45 #undef MATCHER_BCOMP_DEBUG
46 
47 /* BCOMP MATCHER FUNCTIONS */
48 
49 /**
50  * @brief function to add the byte compare subsig into the matcher root struct
51  *
52  * @param root the matcher root struct in question, houses all relevant lsig and subsig info
53  * @param virname virusname as given by the signature
54  * @param hexsig the raw sub signature buffer itself which we will be checking/parsing
55  * @param lsigid the numeric internal reference number which can be used to access this lsig in the root struct
56  * @param options additional options for pattern matching, stored as a bitmask
57  *
58  */
cli_bcomp_addpatt(struct cli_matcher * root,const char * virname,const char * hexsig,const uint32_t * lsigid,unsigned int options)59 cl_error_t cli_bcomp_addpatt(struct cli_matcher *root, const char *virname, const char *hexsig, const uint32_t *lsigid, unsigned int options)
60 {
61 
62     size_t len            = 0;
63     uint32_t i            = 0;
64     const char *buf_start = NULL;
65     const char *buf_end   = NULL;
66     char *buf             = NULL;
67     const char *tokens[4];
68     size_t toks          = 0;
69     int16_t ref_subsigid = -1;
70     int64_t offset_param = 0;
71     int64_t ret          = CL_SUCCESS;
72     size_t byte_length   = 0;
73     int64_t comp_val     = 0;
74     char *comp_buf       = NULL;
75     char *comp_start     = NULL;
76     char *comp_end       = NULL;
77 
78     if (!hexsig || !(*hexsig) || !root || !virname) {
79         return CL_ENULLARG;
80     }
81 
82     /* we'll be using these to help the root matcher struct keep track of each loaded byte compare pattern */
83     struct cli_bcomp_meta **newmetatable;
84     uint32_t bcomp_count = 0;
85 
86     /* zero out our byte compare data struct and tie it to the root struct's mempool instance */
87     struct cli_bcomp_meta *bcomp;
88     bcomp = (struct cli_bcomp_meta *)MPOOL_CALLOC(root->mempool, 1, sizeof(*bcomp));
89     if (!bcomp) {
90         cli_errmsg("cli_bcomp_addpatt: Unable to allocate memory for new byte compare meta\n");
91         return CL_EMEM;
92     }
93 
94     /* allocate virname space with the root structure's mempool instance */
95     bcomp->virname = (char *)CLI_MPOOL_VIRNAME(root->mempool, virname, options & CL_DB_OFFICIAL);
96     if (!bcomp->virname) {
97         cli_errmsg("cli_bcomp_addpatt: Unable to allocate memory for virname or NULL virname\n");
98         cli_bcomp_freemeta(root, bcomp);
99         return CL_EMEM;
100     }
101 
102     /* bring along the standard lsigid vector, first param marks validity of vector, 2nd is lsigid, 3rd is subsigid */
103     if (lsigid) {
104         root->ac_lsigtable[lsigid[0]]->virname = bcomp->virname;
105 
106         bcomp->lsigid[0] = 1;
107         bcomp->lsigid[1] = lsigid[0];
108         bcomp->lsigid[2] = lsigid[1];
109     } else {
110         /* sigtool */
111         bcomp->lsigid[0] = 0;
112     }
113 
114     /* first need to grab the subsig reference, we'll use this later to determine our offset */
115     buf_start = hexsig;
116     buf_end   = hexsig;
117 
118     ref_subsigid = strtol(buf_start, (char **)&buf_end, 10);
119     if (buf_end && buf_end[0] != '(') {
120         cli_errmsg("cli_bcomp_addpatt: while byte compare subsig parsing, reference subsig id was invalid or included non-decimal character\n");
121         cli_bcomp_freemeta(root, bcomp);
122         return CL_EMALFDB;
123     }
124 
125     if (ref_subsigid > MAX_LDB_SUBSIGS) {
126         cli_errmsg("cli_bcomp_addpatt: while byte compare subsig parsing, reference subigid exceeded limits on max LDB subsigs\n");
127         cli_bcomp_freemeta(root, bcomp);
128         return CL_EMALFDB;
129     }
130 
131     bcomp->ref_subsigid = ref_subsigid;
132 
133     /* use the passed hexsig buffer to find the start and ending parens and store the param length (minus starting paren) */
134     buf_start = buf_end;
135     if (buf_start[0] == '(') {
136         if ((buf_end = strchr(buf_start, ')'))) {
137             len = (size_t)(buf_end - ++buf_start);
138         } else {
139             cli_errmsg("cli_bcomp_addpatt: ending paren not found\n");
140             cli_bcomp_freemeta(root, bcomp);
141             return CL_EMALFDB;
142         }
143     } else {
144         cli_errmsg("cli_bcomp_addpatt: opening paren not found\n");
145         cli_bcomp_freemeta(root, bcomp);
146         return CL_EMALFDB;
147     }
148 
149     /* make a working copy of the param buffer */
150     buf = CLI_STRNDUP(buf_start, len);
151 
152     /* break up the new param buffer into its component strings and verify we have exactly 3 */
153     toks = cli_strtokenize(buf, '#', 3 + 1, tokens);
154     if (3 != toks) {
155         cli_errmsg("cli_bcomp_addpatt: %zu (or more) params provided, 3 expected\n", toks);
156         free(buf);
157         cli_bcomp_freemeta(root, bcomp);
158         return CL_EMALFDB;
159     }
160     tokens[3] = NULL;
161 
162     /* since null termination is super guaranteed thanks to strndup and cli_strokenize, we can use strtol to grab the
163      * offset params. this has the added benefit of letting us parse hex values too */
164     buf_end   = NULL;
165     buf_start = tokens[0];
166     switch (buf_start[0]) {
167         case '<':
168             if ((++buf_start)[0] == '<') {
169                 offset_param = strtol(++buf_start, (char **)&buf_end, 0);
170                 if (buf_end && buf_end + 1 != tokens[1]) {
171                     cli_errmsg("cli_bcomp_addpatt: while parsing (%s#%s#%s), offset parameter included invalid characters\n", tokens[0], tokens[1], tokens[2]);
172                     free(buf);
173                     cli_bcomp_freemeta(root, bcomp);
174                     return CL_EMALFDB;
175                 }
176                 /* two's-complement for negative value */
177                 offset_param = (~offset_param) + 1;
178 
179             } else {
180                 cli_errmsg("cli_bcomp_addpatt: while parsing (%s#%s#%s), shift operator not valid\n", tokens[0], tokens[1], tokens[2]);
181                 free(buf);
182                 cli_bcomp_freemeta(root, bcomp);
183                 return CL_EMALFDB;
184             }
185             break;
186 
187         case '>':
188             if ((++buf_start)[0] == '>') {
189                 offset_param = strtol(++buf_start, (char **)&buf_end, 0);
190                 if (buf_end && buf_end + 1 != tokens[1]) {
191                     cli_errmsg("cli_bcomp_addpatt: while parsing (%s#%s#%s), offset parameter included invalid characters\n", tokens[0], tokens[1], tokens[2]);
192                     free(buf);
193                     cli_bcomp_freemeta(root, bcomp);
194                     return CL_EMALFDB;
195                 }
196                 break;
197             } else {
198                 cli_errmsg("cli_bcomp_addpatt: while parsing (%s#%s#%s), shift operator and/or offset not valid\n", tokens[0], tokens[1], tokens[2]);
199                 free(buf);
200                 cli_bcomp_freemeta(root, bcomp);
201                 return CL_EMALFDB;
202             }
203         case '0':
204         case '\0':
205             offset_param = 0;
206             break;
207 
208         default:
209             cli_errmsg("cli_bcomp_addpatt: while parsing (%s#%s#%s), shift operator included invalid characters\n", tokens[0], tokens[1], tokens[2]);
210             free(buf);
211             cli_bcomp_freemeta(root, bcomp);
212             return CL_EMALFDB;
213     }
214 
215     bcomp->offset = offset_param;
216 
217     /* the byte length indicator options are stored in a bitmask--by design each option gets its own nibble */
218     buf_start = tokens[1];
219 
220     while (!isdigit(*buf_start)) {
221 
222         switch (*buf_start) {
223             case 'h':
224                 /* hex, decimal, auto, and binary options are mutually exclusive parameters */
225                 if (bcomp->options & CLI_BCOMP_DEC || bcomp->options & CLI_BCOMP_BIN || bcomp->options & CLI_BCOMP_AUTO) {
226                     ret = CL_EMALFDB;
227                 } else {
228                     bcomp->options |= CLI_BCOMP_HEX;
229                 }
230                 break;
231 
232             case 'd':
233                 /* hex, decimal, auto, and binary options are mutually exclusive parameters */
234                 /* decimal may not be used with little-endian. big-endian is implied. */
235                 if (bcomp->options & CLI_BCOMP_HEX || bcomp->options & CLI_BCOMP_BIN || bcomp->options & CLI_BCOMP_AUTO || bcomp->options & CLI_BCOMP_LE) {
236                     ret = CL_EMALFDB;
237                 } else {
238                     bcomp->options |= CLI_BCOMP_DEC;
239                     bcomp->options |= CLI_BCOMP_BE;
240                 }
241                 break;
242 
243             case 'i':
244                 /* hex, decimal, auto, and binary options are mutually exclusive parameters */
245                 if (bcomp->options & CLI_BCOMP_HEX || bcomp->options & CLI_BCOMP_DEC || bcomp->options & CLI_BCOMP_AUTO) {
246                     ret = CL_EMALFDB;
247                 } else {
248                     bcomp->options |= CLI_BCOMP_BIN;
249                 }
250                 break;
251 
252             case 'a':
253                 /* for automatic hex or decimal run-time detection */
254                 /* hex, decimal, auto, and binary options are mutually exclusive parameters */
255                 if (bcomp->options & CLI_BCOMP_HEX || bcomp->options & CLI_BCOMP_DEC || bcomp->options & CLI_BCOMP_BIN) {
256                     ret = CL_EMALFDB;
257                 } else {
258                     bcomp->options |= CLI_BCOMP_AUTO;
259                 }
260                 break;
261 
262             case 'l':
263                 /* little and big endian options are mutually exclusive parameters */
264                 /* decimal may not be used with little-endian */
265                 if (bcomp->options & CLI_BCOMP_BE || bcomp->options & CLI_BCOMP_DEC) {
266                     ret = CL_EMALFDB;
267                 } else {
268                     bcomp->options |= CLI_BCOMP_LE;
269                 }
270                 break;
271 
272             case 'b':
273                 /* little and big endian options are mutually exclusive parameters */
274                 if (bcomp->options & CLI_BCOMP_LE) {
275                     ret = CL_EMALFDB;
276                 } else {
277                     bcomp->options |= CLI_BCOMP_BE;
278                 }
279                 break;
280 
281             case 'e':
282                 /* for exact byte length matches */
283                 bcomp->options |= CLI_BCOMP_EXACT;
284                 break;
285 
286             default:
287                 ret = CL_EMALFDB;
288                 break;
289         }
290 
291         if (CL_EMALFDB == ret) {
292             cli_errmsg("cli_bcomp_addpatt: while parsing (%s#%s#%s), option parameter was found invalid\n", tokens[0], tokens[1], tokens[2]);
293             free(buf);
294             cli_bcomp_freemeta(root, bcomp);
295             return ret;
296         }
297         buf_start++;
298     }
299 
300     /* parse out the byte length parameter */
301     buf_end     = NULL;
302     byte_length = strtol(buf_start, (char **)&buf_end, 0);
303     if (buf_end && buf_end + 1 != tokens[2]) {
304         cli_errmsg("cli_bcomp_addpatt: while parsing (%s#%s#%s), byte length parameter included invalid characters\n", tokens[0], tokens[1], tokens[2]);
305         free(buf);
306         cli_bcomp_freemeta(root, bcomp);
307         return CL_EMALFDB;
308     }
309 
310     if (bcomp->options & CLI_BCOMP_BIN && (byte_length > CLI_BCOMP_MAX_BIN_BLEN || CLI_BCOMP_MAX_BIN_BLEN % byte_length)) {
311         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]);
312         free(buf);
313         cli_bcomp_freemeta(root, bcomp);
314         return CL_EMALFDB;
315     }
316 
317     /* same deal with hex byte lengths */
318     if (bcomp->options & CLI_BCOMP_HEX && (byte_length > CLI_BCOMP_MAX_HEX_BLEN)) {
319         cli_errmsg("cli_bcomp_addpatt: while parsing (%s#%s#%s), byte length was too long\n", tokens[0], tokens[1], tokens[2]);
320         free(buf);
321         cli_bcomp_freemeta(root, bcomp);
322         return CL_EMALFDB;
323     }
324 
325     bcomp->byte_len = byte_length;
326 
327     /* we can have up to two comparison eval statements, each sperated by a comma, let's parse them in a separate string */
328     comp_buf = cli_strdup(tokens[2]);
329     if (!comp_buf) {
330         cli_errmsg("cli_bcomp_addpatt: Unable to allocate memory for comparison buffer\n");
331         cli_bcomp_freemeta(root, bcomp);
332         return CL_EMEM;
333     }
334     /* use different buffer start and end markers so we can keep track of what we need to free later */
335     buf_start  = comp_buf;
336     comp_start = strchr(comp_buf, ',');
337     comp_end   = strrchr(comp_buf, ',');
338 
339     /* check to see if we have exactly one comma, then set our count and tokenize our string apropriately */
340     if (comp_start && comp_end) {
341         if (comp_end == comp_start) {
342             comp_start[0]     = '\0';
343             bcomp->comp_count = 2;
344 
345         } else {
346             cli_errmsg("cli_bcomp_addpatt: while parsing (%s#%s#%s), too many commas found in comparison string\n", tokens[0], tokens[1], tokens[2]);
347             cli_bcomp_freemeta(root, bcomp);
348             free(buf);
349             free((void *)buf_start);
350             return CL_EPARSE;
351         }
352     } else {
353         comp_start        = comp_buf;
354         bcomp->comp_count = 1;
355     }
356 
357     /* allocate comp struct list space with the root structure's mempool instance */
358     bcomp->comps = (struct cli_bcomp_comp **)MPOOL_CALLOC(root->mempool, bcomp->comp_count, sizeof(struct cli_bcomp_comp *));
359     if (!bcomp->comps) {
360         cli_errmsg("cli_bcomp_addpatt: unable to allocate memory for comp struct pointers\n");
361         free(buf);
362         free((void *)buf_start);
363         cli_bcomp_freemeta(root, bcomp);
364         return CL_EMEM;
365     }
366 
367     /* loop through our new list, allocate, and parse out the needed comparison evaluation bits for this subsig */
368     for (i = 0; i < bcomp->comp_count; i++) {
369 
370         bcomp->comps[i] = (struct cli_bcomp_comp *)MPOOL_CALLOC(root->mempool, 1, sizeof(struct cli_bcomp_comp));
371         if (!bcomp->virname) {
372             cli_errmsg("cli_bcomp_addpatt: unable to allocate memory for comp struct\n");
373             free(buf);
374             free((void *)buf_start);
375             cli_bcomp_freemeta(root, bcomp);
376             return CL_EMEM;
377         }
378 
379         /* currently only >, <, and = are supported comparison symbols--this makes parsing very simple */
380         switch (*comp_buf) {
381             case '<':
382             case '>':
383             case '=':
384                 bcomp->comps[i]->comp_symbol = *comp_buf;
385                 break;
386 
387             default:
388                 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);
389                 free(buf);
390                 free((void *)buf_start);
391                 cli_bcomp_freemeta(root, bcomp);
392                 return CL_EMALFDB;
393         }
394 
395         /* grab the comparison value itself */
396         comp_end = NULL;
397         comp_buf++;
398         comp_val = strtoll(comp_buf, (char **)&comp_end, 0);
399         if (*comp_end) {
400             cli_errmsg("cli_bcomp_addpatt: while parsing (%s#%s#%s), comparison value contained invalid input\n", tokens[0], tokens[1], tokens[2]);
401             free(buf);
402             free((void *)buf_start);
403             cli_bcomp_freemeta(root, bcomp);
404             return CL_EMALFDB;
405         }
406 
407         bcomp->comps[i]->comp_value = comp_val;
408 
409         /* a bit of tricksy pointer stuffs which handles all count cases, taking advantage of where strtoll drops endptr */
410         if (comp_end == comp_start) {
411             comp_buf = comp_start;
412             comp_buf++;
413         }
414 
415         /* manually verify successful pattern parsing */
416         bcm_dbgmsg("Matcher Byte Compare: (%s%ld#%c%c%s%zu#%c%ld)\n",
417                    bcomp->offset == 0 ? "" : (bcomp->offset < 0 ? "<<" : ">>"),
418                    bcomp->offset,
419                    bcomp->options & CLI_BCOMP_HEX ? 'h' : (bcomp->options & CLI_BCOMP_DEC ? 'd' : 'i'),
420                    bcomp->options & CLI_BCOMP_LE ? 'l' : 'b',
421                    bcomp->options & CLI_BCOMP_EXACT ? "e" : "",
422                    bcomp->byte_len,
423                    bcomp->comps[i]->comp_symbol,
424                    bcomp->comps[i]->comp_value);
425     }
426 
427     free((void *)buf_start);
428     buf_start = NULL;
429     /* add byte compare info to the root after reallocation */
430     bcomp_count = root->bcomp_metas + 1;
431 
432     /* allocate space for new meta table to store in root structure and increment number of byte compare patterns added */
433     newmetatable = (struct cli_bcomp_meta **)MPOOL_REALLOC(root->mempool, root->bcomp_metatable, bcomp_count * sizeof(struct cli_bcomp_meta *));
434     if (!newmetatable) {
435         cli_errmsg("cli_bcomp_addpatt: Unable to allocate memory for new bcomp meta table\n");
436         cli_bcomp_freemeta(root, bcomp);
437         return CL_EMEM;
438     }
439 
440     newmetatable[bcomp_count - 1] = bcomp;
441     root->bcomp_metatable         = newmetatable;
442 
443     root->bcomp_metas = bcomp_count;
444 
445     /* if everything went well bcomp has been totally populated, which means we can cleanup and exit */
446     free(buf);
447     return CL_SUCCESS;
448 }
449 
450 /**
451  * @brief function to perform all byte compare matching on the file buffer
452  *
453  * @param map the file map to perform logical byte comparison upon
454  * @param res the result structure, primarily used by sigtool
455  * @param root the root structure in which all byte compare lsig and subsig information is stored
456  * @param mdata the ac data struct which contains offset information from recent subsig matches
457  * @param ctx the clamav context struct
458  *
459  */
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)460 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)
461 {
462 
463     int64_t i = 0, ret = CL_SUCCESS;
464     uint32_t lsigid, ref_subsigid;
465     uint32_t offset              = 0;
466     uint8_t viruses_found        = 0;
467     struct cli_bcomp_meta *bcomp = NULL;
468     struct cli_ac_result *newres = NULL;
469 
470     uint32_t evalcnt = 0;
471     uint64_t evalids = 0;
472     char *subsigid   = NULL;
473 
474     if (!(root) || !(root->bcomp_metas) || !(root->bcomp_metatable) || !(mdata) || !(mdata->offmatrix) || !(ctx)) {
475         return CL_SUCCESS;
476     }
477 
478     for (i = 0; i < root->bcomp_metas; i++) {
479 
480         bcomp        = root->bcomp_metatable[i];
481         lsigid       = bcomp->lsigid[1];
482         ref_subsigid = bcomp->ref_subsigid;
483 
484         /* check to see if we are being run in sigtool or not */
485         if (bcomp->lsigid[0]) {
486 
487             subsigid = cli_calloc(3, sizeof(char));
488             snprintf(subsigid, 3, "%hu", bcomp->ref_subsigid);
489 
490             /* verify the ref_subsigid */
491             if (cli_ac_chklsig(subsigid, subsigid + strlen(subsigid),
492                                mdata->lsigcnt[bcomp->lsigid[1]], &evalcnt, &evalids, 0) != 1) {
493                 bcm_dbgmsg("cli_bcomp_scanbuf: could not verify a match for lsig reference subsigid (%s)\n", subsigid);
494                 continue;
495             }
496 
497             /* ensures the referenced subsig matches as expected, and also ensures mdata has the needed offset */
498             if ((ret = lsig_sub_matched(root, mdata, lsigid, ref_subsigid, CLI_OFF_NONE, 0))) {
499                 break;
500             }
501 
502             /* grab the needed offset using from the last matched subsig offset matrix, i.e. the match performed above */
503             if (mdata->lsigsuboff_last[lsigid]) {
504                 offset = mdata->lsigsuboff_last[lsigid][ref_subsigid];
505             } else {
506                 ret = CL_SUCCESS;
507                 continue;
508             }
509         } else {
510             /* can't run lsig_sub_matched in sigtool, and mdata isn't populated so run the raw matcher stuffs */
511             if (res) {
512                 newres = (struct cli_ac_result *)cli_calloc(1, sizeof(struct cli_ac_result));
513                 if (!newres) {
514                     cli_errmsg("cli_bcomp_scanbuf: can't allocate memory for new result\n");
515                     ret = CL_EMEM;
516                     break;
517                 }
518                 newres->virname    = bcomp->virname;
519                 newres->customdata = NULL;
520                 newres->next       = *res;
521                 *res               = newres;
522             }
523         }
524 
525         /* no offset available, make a best effort */
526         if (offset == CLI_OFF_NONE) {
527             offset = 0;
528         }
529 
530         /* now we have all the pieces of the puzzle, so lets do our byte compare check */
531         ret = cli_bcomp_compare_check(buffer, buffer_length, offset, bcomp);
532 
533         /* set and append our lsig's virus name if the comparison came back positive */
534         if (CL_VIRUS == ret) {
535             viruses_found = 1;
536 
537             if (virname) {
538                 *virname = bcomp->virname;
539             }
540             /* if we aren't scanning all, let's just exit here */
541             if (!SCAN_ALLMATCHES) {
542                 break;
543             } else {
544                 ret = cli_append_virus(ctx, (const char *)bcomp->virname);
545             }
546         }
547     }
548 
549     if (subsigid) {
550         free(subsigid);
551         subsigid = NULL;
552     }
553 
554     if (ret == CL_SUCCESS && viruses_found) {
555         return CL_VIRUS;
556     }
557     return ret;
558 }
559 
560 /**
561  * @brief does a numerical, logical byte comparison on a particular offset given a filemapping and the offset
562  *
563  * @param map the file buffer we'll be accessing to do our comparison check
564  * @param offset the offset of the referenced subsig match from the start of the file buffer
565  * @param bm the byte comparison meta data struct, contains all the other info needed to do the comparison
566  *
567  */
cli_bcomp_compare_check(const unsigned char * f_buffer,size_t buffer_length,int offset,struct cli_bcomp_meta * bm)568 cl_error_t cli_bcomp_compare_check(const unsigned char *f_buffer, size_t buffer_length, int offset, struct cli_bcomp_meta *bm)
569 {
570 
571     uint32_t byte_len         = 0;
572     uint32_t pad_len          = 0;
573     uint32_t norm_len         = 0;
574     uint32_t length           = 0;
575     uint32_t i                = 0;
576     cl_error_t ret            = 0;
577     uint16_t opt              = 0;
578     uint16_t opt_val          = 0;
579     int64_t value             = 0;
580     int64_t bin_value         = 0;
581     int16_t compare_check     = 0;
582     unsigned char *end_buf    = NULL;
583     unsigned char *buffer     = NULL;
584     unsigned char *tmp_buffer = NULL;
585 
586     if (!f_buffer || !bm) {
587         bcm_dbgmsg("cli_bcomp_compare_check: a param is null\n");
588         return CL_ENULLARG;
589     }
590 
591     byte_len = bm->byte_len;
592     length   = buffer_length;
593     opt      = bm->options;
594 
595     /* ensure we won't run off the end of the file buffer */
596     if (!(offset + bm->offset + byte_len <= length)) {
597         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);
598         return CL_CLEAN;
599     }
600     if (!(offset + bm->offset > 0)) {
601         bcm_dbgmsg("cli_bcomp_compare_check: negative offset would underflow buffer\n");
602         return CL_CLEAN;
603     }
604 
605     /* jump to byte compare offset, then store off specified bytes into a null terminated buffer */
606     offset += bm->offset;
607     f_buffer += offset;
608 
609     bcm_dbgmsg("cli_bcomp_compare_check: literal extracted bytes before comparison %.*s\n", byte_len, f_buffer);
610 
611     /* normalize buffer for whitespace */
612 
613     opt_val = opt & 0x000F;
614     if (!(opt_val & CLI_BCOMP_BIN)) {
615         buffer = cli_bcomp_normalize_buffer(f_buffer, byte_len, &pad_len, opt, 1);
616         if (NULL == buffer) {
617             cli_errmsg("cli_bcomp_compare_check: unable to whitespace normalize temp buffer, allocation failed\n");
618             return CL_EMEM;
619         }
620 
621         /* adjust byte_len accordingly */
622         byte_len -= pad_len;
623     }
624 
625     /* normalize buffer for little endian vals */
626     opt_val = opt & 0x00F0;
627     if (opt_val == CLI_BCOMP_LE) {
628         opt_val = opt & 0x000F;
629         if (!(opt_val & CLI_BCOMP_BIN)) {
630             tmp_buffer = cli_bcomp_normalize_buffer(buffer, byte_len, NULL, opt, 0);
631             if (NULL == tmp_buffer) {
632                 cli_errmsg("cli_bcomp_compare_check: unable to normalize temp, allocation failed\n");
633                 return CL_EMEM;
634             }
635         }
636     }
637 
638     opt_val = opt;
639     if (opt_val & CLI_BCOMP_AUTO) {
640         opt = cli_bcomp_chk_hex(buffer, opt_val, byte_len, 0);
641     }
642 
643     /* grab the first byte to handle byte length options to convert the string appropriately */
644     switch (opt & 0x00FF) {
645         /*hl*/
646         case CLI_BCOMP_HEX | CLI_BCOMP_LE:
647             if (byte_len != 1) {
648                 norm_len = (byte_len % 2) == 0 ? byte_len : byte_len + 1;
649             } else {
650                 norm_len = 1;
651             }
652             errno = 0;
653             value = cli_strntol((char *)tmp_buffer, norm_len, (char **)&end_buf, 16);
654             if ((((value == LONG_MAX) || (value == LONG_MIN)) && errno == ERANGE) || NULL == end_buf) {
655 
656                 free(tmp_buffer);
657                 bcm_dbgmsg("cli_bcomp_compare_check: little endian hex conversion unsuccessful\n");
658                 return CL_CLEAN;
659             }
660             /*hle*/
661             if (opt & CLI_BCOMP_EXACT) {
662                 if (tmp_buffer + byte_len != end_buf || pad_len != 0) {
663 
664                     free(tmp_buffer);
665                     free(buffer);
666                     bcm_dbgmsg("cli_bcomp_compare_check: couldn't extract the exact number of requested bytes\n");
667                     return CL_CLEAN;
668                 }
669             }
670 
671             break;
672 
673         /*hb*/
674         case CLI_BCOMP_HEX | CLI_BCOMP_BE:
675             value = cli_strntol((char *)buffer, byte_len, (char **)&end_buf, 16);
676             if ((((value == LONG_MAX) || (value == LONG_MIN)) && errno == ERANGE) || NULL == end_buf) {
677 
678                 bcm_dbgmsg("cli_bcomp_compare_check: big endian hex conversion unsuccessful\n");
679                 return CL_CLEAN;
680             }
681             /*hbe*/
682             if (opt & CLI_BCOMP_EXACT) {
683                 if (buffer + byte_len != end_buf || pad_len != 0) {
684 
685                     free(buffer);
686                     bcm_dbgmsg("cli_bcomp_compare_check: couldn't extract the exact number of requested bytes\n");
687                     return CL_CLEAN;
688                 }
689             }
690 
691             break;
692 
693         /*dl*/
694         case CLI_BCOMP_DEC | CLI_BCOMP_LE:
695             /* it may be possible for the auto option to proc this */
696 
697             if (buffer) {
698                 free(buffer);
699             }
700             bcm_dbgmsg("cli_bcomp_compare_check: auto detection found ascii decimal for specified little endian byte extraction, which is unsupported\n");
701             return CL_CLEAN;
702             break;
703 
704         /*db*/
705         case CLI_BCOMP_DEC | CLI_BCOMP_BE:
706             value = cli_strntol((char *)buffer, byte_len, (char **)&end_buf, 10);
707             if ((((value == LONG_MAX) || (value == LONG_MIN)) && errno == ERANGE) || NULL == end_buf) {
708 
709                 free(buffer);
710                 bcm_dbgmsg("cli_bcomp_compare_check: big endian decimal conversion unsuccessful\n");
711                 return CL_CLEAN;
712             }
713             /*dbe*/
714             if (opt & CLI_BCOMP_EXACT) {
715                 if (buffer + byte_len != end_buf || pad_len != 0) {
716 
717                     free(buffer);
718                     bcm_dbgmsg("cli_bcomp_compare_check: couldn't extract the exact number of requested bytes\n");
719                     return CL_CLEAN;
720                 }
721             }
722 
723             break;
724 
725         /*il*/
726         case CLI_BCOMP_BIN | CLI_BCOMP_LE:
727             /* exact byte_length option is implied for binary extraction */
728             switch (byte_len) {
729                 case 1:
730                     bin_value = (int64_t)(*(uint8_t *)f_buffer);
731                     break;
732                 case 2:
733                     bin_value = (int64_t)le16_to_host(*(uint16_t *)f_buffer);
734                     break;
735                 case 4:
736                     bin_value = (int64_t)le32_to_host(*(uint32_t *)f_buffer);
737                     break;
738                 case 8:
739                     bin_value = (int64_t)le64_to_host(*(uint64_t *)f_buffer);
740                     break;
741 
742                 default:
743                     bcm_dbgmsg("cli_bcomp_compare_check: invalid byte size for binary integer field (%u)\n", byte_len);
744                     free(buffer);
745                     return CL_EARG;
746             }
747             break;
748 
749         /*ib*/
750         case CLI_BCOMP_BIN | CLI_BCOMP_BE:
751             /* exact byte_length option is implied for binary extraction */
752             switch (byte_len) {
753                 case 1:
754                     bin_value = (int64_t)(*(uint8_t *)f_buffer);
755                     break;
756                 case 2:
757                     bin_value = (int64_t)be16_to_host(*(uint16_t *)f_buffer);
758                     break;
759                 case 4:
760                     bin_value = (int64_t)be32_to_host(*(uint32_t *)f_buffer);
761                     break;
762                 case 8:
763                     bin_value = (int64_t)be64_to_host(*(uint64_t *)f_buffer);
764                     break;
765 
766                 default:
767                     bcm_dbgmsg("cli_bcomp_compare_check: invalid byte size for binary integer field (%u)\n", byte_len);
768                     free(buffer);
769                     return CL_EARG;
770             }
771             break;
772 
773         default:
774             bcm_dbgmsg("cli_bcomp_compare_check: options were found invalid\n");
775             if (tmp_buffer) {
776                 free(tmp_buffer);
777             }
778 
779             if (buffer) {
780                 free(buffer);
781             }
782             return CL_ENULLARG;
783     }
784 
785     if (tmp_buffer) {
786         free(tmp_buffer);
787     }
788 
789     if (buffer) {
790         free(buffer);
791     }
792 
793     /* do the actual comparison */
794     ret = CL_CLEAN;
795     for (i = 0; i < bm->comp_count; i++) {
796         if (bm->comps && bm->comps[i]) {
797             switch (bm->comps[i]->comp_symbol) {
798 
799                 case '>':
800                     if (opt & CLI_BCOMP_BIN) {
801                         compare_check = (bin_value > bm->comps[i]->comp_value);
802                     } else {
803                         compare_check = (value > bm->comps[i]->comp_value);
804                     }
805                     if (compare_check) {
806                         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);
807                         ret = CL_VIRUS;
808                     } else {
809                         ret = CL_CLEAN;
810                     }
811                     break;
812 
813                 case '<':
814                     if (opt & CLI_BCOMP_BIN) {
815                         compare_check = (bin_value < bm->comps[i]->comp_value);
816                     } else {
817                         compare_check = (value < bm->comps[i]->comp_value);
818                     }
819                     if (compare_check) {
820                         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);
821                         ret = CL_VIRUS;
822                     } else {
823                         ret = CL_CLEAN;
824                     }
825                     break;
826 
827                 case '=':
828                     if (opt & CLI_BCOMP_BIN) {
829                         compare_check = (bin_value == bm->comps[i]->comp_value);
830                     } else {
831                         compare_check = (value == bm->comps[i]->comp_value);
832                     }
833                     if (compare_check) {
834                         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);
835                         ret = CL_VIRUS;
836                     } else {
837                         ret = CL_CLEAN;
838                     }
839                     break;
840 
841                 default:
842                     bcm_dbgmsg("cli_bcomp_compare_check: comparison symbol (%c) invalid\n", bm->comps[i]->comp_symbol);
843                     return CL_ENULLARG;
844             }
845 
846             if (CL_CLEAN == ret) {
847                 /* comparison was not successful */
848                 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);
849                 return CL_CLEAN;
850             }
851         }
852     }
853 
854     return ret;
855 }
856 
857 /**
858  * @brief checks to see if an ascii buffer should be considered hex or not
859  *
860  * @param buffer is the buffer to evaluate
861  * @param opts the bcomp opts bitfield to set/evaluate during the check
862  * @param len the length of the buffer, must be larger than 3 bytes
863  * @param check_only specifies whether to return true/false or the modified opt value
864  *
865  * @return if check only is set, it will return true or false, otherwise it returns a modifiied byte compare bitfield
866  */
cli_bcomp_chk_hex(const unsigned char * buffer,uint16_t opt,uint32_t len,uint32_t check_only)867 uint16_t cli_bcomp_chk_hex(const unsigned char *buffer, uint16_t opt, uint32_t len, uint32_t check_only)
868 {
869 
870     uint16_t check = 0;
871 
872     if (!buffer || len < 3) {
873         if (buffer && len < 3) {
874             if ((opt & 0x00F0) & CLI_BCOMP_AUTO) {
875                 opt |= CLI_BCOMP_DEC;
876                 opt ^= CLI_BCOMP_AUTO;
877             }
878         }
879         return check_only ? check : opt;
880     }
881 
882     if (!strncmp((char *)buffer, "0x", 2) || !strncmp((char *)buffer, "0X", 2)) {
883         opt |= CLI_BCOMP_HEX;
884         check = 1;
885     } else {
886         opt |= CLI_BCOMP_DEC;
887         check = 0;
888     }
889     opt ^= CLI_BCOMP_AUTO;
890 
891     return check_only ? check : opt;
892 }
893 
894 /**
895  * @brief multipurpose buffer normalization support function for bytcompare
896  *
897  * Currently can be used to normalize a little endian hex buffer to big endian.
898  * Can also be used to trim whitespace from the front of the buffer.
899  *
900  * @param buffer is the ascii bytes which are to be normalized
901  * @param byte_len is the length of these bytes
902  * @param pad_len if the address passed is non-null function will store the amount of whitespace found in bytes
903  * @param opt the byte compare option bitfield
904  * @param whitespace_only if true will only do whitespace normalization, will not perform whitespace
905  * normalization if set to no
906  *
907  * @return returns an allocated, normalized buffer or NULL if an allocation error has occurred
908  */
cli_bcomp_normalize_buffer(const unsigned char * buffer,uint32_t byte_len,uint32_t * pad_len,uint16_t opt,uint16_t whitespace_only)909 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)
910 {
911     uint32_t norm_len         = 0;
912     uint32_t pad              = 0;
913     uint32_t i                = 0;
914     uint16_t opt_val          = 0;
915     uint16_t hex              = 0;
916     unsigned char *tmp_buffer = NULL;
917     unsigned char *hex_buffer = NULL;
918 
919     if (!buffer) {
920         cli_errmsg("cli_bcomp_compare_check: unable to normalize temp buffer, params null\n");
921         return NULL;
922     }
923 
924     if (whitespace_only) {
925         for (i = 0; i < byte_len; i++) {
926             if (isspace(buffer[i])) {
927                 bcm_dbgmsg("cli_bcomp_compare_check: buffer has whitespace \n");
928                 pad++;
929             } else {
930                 /* break on first non-padding whitespace */
931                 break;
932             }
933         }
934         /* keep in mind byte_len is a stack variable so this won't change byte_len in our calling functioning */
935         byte_len   = byte_len - pad;
936         tmp_buffer = cli_calloc(byte_len + 1, sizeof(char));
937         if (NULL == tmp_buffer) {
938             cli_errmsg("cli_bcomp_compare_check: unable to allocate memory for whitespace normalized temp buffer\n");
939             return NULL;
940         }
941         memset(tmp_buffer, '0', byte_len + 1);
942         memcpy(tmp_buffer, buffer + pad, byte_len);
943         tmp_buffer[byte_len] = '\0';
944         if (pad_len) {
945             *pad_len = pad;
946         }
947         return tmp_buffer;
948     }
949 
950     opt_val = opt & 0x000F;
951     if (opt_val & CLI_BCOMP_HEX || opt_val & CLI_BCOMP_AUTO) {
952         norm_len   = (byte_len % 2) == 0 ? byte_len : byte_len + 1;
953         tmp_buffer = cli_calloc(norm_len + 1, sizeof(char));
954         if (NULL == tmp_buffer) {
955             cli_errmsg("cli_bcomp_compare_check: unable to allocate memory for normalized temp buffer\n");
956             return NULL;
957         }
958 
959         hex_buffer = cli_calloc(norm_len + 1, sizeof(char));
960         if (NULL == hex_buffer) {
961             free(tmp_buffer);
962             cli_errmsg("cli_bcomp_compare_check: unable to reallocate memory for hex buffer\n");
963             return NULL;
964         }
965 
966         memset(tmp_buffer, '0', norm_len + 1);
967         memset(hex_buffer, '0', norm_len + 1);
968 
969         if (byte_len == 1) {
970             tmp_buffer[0] = buffer[0];
971         } else {
972 
973             if (norm_len == byte_len + 1) {
974                 opt_val = opt;
975                 if (cli_bcomp_chk_hex(buffer, opt_val, byte_len, 1)) {
976                     memcpy(hex_buffer + 3, buffer + 2, byte_len - 2);
977                     hex_buffer[0] = 'x';
978                 } else {
979                     memcpy(hex_buffer + 1, buffer, byte_len);
980                 }
981             } else {
982                 opt_val = opt;
983                 memcpy(hex_buffer, buffer, byte_len);
984                 if (cli_bcomp_chk_hex(buffer, opt_val, byte_len, 1)) {
985                     hex_buffer[0] = 'x';
986                 }
987             }
988 
989             for (i = 0; i < norm_len; i = i + 2) {
990                 if (((int32_t)norm_len - (int32_t)i) - 2 >= 0) {
991                     /* 0000BA -> B0000A */
992                     if (isxdigit(hex_buffer[norm_len - i - 2]) || toupper(hex_buffer[norm_len - i - 2]) == 'X') {
993                         if (isxdigit(hex_buffer[norm_len - i - 2])) {
994                             hex = 1;
995                         }
996                         tmp_buffer[i] = hex_buffer[norm_len - i - 2];
997                     } else {
998                         /* non-hex detected, our current buffer is invalid so zero it out and continue */
999                         memset(tmp_buffer, '0', norm_len + 1);
1000                         hex = 0;
1001                         /* nibbles after this are non-good, so skip them */
1002                         continue;
1003                     }
1004                 }
1005 
1006                 /* 0000BA -> 0A00B0 */
1007                 if (isxdigit(hex_buffer[norm_len - i - 1]) || toupper(hex_buffer[norm_len - i - 1]) == 'X') {
1008                     if (isxdigit(hex_buffer[norm_len - i - 2])) {
1009                         hex = 1;
1010                     }
1011                     tmp_buffer[i + 1] = hex_buffer[norm_len - i - 1];
1012                 } else {
1013                     /* non-hex detected, our current buffer is invalid so zero it out and continue */
1014                     memset(tmp_buffer, '0', norm_len + 1);
1015                     hex = 0;
1016                 }
1017             }
1018         }
1019         tmp_buffer[norm_len] = '\0';
1020         bcm_dbgmsg("cli_bcomp_compare_check: normalized extracted bytes before comparison %.*s\n", norm_len, tmp_buffer);
1021     }
1022 
1023     return tmp_buffer;
1024 }
1025 
1026 /**
1027  * @brief cleans up the byte compare data struct
1028  *
1029  * @param root the root matcher struct whose mempool instance the bcomp struct has been allocated with
1030  * @param bm the bcomp struct to be freed
1031  *
1032  */
cli_bcomp_freemeta(struct cli_matcher * root,struct cli_bcomp_meta * bm)1033 void cli_bcomp_freemeta(struct cli_matcher *root, struct cli_bcomp_meta *bm)
1034 {
1035 
1036     int i = 0;
1037 
1038     if (!root || !bm) {
1039         return;
1040     }
1041 
1042     if (bm->virname) {
1043         MPOOL_FREE(root->mempool, bm->virname);
1044         bm->virname = NULL;
1045     }
1046 
1047     /* can never have more than 2 */
1048     if (bm->comps) {
1049         for (i = 0; i < 2; i++) {
1050             if (bm->comps[i]) {
1051                 MPOOL_FREE(root->mempool, bm->comps[i]);
1052                 bm->comps[i] = NULL;
1053             }
1054         }
1055 
1056         MPOOL_FREE(root->mempool, bm->comps);
1057         bm->comps = NULL;
1058     }
1059 
1060     MPOOL_FREE(root->mempool, bm);
1061     bm = NULL;
1062 
1063     return;
1064 }
1065