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