pkg_audit.c
1 /*- 2 * Copyright (c) 2011-2012 Julien Laffaye <jlaffaye@FreeBSD.org> 3 * Copyright (c) 2014 Matthew Seaman <matthew@FreeBSD.org> 4 * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org> 5 * Copyright (c) 2020 Baptiste Daroussin <bapt@FreeBSD.org> 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer 13 * in this position and unchanged. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #include <sys/mman.h> 31 32 #include <archive.h> 33 #include <err.h> 34 #include <fcntl.h> 35 #include <fnmatch.h> 36 #include <stdio.h> 37 #include <string.h> 38 #include <utlist.h> 39 #include <xstring.h> 40 41 #include <yxml.h> 42 43 #ifdef __linux__ 44 # ifdef __GLIBC__ 45 # include <sys/time.h> 46 # endif 47 #endif 48 49 #include "pkg.h" 50 #include "pkg/audit.h" 51 #include "private/pkg.h" 52 #include "private/event.h" 53 54 /* 55 * The _sorted stuff. 56 * 57 * We are using the optimized search based on the following observations: 58 * 59 * - number of VuXML entries is more likely to be far greater than 60 * the number of installed ports; thus we should try to optimize 61 * the walk through all entries for a given port; 62 * 63 * - fnmatch() is good and fast, but if we will compare the audit entry 64 * name prefix without globbing characters to the prefix of port name 65 * of the same length and they are different, there is no point to 66 * check the rest; 67 * 68 * - (most important bit): if parsed VuXML entries are lexicographically 69 * sorted per the largest prefix with no globbing characters and we 70 * know how many succeeding entries have the same prefix we can 71 * 72 * a. skip the rest of the entries once the non-globbing prefix is 73 * lexicographically larger than the port name prefix of the 74 * same length: all successive prefixes will be larger as well; 75 * 76 * b. if we have non-globbing prefix that is lexicographically smaller 77 * than port name prefix, we can skip all succeeding entries with 78 * the same prefix; and as some port names tend to repeat due to 79 * multiple vulnerabilities, it could be a large win. 80 */ 81 struct pkg_audit_item { 82 struct pkg_audit_entry *e; /* Entry itself */ 83 size_t noglob_len; /* Prefix without glob characters */ 84 size_t next_pfx_incr; /* Index increment for the entry with 85 different prefix */ 86 }; 87 88 struct pkg_audit { 89 struct pkg_audit_entry *entries; 90 struct pkg_audit_item *items; 91 bool parsed; 92 bool loaded; 93 char **ignore_globs; 94 char **ignore_regexp; 95 void *map; 96 size_t len; 97 }; 98 99 100 /* 101 * Another small optimization to skip the beginning of the 102 * VuXML entry array, if possible. 103 * 104 * audit_entry_first_byte_idx[ch] represents the index 105 * of the first VuXML entry in the sorted array that has 106 * its non-globbing prefix that is started with the character 107 * 'ch'. It allows to skip entries from the beginning of the 108 * VuXML array that aren't relevant for the checked port name. 109 */ 110 static size_t audit_entry_first_byte_idx[256]; 111 112 static void 113 pkg_audit_free_entry(struct pkg_audit_entry *e) 114 { 115 struct pkg_audit_package *ppkg, *ppkg_tmp; 116 struct pkg_audit_versions_range *vers, *vers_tmp; 117 struct pkg_audit_cve *cve, *cve_tmp; 118 struct pkg_audit_pkgname *pname, *pname_tmp; 119 120 if (!e->ref) { 121 LL_FOREACH_SAFE(e->packages, ppkg, ppkg_tmp) { 122 LL_FOREACH_SAFE(ppkg->versions, vers, vers_tmp) { 123 free(vers->v1.version); 124 free(vers->v2.version); 125 free(vers); 126 } 127 128 LL_FOREACH_SAFE(ppkg->names, pname, pname_tmp) { 129 free(pname->pkgname); 130 free(pname); 131 } 132 free(ppkg); 133 } 134 LL_FOREACH_SAFE(e->cve, cve, cve_tmp) { 135 free(cve->cvename); 136 free(cve); 137 } 138 free(e->url); 139 free(e->desc); 140 free(e->id); 141 } 142 free(e); 143 } 144 145 static void 146 pkg_audit_free_list(struct pkg_audit_entry *h) 147 { 148 struct pkg_audit_entry *e; 149 150 while (h) { 151 e = h; 152 h = h->next; 153 pkg_audit_free_entry(e); 154 } 155 } 156 157 struct pkg_audit_extract_cbdata { 158 int out; 159 const char *fname; 160 const char *dest; 161 }; 162 163 static int 164 pkg_audit_sandboxed_extract(int fd, void *ud) 165 { 166 struct pkg_audit_extract_cbdata *cbdata = ud; 167 int rc = EPKG_OK; 168 struct archive *a = NULL; 169 struct archive_entry *ae = NULL; 170 171 a = archive_read_new(); 172 #if ARCHIVE_VERSION_NUMBER < 3000002 173 archive_read_support_compression_all(a); 174 #else 175 archive_read_support_filter_all(a); 176 #endif 177 178 archive_read_support_format_raw(a); 179 180 if (archive_read_open_fd(a, fd, 4096) != ARCHIVE_OK) { 181 pkg_emit_error("archive_read_open_filename(%s) failed: %s", 182 cbdata->fname, archive_error_string(a)); 183 rc = EPKG_FATAL; 184 } 185 else { 186 while (archive_read_next_header(a, &ae) == ARCHIVE_OK) { 187 if (archive_read_data_into_fd(a, cbdata->out) != ARCHIVE_OK) { 188 pkg_emit_error("archive_read_data_into_fd(%s) failed: %s", 189 cbdata->dest, archive_error_string(a)); 190 break; 191 } 192 } 193 archive_read_close(a); 194 archive_read_free(a); 195 } 196 197 return (rc); 198 } 199 200 int 201 pkg_audit_fetch(const char *src, const char *dest) 202 { 203 int fd = -1, outfd = -1; 204 char tmp[MAXPATHLEN]; 205 const char *tmpdir; 206 int retcode = EPKG_FATAL; 207 time_t t = 0; 208 struct stat st; 209 struct pkg_audit_extract_cbdata cbdata; 210 int dfd = -1; 211 struct timespec ts[2] = { 212 { 213 .tv_nsec = 0 214 }, 215 { 216 .tv_nsec = 0 217 } 218 }; 219 220 if (src == NULL) { 221 src = pkg_object_string(pkg_config_get("VULNXML_SITE")); 222 } 223 224 tmpdir = getenv("TMPDIR"); 225 if (tmpdir == NULL) 226 tmpdir = "/tmp"; 227 228 strlcpy(tmp, tmpdir, sizeof(tmp)); 229 strlcat(tmp, "/vuln.xml.XXXXXXXXXX", sizeof(tmp)); 230 231 if (dest != NULL) { 232 if (stat(dest, &st) != -1) 233 t = st.st_mtime; 234 } else { 235 dfd = pkg_get_dbdirfd(); 236 if (fstatat(dfd, "vuln.xml", &st, 0) != -1) 237 t = st.st_mtime; 238 } 239 240 switch (pkg_fetch_file_tmp(NULL, src, tmp, t, &fd)) { 241 case EPKG_OK: 242 break; 243 case EPKG_UPTODATE: 244 pkg_emit_notice("vulnxml file up-to-date"); 245 retcode = EPKG_OK; 246 goto cleanup; 247 default: 248 pkg_emit_error("cannot fetch vulnxml file"); 249 goto cleanup; 250 } 251 /* Open out fd */ 252 if (dest != NULL) { 253 outfd = open(dest, O_RDWR|O_CREAT|O_TRUNC, 254 S_IRUSR|S_IRGRP|S_IROTH); 255 } else { 256 outfd = openat(dfd, "vuln.xml", O_RDWR|O_CREAT|O_TRUNC, 257 S_IRUSR|S_IRGRP|S_IROTH); 258 } 259 if (outfd == -1) { 260 pkg_emit_errno("pkg_audit_fetch", "open out fd"); 261 goto cleanup; 262 } 263 264 cbdata.fname = tmp; 265 cbdata.out = outfd; 266 cbdata.dest = dest; 267 fstat(fd, &st); 268 269 /* Call sandboxed */ 270 retcode = pkg_emit_sandbox_call(pkg_audit_sandboxed_extract, fd, &cbdata); 271 ts[0].tv_sec = st.st_mtime; 272 ts[1].tv_sec = st.st_mtime; 273 futimens(outfd, ts); 274 275 cleanup: 276 if (fd != -1) 277 close(fd); 278 if (outfd != -1) 279 close(outfd); 280 281 return (retcode); 282 } 283 284 /* 285 * Expand multiple names to a set of audit entries 286 */ 287 static void 288 pkg_audit_expand_entry(struct pkg_audit_entry *entry, struct pkg_audit_entry **head) 289 { 290 struct pkg_audit_entry *n; 291 struct pkg_audit_pkgname *ncur; 292 struct pkg_audit_package *pcur; 293 294 /* Set the name of the current entry */ 295 if (entry->packages == NULL || entry->packages->names == NULL) { 296 pkg_audit_free_entry(entry); 297 return; 298 } 299 300 LL_FOREACH(entry->packages, pcur) { 301 LL_FOREACH(pcur->names, ncur) { 302 n = xcalloc(1, sizeof(struct pkg_audit_entry)); 303 n->pkgname = ncur->pkgname; 304 /* Set new entry as reference entry */ 305 n->ref = true; 306 n->cve = entry->cve; 307 n->desc = entry->desc; 308 n->versions = pcur->versions; 309 n->url = entry->url; 310 n->id = entry->id; 311 LL_PREPEND(*head, n); 312 } 313 } 314 LL_PREPEND(*head, entry); 315 } 316 317 enum vulnxml_parse_state { 318 VULNXML_PARSE_INIT = 0, 319 VULNXML_PARSE_VULN, 320 VULNXML_PARSE_TOPIC, 321 VULNXML_PARSE_PACKAGE, 322 VULNXML_PARSE_PACKAGE_NAME, 323 VULNXML_PARSE_RANGE, 324 VULNXML_PARSE_RANGE_GT, 325 VULNXML_PARSE_RANGE_GE, 326 VULNXML_PARSE_RANGE_LT, 327 VULNXML_PARSE_RANGE_LE, 328 VULNXML_PARSE_RANGE_EQ, 329 VULNXML_PARSE_CVE 330 }; 331 332 enum vulnxml_parse_attribute_state { 333 VULNXML_ATTR_NONE = 0, 334 VULNXML_ATTR_VID, 335 }; 336 337 struct vulnxml_userdata { 338 struct pkg_audit_entry *cur_entry; 339 struct pkg_audit *audit; 340 enum vulnxml_parse_state state; 341 xstring *content; 342 int range_num; 343 enum vulnxml_parse_attribute_state attr; 344 }; 345 346 static void 347 vulnxml_start_element(struct vulnxml_userdata *ud, yxml_t *xml) 348 { 349 struct pkg_audit_versions_range *vers; 350 struct pkg_audit_pkgname *name_entry; 351 struct pkg_audit_package *pkg_entry; 352 353 if (ud->state == VULNXML_PARSE_INIT && STRIEQ(xml->elem, "vuln")) { 354 ud->cur_entry = xcalloc(1, sizeof(struct pkg_audit_entry)); 355 ud->cur_entry->next = ud->audit->entries; 356 ud->state = VULNXML_PARSE_VULN; 357 } 358 else if (ud->state == VULNXML_PARSE_VULN && STRIEQ(xml->elem, "topic")) { 359 ud->state = VULNXML_PARSE_TOPIC; 360 } 361 else if (ud->state == VULNXML_PARSE_VULN && STRIEQ(xml->elem, "package")) { 362 pkg_entry = xcalloc(1, sizeof(struct pkg_audit_package)); 363 LL_PREPEND(ud->cur_entry->packages, pkg_entry); 364 ud->state = VULNXML_PARSE_PACKAGE; 365 } 366 else if (ud->state == VULNXML_PARSE_VULN && STRIEQ(xml->elem, "cvename")) { 367 ud->state = VULNXML_PARSE_CVE; 368 } 369 else if (ud->state == VULNXML_PARSE_PACKAGE && STRIEQ(xml->elem, "name")) { 370 ud->state = VULNXML_PARSE_PACKAGE_NAME; 371 name_entry = xcalloc(1, sizeof(struct pkg_audit_pkgname)); 372 LL_PREPEND(ud->cur_entry->packages->names, name_entry); 373 } 374 else if (ud->state == VULNXML_PARSE_PACKAGE && STRIEQ(xml->elem, "range")) { 375 ud->state = VULNXML_PARSE_RANGE; 376 vers = xcalloc(1, sizeof(struct pkg_audit_versions_range)); 377 LL_PREPEND(ud->cur_entry->packages->versions, vers); 378 ud->range_num = 0; 379 } 380 else if (ud->state == VULNXML_PARSE_RANGE && STRIEQ(xml->elem, "gt")) { 381 ud->range_num ++; 382 ud->state = VULNXML_PARSE_RANGE_GT; 383 } 384 else if (ud->state == VULNXML_PARSE_RANGE && STRIEQ(xml->elem, "ge")) { 385 ud->range_num ++; 386 ud->state = VULNXML_PARSE_RANGE_GE; 387 } 388 else if (ud->state == VULNXML_PARSE_RANGE && STRIEQ(xml->elem, "lt")) { 389 ud->range_num ++; 390 ud->state = VULNXML_PARSE_RANGE_LT; 391 } 392 else if (ud->state == VULNXML_PARSE_RANGE && STRIEQ(xml->elem, "le")) { 393 ud->range_num ++; 394 ud->state = VULNXML_PARSE_RANGE_LE; 395 } 396 else if (ud->state == VULNXML_PARSE_RANGE && STRIEQ(xml->elem, "eq")) { 397 ud->range_num ++; 398 ud->state = VULNXML_PARSE_RANGE_EQ; 399 } 400 } 401 402 static void 403 vulnxml_end_element(struct vulnxml_userdata *ud, yxml_t *xml) 404 { 405 struct pkg_audit_cve *cve; 406 struct pkg_audit_entry *entry; 407 struct pkg_audit_versions_range *vers; 408 int range_type = -1; 409 410 fflush(ud->content->fp); 411 if (ud->state == VULNXML_PARSE_VULN && STRIEQ(xml->elem, "vuxml")) { 412 pkg_audit_expand_entry(ud->cur_entry, &ud->audit->entries); 413 ud->state = VULNXML_PARSE_INIT; 414 } 415 else if (ud->state == VULNXML_PARSE_TOPIC && STRIEQ(xml->elem, "vuln")) { 416 ud->cur_entry->desc = xstrdup(ud->content->buf); 417 ud->state = VULNXML_PARSE_VULN; 418 } 419 else if (ud->state == VULNXML_PARSE_CVE && STRIEQ(xml->elem, "references")) { 420 entry = ud->cur_entry; 421 cve = xmalloc(sizeof(struct pkg_audit_cve)); 422 cve->cvename = xstrdup(ud->content->buf); 423 LL_PREPEND(entry->cve, cve); 424 ud->state = VULNXML_PARSE_VULN; 425 } 426 else if (ud->state == VULNXML_PARSE_PACKAGE && STRIEQ(xml->elem, "affects")) { 427 ud->state = VULNXML_PARSE_VULN; 428 } 429 else if (ud->state == VULNXML_PARSE_PACKAGE_NAME && STRIEQ(xml->elem, "package")) { 430 ud->cur_entry->packages->names->pkgname = xstrdup(ud->content->buf); 431 ud->state = VULNXML_PARSE_PACKAGE; 432 } 433 else if (ud->state == VULNXML_PARSE_RANGE && STRIEQ(xml->elem, "package")) { 434 ud->state = VULNXML_PARSE_PACKAGE; 435 } 436 else if (ud->state == VULNXML_PARSE_RANGE_GT && STRIEQ(xml->elem, "range")) { 437 range_type = GT; 438 ud->state = VULNXML_PARSE_RANGE; 439 } 440 else if (ud->state == VULNXML_PARSE_RANGE_GE && STRIEQ(xml->elem, "range")) { 441 range_type = GTE; 442 ud->state = VULNXML_PARSE_RANGE; 443 } 444 else if (ud->state == VULNXML_PARSE_RANGE_LT && STRIEQ(xml->elem, "range")) { 445 range_type = LT; 446 ud->state = VULNXML_PARSE_RANGE; 447 } 448 else if (ud->state == VULNXML_PARSE_RANGE_LE && STRIEQ(xml->elem, "range")) { 449 range_type = LTE; 450 ud->state = VULNXML_PARSE_RANGE; 451 } 452 else if (ud->state == VULNXML_PARSE_RANGE_EQ && STRIEQ(xml->elem, "range")) { 453 range_type = EQ; 454 ud->state = VULNXML_PARSE_RANGE; 455 } 456 457 if (range_type > 0) { 458 vers = ud->cur_entry->packages->versions; 459 if (ud->range_num == 1) { 460 vers->v1.version = xstrdup(ud->content->buf); 461 vers->v1.type = range_type; 462 } 463 else if (ud->range_num == 2) { 464 vers->v2.version = xstrdup(ud->content->buf); 465 vers->v2.type = range_type; 466 } 467 } 468 xstring_reset(ud->content); 469 } 470 471 static void 472 vulnxml_start_attribute(struct vulnxml_userdata *ud, yxml_t *xml) 473 { 474 if (ud->state != VULNXML_PARSE_VULN) 475 return; 476 477 if (STRIEQ(xml->attr, "vid")) 478 ud->attr = VULNXML_ATTR_VID; 479 } 480 481 static void 482 vulnxml_end_attribute(struct vulnxml_userdata *ud, yxml_t *xml __unused) 483 { 484 fflush(ud->content->fp); 485 if (ud->state == VULNXML_PARSE_VULN && ud->attr == VULNXML_ATTR_VID) { 486 ud->cur_entry->id = xstrdup(ud->content->buf); 487 ud->attr = VULNXML_ATTR_NONE; 488 } 489 xstring_reset(ud->content); 490 } 491 492 static void 493 vulnxml_val_attribute(struct vulnxml_userdata *ud, yxml_t *xml) 494 { 495 if (ud->state == VULNXML_PARSE_VULN && ud->attr == VULNXML_ATTR_VID) { 496 fputs(xml->data, ud->content->fp); 497 } 498 } 499 500 static void 501 vulnxml_handle_data(struct vulnxml_userdata *ud, yxml_t *xml) 502 { 503 504 switch(ud->state) { 505 case VULNXML_PARSE_INIT: 506 case VULNXML_PARSE_VULN: 507 case VULNXML_PARSE_PACKAGE: 508 case VULNXML_PARSE_RANGE: 509 /* On these states we do not need any data */ 510 break; 511 case VULNXML_PARSE_TOPIC: 512 case VULNXML_PARSE_PACKAGE_NAME: 513 case VULNXML_PARSE_CVE: 514 case VULNXML_PARSE_RANGE_GT: 515 case VULNXML_PARSE_RANGE_GE: 516 case VULNXML_PARSE_RANGE_LT: 517 case VULNXML_PARSE_RANGE_LE: 518 case VULNXML_PARSE_RANGE_EQ: 519 fputs(xml->data, ud->content->fp); 520 break; 521 } 522 } 523 524 static int 525 pkg_audit_parse_vulnxml(struct pkg_audit *audit) 526 { 527 int ret = EPKG_FATAL; 528 yxml_t x; 529 yxml_ret_t r; 530 char buf[BUFSIZ]; 531 char *walk, *end; 532 struct vulnxml_userdata ud; 533 534 yxml_init(&x, buf, BUFSIZ); 535 ud.cur_entry = NULL; 536 ud.audit = audit; 537 ud.range_num = 0; 538 ud.state = VULNXML_PARSE_INIT; 539 ud.content = xstring_new(); 540 541 walk = audit->map; 542 end = walk + audit->len; 543 while (walk < end) { 544 r = yxml_parse(&x, *walk++); 545 switch (r) { 546 case YXML_EEOF: 547 case YXML_EREF: 548 case YXML_ESTACK: 549 pkg_emit_error("Unexpected EOF while parsing vulnxml"); 550 goto out; 551 case YXML_ESYN: 552 pkg_emit_error("Syntax error while parsing vulnxml"); 553 goto out; 554 case YXML_ECLOSE: 555 pkg_emit_error("Close tag does not match open tag line %d", x.line); 556 goto out; 557 case YXML_ELEMSTART: 558 vulnxml_start_element(&ud, &x); 559 break; 560 case YXML_ELEMEND: 561 vulnxml_end_element(&ud, &x); 562 break; 563 case YXML_CONTENT: 564 vulnxml_handle_data(&ud, &x); 565 break; 566 case YXML_ATTRVAL: 567 vulnxml_val_attribute(&ud, &x); 568 break; 569 case YXML_ATTRSTART: 570 vulnxml_start_attribute(&ud, &x); 571 break; 572 /* ignore */ 573 case YXML_ATTREND: 574 vulnxml_end_attribute(&ud, &x); 575 /* ignore */ 576 break; 577 case YXML_OK: 578 case YXML_PISTART: 579 case YXML_PICONTENT: 580 case YXML_PIEND: 581 break; 582 } 583 } 584 585 if (yxml_eof(&x) == YXML_OK) 586 ret = EPKG_OK; 587 else 588 pkg_emit_error("Invalid end of XML"); 589 out: 590 xstring_free(ud.content); 591 592 return (ret); 593 } 594 595 /* 596 * Returns the length of the largest prefix without globbing 597 * characters, as per fnmatch(). 598 */ 599 static size_t 600 pkg_audit_str_noglob_len(const char *s) 601 { 602 size_t n; 603 604 for (n = 0; s[n] && s[n] != '*' && s[n] != '?' && 605 s[n] != '[' && s[n] != '{' && s[n] != '\\'; n++); 606 607 return (n); 608 } 609 610 /* 611 * Helper for quicksort that lexicographically orders prefixes. 612 */ 613 static int 614 pkg_audit_entry_cmp(const void *a, const void *b) 615 { 616 const struct pkg_audit_item *e1, *e2; 617 size_t min_len; 618 int result; 619 620 e1 = (const struct pkg_audit_item *)a; 621 e2 = (const struct pkg_audit_item *)b; 622 623 min_len = (e1->noglob_len < e2->noglob_len ? 624 e1->noglob_len : e2->noglob_len); 625 result = strncmp(e1->e->pkgname, e2->e->pkgname, min_len); 626 /* 627 * Additional check to see if some word is a prefix of an 628 * another one and, thus, should go before the former. 629 */ 630 if (result == 0) { 631 if (e1->noglob_len < e2->noglob_len) 632 result = -1; 633 else if (e1->noglob_len > e2->noglob_len) 634 result = 1; 635 } 636 637 return (result); 638 } 639 640 /* 641 * Sorts VuXML entries and calculates increments to jump to the 642 * next distinct prefix. 643 */ 644 static struct pkg_audit_item * 645 pkg_audit_preprocess(struct pkg_audit_entry *h) 646 { 647 struct pkg_audit_entry *e; 648 struct pkg_audit_item *ret; 649 size_t i, n, tofill; 650 651 n = 0; 652 LL_FOREACH(h, e) 653 n++; 654 655 ret = xcalloc(n + 1, sizeof(ret[0])); 656 n = 0; 657 LL_FOREACH(h, e) { 658 if (e->pkgname != NULL) { 659 ret[n].e = e; 660 ret[n].noglob_len = pkg_audit_str_noglob_len(e->pkgname); 661 ret[n].next_pfx_incr = 1; 662 n++; 663 } 664 } 665 666 qsort(ret, n, sizeof(*ret), pkg_audit_entry_cmp); 667 668 if (n < 2) 669 goto first_byte_idx; 670 671 /* 672 * Determining jump indexes to the next different prefix. 673 * Only non-1 increments are calculated there. 674 * 675 * Due to the current usage that picks only increment for the 676 * first of the non-unique prefixes in a row, we could 677 * calculate only that one and skip calculations for the 678 * succeeding, but for the uniformity and clarity we're 679 * calculating 'em all. 680 */ 681 for (n = 1, tofill = 0; ret[n].e; n++) { 682 if (ret[n - 1].noglob_len != ret[n].noglob_len) { 683 struct pkg_audit_item *base; 684 685 base = ret + n - tofill; 686 for (i = 0; tofill > 1; i++, tofill--) 687 base[i].next_pfx_incr = tofill; 688 tofill = 1; 689 } else if (STREQ(ret[n - 1].e->pkgname, ret[n].e->pkgname)) { 690 tofill++; 691 } else { 692 tofill = 1; 693 } 694 } 695 696 first_byte_idx: 697 /* Calculate jump indexes for the first byte of the package name */ 698 memset(audit_entry_first_byte_idx, '\0', sizeof(audit_entry_first_byte_idx)); 699 for (n = 1, i = 0; n < 256; n++) { 700 while (ret[i].e != NULL && 701 (size_t)(ret[i].e->pkgname[0]) < n) 702 i++; 703 audit_entry_first_byte_idx[n] = i; 704 } 705 706 return (ret); 707 } 708 709 static bool 710 pkg_audit_version_match(const char *pkgversion, struct pkg_audit_version *v) 711 { 712 bool res = false; 713 714 /* 715 * Return true so it is easier for the caller to handle case where there is 716 * only one version to match: the missing one will always match. 717 */ 718 if (v->version == NULL) 719 return (true); 720 721 switch (pkg_version_cmp(pkgversion, v->version)) { 722 case -1: 723 if (v->type == LT || v->type == LTE) 724 res = true; 725 break; 726 case 0: 727 if (v->type == EQ || v->type == LTE || v->type == GTE) 728 res = true; 729 break; 730 case 1: 731 if (v->type == GT || v->type == GTE) 732 res = true; 733 break; 734 } 735 return (res); 736 } 737 738 static void 739 pkg_audit_add_entry(struct pkg_audit_entry *e, struct pkg_audit_issues **ai) 740 { 741 struct pkg_audit_issue *issue; 742 743 if (*ai == NULL) 744 *ai = xcalloc(1, sizeof(**ai)); 745 issue = xcalloc(1, sizeof(*issue)); 746 issue->audit = e; 747 (*ai)->count++; 748 LL_APPEND((*ai)->issues, issue); 749 } 750 751 bool 752 pkg_audit_is_vulnerable(struct pkg_audit *audit, struct pkg *pkg, 753 struct pkg_audit_issues **ai, bool stop_quick) 754 { 755 struct pkg_audit_entry *e; 756 struct pkg_audit_versions_range *vers; 757 struct pkg_audit_item *a; 758 bool res = false, res1, res2; 759 760 if (!audit->parsed) 761 return false; 762 763 /* check if we decided to ignore that package or not */ 764 if (match_ucl_lists(pkg->name, 765 pkg_config_get("AUDIT_IGNORE_GLOB"), 766 pkg_config_get("AUDIT_IGNORE_REGEX"))) 767 return (false); 768 769 a = audit->items; 770 a += audit_entry_first_byte_idx[(size_t)pkg->name[0]]; 771 772 for (; (e = a->e) != NULL; a += a->next_pfx_incr) { 773 int cmp; 774 size_t i; 775 776 /* 777 * Audit entries are sorted, so if we had found one 778 * that is lexicographically greater than our name, 779 * it and the rest won't match our name. 780 */ 781 cmp = strncmp(pkg->name, e->pkgname, a->noglob_len); 782 if (cmp > 0) 783 continue; 784 else if (cmp < 0) 785 break; 786 787 for (i = 0; i < a->next_pfx_incr; i++) { 788 e = a[i].e; 789 if (fnmatch(e->pkgname, pkg->name, 0) != 0) 790 continue; 791 792 if (pkg->version == NULL) { 793 /* 794 * Assume that all versions should be checked 795 */ 796 res = true; 797 pkg_audit_add_entry(e, ai); 798 } 799 else { 800 LL_FOREACH(e->versions, vers) { 801 res1 = pkg_audit_version_match(pkg->version, &vers->v1); 802 res2 = pkg_audit_version_match(pkg->version, &vers->v2); 803 804 if (res1 && res2) { 805 res = true; 806 pkg_audit_add_entry(e, ai); 807 break; 808 } 809 } 810 } 811 812 if (res && stop_quick) 813 return (res); 814 } 815 } 816 817 return (res); 818 } 819 820 struct pkg_audit * 821 pkg_audit_new(void) 822 { 823 struct pkg_audit *audit; 824 825 audit = xcalloc(1, sizeof(struct pkg_audit)); 826 827 return (audit); 828 } 829 830 int 831 pkg_audit_load(struct pkg_audit *audit, const char *fname) 832 { 833 int dfd, fd; 834 void *mem; 835 struct stat st; 836 837 if (fname != NULL) { 838 if ((fd = open(fname, O_RDONLY)) == -1) 839 return (EPKG_FATAL); 840 } else { 841 dfd = pkg_get_dbdirfd(); 842 if ((fd = openat(dfd, "vuln.xml", O_RDONLY)) == -1) 843 return (EPKG_FATAL); 844 } 845 846 if (fstat(fd, &st) == -1) { 847 close(fd); 848 return (EPKG_FATAL); 849 } 850 851 if ((mem = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) { 852 close(fd); 853 return (EPKG_FATAL); 854 } 855 close(fd); 856 857 audit->map = mem; 858 audit->len = st.st_size; 859 audit->loaded = true; 860 861 return (EPKG_OK); 862 } 863 864 /* This can and should be executed after cap_enter(3) */ 865 int 866 pkg_audit_process(struct pkg_audit *audit) 867 { 868 if (geteuid() == 0) 869 return (EPKG_FATAL); 870 871 if (!audit->loaded) 872 return (EPKG_FATAL); 873 874 if (pkg_audit_parse_vulnxml(audit) == EPKG_FATAL) 875 return (EPKG_FATAL); 876 877 audit->items = pkg_audit_preprocess(audit->entries); 878 audit->parsed = true; 879 880 return (EPKG_OK); 881 } 882 883 void 884 pkg_audit_free (struct pkg_audit *audit) 885 { 886 if (audit != NULL) { 887 if (audit->parsed) { 888 pkg_audit_free_list(audit->entries); 889 free(audit->items); 890 } 891 if (audit->loaded) { 892 munmap(audit->map, audit->len); 893 } 894 free(audit); 895 } 896 } 897 898 void 899 pkg_audit_issues_free(struct pkg_audit_issues *issues) 900 { 901 struct pkg_audit_issue *i, *issue; 902 903 if (issues == NULL) 904 return; 905 906 LL_FOREACH_SAFE(issues->issues, issue, i) { 907 LL_DELETE(issues->issues, issue); 908 free(issue); 909 } 910 free(issues); 911 }