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
parse_options(int argc,char ** argv,const char * options)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
check_exact_pkg(const char * pkg)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
check_batch_exact_pkgs(const char * fname)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
check_one_installed_pkg(const char * pkg,void * cookie)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
check_installed_pattern(const char * pattern)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
check_and_read_pkg_vulnerabilities(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
audit_pkgdb(int argc,char ** argv)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
audit_pkg(int argc,char ** argv)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
audit_batch(int argc,char ** argv)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
check_pkg_vulnerabilities(int argc,char ** argv)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
fetch_pkg_vulnerabilities(int argc,char ** argv)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
check_pkg_history_pattern(const char * pkg,const char * pattern)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
check_pkg_history1(const char * pkg,const char * pattern)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
check_pkg_history(const char * pkg)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
audit_history(int argc,char ** argv)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