Branch data Line data Source code
1 : : /*-
2 : : * Copyright (c) 2011-2012 Julien Laffaye <jlaffaye@FreeBSD.org>
3 : : * Copyright (c) 2014-2015 Matthew Seaman <matthew@FreeBSD.org>
4 : : * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.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 : : * 1. Redistributions of source code must retain the above copyright
11 : : * notice, this list of conditions and the following disclaimer
12 : : * in this position and unchanged.
13 : : * 2. Redistributions in binary form must reproduce the above copyright
14 : : * notice, this list of conditions and the following disclaimer in the
15 : : * documentation and/or other materials provided with the distribution.
16 : : *
17 : : * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
18 : : * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 : : * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 : : * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
21 : : * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 : : * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 : : * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 : : * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 : : * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 : : * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 : : */
28 : :
29 : : #include "pkg_config.h"
30 : :
31 : : #include <sys/param.h>
32 : : #include <sys/queue.h>
33 : : #include <sys/stat.h>
34 : : #include <sys/mman.h>
35 : :
36 : : #include <archive.h>
37 : : #include <err.h>
38 : : #include <errno.h>
39 : : #include <fcntl.h>
40 : : #include <fnmatch.h>
41 : : #include <getopt.h>
42 : : #include <stdbool.h>
43 : : #include <stdio.h>
44 : : #include <string.h>
45 : : #include <unistd.h>
46 : : #include <utlist.h>
47 : : #include <ucl.h>
48 : :
49 : : #ifdef HAVE_SYS_CAPSICUM_H
50 : : #include <sys/capsicum.h>
51 : : #endif
52 : :
53 : : #ifdef HAVE_CAPSICUM
54 : : #include <sys/capsicum.h>
55 : : #endif
56 : :
57 : : #include <pkg.h>
58 : : #include <pkg/audit.h>
59 : : #include "pkgcli.h"
60 : : #include "xmalloc.h"
61 : : #include "pkghash.h"
62 : :
63 : : static const char* vop_names[] = {
64 : : [0] = "",
65 : : [EQ] = "=",
66 : : [LT] = "<",
67 : : [LTE] = "<=",
68 : : [GT] = ">",
69 : : [GTE] = ">="
70 : : };
71 : :
72 : : void
73 : 0 : usage_audit(void)
74 : : {
75 : 0 : fprintf(stderr, "Usage: pkg audit [-RFqr] [--raw[=format]|-R[format]| [-f file] <pattern>\n\n");
76 : 0 : fprintf(stderr, "For more information see 'pkg help audit'.\n");
77 : 0 : }
78 : :
79 : : static void
80 : 0 : add_to_check(pkghash *check, struct pkg *pkg)
81 : : {
82 : : const char *uid;
83 : :
84 : 0 : pkg_get(pkg, PKG_UNIQUEID, &uid);
85 [ # # # # ]: 0 : pkghash_safe_add(check, uid, pkg, NULL);
86 : 0 : }
87 : :
88 : : static void
89 : 0 : print_recursive_rdeps(pkghash *head, struct pkg *p, pkghash *seen, bool top, ucl_object_t *array)
90 : : {
91 : : pkghash_entry *e;
92 : 0 : struct pkg_dep *dep = NULL;
93 : :
94 [ # # ]: 0 : while(pkg_rdeps(p, &dep) == EPKG_OK) {
95 : 0 : const char *name = pkg_dep_get(dep, PKG_DEP_NAME);
96 : :
97 [ # # ]: 0 : if (pkghash_get(seen, name) != NULL)
98 : 0 : continue;
99 : :
100 [ # # ]: 0 : if ((e = pkghash_get(head, name)) == NULL)
101 : 0 : continue;
102 : :
103 [ # # # # ]: 0 : pkghash_safe_add(seen, name, NULL, NULL);
104 [ # # ]: 0 : if (array == NULL) {
105 [ # # ]: 0 : if (!top)
106 : 0 : printf(", ");
107 : :
108 : 0 : printf("%s", name);
109 : 0 : } else {
110 : 0 : ucl_array_append(array, ucl_object_fromstring(name));
111 : : }
112 : :
113 : 0 : print_recursive_rdeps(head, (struct pkg *)e->value, seen, false, array);
114 : :
115 : 0 : top = false;
116 : : }
117 : 0 : }
118 : :
119 : : static void
120 : 0 : print_issue(struct pkg *p, struct pkg_audit_issue *issue)
121 : : {
122 : : const char *version;
123 : : struct pkg_audit_versions_range *vers;
124 : : const struct pkg_audit_entry *e;
125 : : struct pkg_audit_cve *cve;
126 : :
127 : 0 : pkg_get(p, PKG_VERSION, &version);
128 : :
129 : 0 : e = issue->audit;
130 [ # # ]: 0 : if (version == NULL) {
131 : 0 : printf(" Affected versions:\n");
132 [ # # ]: 0 : LL_FOREACH(e->versions, vers) {
133 [ # # # # ]: 0 : if (vers->v1.type > 0 && vers->v2.type > 0)
134 : 0 : printf(" %s %s : %s %s\n",
135 : 0 : vop_names[vers->v1.type], vers->v1.version,
136 : 0 : vop_names[vers->v2.type], vers->v2.version);
137 [ # # ]: 0 : else if (vers->v1.type > 0)
138 : 0 : printf(" %s %s\n",
139 : 0 : vop_names[vers->v1.type], vers->v1.version);
140 : : else
141 : 0 : printf(" %s %s\n",
142 : 0 : vop_names[vers->v2.type], vers->v2.version);
143 : 0 : }
144 : 0 : }
145 : 0 : printf(" %s\n", e->desc);
146 [ # # ]: 0 : LL_FOREACH(e->cve, cve) {
147 : 0 : printf(" CVE: %s\n", cve->cvename);
148 : 0 : }
149 [ # # ]: 0 : if (e->url)
150 : 0 : printf(" WWW: %s\n\n", e->url);
151 [ # # ]: 0 : else if (e->id)
152 : 0 : printf(" WWW: https://vuxml.FreeBSD.org/freebsd/%s.html\n\n", e->id);
153 : 0 : }
154 : :
155 : : static void
156 : 0 : format_issue(struct pkg *p, struct pkg_audit_issue *issue, ucl_object_t *array)
157 : : {
158 : : const char *version;
159 : : struct pkg_audit_versions_range *vers;
160 : : const struct pkg_audit_entry *e;
161 : : struct pkg_audit_cve *cve;
162 : 0 : ucl_object_t *o = ucl_object_typed_new(UCL_OBJECT);
163 : 0 : ucl_object_t *affected_versions = ucl_object_typed_new(UCL_ARRAY);
164 : :
165 : 0 : pkg_get(p, PKG_VERSION, &version);
166 : 0 : ucl_array_append(array, o);
167 : :
168 : 0 : e = issue->audit;
169 : 0 : ucl_object_insert_key(o, affected_versions, "Affected versions", 17, false);
170 [ # # ]: 0 : LL_FOREACH(e->versions, vers) {
171 : : char *ver;
172 [ # # # # ]: 0 : if (vers->v1.type > 0 && vers->v2.type > 0)
173 : 0 : xasprintf(&ver, "%s %s : %s %s",
174 : 0 : vop_names[vers->v1.type], vers->v1.version,
175 : 0 : vop_names[vers->v2.type], vers->v2.version);
176 [ # # ]: 0 : else if (vers->v1.type > 0)
177 : 0 : xasprintf(&ver, "%s %s",
178 : 0 : vop_names[vers->v1.type], vers->v1.version);
179 : : else
180 : 0 : xasprintf(&ver, "%s %s",
181 : 0 : vop_names[vers->v2.type], vers->v2.version);
182 : 0 : ucl_array_append(affected_versions, ucl_object_fromstring(ver));
183 : 0 : free(ver);
184 : 0 : }
185 : 0 : ucl_object_insert_key(o, ucl_object_fromstring(e->desc), "description", 11, false);
186 [ # # ]: 0 : if (e->cve) {
187 : 0 : ucl_object_t *acve = ucl_object_typed_new(UCL_ARRAY);
188 [ # # ]: 0 : LL_FOREACH(e->cve, cve) {
189 : 0 : ucl_array_append(acve, ucl_object_fromstring(cve->cvename));
190 : 0 : }
191 : 0 : ucl_object_insert_key(o, acve, "cve", 3, false);
192 : 0 : }
193 [ # # ]: 0 : if (e->url)
194 : 0 : ucl_object_insert_key(o, ucl_object_fromstring(e->url), "url", 3, false);
195 [ # # ]: 0 : else if (e->id) {
196 : : char *url;
197 : 0 : xasprintf(&url, "https://vuxml.FreeBSD.org/freebsd/%s.html\n\n", e->id);
198 : 0 : ucl_object_insert_key(o, ucl_object_fromstring(url), "url", 3, false);
199 : 0 : free(url);
200 : 0 : }
201 : 0 : }
202 : :
203 : : int
204 : 4 : exec_audit(int argc, char **argv)
205 : : {
206 : : struct pkg_audit *audit;
207 : : struct pkg_audit_issues *issues;
208 : : struct pkg_audit_issue *issue;
209 : 4 : struct pkgdb *db = NULL;
210 : 4 : struct pkgdb_it *it = NULL;
211 : 4 : struct pkg *pkg = NULL;
212 : : char *name;
213 : : char *version;
214 : 4 : char *audit_file = NULL;
215 : 4 : int affected = 0, vuln = 0;
216 : 4 : bool fetch = false, recursive = false;
217 : : int ch, i;
218 : : int raw;
219 : 4 : int ret = EXIT_SUCCESS;
220 : 4 : pkghash *check = NULL;
221 : : pkghash_it hit;
222 : 4 : ucl_object_t *top = NULL, *vuln_objs = NULL;
223 : 4 : ucl_object_t *obj = NULL;
224 : :
225 : 4 : struct option longopts[] = {
226 : : { "fetch", no_argument, NULL, 'F' },
227 : : { "file", required_argument, NULL, 'f' },
228 : : { "recursive", no_argument, NULL, 'r' },
229 : : { "raw", optional_argument, NULL, 'R' },
230 : : { "quiet", no_argument, NULL, 'q' },
231 : : { NULL, 0, NULL, 0 },
232 : : };
233 : :
234 [ + + ]: 8 : while ((ch = getopt_long(argc, argv, "+Ff:qrR::", longopts, NULL)) != -1) {
235 [ + - - - : 4 : switch (ch) {
- - ]
236 : : case 'F':
237 : 4 : fetch = true;
238 : 4 : break;
239 : : case 'f':
240 : 0 : audit_file = optarg;
241 : 0 : break;
242 : : case 'q':
243 : 0 : quiet = true;
244 : 0 : break;
245 : : case 'r':
246 : 0 : recursive = true;
247 : 0 : break;
248 : : case 'R':
249 [ # # ]: 0 : if (optarg == NULL) {
250 : 0 : raw = UCL_EMIT_CONFIG;
251 [ # # ]: 0 : } else if (strcasecmp(optarg, "ucl") == 0) {
252 : 0 : raw = UCL_EMIT_CONFIG;
253 [ # # ]: 0 : } else if (strcasecmp(optarg, "json") == 0) {
254 : 0 : raw = UCL_EMIT_JSON;
255 [ # # ]: 0 : } else if (strcasecmp(optarg, "json-compact") == 0) {
256 : 0 : raw = UCL_EMIT_JSON_COMPACT;
257 [ # # ]: 0 : } else if (strcasecmp(optarg, "yaml") == 0) {
258 : 0 : raw = UCL_EMIT_YAML;
259 : 0 : } else {
260 : 0 : errx(EXIT_FAILURE, "invalid argument %s for --raw option", optarg);
261 : : }
262 : 0 : top = ucl_object_typed_new(UCL_OBJECT);
263 : 0 : break;
264 : : default:
265 : 0 : usage_audit();
266 : 0 : return(EXIT_FAILURE);
267 : : }
268 : : }
269 : 4 : argc -= optind;
270 : 4 : argv += optind;
271 : :
272 : 4 : audit = pkg_audit_new();
273 : :
274 [ - + ]: 4 : if (fetch == true) {
275 [ - + ]: 4 : if (pkg_audit_fetch(NULL, audit_file) != EPKG_OK) {
276 : 0 : pkg_audit_free(audit);
277 : 0 : return (EXIT_FAILURE);
278 : : }
279 : 4 : }
280 : :
281 [ - + ]: 4 : if (pkg_audit_load(audit, audit_file) != EPKG_OK) {
282 [ # # ]: 0 : if (errno == ENOENT)
283 : 0 : warnx("vulnxml file %s does not exist. "
284 : : "Try running 'pkg audit -F' first",
285 : 0 : audit_file);
286 : : else
287 : 0 : warn("unable to open vulnxml file %s",
288 : 0 : audit_file);
289 : :
290 : 0 : pkg_audit_free(audit);
291 : 0 : return (EXIT_FAILURE);
292 : : }
293 : :
294 : 4 : check = pkghash_new();
295 [ - + ]: 4 : if (argc >= 1) {
296 [ # # ]: 0 : for (i = 0; i < argc; i ++) {
297 : 0 : name = argv[i];
298 : 0 : version = strrchr(name, '-');
299 [ # # ]: 0 : if (version != NULL) {
300 : 0 : version[0] = '\0';
301 : 0 : version++;
302 : 0 : }
303 [ # # ]: 0 : if (pkg_new(&pkg, PKG_FILE) != EPKG_OK)
304 : 0 : err(EXIT_FAILURE, "malloc");
305 [ # # ]: 0 : if (version != NULL)
306 : 0 : pkg_set(pkg, PKG_NAME, name, PKG_VERSION, version);
307 : : else
308 : 0 : pkg_set(pkg, PKG_NAME, name);
309 : : /* Fake uniqueid */
310 : 0 : pkg_set(pkg, PKG_UNIQUEID, name);
311 : 0 : add_to_check(check, pkg);
312 : 0 : pkg = NULL;
313 : 0 : }
314 : 0 : }
315 : : else {
316 : :
317 : : /*
318 : : * if the database doesn't exist it just means there are no
319 : : * packages to audit.
320 : : */
321 : :
322 : 4 : ret = pkgdb_access(PKGDB_MODE_READ, PKGDB_DB_LOCAL);
323 [ + - ]: 4 : if (ret == EPKG_ENODB) {
324 : 4 : pkg_audit_free(audit);
325 : 4 : pkghash_destroy(check);
326 : 4 : return (EXIT_SUCCESS);
327 [ # # ]: 0 : } else if (ret == EPKG_ENOACCESS) {
328 : 0 : warnx("Insufficient privileges to read the package database");
329 : 0 : pkg_audit_free(audit);
330 : 0 : pkghash_destroy(check);
331 : 0 : return (EXIT_FAILURE);
332 [ # # ]: 0 : } else if (ret != EPKG_OK) {
333 : 0 : warnx("Error accessing the package database");
334 : 0 : pkg_audit_free(audit);
335 : 0 : pkghash_destroy(check);
336 : 0 : return (EXIT_FAILURE);
337 : : }
338 : :
339 [ # # ]: 0 : if (pkgdb_open(&db, PKGDB_DEFAULT) != EPKG_OK) {
340 : 0 : pkg_audit_free(audit);
341 : 0 : pkghash_destroy(check);
342 : 0 : return (EXIT_FAILURE);
343 : : }
344 : :
345 [ # # ]: 0 : if (pkgdb_obtain_lock(db, PKGDB_LOCK_READONLY) != EPKG_OK) {
346 : 0 : pkgdb_close(db);
347 : 0 : pkg_audit_free(audit);
348 : 0 : pkghash_destroy(check);
349 : 0 : warnx("Cannot get a read lock on a database, it is locked by another process");
350 : 0 : return (EXIT_FAILURE);
351 : : }
352 : :
353 [ # # ]: 0 : if ((it = pkgdb_query(db, NULL, MATCH_ALL)) == NULL) {
354 : 0 : warnx("Error accessing the package database");
355 : 0 : ret = EXIT_FAILURE;
356 : 0 : }
357 : : else {
358 [ # # # # ]: 0 : while (pkgdb_it_next(it, &pkg, PKG_LOAD_BASIC|PKG_LOAD_RDEPS)
359 : 0 : == EPKG_OK) {
360 : 0 : add_to_check(check, pkg);
361 : 0 : pkg = NULL;
362 : : }
363 : 0 : ret = EXIT_SUCCESS;
364 : : }
365 [ # # ]: 0 : if (db != NULL) {
366 : 0 : pkgdb_it_free(it);
367 : 0 : pkgdb_release_lock(db, PKGDB_LOCK_READONLY);
368 : 0 : pkgdb_close(db);
369 : 0 : }
370 [ # # ]: 0 : if (ret != EXIT_SUCCESS) {
371 : 0 : pkg_audit_free(audit);
372 : 0 : pkghash_destroy(check);
373 : 0 : return (ret);
374 : : }
375 : : }
376 : :
377 : 0 : drop_privileges();
378 : :
379 : : /* Now we have vulnxml loaded and check list formed */
380 : : #ifdef HAVE_CAPSICUM
381 : : #ifndef PKG_COVERAGE
382 : : if (cap_enter() < 0 && errno != ENOSYS) {
383 : : warn("cap_enter() failed");
384 : : pkg_audit_free(audit);
385 : : pkghash_destroy(check);
386 : : return (EPKG_FATAL);
387 : : }
388 : : #endif
389 : : #endif
390 : :
391 [ # # ]: 0 : if (pkg_audit_process(audit) == EPKG_OK) {
392 : 0 : hit = pkghash_iterator(check);
393 [ # # ]: 0 : while (pkghash_next(&hit)) {
394 : 0 : issues = NULL;
395 : 0 : pkg = (struct pkg *) hit.value;
396 [ # # ]: 0 : if (pkg_audit_is_vulnerable(audit, pkg, &issues, quiet)) {
397 : : const char *version;
398 : 0 : const char *name = NULL;
399 : 0 : ucl_object_t *array = NULL;
400 : 0 : vuln ++;
401 : :
402 [ # # ]: 0 : if (top == NULL) {
403 : 0 : affected += issues->count;
404 : 0 : pkg_get(pkg, PKG_VERSION, &version);
405 [ # # ]: 0 : if (quiet) {
406 [ # # ]: 0 : if (version != NULL)
407 : 0 : pkg_printf("%n-%v\n", pkg, pkg);
408 : : else
409 : 0 : pkg_printf("%s\n", pkg);
410 : 0 : continue;
411 : : }
412 : :
413 : 0 : pkg_printf("%n", pkg);
414 [ # # ]: 0 : if (version != NULL)
415 : 0 : pkg_printf("-%v", pkg);
416 [ # # ]: 0 : if (!quiet)
417 : 0 : printf(" is vulnerable");
418 : 0 : printf(":\n");
419 : 0 : } else {
420 [ # # ]: 0 : if (vuln_objs == NULL)
421 : 0 : vuln_objs = ucl_object_typed_new(UCL_OBJECT);
422 : 0 : obj = ucl_object_typed_new(UCL_OBJECT);
423 : 0 : pkg_get(pkg, PKG_NAME, &name, PKG_VERSION, &version);
424 [ # # ]: 0 : if (version != NULL)
425 : 0 : ucl_object_insert_key(obj, ucl_object_fromstring(version), "version", 7 , false);
426 : 0 : ucl_object_insert_key(obj, ucl_object_fromint(issues->count), "issue_count", 11, false);
427 : : }
428 : :
429 [ # # ]: 0 : if (top != NULL)
430 : 0 : array = ucl_object_typed_new(UCL_ARRAY);
431 [ # # ]: 0 : LL_FOREACH(issues->issues, issue) {
432 [ # # ]: 0 : if (top == NULL)
433 : 0 : print_issue(pkg, issue);
434 : : else
435 : 0 : format_issue(pkg, issue, array);
436 : 0 : }
437 [ # # ]: 0 : if (top != NULL)
438 : 0 : ucl_object_insert_key(obj, array, "issues", 6, false);
439 : 0 : array = NULL;
440 : :
441 [ # # # # ]: 0 : if (top != NULL || recursive) {
442 : 0 : pkghash *seen = pkghash_new();
443 : :
444 [ # # ]: 0 : if (name == NULL)
445 : 0 : pkg_get(pkg, PKG_NAME, &name);
446 [ # # ]: 0 : if (top == NULL) {
447 : 0 : printf(" Packages that depend on %s: ", name);
448 : 0 : } else {
449 : 0 : array = ucl_object_typed_new(UCL_ARRAY);
450 : : }
451 : 0 : print_recursive_rdeps(check, pkg , seen, true, array);
452 [ # # ]: 0 : if (top == NULL)
453 : 0 : printf("\n\n");
454 : :
455 : 0 : pkghash_destroy(seen);
456 : 0 : }
457 [ # # ]: 0 : if (top != NULL) {
458 : 0 : ucl_object_insert_key(obj, array, "reverse dependencies", 20, false);
459 : 0 : ucl_object_insert_key(vuln_objs, obj, xstrdup(name), strlen(name), false);
460 : 0 : }
461 : 0 : }
462 : 0 : pkg_audit_issues_free(issues);
463 : 0 : pkg_free(pkg);
464 : : }
465 : 0 : pkghash_destroy(check);
466 : :
467 [ # # # # ]: 0 : if (ret == EPKG_END && vuln == 0)
468 : 0 : ret = EXIT_SUCCESS;
469 : :
470 [ # # ]: 0 : if (top == NULL) {
471 [ # # ]: 0 : if (!quiet)
472 : 0 : printf("%u problem(s) in %u installed package(s) found.\n",
473 : 0 : affected, vuln);
474 : :
475 : 0 : } else {
476 : 0 : ucl_object_insert_key(top, ucl_object_fromint(vuln), "pkg_count", 9, false );
477 : 0 : ucl_object_insert_key(top, vuln_objs, "packages", 8, false);
478 : 0 : fprintf(stdout, "%s\n", ucl_object_emit(top, raw));
479 : 0 : ucl_object_unref(top);
480 : : }
481 : 0 : } else {
482 : 0 : warnx("cannot process vulnxml");
483 : 0 : ret = EXIT_FAILURE;
484 : 0 : pkghash_destroy(check);
485 : : }
486 : :
487 : 0 : pkg_audit_free(audit);
488 [ # # ]: 0 : if (vuln != 0)
489 : 0 : ret = EXIT_FAILURE;
490 : :
491 : 0 : return (ret);
492 : 4 : }
|