Line data Source code
1 : /*-
2 : * Copyright (c) 2011-2012 Julien Laffaye <jlaffaye@FreeBSD.org>
3 : * Copyright (c) 2014 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 <sys/mman.h>
30 :
31 : #define _WITH_GETLINE
32 :
33 : #include <archive.h>
34 : #include <err.h>
35 : #include <fcntl.h>
36 : #include <fnmatch.h>
37 : #include <stdio.h>
38 : #include <string.h>
39 : #include <utlist.h>
40 :
41 : #include <expat.h>
42 :
43 : #ifdef HAVE_SYS_CAPSICUM_H
44 : #include <sys/capsicum.h>
45 : #endif
46 :
47 : #include "pkg.h"
48 : #include "private/pkg.h"
49 : #include "private/event.h"
50 :
51 : #define EQ 1
52 : #define LT 2
53 : #define LTE 3
54 : #define GT 4
55 : #define GTE 5
56 :
57 : static const char* vop_names[] = {
58 : [0] = "",
59 : [EQ] = "=",
60 : [LT] = "<",
61 : [LTE] = "<=",
62 : [GT] = ">",
63 : [GTE] = ">="
64 : };
65 :
66 : struct pkg_audit_version {
67 : char *version;
68 : int type;
69 : };
70 :
71 : struct pkg_audit_versions_range {
72 : struct pkg_audit_version v1;
73 : struct pkg_audit_version v2;
74 : struct pkg_audit_versions_range *next;
75 : };
76 :
77 : struct pkg_audit_cve {
78 : char *cvename;
79 : struct pkg_audit_cve *next;
80 : };
81 :
82 : struct pkg_audit_pkgname {
83 : char *pkgname;
84 : struct pkg_audit_pkgname *next;
85 : };
86 :
87 : struct pkg_audit_package {
88 : struct pkg_audit_pkgname *names;
89 : struct pkg_audit_versions_range *versions;
90 : struct pkg_audit_package *next;
91 : };
92 :
93 : struct pkg_audit_entry {
94 : const char *pkgname;
95 : struct pkg_audit_package *packages;
96 : struct pkg_audit_pkgname *names;
97 : struct pkg_audit_versions_range *versions;
98 : struct pkg_audit_cve *cve;
99 : char *url;
100 : char *desc;
101 : char *id;
102 : bool ref;
103 : struct pkg_audit_entry *next;
104 : };
105 :
106 : /*
107 : * The _sorted stuff.
108 : *
109 : * We are using the optimized search based on the following observations:
110 : *
111 : * - number of VuXML entries is more likely to be far greater than
112 : * the number of installed ports; thus we should try to optimize
113 : * the walk through all entries for a given port;
114 : *
115 : * - fnmatch() is good and fast, but if we will compare the audit entry
116 : * name prefix without globbing characters to the prefix of port name
117 : * of the same length and they are different, there is no point to
118 : * check the rest;
119 : *
120 : * - (most important bit): if parsed VuXML entries are lexicographically
121 : * sorted per the largest prefix with no globbing characters and we
122 : * know how many succeeding entries have the same prefix we can
123 : *
124 : * a. skip the rest of the entries once the non-globbing prefix is
125 : * lexicographically larger than the port name prefix of the
126 : * same length: all successive prefixes will be larger as well;
127 : *
128 : * b. if we have non-globbing prefix that is lexicographically smaller
129 : * than port name prefix, we can skip all succeeding entries with
130 : * the same prefix; and as some port names tend to repeat due to
131 : * multiple vulnerabilities, it could be a large win.
132 : */
133 : struct pkg_audit_item {
134 : struct pkg_audit_entry *e; /* Entry itself */
135 : size_t noglob_len; /* Prefix without glob characters */
136 : size_t next_pfx_incr; /* Index increment for the entry with
137 : different prefix */
138 : };
139 :
140 : struct pkg_audit {
141 : struct pkg_audit_entry *entries;
142 : struct pkg_audit_item *items;
143 : bool parsed;
144 : bool loaded;
145 : void *map;
146 : size_t len;
147 : };
148 :
149 :
150 : /*
151 : * Another small optimization to skip the beginning of the
152 : * VuXML entry array, if possible.
153 : *
154 : * audit_entry_first_byte_idx[ch] represents the index
155 : * of the first VuXML entry in the sorted array that has
156 : * its non-globbing prefix that is started with the character
157 : * 'ch'. It allows to skip entries from the beginning of the
158 : * VuXML array that aren't relevant for the checked port name.
159 : */
160 : static size_t audit_entry_first_byte_idx[256];
161 :
162 : static void
163 0 : pkg_audit_free_entry(struct pkg_audit_entry *e)
164 : {
165 : struct pkg_audit_package *ppkg, *ppkg_tmp;
166 : struct pkg_audit_versions_range *vers, *vers_tmp;
167 : struct pkg_audit_cve *cve, *cve_tmp;
168 : struct pkg_audit_pkgname *pname, *pname_tmp;
169 :
170 0 : if (!e->ref) {
171 0 : LL_FOREACH_SAFE(e->packages, ppkg, ppkg_tmp) {
172 0 : LL_FOREACH_SAFE(ppkg->versions, vers, vers_tmp) {
173 0 : free(vers->v1.version);
174 0 : free(vers->v2.version);
175 0 : free(vers);
176 : }
177 :
178 0 : LL_FOREACH_SAFE(ppkg->names, pname, pname_tmp) {
179 0 : free(pname->pkgname);
180 0 : free(pname);
181 : }
182 : }
183 0 : LL_FOREACH_SAFE(e->cve, cve, cve_tmp) {
184 0 : free(cve->cvename);
185 0 : free(cve);
186 : }
187 0 : free(e->url);
188 0 : free(e->desc);
189 0 : free(e->id);
190 : }
191 0 : free(e);
192 0 : }
193 :
194 : static void
195 0 : pkg_audit_free_list(struct pkg_audit_entry *h)
196 : {
197 : struct pkg_audit_entry *e;
198 :
199 0 : while (h) {
200 0 : e = h;
201 0 : h = h->next;
202 0 : pkg_audit_free_entry(e);
203 : }
204 0 : }
205 :
206 : struct pkg_audit_extract_cbdata {
207 : int out;
208 : const char *fname;
209 : const char *dest;
210 : };
211 :
212 : static int
213 0 : pkg_audit_sandboxed_extract(int fd, void *ud)
214 : {
215 0 : struct pkg_audit_extract_cbdata *cbdata = ud;
216 0 : int ret, rc = EPKG_OK;
217 0 : struct archive *a = NULL;
218 0 : struct archive_entry *ae = NULL;
219 :
220 0 : a = archive_read_new();
221 : #if ARCHIVE_VERSION_NUMBER < 3000002
222 : archive_read_support_compression_all(a);
223 : #else
224 0 : archive_read_support_filter_all(a);
225 : #endif
226 :
227 0 : archive_read_support_format_raw(a);
228 :
229 0 : if (archive_read_open_fd(a, fd, 4096) != ARCHIVE_OK) {
230 0 : pkg_emit_error("archive_read_open_filename(%s) failed: %s",
231 : cbdata->fname, archive_error_string(a));
232 0 : rc = EPKG_FATAL;
233 : }
234 : else {
235 0 : while ((ret = archive_read_next_header(a, &ae)) == ARCHIVE_OK) {
236 0 : if (archive_read_data_into_fd(a, cbdata->out) != ARCHIVE_OK) {
237 0 : pkg_emit_error("archive_read_data_into_fd(%s) failed: %s",
238 : cbdata->dest, archive_error_string(a));
239 0 : break;
240 : }
241 : }
242 0 : archive_read_close(a);
243 0 : archive_read_free(a);
244 : }
245 :
246 0 : return (rc);
247 : }
248 :
249 : int
250 0 : pkg_audit_fetch(const char *src, const char *dest)
251 : {
252 0 : int fd = -1, outfd = -1;
253 : char tmp[MAXPATHLEN];
254 : const char *tmpdir;
255 0 : int retcode = EPKG_FATAL;
256 0 : time_t t = 0;
257 : struct stat st;
258 : struct pkg_audit_extract_cbdata cbdata;
259 :
260 0 : tmpdir = getenv("TMPDIR");
261 0 : if (tmpdir == NULL)
262 0 : tmpdir = "/tmp";
263 :
264 0 : strlcpy(tmp, tmpdir, sizeof(tmp));
265 0 : strlcat(tmp, "/vuln.xml.bz2.XXXXXXXX", sizeof(tmp));
266 :
267 0 : if (stat(dest, &st) != -1)
268 0 : t = st.st_mtime;
269 :
270 0 : switch (pkg_fetch_file_tmp(NULL, src, tmp, t)) {
271 : case EPKG_OK:
272 0 : break;
273 : case EPKG_UPTODATE:
274 0 : pkg_emit_notice("vulnxml file up-to-date");
275 0 : retcode = EPKG_OK;
276 0 : goto cleanup;
277 : default:
278 0 : pkg_emit_error("cannot fetch vulnxml file");
279 0 : goto cleanup;
280 : }
281 :
282 : /* Open input fd */
283 0 : fd = open(tmp, O_RDONLY);
284 0 : if (fd == -1) {
285 0 : retcode = EPKG_FATAL;
286 0 : goto cleanup;
287 : }
288 : /* Open out fd */
289 0 : outfd = open(dest, O_RDWR|O_CREAT|O_TRUNC,
290 : S_IRUSR|S_IRGRP|S_IROTH);
291 0 : if (outfd == -1) {
292 0 : pkg_emit_errno("pkg_audit_fetch", "open out fd");
293 0 : goto cleanup;
294 : }
295 :
296 0 : cbdata.fname = tmp;
297 0 : cbdata.out = outfd;
298 0 : cbdata.dest = dest;
299 :
300 : /* Call sandboxed */
301 0 : retcode = pkg_emit_sandbox_call(pkg_audit_sandboxed_extract, fd, &cbdata);
302 :
303 : cleanup:
304 0 : unlink(tmp);
305 :
306 0 : if (fd != -1)
307 0 : close(fd);
308 0 : if (outfd != -1)
309 0 : close(outfd);
310 :
311 0 : return (retcode);
312 : }
313 :
314 : /*
315 : * Expand multiple names to a set of audit entries
316 : */
317 : static void
318 0 : pkg_audit_expand_entry(struct pkg_audit_entry *entry, struct pkg_audit_entry **head)
319 : {
320 : struct pkg_audit_entry *n;
321 : struct pkg_audit_pkgname *ncur;
322 : struct pkg_audit_package *pcur;
323 :
324 : /* Set the name of the current entry */
325 0 : if (entry->packages == NULL || entry->packages->names == NULL) {
326 0 : pkg_audit_free_entry(entry);
327 0 : return;
328 : }
329 :
330 0 : LL_FOREACH(entry->packages, pcur) {
331 0 : LL_FOREACH(pcur->names, ncur) {
332 0 : n = calloc(1, sizeof(struct pkg_audit_entry));
333 0 : if (n == NULL)
334 0 : err(1, "calloc(audit_entry)");
335 0 : n->pkgname = ncur->pkgname;
336 : /* Set new entry as reference entry */
337 0 : n->ref = true;
338 0 : n->cve = entry->cve;
339 0 : n->desc = entry->desc;
340 0 : n->versions = pcur->versions;
341 0 : n->url = entry->url;
342 0 : n->id = entry->id;
343 0 : LL_PREPEND(*head, n);
344 : }
345 : }
346 0 : LL_PREPEND(*head, entry);
347 : }
348 :
349 : enum vulnxml_parse_state {
350 : VULNXML_PARSE_INIT = 0,
351 : VULNXML_PARSE_VULN,
352 : VULNXML_PARSE_TOPIC,
353 : VULNXML_PARSE_PACKAGE,
354 : VULNXML_PARSE_PACKAGE_NAME,
355 : VULNXML_PARSE_RANGE,
356 : VULNXML_PARSE_RANGE_GT,
357 : VULNXML_PARSE_RANGE_GE,
358 : VULNXML_PARSE_RANGE_LT,
359 : VULNXML_PARSE_RANGE_LE,
360 : VULNXML_PARSE_RANGE_EQ,
361 : VULNXML_PARSE_CVE
362 : };
363 :
364 : struct vulnxml_userdata {
365 : struct pkg_audit_entry *cur_entry;
366 : struct pkg_audit *audit;
367 : enum vulnxml_parse_state state;
368 : int range_num;
369 : };
370 :
371 : static void
372 0 : vulnxml_start_element(void *data, const char *element, const char **attributes)
373 : {
374 0 : struct vulnxml_userdata *ud = (struct vulnxml_userdata *)data;
375 : struct pkg_audit_versions_range *vers;
376 : struct pkg_audit_pkgname *name_entry;
377 : struct pkg_audit_package *pkg_entry;
378 : int i;
379 :
380 0 : if (ud->state == VULNXML_PARSE_INIT && strcasecmp(element, "vuln") == 0) {
381 0 : ud->cur_entry = calloc(1, sizeof(struct pkg_audit_entry));
382 0 : if (ud->cur_entry == NULL)
383 0 : err(1, "calloc(audit_entry)");
384 0 : for (i = 0; attributes[i]; i += 2) {
385 0 : if (strcasecmp(attributes[i], "vid") == 0) {
386 0 : ud->cur_entry->id = strdup(attributes[i + 1]);
387 0 : break;
388 : }
389 : }
390 0 : ud->cur_entry->next = ud->audit->entries;
391 0 : ud->state = VULNXML_PARSE_VULN;
392 : }
393 0 : else if (ud->state == VULNXML_PARSE_VULN && strcasecmp(element, "topic") == 0) {
394 0 : ud->state = VULNXML_PARSE_TOPIC;
395 : }
396 0 : else if (ud->state == VULNXML_PARSE_VULN && strcasecmp(element, "package") == 0) {
397 0 : pkg_entry = calloc(1, sizeof(struct pkg_audit_package));
398 0 : if (pkg_entry == NULL)
399 0 : err(1, "calloc(audit_package_entry)");
400 0 : LL_PREPEND(ud->cur_entry->packages, pkg_entry);
401 0 : ud->state = VULNXML_PARSE_PACKAGE;
402 : }
403 0 : else if (ud->state == VULNXML_PARSE_VULN && strcasecmp(element, "cvename") == 0) {
404 0 : ud->state = VULNXML_PARSE_CVE;
405 : }
406 0 : else if (ud->state == VULNXML_PARSE_PACKAGE && strcasecmp(element, "name") == 0) {
407 0 : ud->state = VULNXML_PARSE_PACKAGE_NAME;
408 0 : name_entry = calloc(1, sizeof(struct pkg_audit_pkgname));
409 0 : if (name_entry == NULL)
410 0 : err(1, "calloc(audit_pkgname_entry)");
411 0 : LL_PREPEND(ud->cur_entry->packages->names, name_entry);
412 : }
413 0 : else if (ud->state == VULNXML_PARSE_PACKAGE && strcasecmp(element, "range") == 0) {
414 0 : ud->state = VULNXML_PARSE_RANGE;
415 0 : vers = calloc(1, sizeof(struct pkg_audit_versions_range));
416 0 : if (vers == NULL)
417 0 : err(1, "calloc(audit_versions)");
418 0 : LL_PREPEND(ud->cur_entry->packages->versions, vers);
419 0 : ud->range_num = 0;
420 : }
421 0 : else if (ud->state == VULNXML_PARSE_RANGE && strcasecmp(element, "gt") == 0) {
422 0 : ud->range_num ++;
423 0 : ud->state = VULNXML_PARSE_RANGE_GT;
424 : }
425 0 : else if (ud->state == VULNXML_PARSE_RANGE && strcasecmp(element, "ge") == 0) {
426 0 : ud->range_num ++;
427 0 : ud->state = VULNXML_PARSE_RANGE_GE;
428 : }
429 0 : else if (ud->state == VULNXML_PARSE_RANGE && strcasecmp(element, "lt") == 0) {
430 0 : ud->range_num ++;
431 0 : ud->state = VULNXML_PARSE_RANGE_LT;
432 : }
433 0 : else if (ud->state == VULNXML_PARSE_RANGE && strcasecmp(element, "le") == 0) {
434 0 : ud->range_num ++;
435 0 : ud->state = VULNXML_PARSE_RANGE_LE;
436 : }
437 0 : else if (ud->state == VULNXML_PARSE_RANGE && strcasecmp(element, "eq") == 0) {
438 0 : ud->range_num ++;
439 0 : ud->state = VULNXML_PARSE_RANGE_EQ;
440 : }
441 0 : }
442 :
443 : static void
444 0 : vulnxml_end_element(void *data, const char *element)
445 : {
446 0 : struct vulnxml_userdata *ud = (struct vulnxml_userdata *)data;
447 :
448 0 : if (ud->state == VULNXML_PARSE_VULN && strcasecmp(element, "vuln") == 0) {
449 0 : pkg_audit_expand_entry(ud->cur_entry, &ud->audit->entries);
450 0 : ud->state = VULNXML_PARSE_INIT;
451 : }
452 0 : else if (ud->state == VULNXML_PARSE_TOPIC && strcasecmp(element, "topic") == 0) {
453 0 : ud->state = VULNXML_PARSE_VULN;
454 : }
455 0 : else if (ud->state == VULNXML_PARSE_CVE && strcasecmp(element, "cvename") == 0) {
456 0 : ud->state = VULNXML_PARSE_VULN;
457 : }
458 0 : else if (ud->state == VULNXML_PARSE_PACKAGE && strcasecmp(element, "package") == 0) {
459 0 : ud->state = VULNXML_PARSE_VULN;
460 : }
461 0 : else if (ud->state == VULNXML_PARSE_PACKAGE_NAME && strcasecmp(element, "name") == 0) {
462 0 : ud->state = VULNXML_PARSE_PACKAGE;
463 : }
464 0 : else if (ud->state == VULNXML_PARSE_RANGE && strcasecmp(element, "range") == 0) {
465 0 : ud->state = VULNXML_PARSE_PACKAGE;
466 : }
467 0 : else if (ud->state == VULNXML_PARSE_RANGE_GT && strcasecmp(element, "gt") == 0) {
468 0 : ud->state = VULNXML_PARSE_RANGE;
469 : }
470 0 : else if (ud->state == VULNXML_PARSE_RANGE_GE && strcasecmp(element, "ge") == 0) {
471 0 : ud->state = VULNXML_PARSE_RANGE;
472 : }
473 0 : else if (ud->state == VULNXML_PARSE_RANGE_LT && strcasecmp(element, "lt") == 0) {
474 0 : ud->state = VULNXML_PARSE_RANGE;
475 : }
476 0 : else if (ud->state == VULNXML_PARSE_RANGE_LE && strcasecmp(element, "le") == 0) {
477 0 : ud->state = VULNXML_PARSE_RANGE;
478 : }
479 0 : else if (ud->state == VULNXML_PARSE_RANGE_EQ && strcasecmp(element, "eq") == 0) {
480 0 : ud->state = VULNXML_PARSE_RANGE;
481 : }
482 0 : }
483 :
484 : static void
485 0 : vulnxml_handle_data(void *data, const char *content, int length)
486 : {
487 0 : struct vulnxml_userdata *ud = (struct vulnxml_userdata *)data;
488 : struct pkg_audit_versions_range *vers;
489 : struct pkg_audit_cve *cve;
490 : struct pkg_audit_entry *entry;
491 0 : int range_type = -1;
492 :
493 0 : switch(ud->state) {
494 : case VULNXML_PARSE_INIT:
495 : case VULNXML_PARSE_VULN:
496 : case VULNXML_PARSE_PACKAGE:
497 : case VULNXML_PARSE_RANGE:
498 : /* On these states we do not need any data */
499 0 : break;
500 : case VULNXML_PARSE_TOPIC:
501 0 : ud->cur_entry->desc = strndup(content, length);
502 0 : break;
503 : case VULNXML_PARSE_PACKAGE_NAME:
504 0 : ud->cur_entry->packages->names->pkgname = strndup(content, length);
505 0 : break;
506 : case VULNXML_PARSE_RANGE_GT:
507 0 : range_type = GT;
508 0 : break;
509 : case VULNXML_PARSE_RANGE_GE:
510 0 : range_type = GTE;
511 0 : break;
512 : case VULNXML_PARSE_RANGE_LT:
513 0 : range_type = LT;
514 0 : break;
515 : case VULNXML_PARSE_RANGE_LE:
516 0 : range_type = LTE;
517 0 : break;
518 : case VULNXML_PARSE_RANGE_EQ:
519 0 : range_type = EQ;
520 0 : break;
521 : case VULNXML_PARSE_CVE:
522 0 : entry = ud->cur_entry;
523 0 : cve = malloc(sizeof(struct pkg_audit_cve));
524 0 : cve->cvename = strndup(content, length);
525 0 : LL_PREPEND(entry->cve, cve);
526 0 : break;
527 : }
528 :
529 0 : if (range_type > 0) {
530 0 : vers = ud->cur_entry->packages->versions;
531 0 : if (ud->range_num == 1) {
532 0 : vers->v1.version = strndup(content, length);
533 0 : vers->v1.type = range_type;
534 : }
535 0 : else if (ud->range_num == 2) {
536 0 : vers->v2.version = strndup(content, length);
537 0 : vers->v2.type = range_type;
538 : }
539 : }
540 0 : }
541 :
542 : static int
543 0 : pkg_audit_parse_vulnxml(struct pkg_audit *audit)
544 : {
545 : XML_Parser parser;
546 : struct vulnxml_userdata ud;
547 0 : int ret = EPKG_OK;
548 :
549 0 : parser = XML_ParserCreate(NULL);
550 0 : XML_SetElementHandler(parser, vulnxml_start_element, vulnxml_end_element);
551 0 : XML_SetCharacterDataHandler(parser, vulnxml_handle_data);
552 0 : XML_SetUserData(parser, &ud);
553 :
554 0 : ud.cur_entry = NULL;
555 0 : ud.audit = audit;
556 0 : ud.range_num = 0;
557 0 : ud.state = VULNXML_PARSE_INIT;
558 :
559 0 : if (XML_Parse(parser, audit->map, audit->len, XML_TRUE) == XML_STATUS_ERROR) {
560 0 : pkg_emit_error("vulnxml parsing error: %s",
561 : XML_ErrorString(XML_GetErrorCode(parser)));
562 0 : ret = EPKG_FATAL;
563 : }
564 :
565 0 : XML_ParserFree(parser);
566 :
567 0 : return (ret);
568 : }
569 :
570 : /*
571 : * Returns the length of the largest prefix without globbing
572 : * characters, as per fnmatch().
573 : */
574 : static size_t
575 0 : pkg_audit_str_noglob_len(const char *s)
576 : {
577 : size_t n;
578 :
579 0 : for (n = 0; s[n] && s[n] != '*' && s[n] != '?' &&
580 0 : s[n] != '[' && s[n] != '{' && s[n] != '\\'; n++);
581 :
582 0 : return (n);
583 : }
584 :
585 : /*
586 : * Helper for quicksort that lexicographically orders prefixes.
587 : */
588 : static int
589 0 : pkg_audit_entry_cmp(const void *a, const void *b)
590 : {
591 : const struct pkg_audit_item *e1, *e2;
592 : size_t min_len;
593 : int result;
594 :
595 0 : e1 = (const struct pkg_audit_item *)a;
596 0 : e2 = (const struct pkg_audit_item *)b;
597 :
598 0 : min_len = (e1->noglob_len < e2->noglob_len ?
599 0 : e1->noglob_len : e2->noglob_len);
600 0 : result = strncmp(e1->e->pkgname, e2->e->pkgname, min_len);
601 : /*
602 : * Additional check to see if some word is a prefix of an
603 : * another one and, thus, should go before the former.
604 : */
605 0 : if (result == 0) {
606 0 : if (e1->noglob_len < e2->noglob_len)
607 0 : result = -1;
608 0 : else if (e1->noglob_len > e2->noglob_len)
609 0 : result = 1;
610 : }
611 :
612 0 : return (result);
613 : }
614 :
615 : /*
616 : * Sorts VuXML entries and calculates increments to jump to the
617 : * next distinct prefix.
618 : */
619 : static struct pkg_audit_item *
620 0 : pkg_audit_preprocess(struct pkg_audit_entry *h)
621 : {
622 : struct pkg_audit_entry *e;
623 : struct pkg_audit_item *ret;
624 : size_t i, n, tofill;
625 :
626 0 : n = 0;
627 0 : LL_FOREACH(h, e)
628 0 : n++;
629 :
630 0 : ret = (struct pkg_audit_item *)calloc(n + 1, sizeof(ret[0]));
631 0 : if (ret == NULL)
632 0 : err(1, "calloc(audit_entry_sorted*)");
633 0 : bzero((void *)ret, (n + 1) * sizeof(ret[0]));
634 :
635 0 : n = 0;
636 0 : LL_FOREACH(h, e) {
637 0 : if (e->pkgname != NULL) {
638 0 : ret[n].e = e;
639 0 : ret[n].noglob_len = pkg_audit_str_noglob_len(e->pkgname);
640 0 : ret[n].next_pfx_incr = 1;
641 0 : n++;
642 : }
643 : }
644 :
645 0 : qsort(ret, n, sizeof(*ret), pkg_audit_entry_cmp);
646 :
647 : /*
648 : * Determining jump indexes to the next different prefix.
649 : * Only non-1 increments are calculated there.
650 : *
651 : * Due to the current usage that picks only increment for the
652 : * first of the non-unique prefixes in a row, we could
653 : * calculate only that one and skip calculations for the
654 : * succeeding, but for the uniformity and clarity we're
655 : * calculating 'em all.
656 : */
657 0 : for (n = 1, tofill = 0; ret[n].e; n++) {
658 0 : if (ret[n - 1].noglob_len != ret[n].noglob_len) {
659 : struct pkg_audit_item *base;
660 :
661 0 : base = ret + n - tofill;
662 0 : for (i = 0; tofill > 1; i++, tofill--)
663 0 : base[i].next_pfx_incr = tofill;
664 0 : tofill = 1;
665 0 : } else if (strcmp(ret[n - 1].e->pkgname,
666 0 : ret[n].e->pkgname) == 0) {
667 0 : tofill++;
668 : } else {
669 0 : tofill = 1;
670 : }
671 : }
672 :
673 : /* Calculate jump indexes for the first byte of the package name */
674 0 : bzero(audit_entry_first_byte_idx, sizeof(audit_entry_first_byte_idx));
675 0 : for (n = 1, i = 0; n < 256; n++) {
676 0 : while (ret[i].e != NULL &&
677 0 : (size_t)(ret[i].e->pkgname[0]) < n)
678 0 : i++;
679 0 : audit_entry_first_byte_idx[n] = i;
680 : }
681 :
682 0 : return (ret);
683 : }
684 :
685 : static bool
686 0 : pkg_audit_version_match(const char *pkgversion, struct pkg_audit_version *v)
687 : {
688 0 : bool res = false;
689 :
690 : /*
691 : * Return true so it is easier for the caller to handle case where there is
692 : * only one version to match: the missing one will always match.
693 : */
694 0 : if (v->version == NULL)
695 0 : return (true);
696 :
697 0 : switch (pkg_version_cmp(pkgversion, v->version)) {
698 : case -1:
699 0 : if (v->type == LT || v->type == LTE)
700 0 : res = true;
701 0 : break;
702 : case 0:
703 0 : if (v->type == EQ || v->type == LTE || v->type == GTE)
704 0 : res = true;
705 0 : break;
706 : case 1:
707 0 : if (v->type == GT || v->type == GTE)
708 0 : res = true;
709 0 : break;
710 : }
711 0 : return (res);
712 : }
713 :
714 : static void
715 0 : pkg_audit_print_versions(struct pkg_audit_entry *e, struct sbuf *sb)
716 : {
717 : struct pkg_audit_versions_range *vers;
718 :
719 0 : sbuf_cat(sb, "Affected versions:\n");
720 0 : LL_FOREACH(e->versions, vers) {
721 0 : if (vers->v1.type > 0 && vers->v2.type > 0)
722 0 : sbuf_printf(sb, "%s %s : %s %s\n",
723 0 : vop_names[vers->v1.type], vers->v1.version,
724 0 : vop_names[vers->v2.type], vers->v2.version);
725 0 : else if (vers->v1.type > 0)
726 0 : sbuf_printf(sb, "%s %s\n",
727 0 : vop_names[vers->v1.type], vers->v1.version);
728 : else
729 0 : sbuf_printf(sb, "%s %s\n",
730 0 : vop_names[vers->v2.type], vers->v2.version);
731 : }
732 0 : }
733 :
734 : static void
735 0 : pkg_audit_print_entry(struct pkg_audit_entry *e, struct sbuf *sb,
736 : const char *pkgname, const char *pkgversion, bool quiet)
737 : {
738 : struct pkg_audit_cve *cve;
739 :
740 0 : if (quiet) {
741 0 : if (pkgversion != NULL)
742 0 : sbuf_printf(sb, "%s-%s\n", pkgname, pkgversion);
743 : else
744 0 : sbuf_printf(sb, "%s\n", pkgname);
745 0 : sbuf_finish(sb);
746 : } else {
747 0 : if (pkgversion != NULL)
748 0 : sbuf_printf(sb, "%s-%s is vulnerable:\n", pkgname, pkgversion);
749 : else {
750 0 : sbuf_printf(sb, "%s is vulnerable:\n", pkgname);
751 0 : pkg_audit_print_versions(e, sb);
752 : }
753 :
754 0 : sbuf_printf(sb, "%s\n", e->desc);
755 : /* XXX: for vulnxml we should use more clever approach indeed */
756 0 : if (e->cve) {
757 0 : cve = e->cve;
758 0 : while (cve) {
759 0 : sbuf_printf(sb, "CVE: %s\n", cve->cvename);
760 0 : cve = cve->next;
761 : }
762 : }
763 0 : if (e->url)
764 0 : sbuf_printf(sb, "WWW: %s\n\n", e->url);
765 0 : else if (e->id)
766 0 : sbuf_printf(sb,
767 : "WWW: https://vuxml.FreeBSD.org/freebsd/%s.html\n\n",
768 : e->id);
769 : }
770 0 : }
771 :
772 : bool
773 0 : pkg_audit_is_vulnerable(struct pkg_audit *audit, struct pkg *pkg,
774 : bool quiet, struct sbuf **result)
775 : {
776 : struct pkg_audit_entry *e;
777 : struct pkg_audit_versions_range *vers;
778 : struct sbuf *sb;
779 : struct pkg_audit_item *a;
780 0 : bool res = false, res1, res2;
781 :
782 0 : if (!audit->parsed)
783 0 : return false;
784 :
785 0 : a = audit->items;
786 0 : a += audit_entry_first_byte_idx[(size_t)pkg->name[0]];
787 0 : sb = sbuf_new_auto();
788 :
789 0 : for (; (e = a->e) != NULL; a += a->next_pfx_incr) {
790 : int cmp;
791 : size_t i;
792 :
793 : /*
794 : * Audit entries are sorted, so if we had found one
795 : * that is lexicographically greater than our name,
796 : * it and the rest won't match our name.
797 : */
798 0 : cmp = strncmp(pkg->name, e->pkgname, a->noglob_len);
799 0 : if (cmp > 0)
800 0 : continue;
801 0 : else if (cmp < 0)
802 0 : break;
803 :
804 0 : for (i = 0; i < a->next_pfx_incr; i++) {
805 0 : e = a[i].e;
806 0 : if (fnmatch(e->pkgname, pkg->name, 0) != 0)
807 0 : continue;
808 :
809 0 : if (pkg->version == NULL) {
810 : /*
811 : * Assume that all versions should be checked
812 : */
813 0 : res = true;
814 0 : pkg_audit_print_entry(e, sb, pkg->name, NULL, quiet);
815 : }
816 : else {
817 0 : LL_FOREACH(e->versions, vers) {
818 0 : res1 = pkg_audit_version_match(pkg->version, &vers->v1);
819 0 : res2 = pkg_audit_version_match(pkg->version, &vers->v2);
820 :
821 0 : if (res1 && res2) {
822 0 : res = true;
823 0 : pkg_audit_print_entry(e, sb, pkg->name, pkg->version, quiet);
824 0 : break;
825 : }
826 : }
827 : }
828 :
829 0 : if (res && quiet)
830 0 : goto out;
831 : }
832 : }
833 :
834 : out:
835 0 : if (res) {
836 0 : sbuf_finish(sb);
837 0 : *result = sb;
838 : }
839 : else {
840 0 : sbuf_delete(sb);
841 : }
842 :
843 0 : return (res);
844 : }
845 :
846 : struct pkg_audit *
847 0 : pkg_audit_new(void)
848 : {
849 : struct pkg_audit *audit;
850 :
851 0 : audit = calloc(1, sizeof(struct pkg_audit));
852 :
853 0 : return (audit);
854 : }
855 :
856 : int
857 0 : pkg_audit_load(struct pkg_audit *audit, const char *fname)
858 : {
859 : int fd;
860 : void *mem;
861 : struct stat st;
862 :
863 0 : if (stat(fname, &st) == -1)
864 0 : return (EPKG_FATAL);
865 :
866 0 : if ((fd = open(fname, O_RDONLY)) == -1)
867 0 : return (EPKG_FATAL);
868 :
869 0 : if ((mem = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) {
870 0 : close(fd);
871 0 : return (EPKG_FATAL);
872 : }
873 0 : close(fd);
874 :
875 0 : audit->map = mem;
876 0 : audit->len = st.st_size;
877 0 : audit->loaded = true;
878 :
879 0 : return (EPKG_OK);
880 : }
881 :
882 : /* This can and should be executed after cap_enter(3) */
883 : int
884 0 : pkg_audit_process(struct pkg_audit *audit)
885 : {
886 0 : if (!audit->loaded)
887 0 : return (EPKG_FATAL);
888 :
889 0 : if (pkg_audit_parse_vulnxml(audit) == EPKG_FATAL)
890 0 : return (EPKG_FATAL);
891 :
892 0 : audit->items = pkg_audit_preprocess(audit->entries);
893 0 : audit->parsed = true;
894 :
895 0 : return (EPKG_OK);
896 : }
897 :
898 : void
899 0 : pkg_audit_free (struct pkg_audit *audit)
900 : {
901 0 : if (audit != NULL) {
902 0 : if (audit->parsed) {
903 0 : pkg_audit_free_list(audit->entries);
904 0 : free(audit->items);
905 : }
906 0 : if (audit->loaded) {
907 0 : munmap(audit->map, audit->len);
908 : }
909 0 : free(audit);
910 : }
911 0 : }
|