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