portstatus zeigt, was pkg audit nicht sagt

Als Mitglied des ports-secteam ist es meine Aufgabe und die der anderen im Team, schnell auf CVEs zu reagieren. Leider kannte ich bis heute keine Seite, auf der ich sehen konnte, welche CVEs für die FreeBSD-Ports relevant sind und welche Ports überhaupt Schwachstellen haben. Auch war es schwierig, einen Zusammenhang zwischen CVEs, VuXML und den Ports herzustellen. Deshalb habe ich mich entschieden, eine eigene Seite zu bauen, mit der ich schnell und effektiv auf neue Schwachstellen in den Ports reagieren kann. Herausgekommen ist portstatus.bsdproject.de.

Ein Beispiel, warum es die Seite braucht: print/py-pypdf liegt in latest und quarterly in Version 6.9.2. Ich frage pkg audit, ob ich mir mit diesem Paket Sorgen machen muss:

# pkg audit -F py312-pypdf-6.9.2
vulnxml file up-to-date
0 problem(s) in 0 package(s) found.

Null Probleme. Die CVE-Einträge sehen das anders: 43 Schwachstellen betreffen genau diese Version, 13 davon mit einem CVSS ab 7, die ältesten sind seit April bekannt. Upstream hat alle 43 bis Version 6.21.2 behoben. Fast alle laufen auf zu hohen Speicher- oder CPU-Verbrauch durch präparierte PDFs hinaus. Kritisch ist das, wenn dein Dienst PDFs von außen annimmt, etwa Paperless, ein Webshop oder ein Mailfilter. pkg audit lügt dabei nicht. Es kennt nur, was jemand in VuXML eingetragen hat, und für pypdf hat das bisher niemand getan.

Genau diese Lücke macht portstatus sichtbar. Die Seite gleicht jeden Port, den sie einem Produkt zuordnen kann, mit den CVE-Datenbanken ab und zeigt, ob es einen VuXML-Eintrag gibt und ob upstream eine neuere Version existiert. Für den ganzen Baum sind das heute 3.295 CVEs in 670 Ports, die pkg audit nicht meldet. Die Zahl gilt für alle rund 34.000 Ports und die Pakete in latest. Auf deinem Server ist es ein Bruchteil davon, aber eben nicht null.

Wichtig: portstatus ist trotzdem mein privates Projekt und kein offizielles Angebot des FreeBSD-Projekts oder des ports-secteam.

Startseite von portstatus: die neuesten Commits im Ports-Baum mit Version, Paketstand und offenen CVEs, rechts die Liste der neuen CVEs
Die Startseite: jeder Commit auf main mit der Version, auf die er den Port bringt, dem gebauten Paket und dem, was daran noch offen ist.

Warum VuXML allein nicht reicht

Ein VuXML-Eintrag sagt: Paket X in den Versionen bis Y ist betroffen, hier sind die CVEs, hier die Quelle. pkg audit gleicht das mit deinen installierten Paketen ab, schnell und mit einem einzigen Download. Für das, was drinsteht, funktioniert das hervorragend.

Das Problem ist das Wort „jemand“. Einen VuXML-Eintrag schreibt ein Committer, das ports-secteam oder, über einen Problem Report, der Maintainer. Bei Firefox, Chromium, OpenSSL oder nginx passiert das oft noch am selben Tag. Bei einer Perl-Bibliothek, einem PHP-Webprojekt oder einem Python-Modul wie pypdf manchmal nie. Es kommen zu viele CVEs, und niemand liest alle.

Der zweite Haken liegt im Ports-Baum selbst. Um ein CVE automatisch einem Port zuzuordnen, braucht man einen CPE, also die Kennung „Hersteller und Produkt“, die auch in den CVE-Einträgen steht. Von 34.339 Ports bringen 3.306 eine solche Kennung mit. Das sind keine zehn Prozent. Für Maintainer ist das der einfachste Hebel: USES=cpe im Makefile (siehe Porter’s Handbook), und der Port ist für jedes Werkzeug sichtbar.

Versagt hat hier niemand. Der Prozess baut darauf, dass Menschen hinschauen, und es fehlte ein Ort, an dem man sieht, wo keiner hingeschaut hat.

Rundgang durch eine Portseite

Jedes Paar aus CVE und Port landet in genau einem Zustand. Rot steht für unaddressed: Der CVE-Eintrag nennt die gebaute Paketversion als betroffen, und kein VuXML-Eintrag nennt das CVE. Gelb heißt „VuXML only“: pkg audit schlägt an, es fehlt nur noch das Update. Was sich nicht sauber vergleichen lässt, steht gesondert und zählt nicht mit. Lieber eine Zahl, die etwas zu niedrig ist, als eine, der niemand glaubt.

Entscheidungsweg: liegt die Paketversion im betroffenen Bereich, nennt ein VuXML-Eintrag das CVE, sind die Versionen vergleichbar; daraus folgen die Zustände nicht betroffen, VuXML only oder fixed, unaddressed und not comparable
Drei Fragen entscheiden, ob ein CVE rot, gelb oder gar nicht zählt.

Wenn du print/py-pypdf in die Suche tippst, bekommst du das hier:

Portseite von print/py-pypdf: 43 unaddressed CVEs, Hinweis, dass das Update auf 6.21.2 alle 43 behebt, darunter die Liste der CVEs
Oben die Kacheln, darunter die Antwort auf die eigentliche Frage: welches Update hilft.

Oben stehen vier Kacheln: unaddressed, VuXML only, fixed und offene Problem Reports. Sie beziehen sich auf das Paket in latest. Darunter steht der Satz, den ich für den wichtigsten der Seite halte: „Update to 6.21.2 fixes all 43 unaddressed CVEs.“ Die Zeile darunter zeigt, wie viele CVEs jede Zwischenversion schon erledigt. Das hilft, wenn der Sprung auf die neueste Version gerade nicht drin ist. Bekommt ein alter Zweig keinen Fix mehr, nennt die Seite den Nachfolger, bei net/freerdp etwa net/freerdp3.

Dein Paket ist rot, was jetzt?

  1. Finde deine Ports. pkg query '%o' listet die Origins aller installierten Pakete, pkg info -r <paket> zeigt, wer ein Paket mitgezogen hat. Prüf zuerst, was von außen erreichbar ist oder fremde Daten verarbeitet.
  2. Lies die CVEs. Betrifft es nur den Windows-Client oder eine Funktion, die du nicht nutzt, kannst du es gelassener sehen.
  3. Schau in die Zeile mit dem Update. Gibt es eine neuere Version oder einen Nachfolger-Port?
  4. Steht der Fix nur in main? Ist das MFH angefragt oder ein Problem Report offen, dann warten oder freundlich nachhaken.
  5. Gibt es weder Update noch Problem Report? Dann eröffne einen auf bugs.freebsd.org unter „Ports & Packages“, Betreff etwa print/py-pypdf: Update to 6.21.2 (security), im Text die CVE-Nummern und der Link auf die portstatus-Seite. Ein Bugzilla-Konto reicht, Committer musst du nicht sein.
  6. Brauchst du den Fix sofort? Bau den Port selbst mit poudriere aus einem Quarterly-Checkout (Zweig 2026Q4) plus dem Fix. Den Hash zeigt die Seite am Commit. Einzelne Pakete aus latest in ein Quarterly-System zu mischen, geht oft schief, weil Bibliotheksversionen auseinanderlaufen.
% git -C ~/ports switch 2026Q4                 # Checkout auf dem Quarterly-Zweig
% git -C ~/ports cherry-pick -x 1a66662c5b   # Fix aus main, Hash von der Portseite
# poudriere bulk -j 151amd64 -p quarterly mail/roundcube

Quarterly: behoben, aber noch nicht bei dir

Wer pkg auf einem RELEASE nutzt, bekommt seine Pakete aus quarterly. Dort landen nur Fixes, die jemand per MFH („merged from head“) aus main übernimmt. mail/roundcube bekam am 19. Oktober in main das Sicherheitsupdate auf 1.7.5. Der Commit fordert das MFH nach 2026Q4 an, gemergt ist es bisher nicht:

Portseite von mail/roundcube mit rotem Hinweis: in main behoben, nicht nach 2026Q4 gemergt; Quarterly 1.7.4 mit einem VuXML-Eintrag, dazu ein offener Problem Report
Seit dem 19. Oktober in main behoben, in 2026Q4 noch offen.

Hier greift VuXML, zumindest für php82 bis php85: pkg audit warnt dich auch auf quarterly.

# pkg audit roundcube-php84-1.7.4,1
roundcube-php84-1.7.4,1 is vulnerable:
  Roundcube -- Multiple vulnerabilities
  WWW: https://vuxml.FreeBSD.org/freebsd/69adacad-cba3-11f1-8135-8447094a420f.html

1 problem(s) in 1 package(s) found.

Der Eintrag nennt roundcube-php85 aber doppelt und roundcube-php86 gar nicht. Für php86 bleibt pkg audit stumm, und genau diese Pakete zeigt portstatus rot, mit einem Hinweis auf den fehlenden Paketnamen. Was pkg audit dir außerdem nicht sagt: dass der Fix in main längst liegt, dass das MFH angefragt ist und dass ein offener Problem Report genau das anmahnt.

Install und Pakete je Plattform

Unter den Kacheln liegen drei Registerkarten. „Port info“ fasst Maintainer, Paketstand, Upstream und Quarterly zusammen. „Install“ zeigt, was du bekommst, bevor du es installierst: Größe, Abhängigkeiten, Lizenz, welche Benutzer das Paket anlegt, mit welchen Optionen es gebaut wurde und was in der pkg-message steht.

Registerkarte Install für www/nginx: pkg install und Portbau, Größe, Abhängigkeit pcre2, Lizenz, Benutzer www und die 29 eingeschalteten Optionen
Die Optionen des Pakets auf einen Blick: bei nginx 29 von 118 eingeschaltet.

Die Optionen sind mein Favorit. Wer schon einmal ein Paket installiert hat und dann feststellte, dass ausgerechnet das eine Modul fehlt, weiß warum. Bei nginx siehst du sofort: HTTPV3 ja, BROTLI nein.

Die dritte Karte zeigt, welche Version es auf welcher FreeBSD-Version und Architektur gibt, getrennt nach latest und quarterly:

Tabelle mit den nginx-Paketversionen auf FreeBSD 14, 15 und 16 für amd64, aarch64 und i386, getrennt nach latest und quarterly; abweichende Versionen gelb markiert
Gelb ist, was vom Stand im Baum abweicht. aarch64 läuft hier hinterher.

Auf FreeBSD 14 und 16 steht aarch64 noch auf 1.30.5. Paketsets, die seit 60 Tagen nicht neu gebaut wurden, blendet die Seite aus. armv6 und armv7 tauchen deshalb gar nicht erst auf, ihre Indizes stammen aus 2023 und 2025.

Kein Paket heißt nicht „kommt bald“

Fehlt einem Port das Paket, sagt die Seite, warum. www/py-grimoirelab etwa hat auf FreeBSD 15 amd64 kein Paket, weil irgendwo in seinen Abhängigkeiten eines fehlt. Nur wo?

Portseite von www/py-grimoirelab: kein Paket im latest-Repository, weil die Abhängigkeit archivers/py-brotli kein Paket hat
Statt „no package built yet“ nennt die Seite den Port, an dem es hängt.

Die Kette ist länger, als man denkt: grimoirelab braucht py-graal, das braucht py-networkx, das wiederum py-matplotlib, matplotlib braucht py-fonttools, und fonttools hängt an archivers/py-brotli. py-brotli ist beim Update von brotli auf 1.2.1 kaputtgegangen. Ich hätte auf networkx getippt und lag falsch. Auf der Seite von py-brotli steht, was das anrichtet: 350 Ports ohne Paket, nur wegen dieses einen. Der Fix ist seit dem 24. Oktober im Baum. Weil der Paketbau Tage hinterherläuft, nennt die Seite beide Möglichkeiten, einen echten Buildfehler oder einen noch nicht gebauten Fix, und zeigt den letzten Commit dazu.

Portseite von archivers/py-brotli: kein Paket, letzter Commit am 24. Oktober vermutlich noch nicht gebaut, blockiert 350 Ports
Ein kaputter Port, 350 Ports ohne Paket.

Für Committer und Maintainer

Wenn du Ports betreust, ist die Maintainer-Seite der schnellste Einstieg. Such nach deiner Adresse, und du siehst alle deine Ports mit offenen CVEs, VuXML-Treffern und verfügbaren Updates auf einer Seite. Angenehm ist das nicht immer, nützlich schon.

Ein „unaddressed“ löst du so auf: Port über den betroffenen Bereich hinaus aktualisieren und im selben Zug einen VuXML-Eintrag schreiben, damit Nutzer der alten Version gewarnt werden. Gibt es noch keinen Fix, reicht der VuXML-Eintrag allein. Bei Sicherheitsfixes gehört das MFH ins Quarterly dazu. Das Gerüst für den Eintrag liefert der Baum, mit CVE_ID füllt es die Angaben aus dem CVE schon vor:

% cd ~/ports/security/vuxml
% make newentry CVE_ID=CVE-2026-103000
% make validate

Nach dem nächsten stündlichen Lauf steht der Port gelb statt rot, mit dem neuen Paket dann gar nicht mehr. An jedem Commit zeigt die Seite außerdem, ob das MFH angefragt oder schon erledigt ist. Unter /vuxml-gaps stehen die VuXML-Einträge, denen CVE-Nummern oder Paketnamen fehlen.

Daneben gibt es /updates: 6.318 Ports, für die upstream eine neuere Version existiert, nach Kategorie, Art des Updates (major, minor, patch) und der Zahl der CVEs, die das Update erledigen würde. Ports, die bewusst einen Zweig führen wie mysql84, zählen dort nicht als veraltet.

Was im Hintergrund läuft

Die Seite selbst rechnet nichts. Alles passiert in einem Collector in Python, der auf einer eigenen VM läuft und seine Ergebnisse in MySQL schreibt. Die Webseite liest nur, setzt keine Cookies, trackt nicht und braucht keine Anmeldung. Die Quellen sind alle offen:

  • Paketindex von pkg.freebsd.org für latest und quarterly, dazu die Indizes aller FreeBSD-Versionen und Architekturen, die noch Pakete bekommen
  • ports.git: jeder Commit auf main seit 1994, die Quarterly-Zweige, VuXML, MOVED
  • NVD und cvelistV5 von CVE.org: die CVEs mit betroffenen Produkten und Versionsbereichen
  • Repology: CPE für Ports, die selbst keinen haben, und die neueste Upstream-Version
  • Bugzilla: offene Problem Reports je Port
Schaubild: Quellen wie Paketindex, ports.git, NVD, cvelistV5, Repology und Bugzilla fließen in den Collector, der in MySQL schreibt; die PHP-Seite liest nur
Gerechnet wird im Collector, die Seite zeigt nur an. Fällt eine Quelle aus, bleibt ihr letzter Stand stehen.

Neue Commits holt der Collector jede Minute. VuXML, die CVE-Einträge aus cvelistV5 und die Problem Reports kommen stündlich, NVD, Pakete und Upstream-Versionen einmal am Tag. Versionen vergleicht er mit libpkg, also exakt so wie pkg version -t. Vergleicht man 1.30.6_5,3 falsch mit „before 1.30.6“, ist die Liste voller Fehlalarme und damit wertlos. Fehlende Pakete führt er über die Abhängigkeiten im Paketindex bis zur Wurzel zurück:

def root(o, path):
    # fehlende Abhängigkeiten, die es in main noch gibt
    sub = [d for d in gaps(o) if d not in path]
    if not sub:
        return {o}        # hier endet die Kette
    res = set()
    for d in sub:
        res |= root(d, path | {o})
    return res

Wo die Seite noch Schwächen hat

  • CVE-Einträge sind oft zu weit gefasst. Wenn ein CNA, also die Stelle, die den CVE-Eintrag herausgibt, „before 3.29.0“ schreibt, gilt das formal für jeden älteren Zweig, ob der alte Code die Lücke hat oder nicht. net/freerdp 2.x steht deshalb mit 96 CVEs da, obwohl der Port abgekündigt ist, Ende 2026 entfernt wird und per pkg-message auf net/freerdp3 verweist. portstatus rechnet mit dem, was im Eintrag steht.
  • VuXML nennt nicht immer alle CVEs. Warnt pkg audit ohnehin vor der Version und reicht der Eintrag bis zur Fix-Version, zählt portstatus das CVE als gelb, denn das Update behebt es mit. Das betrifft heute 582 CVEs aus 7 Einträgen, 550 davon allein im Chromium-Eintrag. Ohne diese Regel stünden 3.865 statt 3.295 CVEs auf der roten Liste.
  • Backports sieht man an der Version nicht. Gerade bei den Linux-Ports (linux-rl9-*) fließen Fixes in den RPM-Release, die Upstream-Version bleibt gleich. Die landen deshalb als „possibly backported“ gesondert.
  • Ohne CPE kein sicherer Treffer. Für über 90 Prozent der Ports braucht die Seite Ersatzwege über Repology oder den Paketnamen bei PyPI und CPAN. Das fängt viel ab, aber nicht alles.
  • Der Paketbau hinkt. Was im Baum gefixt ist, steckt erst Tage später im Paket. Die Seite bezieht den Sicherheitsstatus bewusst auf das gebaute Paket, weil das bei dir installiert ist.

Wie die Seite zählt, steht auf der About-Seite. Fehlalarme sammle ich über die Kommentare hier. Was mir noch fehlt: ein Feed je Maintainer oder Port, damit niemand die Seite aktiv besuchen muss, und eine Möglichkeit, einen Treffer als „geprüft, betrifft uns nicht“ zu markieren, mit Begründung und für alle sichtbar.

Fazit

  • pkg audit ist so gut wie VuXML. Was dort fehlt, meldet es nicht: im ganzen Baum heute 3.295 CVEs in 670 Ports.
  • portstatus gleicht die Ports stündlich mit cvelistV5 und täglich mit NVD ab und sagt dir pro Port, ob es einen Fix gibt und wo.
  • Quarterly-Nutzer sehen, was in main behoben, bei ihnen aber noch offen ist.
  • Committer finden über die Maintainer-Suche schnell, wo ein VuXML-Eintrag, ein Update oder ein MFH fehlt, und unter /vuxml-gaps, welchen Einträgen CVE-Nummern oder Paketnamen fehlen.

Die Seite ersetzt pkg audit nicht. Sie zeigt die Lücke daneben.

Jetzt bist du dran: Such deine wichtigsten Ports auf portstatus.bsdproject.de. Welcher steht bei dir rot, obwohl du sicher bist, dass er sauber ist? Schreib es mit Portname und CVE in die Kommentare.

Schreibe einen Kommentar