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