How FreshPorts processes vuxml entries

One of my favorite FreeBSD features is security/vuxml – from it we get these great tools (provided by pkg):

* pkg audit (what installed packages contain known vulnerabilities?)
* /usr/local/etc/periodic/security/410.pkg-auditpkg-audit for host and jails
* /usr/local/etc/periodic/security/405.pkg-base-audit – same as above, but for base system

I use the latter two command within my monitoring system (Nagios). This pkg-base-audit.sh example can be easily adapted by you for pkg-audit.

In this post:

With that stated, let’s start with the life cycle of how the process runs.

The commit

The details of FreshPorts commit processing is outside scope. However, we will cover the part which pertains to vuxml updates. There are certain files which are flagged for special handling after the commit is processed. That is implemented in modules/special_processing_files.pm – at the time of writing those special files were:

  1. $FreshPorts::Constants::PORTS_MOVED/ports/head/MOVED
  2. $FreshPorts::Constants::PORTS_UPDATING/ports/head/UPDATING
  3. $FreshPorts::Constants::VUXML\/ports\/head\/security\/vuxml/vuln/(\d{4})?.xml – i.e. all the vuxml vuln database files, sorted by year
  4. $FreshPorts::Constants::DEFAULT_VERSION/ports/head/Mk/bsd.default-versions.mk

When the right file is encountered, the following flags are set:

                `/usr/bin/touch $FreshPorts::Config::VuXMLFileFlag`;
                `/usr/bin/touch $FreshPorts::Config::JobWaiting`;

Those signal files are located in ~freshports/signals/

A typical set of processes

This is a typical set of processes running in an otherwise-idle FreshPorts ingress node:

[11:58 dev-ingress01 dvl ~/scripts] % ps auwwx | grep -v dvl
USER         PID %CPU %MEM   VSZ   RSS TT  STAT STARTED    TIME COMMAND
nagios      7821  0.0  0.0 19596  7728  -  IsJ   6Jul25 0:44.58 /usr/local/sbin/nrpe -c /usr/local/etc/nrpe.cfg -d
root       16842  0.0  0.0 13904  2724  -  SsJ   4Jul25 0:37.54 /usr/sbin/syslogd -c -ss
root       16968  0.0  0.0 23864 10060  -  IsJ   4Jul25 0:07.12 sshd: /usr/sbin/sshd [listener] 0 of 10-100 startups (sshd)
root       16994  0.0  0.0 13944  2488  -  IsJ   4Jul25 0:11.34 /usr/sbin/cron -s
ingress    40580  0.0  0.0 13856  2308  -  IsJ  21Jul25 0:00.60 daemon: ingress[40581] (daemon)
ingress    40581  0.0  0.0 14408  2908  -  SJ   21Jul25 0:30.01 /bin/sh /usr/local/libexec/freshports-service/ingress.sh
freshports 40603  0.0  0.0 13856  2312  -  IsJ  21Jul25 0:00.27 daemon: freshports[40604] (daemon)
freshports 40604  0.0  0.0 14408  3272  -  SJ   21Jul25 3:06.95 /bin/sh /usr/local/libexec/freshports-service/freshports.sh
freshports 94259  0.0  0.0 13740  2144  -  SCJ  11:58   0:00.00 sleep 3
ingress    94261  0.0  0.0 13740  2144  -  SCJ  11:58   0:00.00 sleep 3

The two main processes are ingress (pulling commits out of git and into this node) and freshports (processing those commits into the database).

Detecting the flag

When the freshports process detects the JobWaiting flag, it launches job-waiting.pl. This script is launched by both daemons (ingress and freshports). It decides what it can do based upon what user is running it.

The script associated with the VuXMLFileFlag flag/signal is process_vuxml.sh

job_waiting.pl is a simple script. It looks to see what flags are set and launches a script – no parameters, nothing fancy – it just launches the indicated script.

VuXMLFileFlag

process_vuxml.sh has two main tasks:

  1. /usr/local/bin/perl ./process_vuxml.pl –filename=${VULNFILE} –showreasons >> ${LOGFILE} – refreshes the database
  2. /usr/local/bin/perl ./vuln_latest.pl >> ${LOGFILE} – updates the HTML for the Latest Vulnerabilities box at the tope right of the webpage

We will concentrate on process_vuxml.pl.

process_vuxml.pl

The main job of this script, as taken from the comments:

# Split up the vuln.xml file into sections for individual
# vulnerabilities.  Save into files using the vid guid field as name.
# Calculate SHA256 checksum for the XML snippet and write out to an
# index file.

The code is set up to process each entry in the vuxml database provided by security/vuxml.

What it needs to do is check for any entries in the FreshPorts database which is not found in the vuxml database.

Idea

To do this simply, the code would:

  1. Grab the list of vid values from the vuxml table in the FreshPorts database
  2. Store that list in a perl hash/array
  3. As each vuln from the security/vuxml database is processed, remove that vid from the hash
  4. When processing is completed, any entry remaining in the hash can be deleted from the FreshPorts database.

But wait! There’s more!

While typing the previous section, I looked at the database schema. I found:

[12:15 pg03 dvl ~] % psql freshports.dev
psql (16.9)
Type "help" for help.

freshports.dev=# \d vuxml
                                   Table "public.vuxml"
     Column     |     Type     | Collation | Nullable |              Default              
----------------+--------------+-----------+----------+-----------------------------------
 id             | integer      |           | not null | nextval('vuxml_id_seq'::regclass)
 vid            | text         |           | not null | 
 topic          | text         |           | not null | 
 description    | text         |           | not null | 
 date_discovery | text         |           |          | 
 date_entry     | text         |           |          | 
 date_modified  | text         |           |          | 
 status         | character(1) |           | not null | 
 checksum       | text         |           |          | 
Indexes:
    "vuxml_pkey" PRIMARY KEY, btree (id)
    "vuxml_vid_idx" btree (vid)
Referenced by:
    TABLE "vuxml_affected" CONSTRAINT "$1" FOREIGN KEY (vuxml_id) REFERENCES vuxml(id) ON UPDATE CASCADE ON DELETE CASCADE
    TABLE "vuxml_references" CONSTRAINT "$1" FOREIGN KEY (vuxml_id) REFERENCES vuxml(id) ON UPDATE CASCADE ON DELETE CASCADE
    TABLE "commit_log_ports_vuxml" CONSTRAINT "$1" FOREIGN KEY (vuxml_id) REFERENCES vuxml(id) ON UPDATE CASCADE ON DELETE CASCADE

freshports.dev=# select distinct status from vuxml;
 status 
--------
 A
(1 row)

freshports.dev=# select count(*) from vuxml;
 count 
-------
  6061
(1 row)

freshports.dev=# 

See that status field on line 16? How about setting that to ‘D’ at the start of vuxml processing?

As each entry is updated, we reset status to ‘A’. At the end, delete everything with ‘D’.

That’s very simple.

However, it bumps your database IO from 1 to 6000 updates – every row in that table is updated every time the vuxml files are processed.

Initially, I was tempted by the above solution, now, the perl hash solution seems better.

I’m sure this status field is otherwise unused and exists for future use.

A temp table

What we could also do: load all the vid values from the vuxml files into a temporary table. Then delete from the vuxml table any vid values which do not appear in the temporary table.

This would not need 6000 inserts into the temp table – COPY could be used instead. If we also inserted the checksums into this table, we could use SQL to determine the changed entries, instead of doing 6000 selects (which is what the code does now).

I think this solution may be our best option. Always let the database do the work.

The new process_vuxml.pl

The amended process_vuxml.pl process then becomes:

  1. create a text file for use by COPY
  2. file contains the checksum for each vid entry
  3. this means you need to create 6000 files and 6000 checksums
  4. create the temp table
  5. load the table via COPY
  6. run a query to identify changed checksums
  7. run a query to identify new checksums (i.e new vuxml entries)
  8. run a query to identify deleted vid values
  9. do the required updates based on the above
  10. done
  11. profit

The required updates part might occur right after each query is run, or it might not.

Ideas & feedback welcome.

Edit 2025-08-01: Work has commenced. See The new process_vuxml.pl

Website Pin Facebook Twitter Myspace Friendfeed Technorati del.icio.us Digg Google StumbleUpon Premium Responsive

Leave a Comment

Scroll to Top