1 /* $NetBSD: vulnerabilities-file.c,v 1.1.1.5 2010/06/26 00:14:33 joerg 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.1.1.5 2010/06/26 00:14:33 joerg 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 void 81 verify_signature_pkcs7(const char *input) 82 { 83 #ifdef HAVE_SSL 84 const char *begin_pkgvul, *end_pkgvul, *begin_sig, *end_sig; 85 86 if (strncmp(input, pgp_msg_start, strlen(pgp_msg_start)) == 0) { 87 begin_pkgvul = input + strlen(pgp_msg_start); 88 if ((end_pkgvul = strstr(begin_pkgvul, pgp_msg_end)) == NULL) 89 errx(EXIT_FAILURE, "Invalid PGP signature"); 90 if ((begin_sig = strstr(end_pkgvul, pkcs7_begin)) == NULL) 91 errx(EXIT_FAILURE, "No PKCS7 signature"); 92 } else { 93 begin_pkgvul = input; 94 if ((begin_sig = strstr(begin_pkgvul, pkcs7_begin)) == NULL) 95 errx(EXIT_FAILURE, "No PKCS7 signature"); 96 end_pkgvul = begin_sig; 97 } 98 if ((end_sig = strstr(begin_sig, pkcs7_end)) == NULL) 99 errx(EXIT_FAILURE, "Invalid PKCS7 signature"); 100 end_sig += strlen(pkcs7_end); 101 102 if (easy_pkcs7_verify(begin_pkgvul, end_pkgvul - begin_pkgvul, 103 begin_sig, end_sig - begin_sig, certs_pkg_vulnerabilities, 0)) 104 errx(EXIT_FAILURE, "Unable to verify PKCS7 signature"); 105 #else 106 errx(EXIT_FAILURE, "OpenSSL support is not compiled in"); 107 #endif 108 } 109 110 static void 111 verify_signature(const char *input, size_t input_len) 112 { 113 if (gpg_cmd == NULL && certs_pkg_vulnerabilities == NULL) 114 errx(EXIT_FAILURE, 115 "At least GPG or CERTIFICATE_ANCHOR_PKGVULN " 116 "must be configured"); 117 if (gpg_cmd != NULL) 118 inline_gpg_verify(input, input_len, gpg_keyring_pkgvuln); 119 if (certs_pkg_vulnerabilities != NULL) 120 verify_signature_pkcs7(input); 121 } 122 123 static void * 124 sha512_hash_init(void) 125 { 126 static SHA512_CTX hash_ctx; 127 128 SHA512_Init(&hash_ctx); 129 return &hash_ctx; 130 } 131 132 static void 133 sha512_hash_update(void *ctx, const void *data, size_t len) 134 { 135 SHA512_CTX *hash_ctx = ctx; 136 137 SHA512_Update(hash_ctx, data, len); 138 } 139 140 static const char * 141 sha512_hash_finish(void *ctx) 142 { 143 static char hash[SHA512_DIGEST_STRING_LENGTH]; 144 unsigned char digest[SHA512_DIGEST_LENGTH]; 145 SHA512_CTX *hash_ctx = ctx; 146 int i; 147 148 SHA512_Final(digest, hash_ctx); 149 for (i = 0; i < SHA512_DIGEST_LENGTH; ++i) { 150 unsigned char c; 151 152 c = digest[i] / 16; 153 if (c < 10) 154 hash[2 * i] = '0' + c; 155 else 156 hash[2 * i] = 'a' - 10 + c; 157 158 c = digest[i] % 16; 159 if (c < 10) 160 hash[2 * i + 1] = '0' + c; 161 else 162 hash[2 * i + 1] = 'a' - 10 + c; 163 } 164 hash[2 * i] = '\0'; 165 166 return hash; 167 } 168 169 static void * 170 sha1_hash_init(void) 171 { 172 static SHA1_CTX hash_ctx; 173 174 SHA1Init(&hash_ctx); 175 return &hash_ctx; 176 } 177 178 static void 179 sha1_hash_update(void *ctx, const void *data, size_t len) 180 { 181 SHA1_CTX *hash_ctx = ctx; 182 183 SHA1Update(hash_ctx, data, len); 184 } 185 186 static const char * 187 sha1_hash_finish(void *ctx) 188 { 189 static char hash[SHA1_DIGEST_STRING_LENGTH]; 190 SHA1_CTX *hash_ctx = ctx; 191 192 SHA1End(hash_ctx, hash); 193 194 return hash; 195 } 196 197 static const struct hash_algorithm { 198 const char *name; 199 size_t name_len; 200 void * (*init)(void); 201 void (*update)(void *, const void *, size_t); 202 const char * (* finish)(void *); 203 } hash_algorithms[] = { 204 { "SHA512", 6, sha512_hash_init, sha512_hash_update, 205 sha512_hash_finish }, 206 { "SHA1", 4, sha1_hash_init, sha1_hash_update, 207 sha1_hash_finish }, 208 { NULL, 0, NULL, NULL, NULL } 209 }; 210 211 static void 212 verify_hash(const char *input, const char *hash_line) 213 { 214 const struct hash_algorithm *hash; 215 void *ctx; 216 const char *last_start, *next, *hash_value; 217 int in_pgp_msg; 218 219 for (hash = hash_algorithms; hash->name != NULL; ++hash) { 220 if (strncmp(hash_line, hash->name, hash->name_len)) 221 continue; 222 if (isspace((unsigned char)hash_line[hash->name_len])) 223 break; 224 } 225 if (hash->name == NULL) { 226 const char *end_name; 227 for (end_name = hash_line; *end_name != '\0'; ++end_name) { 228 if (!isalnum((unsigned char)*end_name)) 229 break; 230 } 231 warnx("Unsupported hash algorithm: %.*s", 232 (int)(end_name - hash_line), hash_line); 233 return; 234 } 235 236 hash_line += hash->name_len; 237 if (!isspace((unsigned char)*hash_line)) 238 errx(EXIT_FAILURE, "Invalid #CHECKSUM"); 239 while (isspace((unsigned char)*hash_line) && *hash_line != '\n') 240 ++hash_line; 241 242 if (*hash_line == '\n') 243 errx(EXIT_FAILURE, "Invalid #CHECKSUM"); 244 245 ctx = (*hash->init)(); 246 if (strncmp(input, pgp_msg_start, strlen(pgp_msg_start)) == 0) { 247 input += strlen(pgp_msg_start); 248 in_pgp_msg = 1; 249 } else { 250 in_pgp_msg = 0; 251 } 252 for (last_start = input; *input != '\0'; input = next) { 253 if ((next = strchr(input, '\n')) == NULL) 254 errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities"); 255 ++next; 256 if (in_pgp_msg && strncmp(input, pgp_msg_end, strlen(pgp_msg_end)) == 0) 257 break; 258 if (!in_pgp_msg && strncmp(input, pkcs7_begin, strlen(pkcs7_begin)) == 0) 259 break; 260 if (*input == '\n' || 261 strncmp(input, "Hash:", 5) == 0 || 262 strncmp(input, "# $NetBSD", 9) == 0 || 263 strncmp(input, "#CHECKSUM", 9) == 0) { 264 (*hash->update)(ctx, last_start, input - last_start); 265 last_start = next; 266 } 267 } 268 (*hash->update)(ctx, last_start, input - last_start); 269 hash_value = (*hash->finish)(ctx); 270 if (strncmp(hash_line, hash_value, strlen(hash_value))) 271 errx(EXIT_FAILURE, "%s hash doesn't match", hash->name); 272 hash_line += strlen(hash_value); 273 274 while (isspace((unsigned char)*hash_line) && *hash_line != '\n') 275 ++hash_line; 276 277 if (!isspace((unsigned char)*hash_line)) 278 errx(EXIT_FAILURE, "Invalid #CHECKSUM"); 279 } 280 281 static void 282 add_vulnerability(struct pkg_vulnerabilities *pv, size_t *allocated, const char *line) 283 { 284 size_t len_pattern, len_class, len_url; 285 const char *start_pattern, *start_class, *start_url; 286 287 start_pattern = line; 288 289 start_class = line; 290 while (*start_class != '\0' && !isspace((unsigned char)*start_class)) 291 ++start_class; 292 len_pattern = start_class - line; 293 294 while (*start_class != '\n' && isspace((unsigned char)*start_class)) 295 ++start_class; 296 297 if (*start_class == '0' || *start_class == '\n') 298 errx(EXIT_FAILURE, "Input error: missing classification"); 299 300 start_url = start_class; 301 while (*start_url != '\0' && !isspace((unsigned char)*start_url)) 302 ++start_url; 303 len_class = start_url - start_class; 304 305 while (*start_url != '\n' && isspace((unsigned char)*start_url)) 306 ++start_url; 307 308 if (*start_url == '0' || *start_url == '\n') 309 errx(EXIT_FAILURE, "Input error: missing URL"); 310 311 line = start_url; 312 while (*line != '\0' && !isspace((unsigned char)*line)) 313 ++line; 314 len_url = line - start_url; 315 316 if (pv->entries == *allocated) { 317 if (*allocated == 0) 318 *allocated = 16; 319 else if (*allocated <= SSIZE_MAX / 2) 320 *allocated *= 2; 321 else 322 errx(EXIT_FAILURE, "Too many vulnerabilities"); 323 pv->vulnerability = xrealloc(pv->vulnerability, 324 sizeof(char *) * *allocated); 325 pv->classification = xrealloc(pv->classification, 326 sizeof(char *) * *allocated); 327 pv->advisory = xrealloc(pv->advisory, 328 sizeof(char *) * *allocated); 329 } 330 331 pv->vulnerability[pv->entries] = xmalloc(len_pattern + 1); 332 memcpy(pv->vulnerability[pv->entries], start_pattern, len_pattern); 333 pv->vulnerability[pv->entries][len_pattern] = '\0'; 334 pv->classification[pv->entries] = xmalloc(len_class + 1); 335 memcpy(pv->classification[pv->entries], start_class, len_class); 336 pv->classification[pv->entries][len_class] = '\0'; 337 pv->advisory[pv->entries] = xmalloc(len_url + 1); 338 memcpy(pv->advisory[pv->entries], start_url, len_url); 339 pv->advisory[pv->entries][len_url] = '\0'; 340 341 ++pv->entries; 342 } 343 344 struct pkg_vulnerabilities * 345 read_pkg_vulnerabilities_memory(void *buf, size_t len, int check_sum) 346 { 347 #ifdef BOOTSTRAP 348 errx(EXIT_FAILURE, "Audit functions are unsupported during bootstrap"); 349 #else 350 struct archive *a; 351 struct pkg_vulnerabilities *pv; 352 353 if ((a = archive_read_new()) == NULL) 354 errx(EXIT_FAILURE, "memory allocation failed"); 355 356 if (archive_read_support_compression_all(a) != ARCHIVE_OK || 357 archive_read_support_format_raw(a) != ARCHIVE_OK || 358 archive_read_open_memory(a, buf, len) != ARCHIVE_OK) 359 errx(EXIT_FAILURE, "Cannot open pkg_vulnerabilies buffer: %s", 360 archive_error_string(a)); 361 362 pv = read_pkg_vulnerabilities_archive(a, check_sum); 363 364 return pv; 365 #endif 366 } 367 368 struct pkg_vulnerabilities * 369 read_pkg_vulnerabilities_file(const char *path, int ignore_missing, int check_sum) 370 { 371 #ifdef BOOTSTRAP 372 errx(EXIT_FAILURE, "Audit functions are unsupported during bootstrap"); 373 #else 374 struct archive *a; 375 struct pkg_vulnerabilities *pv; 376 int fd; 377 378 if ((fd = open(path, O_RDONLY)) == -1) { 379 if (errno == ENOENT && ignore_missing) 380 return NULL; 381 err(EXIT_FAILURE, "Cannot open %s", path); 382 } 383 384 if ((a = archive_read_new()) == NULL) 385 errx(EXIT_FAILURE, "memory allocation failed"); 386 387 if (archive_read_support_compression_all(a) != ARCHIVE_OK || 388 archive_read_support_format_raw(a) != ARCHIVE_OK || 389 archive_read_open_fd(a, fd, 65536) != ARCHIVE_OK) 390 errx(EXIT_FAILURE, "Cannot open ``%s'': %s", path, 391 archive_error_string(a)); 392 393 pv = read_pkg_vulnerabilities_archive(a, check_sum); 394 close(fd); 395 396 return pv; 397 #endif 398 } 399 400 #ifndef BOOTSTRAP 401 static struct pkg_vulnerabilities * 402 read_pkg_vulnerabilities_archive(struct archive *a, int check_sum) 403 { 404 struct archive_entry *ae; 405 struct pkg_vulnerabilities *pv; 406 char *buf; 407 size_t buf_len, off; 408 ssize_t r; 409 410 if (archive_read_next_header(a, &ae) != ARCHIVE_OK) 411 errx(EXIT_FAILURE, "Cannot read pkg_vulnerabilities: %s", 412 archive_error_string(a)); 413 414 off = 0; 415 buf_len = 65536; 416 buf = xmalloc(buf_len + 1); 417 418 for (;;) { 419 r = archive_read_data(a, buf + off, buf_len - off); 420 if (r <= 0) 421 break; 422 off += r; 423 if (off == buf_len) { 424 buf_len *= 2; 425 if (buf_len < off) 426 errx(EXIT_FAILURE, "pkg_vulnerabilties too large"); 427 buf = xrealloc(buf, buf_len + 1); 428 } 429 } 430 431 if (r != ARCHIVE_OK) 432 errx(EXIT_FAILURE, "Cannot read pkg_vulnerabilities: %s", 433 archive_error_string(a)); 434 435 archive_read_close(a); 436 437 buf[off] = '\0'; 438 pv = parse_pkg_vuln(buf, off, check_sum); 439 free(buf); 440 return pv; 441 } 442 443 static struct pkg_vulnerabilities * 444 parse_pkg_vuln(const char *input, size_t input_len, int check_sum) 445 { 446 struct pkg_vulnerabilities *pv; 447 long version; 448 char *end; 449 const char *iter, *next; 450 size_t allocated_vulns; 451 int in_pgp_msg; 452 453 #if defined(__minix) 454 next = NULL; /* LSC: Fix -Os compilation: -Werror=maybe-uninitialized */ 455 #endif /* defined(__minix) */ 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 568 void 569 free_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 584 static int 585 check_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 611 int 612 audit_package(struct pkg_vulnerabilities *pv, const char *pkgname, 613 const char *limit_vul_types, int output_type) 614 { 615 FILE *output = output_type == 1 ? stdout : stderr; 616 size_t i; 617 int retval, do_eol; 618 619 retval = 0; 620 621 do_eol = (strcasecmp(check_eol, "yes") == 0); 622 623 for (i = 0; i < pv->entries; ++i) { 624 if (check_ignored_entry(pv, i)) 625 continue; 626 if (limit_vul_types != NULL && 627 strcmp(limit_vul_types, pv->classification[i])) 628 continue; 629 if (!pkg_match(pv->vulnerability[i], pkgname)) 630 continue; 631 if (strcmp("eol", pv->classification[i]) == 0) { 632 if (!do_eol) 633 continue; 634 retval = 1; 635 if (output_type == 0) { 636 puts(pkgname); 637 continue; 638 } 639 fprintf(output, 640 "Package %s has reached end-of-life (eol), " 641 "see %s/eol-packages\n", pkgname, 642 tnf_vulnerability_base); 643 continue; 644 } 645 retval = 1; 646 if (output_type == 0) { 647 puts(pkgname); 648 } else { 649 fprintf(output, 650 "Package %s has a %s vulnerability, see %s\n", 651 pkgname, pv->classification[i], pv->advisory[i]); 652 } 653 } 654 return retval; 655 } 656