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