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