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