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