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