1 /* $NetBSD: audit.c,v 1.4 2021/04/10 19:49:59 nia Exp $ */ 2 3 #if HAVE_CONFIG_H 4 #include "config.h" 5 #endif 6 #include <nbcompat.h> 7 #if HAVE_SYS_CDEFS_H 8 #include <sys/cdefs.h> 9 #endif 10 __RCSID("$NetBSD: audit.c,v 1.4 2021/04/10 19:49:59 nia Exp $"); 11 12 /*- 13 * Copyright (c) 2008 Joerg Sonnenberger <joerg@NetBSD.org>. 14 * All rights reserved. 15 * 16 * Redistribution and use in source and binary forms, with or without 17 * modification, are permitted provided that the following conditions 18 * are met: 19 * 20 * 1. Redistributions of source code must retain the above copyright 21 * notice, this list of conditions and the following disclaimer. 22 * 2. Redistributions in binary form must reproduce the above copyright 23 * notice, this list of conditions and the following disclaimer in 24 * the documentation and/or other materials provided with the 25 * distribution. 26 * 27 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 28 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 29 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 30 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 31 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 32 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, 33 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 34 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 35 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 36 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 37 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 38 * SUCH DAMAGE. 39 */ 40 41 #if HAVE_SYS_TYPES_H 42 #include <sys/types.h> 43 #endif 44 #if HAVE_SYS_STAT_H 45 #include <sys/stat.h> 46 #endif 47 #if HAVE_ERR_H 48 #include <err.h> 49 #endif 50 #if HAVE_ERRNO_H 51 #include <errno.h> 52 #endif 53 #if HAVE_FCNTL_H 54 #include <fcntl.h> 55 #endif 56 #if HAVE_SIGNAL_H 57 #include <signal.h> 58 #endif 59 #if HAVE_STDIO_H 60 #include <stdio.h> 61 #endif 62 #if HAVE_STRING_H 63 #include <string.h> 64 #endif 65 #ifdef NETBSD 66 #include <unistd.h> 67 #else 68 #include <nbcompat/unistd.h> 69 #endif 70 71 #include <fetch.h> 72 73 #include "admin.h" 74 #include "lib.h" 75 76 static int check_ignored_advisories = 0; 77 static int check_signature = 0; 78 static const char *limit_vul_types = NULL; 79 static int update_pkg_vuln = 0; 80 81 static struct pkg_vulnerabilities *pv; 82 83 static const char audit_options[] = "eist:"; 84 85 static void 86 parse_options(int argc, char **argv, const char *options) 87 { 88 int ch; 89 90 optreset = 1; 91 /* 92 * optind == 0 is interpreted as partial reset request 93 * by GNU getopt, so compensate against this and cleanup 94 * at the end. 95 */ 96 optind = 1; 97 ++argc; 98 --argv; 99 100 while ((ch = getopt(argc, argv, options)) != -1) { 101 switch (ch) { 102 case 'e': 103 check_eol = "yes"; 104 break; 105 case 'i': 106 check_ignored_advisories = 1; 107 break; 108 case 's': 109 check_signature = 1; 110 break; 111 case 't': 112 limit_vul_types = optarg; 113 break; 114 case 'u': 115 update_pkg_vuln = 1; 116 break; 117 default: 118 usage(); 119 /* NOTREACHED */ 120 } 121 } 122 123 --optind; /* See above comment. */ 124 } 125 126 static int 127 check_exact_pkg(const char *pkg) 128 { 129 return audit_package(pv, pkg, limit_vul_types, 130 check_ignored_advisories, quiet ? 0 : 1); 131 } 132 133 static int 134 check_batch_exact_pkgs(const char *fname) 135 { 136 FILE *f; 137 char buf[4096], *line, *eol; 138 int ret; 139 140 ret = 0; 141 if (strcmp(fname, "-") == 0) 142 f = stdin; 143 else { 144 f = fopen(fname, "r"); 145 if (f == NULL) 146 err(EXIT_FAILURE, "Failed to open input file %s", 147 fname); 148 } 149 while ((line = fgets(buf, sizeof(buf), f)) != NULL) { 150 eol = line + strlen(line); 151 if (eol == line) 152 continue; 153 --eol; 154 if (*eol == '\n') { 155 if (eol == line) 156 continue; 157 *eol = '\0'; 158 } 159 ret |= check_exact_pkg(line); 160 } 161 if (f != stdin) 162 fclose(f); 163 164 return ret; 165 } 166 167 static int 168 check_one_installed_pkg(const char *pkg, void *cookie) 169 { 170 int *ret = cookie; 171 172 *ret |= check_exact_pkg(pkg); 173 return 0; 174 } 175 176 static int 177 check_installed_pattern(const char *pattern) 178 { 179 int ret = 0; 180 181 match_installed_pkgs(pattern, check_one_installed_pkg, &ret); 182 183 return ret; 184 } 185 186 static void 187 check_and_read_pkg_vulnerabilities(void) 188 { 189 struct stat st; 190 time_t now; 191 192 if (pkg_vulnerabilities_file == NULL) 193 errx(EXIT_FAILURE, "PKG_VULNERABILITIES is not set"); 194 195 if (verbose >= 1) { 196 if (stat(pkg_vulnerabilities_file, &st) == -1) { 197 if (errno == ENOENT) 198 errx(EXIT_FAILURE, 199 "pkg-vulnerabilities not found, run %s -d", 200 getprogname()); 201 errx(EXIT_FAILURE, "pkg-vulnerabilities not readable"); 202 } 203 now = time(NULL); 204 now -= st.st_mtime; 205 if (now < 0) 206 warnx("pkg-vulnerabilities is from the future"); 207 else if (now > 86400 * 7) 208 warnx("pkg-vulnerabilities is out of date (%ld days old)", 209 (long)(now / 86400)); 210 else if (verbose >= 2) 211 warnx("pkg-vulnerabilities is %ld day%s old", 212 (long)(now / 86400), now / 86400 == 1 ? "" : "s"); 213 } 214 215 pv = read_pkg_vulnerabilities_file(pkg_vulnerabilities_file, 0, check_signature); 216 } 217 218 void 219 audit_pkgdb(int argc, char **argv) 220 { 221 int rv; 222 223 parse_options(argc, argv, audit_options); 224 argv += optind; 225 226 check_and_read_pkg_vulnerabilities(); 227 228 rv = 0; 229 if (*argv == NULL) 230 rv |= check_installed_pattern("*"); 231 else { 232 for (; *argv != NULL; ++argv) 233 rv |= check_installed_pattern(*argv); 234 } 235 free_pkg_vulnerabilities(pv); 236 237 if (rv == 0 && verbose >= 1) 238 fputs("No vulnerabilities found\n", stderr); 239 exit(rv ? EXIT_FAILURE : EXIT_SUCCESS); 240 } 241 242 void 243 audit_pkg(int argc, char **argv) 244 { 245 int rv; 246 247 parse_options(argc, argv, audit_options); 248 argv += optind; 249 250 check_and_read_pkg_vulnerabilities(); 251 rv = 0; 252 for (; *argv != NULL; ++argv) 253 rv |= check_exact_pkg(*argv); 254 255 free_pkg_vulnerabilities(pv); 256 257 if (rv == 0 && verbose >= 1) 258 fputs("No vulnerabilities found\n", stderr); 259 exit(rv ? EXIT_FAILURE : EXIT_SUCCESS); 260 } 261 262 void 263 audit_batch(int argc, char **argv) 264 { 265 int rv; 266 267 parse_options(argc, argv, audit_options); 268 argv += optind; 269 270 check_and_read_pkg_vulnerabilities(); 271 rv = 0; 272 for (; *argv != NULL; ++argv) 273 rv |= check_batch_exact_pkgs(*argv); 274 free_pkg_vulnerabilities(pv); 275 276 if (rv == 0 && verbose >= 1) 277 fputs("No vulnerabilities found\n", stderr); 278 exit(rv ? EXIT_FAILURE : EXIT_SUCCESS); 279 } 280 281 void 282 check_pkg_vulnerabilities(int argc, char **argv) 283 { 284 parse_options(argc, argv, "s"); 285 if (argc != optind + 1) 286 usage(); 287 288 pv = read_pkg_vulnerabilities_file(argv[optind], 0, check_signature); 289 free_pkg_vulnerabilities(pv); 290 } 291 292 void 293 fetch_pkg_vulnerabilities(int argc, char **argv) 294 { 295 struct pkg_vulnerabilities *pv_check; 296 char *buf; 297 size_t buf_len, buf_fetched; 298 ssize_t cur_fetched; 299 struct url *url; 300 struct url_stat st; 301 fetchIO *f; 302 int fd; 303 struct stat sb; 304 char my_flags[20]; 305 const char *flags; 306 307 parse_options(argc, argv, "su"); 308 if (argc != optind) 309 usage(); 310 311 if (verbose >= 2) 312 fprintf(stderr, "Fetching %s\n", pkg_vulnerabilities_url); 313 314 url = fetchParseURL(pkg_vulnerabilities_url); 315 if (url == NULL) 316 errx(EXIT_FAILURE, 317 "Could not parse location of pkg_vulnerabilities: %s", 318 fetchLastErrString); 319 320 flags = fetch_flags; 321 if (update_pkg_vuln) { 322 fd = open(pkg_vulnerabilities_file, O_RDONLY); 323 if (fd != -1 && fstat(fd, &sb) != -1) { 324 url->last_modified = sb.st_mtime; 325 snprintf(my_flags, sizeof(my_flags), "%si", 326 fetch_flags); 327 flags = my_flags; 328 } else 329 update_pkg_vuln = 0; 330 if (fd != -1) 331 close(fd); 332 } 333 334 f = fetchXGet(url, &st, flags); 335 if (f == NULL && update_pkg_vuln && 336 fetchLastErrCode == FETCH_UNCHANGED) { 337 if (verbose >= 1) 338 fprintf(stderr, "%s is not newer\n", 339 pkg_vulnerabilities_url); 340 exit(EXIT_SUCCESS); 341 } 342 343 if (f == NULL) 344 errx(EXIT_FAILURE, "Could not fetch vulnerability file: %s", 345 fetchLastErrString); 346 347 if (st.size > SSIZE_MAX - 1) 348 errx(EXIT_FAILURE, "pkg-vulnerabilities is too large"); 349 350 buf_len = st.size; 351 buf = xmalloc(buf_len + 1); 352 buf_fetched = 0; 353 354 while (buf_fetched < buf_len) { 355 cur_fetched = fetchIO_read(f, buf + buf_fetched, 356 buf_len - buf_fetched); 357 if (cur_fetched == 0) 358 errx(EXIT_FAILURE, 359 "Truncated pkg-vulnerabilities received"); 360 else if (cur_fetched == -1) 361 errx(EXIT_FAILURE, 362 "IO error while fetching pkg-vulnerabilities: %s", 363 fetchLastErrString); 364 buf_fetched += cur_fetched; 365 } 366 367 buf[buf_len] = '\0'; 368 369 pv_check = read_pkg_vulnerabilities_memory(buf, buf_len, check_signature); 370 free_pkg_vulnerabilities(pv_check); 371 372 fd = open(pkg_vulnerabilities_file, O_WRONLY | O_CREAT | O_TRUNC, 0644); 373 if (fd == -1) 374 err(EXIT_FAILURE, "Cannot create pkg-vulnerability file %s", 375 pkg_vulnerabilities_file); 376 377 if (write(fd, buf, buf_len) != (ssize_t)buf_len) 378 err(EXIT_FAILURE, "Cannot write pkg-vulnerability file"); 379 if (close(fd) == -1) 380 err(EXIT_FAILURE, "Cannot close pkg-vulnerability file after write"); 381 382 free(buf); 383 384 exit(EXIT_SUCCESS); 385 } 386 387 static int 388 check_pkg_history_pattern(const char *pkg, const char *pattern) 389 { 390 const char *delim, *end_base; 391 392 if (strpbrk(pattern, "*[") != NULL) { 393 end_base = NULL; 394 for (delim = pattern; 395 *delim != '\0' && *delim != '['; delim++) { 396 if (*delim == '-') 397 end_base = delim; 398 } 399 400 if (end_base == NULL) 401 errx(EXIT_FAILURE, "Missing - in wildcard pattern %s", 402 pattern); 403 if ((delim = strchr(pattern, '>')) != NULL || 404 (delim = strchr(pattern, '<')) != NULL) 405 errx(EXIT_FAILURE, 406 "Mixed relational and wildcard patterns in %s", 407 pattern); 408 } else if ((delim = strchr(pattern, '>')) != NULL) { 409 end_base = delim; 410 if ((delim = strchr(pattern, '<')) != NULL && delim < end_base) 411 errx(EXIT_FAILURE, "Inverted operators in %s", 412 pattern); 413 } else if ((delim = strchr(pattern, '<')) != NULL) { 414 end_base = delim; 415 } else if ((end_base = strrchr(pattern, '-')) == NULL) { 416 errx(EXIT_FAILURE, "Missing - in absolute pattern %s", 417 pattern); 418 } 419 420 if (strncmp(pkg, pattern, end_base - pattern) != 0) 421 return 0; 422 if (pkg[end_base - pattern] != '\0') 423 return 0; 424 425 return 1; 426 } 427 428 static int 429 check_pkg_history1(const char *pkg, const char *pattern) 430 { 431 const char *open_brace, *close_brace, *inner_brace, *suffix, *iter; 432 size_t prefix_len, suffix_len, middle_len; 433 char *expanded_pkg; 434 435 open_brace = strchr(pattern, '{'); 436 if (open_brace == NULL) { 437 if ((close_brace = strchr(pattern, '}')) != NULL) 438 errx(EXIT_FAILURE, "Unbalanced {} in pattern %s", 439 pattern); 440 return check_pkg_history_pattern(pkg, pattern); 441 } 442 close_brace = strchr(open_brace, '}'); 443 if (strchr(pattern, '}') != close_brace) 444 errx(EXIT_FAILURE, "Unbalanced {} in pattern %s", 445 pattern); 446 447 while ((inner_brace = strchr(open_brace + 1, '{')) != NULL) { 448 if (inner_brace >= close_brace) 449 break; 450 open_brace = inner_brace; 451 } 452 453 expanded_pkg = xmalloc(strlen(pattern)); /* {} are going away... */ 454 455 prefix_len = open_brace - pattern; 456 suffix = close_brace + 1; 457 suffix_len = strlen(suffix) + 1; 458 memcpy(expanded_pkg, pattern, prefix_len); 459 460 ++open_brace; 461 462 do { 463 iter = strchr(open_brace, ','); 464 if (iter == NULL || iter > close_brace) 465 iter = close_brace; 466 467 middle_len = iter - open_brace; 468 memcpy(expanded_pkg + prefix_len, open_brace, middle_len); 469 memcpy(expanded_pkg + prefix_len + middle_len, suffix, 470 suffix_len); 471 if (check_pkg_history1(pkg, expanded_pkg)) { 472 free(expanded_pkg); 473 return 1; 474 } 475 open_brace = iter + 1; 476 } while (iter < close_brace); 477 478 free(expanded_pkg); 479 return 0; 480 } 481 482 static void 483 check_pkg_history(const char *pkg) 484 { 485 size_t i; 486 487 for (i = 0; i < pv->entries; ++i) { 488 if (!quick_pkg_match(pv->vulnerability[i], pkg)) 489 continue; 490 if (strcmp("eol", pv->classification[i]) == 0) 491 continue; 492 if (check_pkg_history1(pkg, pv->vulnerability[i]) == 0) 493 continue; 494 495 printf("%s %s %s\n", pv->vulnerability[i], 496 pv->classification[i], pv->advisory[i]); 497 } 498 } 499 500 void 501 audit_history(int argc, char **argv) 502 { 503 parse_options(argc, argv, "st:"); 504 argv += optind; 505 506 check_and_read_pkg_vulnerabilities(); 507 for (; *argv != NULL; ++argv) 508 check_pkg_history(*argv); 509 510 free_pkg_vulnerabilities(pv); 511 exit(EXIT_SUCCESS); 512 } 513