1 /* $NetBSD: vulnerabilities-file.c,v 1.3 2018/03/25 04:04:36 sevan Exp $ */ 2 3 /*- 4 * Copyright (c) 2008, 2010 Joerg Sonnenberger <joerg@NetBSD.org>. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the 16 * distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 26 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 28 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #if HAVE_CONFIG_H 33 #include "config.h" 34 #endif 35 36 #include <nbcompat.h> 37 38 #if HAVE_SYS_CDEFS_H 39 #include <sys/cdefs.h> 40 #endif 41 __RCSID("$NetBSD: vulnerabilities-file.c,v 1.3 2018/03/25 04:04:36 sevan Exp $"); 42 43 #if HAVE_SYS_STAT_H 44 #include <sys/stat.h> 45 #endif 46 #if HAVE_SYS_WAIT_H 47 #include <sys/wait.h> 48 #endif 49 #ifndef BOOTSTRAP 50 #include <archive.h> 51 #endif 52 #include <ctype.h> 53 #if HAVE_ERR_H 54 #include <err.h> 55 #endif 56 #include <errno.h> 57 #include <fcntl.h> 58 #include <limits.h> 59 #include <stdlib.h> 60 #include <string.h> 61 #ifndef NETBSD 62 #include <nbcompat/sha1.h> 63 #include <nbcompat/sha2.h> 64 #else 65 #include <sha1.h> 66 #include <sha2.h> 67 #endif 68 #include <unistd.h> 69 70 #include "lib.h" 71 72 static struct pkg_vulnerabilities *read_pkg_vulnerabilities_archive(struct archive *, int); 73 static struct pkg_vulnerabilities *parse_pkg_vuln(const char *, size_t, int); 74 75 static const char pgp_msg_start[] = "-----BEGIN PGP SIGNED MESSAGE-----\n"; 76 static const char pgp_msg_end[] = "-----BEGIN PGP SIGNATURE-----\n"; 77 static const char pkcs7_begin[] = "-----BEGIN PKCS7-----\n"; 78 static const char pkcs7_end[] = "-----END PKCS7-----\n"; 79 80 static struct archive * 81 prepare_raw_file(void) 82 { 83 struct archive *a = archive_read_new(); 84 if (a == NULL) 85 errx(EXIT_FAILURE, "memory allocation failed"); 86 87 archive_read_support_filter_gzip(a); 88 archive_read_support_filter_bzip2(a); 89 archive_read_support_filter_xz(a); 90 archive_read_support_format_raw(a); 91 return a; 92 } 93 94 static void 95 verify_signature_pkcs7(const char *input) 96 { 97 #ifdef HAVE_SSL 98 const char *begin_pkgvul, *end_pkgvul, *begin_sig, *end_sig; 99 100 if (strncmp(input, pgp_msg_start, strlen(pgp_msg_start)) == 0) { 101 begin_pkgvul = input + strlen(pgp_msg_start); 102 if ((end_pkgvul = strstr(begin_pkgvul, pgp_msg_end)) == NULL) 103 errx(EXIT_FAILURE, "Invalid PGP signature"); 104 if ((begin_sig = strstr(end_pkgvul, pkcs7_begin)) == NULL) 105 errx(EXIT_FAILURE, "No PKCS7 signature"); 106 } else { 107 begin_pkgvul = input; 108 if ((begin_sig = strstr(begin_pkgvul, pkcs7_begin)) == NULL) 109 errx(EXIT_FAILURE, "No PKCS7 signature"); 110 end_pkgvul = begin_sig; 111 } 112 if ((end_sig = strstr(begin_sig, pkcs7_end)) == NULL) 113 errx(EXIT_FAILURE, "Invalid PKCS7 signature"); 114 end_sig += strlen(pkcs7_end); 115 116 if (easy_pkcs7_verify(begin_pkgvul, end_pkgvul - begin_pkgvul, 117 begin_sig, end_sig - begin_sig, certs_pkg_vulnerabilities, 0)) 118 errx(EXIT_FAILURE, "Unable to verify PKCS7 signature"); 119 #else 120 errx(EXIT_FAILURE, "OpenSSL support is not compiled in"); 121 #endif 122 } 123 124 static void 125 verify_signature(const char *input, size_t input_len) 126 { 127 gpg_verify(input, input_len, gpg_keyring_pkgvuln, NULL, 0); 128 if (certs_pkg_vulnerabilities != NULL) 129 verify_signature_pkcs7(input); 130 } 131 132 static void * 133 sha512_hash_init(void) 134 { 135 static SHA512_CTX hash_ctx; 136 137 SHA512_Init(&hash_ctx); 138 return &hash_ctx; 139 } 140 141 static void 142 sha512_hash_update(void *ctx, const void *data, size_t len) 143 { 144 SHA512_CTX *hash_ctx = ctx; 145 146 SHA512_Update(hash_ctx, data, len); 147 } 148 149 static const char * 150 sha512_hash_finish(void *ctx) 151 { 152 static char hash[SHA512_DIGEST_STRING_LENGTH]; 153 unsigned char digest[SHA512_DIGEST_LENGTH]; 154 SHA512_CTX *hash_ctx = ctx; 155 int i; 156 157 SHA512_Final(digest, hash_ctx); 158 for (i = 0; i < SHA512_DIGEST_LENGTH; ++i) { 159 unsigned char c; 160 161 c = digest[i] / 16; 162 if (c < 10) 163 hash[2 * i] = '0' + c; 164 else 165 hash[2 * i] = 'a' - 10 + c; 166 167 c = digest[i] % 16; 168 if (c < 10) 169 hash[2 * i + 1] = '0' + c; 170 else 171 hash[2 * i + 1] = 'a' - 10 + c; 172 } 173 hash[2 * i] = '\0'; 174 175 return hash; 176 } 177 178 static void * 179 sha1_hash_init(void) 180 { 181 static SHA1_CTX hash_ctx; 182 183 SHA1Init(&hash_ctx); 184 return &hash_ctx; 185 } 186 187 static void 188 sha1_hash_update(void *ctx, const void *data, size_t len) 189 { 190 SHA1_CTX *hash_ctx = ctx; 191 192 SHA1Update(hash_ctx, data, len); 193 } 194 195 static const char * 196 sha1_hash_finish(void *ctx) 197 { 198 static char hash[SHA1_DIGEST_STRING_LENGTH]; 199 SHA1_CTX *hash_ctx = ctx; 200 201 SHA1End(hash_ctx, hash); 202 203 return hash; 204 } 205 206 static const struct hash_algorithm { 207 const char *name; 208 size_t name_len; 209 void * (*init)(void); 210 void (*update)(void *, const void *, size_t); 211 const char * (* finish)(void *); 212 } hash_algorithms[] = { 213 { "SHA512", 6, sha512_hash_init, sha512_hash_update, 214 sha512_hash_finish }, 215 { "SHA1", 4, sha1_hash_init, sha1_hash_update, 216 sha1_hash_finish }, 217 { NULL, 0, NULL, NULL, NULL } 218 }; 219 220 static void 221 verify_hash(const char *input, const char *hash_line) 222 { 223 const struct hash_algorithm *hash; 224 void *ctx; 225 const char *last_start, *next, *hash_value; 226 int in_pgp_msg; 227 228 for (hash = hash_algorithms; hash->name != NULL; ++hash) { 229 if (strncmp(hash_line, hash->name, hash->name_len)) 230 continue; 231 if (isspace((unsigned char)hash_line[hash->name_len])) 232 break; 233 } 234 if (hash->name == NULL) { 235 const char *end_name; 236 for (end_name = hash_line; *end_name != '\0'; ++end_name) { 237 if (!isalnum((unsigned char)*end_name)) 238 break; 239 } 240 warnx("Unsupported hash algorithm: %.*s", 241 (int)(end_name - hash_line), hash_line); 242 return; 243 } 244 245 hash_line += hash->name_len; 246 if (!isspace((unsigned char)*hash_line)) 247 errx(EXIT_FAILURE, "Invalid #CHECKSUM"); 248 while (isspace((unsigned char)*hash_line) && *hash_line != '\n') 249 ++hash_line; 250 251 if (*hash_line == '\n') 252 errx(EXIT_FAILURE, "Invalid #CHECKSUM"); 253 254 ctx = (*hash->init)(); 255 if (strncmp(input, pgp_msg_start, strlen(pgp_msg_start)) == 0) { 256 input += strlen(pgp_msg_start); 257 in_pgp_msg = 1; 258 } else { 259 in_pgp_msg = 0; 260 } 261 for (last_start = input; *input != '\0'; input = next) { 262 if ((next = strchr(input, '\n')) == NULL) 263 errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities"); 264 ++next; 265 if (in_pgp_msg && strncmp(input, pgp_msg_end, strlen(pgp_msg_end)) == 0) 266 break; 267 if (!in_pgp_msg && strncmp(input, pkcs7_begin, strlen(pkcs7_begin)) == 0) 268 break; 269 if (*input == '\n' || 270 strncmp(input, "Hash:", 5) == 0 || 271 strncmp(input, "# $NetBSD", 9) == 0 || 272 strncmp(input, "#CHECKSUM", 9) == 0) { 273 (*hash->update)(ctx, last_start, input - last_start); 274 last_start = next; 275 } 276 } 277 (*hash->update)(ctx, last_start, input - last_start); 278 hash_value = (*hash->finish)(ctx); 279 if (strncmp(hash_line, hash_value, strlen(hash_value))) 280 errx(EXIT_FAILURE, "%s hash doesn't match", hash->name); 281 hash_line += strlen(hash_value); 282 283 while (isspace((unsigned char)*hash_line) && *hash_line != '\n') 284 ++hash_line; 285 286 if (!isspace((unsigned char)*hash_line)) 287 errx(EXIT_FAILURE, "Invalid #CHECKSUM"); 288 } 289 290 static void 291 add_vulnerability(struct pkg_vulnerabilities *pv, size_t *allocated, const char *line) 292 { 293 size_t len_pattern, len_class, len_url; 294 const char *start_pattern, *start_class, *start_url; 295 296 start_pattern = line; 297 298 start_class = line; 299 while (*start_class != '\0' && !isspace((unsigned char)*start_class)) 300 ++start_class; 301 len_pattern = start_class - line; 302 303 while (*start_class != '\n' && isspace((unsigned char)*start_class)) 304 ++start_class; 305 306 if (*start_class == '0' || *start_class == '\n') 307 errx(EXIT_FAILURE, "Input error: missing classification"); 308 309 start_url = start_class; 310 while (*start_url != '\0' && !isspace((unsigned char)*start_url)) 311 ++start_url; 312 len_class = start_url - start_class; 313 314 while (*start_url != '\n' && isspace((unsigned char)*start_url)) 315 ++start_url; 316 317 if (*start_url == '0' || *start_url == '\n') 318 errx(EXIT_FAILURE, "Input error: missing URL"); 319 320 line = start_url; 321 while (*line != '\0' && !isspace((unsigned char)*line)) 322 ++line; 323 len_url = line - start_url; 324 325 if (pv->entries == *allocated) { 326 if (*allocated == 0) 327 *allocated = 16; 328 else if (*allocated <= SSIZE_MAX / 2) 329 *allocated *= 2; 330 else 331 errx(EXIT_FAILURE, "Too many vulnerabilities"); 332 pv->vulnerability = xrealloc(pv->vulnerability, 333 sizeof(char *) * *allocated); 334 pv->classification = xrealloc(pv->classification, 335 sizeof(char *) * *allocated); 336 pv->advisory = xrealloc(pv->advisory, 337 sizeof(char *) * *allocated); 338 } 339 340 pv->vulnerability[pv->entries] = xmalloc(len_pattern + 1); 341 memcpy(pv->vulnerability[pv->entries], start_pattern, len_pattern); 342 pv->vulnerability[pv->entries][len_pattern] = '\0'; 343 pv->classification[pv->entries] = xmalloc(len_class + 1); 344 memcpy(pv->classification[pv->entries], start_class, len_class); 345 pv->classification[pv->entries][len_class] = '\0'; 346 pv->advisory[pv->entries] = xmalloc(len_url + 1); 347 memcpy(pv->advisory[pv->entries], start_url, len_url); 348 pv->advisory[pv->entries][len_url] = '\0'; 349 350 ++pv->entries; 351 } 352 353 struct pkg_vulnerabilities * 354 read_pkg_vulnerabilities_memory(void *buf, size_t len, int check_sum) 355 { 356 #ifdef BOOTSTRAP 357 errx(EXIT_FAILURE, "Audit functions are unsupported during bootstrap"); 358 #else 359 struct archive *a; 360 struct pkg_vulnerabilities *pv; 361 362 a = prepare_raw_file(); 363 if (archive_read_open_memory(a, buf, len) != ARCHIVE_OK) 364 errx(EXIT_FAILURE, "Cannot open pkg_vulnerabilies buffer: %s", 365 archive_error_string(a)); 366 367 pv = read_pkg_vulnerabilities_archive(a, check_sum); 368 369 return pv; 370 #endif 371 } 372 373 struct pkg_vulnerabilities * 374 read_pkg_vulnerabilities_file(const char *path, int ignore_missing, int check_sum) 375 { 376 #ifdef BOOTSTRAP 377 errx(EXIT_FAILURE, "Audit functions are unsupported during bootstrap"); 378 #else 379 struct archive *a; 380 struct pkg_vulnerabilities *pv; 381 int fd; 382 383 if ((fd = open(path, O_RDONLY)) == -1) { 384 if (errno == ENOENT && ignore_missing) 385 return NULL; 386 err(EXIT_FAILURE, "Cannot open %s", path); 387 } 388 389 a = prepare_raw_file(); 390 if (archive_read_open_fd(a, fd, 65536) != ARCHIVE_OK) 391 errx(EXIT_FAILURE, "Cannot open ``%s'': %s", path, 392 archive_error_string(a)); 393 394 pv = read_pkg_vulnerabilities_archive(a, check_sum); 395 close(fd); 396 397 return pv; 398 #endif 399 } 400 401 #ifndef BOOTSTRAP 402 static struct pkg_vulnerabilities * 403 read_pkg_vulnerabilities_archive(struct archive *a, int check_sum) 404 { 405 struct archive_entry *ae; 406 struct pkg_vulnerabilities *pv; 407 char *buf; 408 size_t buf_len, off; 409 ssize_t r; 410 411 if (archive_read_next_header(a, &ae) != ARCHIVE_OK) 412 errx(EXIT_FAILURE, "Cannot read pkg_vulnerabilities: %s", 413 archive_error_string(a)); 414 415 off = 0; 416 buf_len = 65536; 417 buf = xmalloc(buf_len + 1); 418 419 for (;;) { 420 r = archive_read_data(a, buf + off, buf_len - off); 421 if (r <= 0) 422 break; 423 off += r; 424 if (off == buf_len) { 425 buf_len *= 2; 426 if (buf_len < off) 427 errx(EXIT_FAILURE, "pkg_vulnerabilties too large"); 428 buf = xrealloc(buf, buf_len + 1); 429 } 430 } 431 432 if (r != ARCHIVE_OK) 433 errx(EXIT_FAILURE, "Cannot read pkg_vulnerabilities: %s", 434 archive_error_string(a)); 435 436 archive_read_close(a); 437 438 buf[off] = '\0'; 439 pv = parse_pkg_vuln(buf, off, check_sum); 440 free(buf); 441 return pv; 442 } 443 444 static struct pkg_vulnerabilities * 445 parse_pkg_vuln(const char *input, size_t input_len, int check_sum) 446 { 447 struct pkg_vulnerabilities *pv; 448 long version; 449 char *end; 450 const char *iter, *next; 451 size_t allocated_vulns; 452 int in_pgp_msg; 453 454 pv = xmalloc(sizeof(*pv)); 455 456 allocated_vulns = pv->entries = 0; 457 pv->vulnerability = NULL; 458 pv->classification = NULL; 459 pv->advisory = NULL; 460 461 if (strlen(input) != input_len) 462 errx(1, "Invalid input (NUL character found)"); 463 464 if (check_sum) 465 verify_signature(input, input_len); 466 467 if (strncmp(input, pgp_msg_start, strlen(pgp_msg_start)) == 0) { 468 iter = input + strlen(pgp_msg_start); 469 in_pgp_msg = 1; 470 } else { 471 iter = input; 472 in_pgp_msg = 0; 473 } 474 475 for (; *iter; iter = next) { 476 if ((next = strchr(iter, '\n')) == NULL) 477 errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities"); 478 ++next; 479 if (*iter == '\0' || *iter == '\n') 480 continue; 481 if (strncmp(iter, "Hash:", 5) == 0) 482 continue; 483 if (strncmp(iter, "# $NetBSD", 9) == 0) 484 continue; 485 if (*iter == '#' && isspace((unsigned char)iter[1])) { 486 for (++iter; iter != next; ++iter) { 487 if (!isspace((unsigned char)*iter)) 488 errx(EXIT_FAILURE, "Invalid header"); 489 } 490 continue; 491 } 492 493 if (strncmp(iter, "#FORMAT", 7) != 0) 494 errx(EXIT_FAILURE, "Input header is malformed"); 495 496 iter += 7; 497 if (!isspace((unsigned char)*iter)) 498 errx(EXIT_FAILURE, "Invalid #FORMAT"); 499 ++iter; 500 version = strtol(iter, &end, 10); 501 if (iter == end || version != 1 || *end != '.') 502 errx(EXIT_FAILURE, "Input #FORMAT"); 503 iter = end + 1; 504 version = strtol(iter, &end, 10); 505 if (iter == end || version != 1 || *end != '.') 506 errx(EXIT_FAILURE, "Input #FORMAT"); 507 iter = end + 1; 508 version = strtol(iter, &end, 10); 509 if (iter == end || version != 0) 510 errx(EXIT_FAILURE, "Input #FORMAT"); 511 for (iter = end; iter != next; ++iter) { 512 if (!isspace((unsigned char)*iter)) 513 errx(EXIT_FAILURE, "Input #FORMAT"); 514 } 515 break; 516 } 517 if (*iter == '\0') 518 errx(EXIT_FAILURE, "Missing #CHECKSUM or content"); 519 520 for (iter = next; *iter; iter = next) { 521 if ((next = strchr(iter, '\n')) == NULL) 522 errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities"); 523 ++next; 524 if (*iter == '\0' || *iter == '\n') 525 continue; 526 if (in_pgp_msg && strncmp(iter, pgp_msg_end, strlen(pgp_msg_end)) == 0) 527 break; 528 if (!in_pgp_msg && strncmp(iter, pkcs7_begin, strlen(pkcs7_begin)) == 0) 529 break; 530 if (*iter == '#' && 531 (iter[1] == '\0' || iter[1] == '\n' || isspace((unsigned char)iter[1]))) 532 continue; 533 if (strncmp(iter, "#CHECKSUM", 9) == 0) { 534 iter += 9; 535 if (!isspace((unsigned char)*iter)) 536 errx(EXIT_FAILURE, "Invalid #CHECKSUM"); 537 while (isspace((unsigned char)*iter)) 538 ++iter; 539 verify_hash(input, iter); 540 continue; 541 } 542 if (*iter == '#') { 543 /* 544 * This should really be an error, 545 * but it is still used. 546 */ 547 /* errx(EXIT_FAILURE, "Invalid data line starting with #"); */ 548 continue; 549 } 550 add_vulnerability(pv, &allocated_vulns, iter); 551 } 552 553 if (pv->entries != allocated_vulns) { 554 pv->vulnerability = xrealloc(pv->vulnerability, 555 sizeof(char *) * pv->entries); 556 pv->classification = xrealloc(pv->classification, 557 sizeof(char *) * pv->entries); 558 pv->advisory = xrealloc(pv->advisory, 559 sizeof(char *) * pv->entries); 560 } 561 562 return pv; 563 } 564 #endif 565 566 void 567 free_pkg_vulnerabilities(struct pkg_vulnerabilities *pv) 568 { 569 size_t i; 570 571 for (i = 0; i < pv->entries; ++i) { 572 free(pv->vulnerability[i]); 573 free(pv->classification[i]); 574 free(pv->advisory[i]); 575 } 576 free(pv->vulnerability); 577 free(pv->classification); 578 free(pv->advisory); 579 free(pv); 580 } 581 582 static int 583 check_ignored_entry(struct pkg_vulnerabilities *pv, size_t i) 584 { 585 const char *iter, *next; 586 size_t entry_len, url_len; 587 588 if (ignore_advisories == NULL) 589 return 0; 590 591 url_len = strlen(pv->advisory[i]); 592 593 for (iter = ignore_advisories; *iter; iter = next) { 594 if ((next = strchr(iter, '\n')) == NULL) { 595 entry_len = strlen(iter); 596 next = iter + entry_len; 597 } else { 598 entry_len = next - iter; 599 ++next; 600 } 601 if (url_len != entry_len) 602 continue; 603 if (strncmp(pv->advisory[i], iter, entry_len) == 0) 604 return 1; 605 } 606 return 0; 607 } 608 609 int 610 audit_package(struct pkg_vulnerabilities *pv, const char *pkgname, 611 const char *limit_vul_types, int include_ignored, int output_type) 612 { 613 FILE *output = output_type == 1 ? stdout : stderr; 614 size_t i; 615 int retval, do_eol, ignored; 616 617 retval = 0; 618 619 do_eol = (strcasecmp(check_eol, "yes") == 0); 620 621 for (i = 0; i < pv->entries; ++i) { 622 ignored = check_ignored_entry(pv, i); 623 if (ignored && !include_ignored) 624 continue; 625 if (limit_vul_types != NULL && 626 strcmp(limit_vul_types, pv->classification[i])) 627 continue; 628 if (!pkg_match(pv->vulnerability[i], pkgname)) 629 continue; 630 if (strcmp("eol", pv->classification[i]) == 0) { 631 if (!do_eol) 632 continue; 633 retval = 1; 634 if (output_type == 0) { 635 puts(pkgname); 636 continue; 637 } 638 fprintf(output, 639 "Package %s has reached end-of-life (eol), " 640 "see %s/eol-packages\n", pkgname, 641 tnf_vulnerability_base); 642 continue; 643 } 644 retval = 1; 645 if (output_type == 0) { 646 fprintf(stdout, "%s%s\n", 647 pkgname, ignored ? " (ignored)" : ""); 648 } else { 649 fprintf(output, 650 "Package %s has a%s %s vulnerability, see %s\n", 651 pkgname, ignored ? "n ignored" : "", 652 pv->classification[i], pv->advisory[i]); 653 } 654 } 655 return retval; 656 } 657