LCOV - code coverage report
Current view: top level - libpkg - pkg_audit.c (source / functions) Hit Total Coverage
Test: rapport Lines: 56 458 12.2 %
Date: 2021-12-10 16:22:55 Functions: 4 23 17.4 %
Branches: 17 315 5.4 %

           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 : }

Generated by: LCOV version 1.15