|
|
|
@@ -16,7 +16,7 @@ type AdvisorySource interface {
|
|
|
|
|
// Result is one vulnerable package on one server, before it becomes a finding.
|
|
|
|
|
//
|
|
|
|
|
// Severity, Title, CVSSScore and References are only as good as the advisory
|
|
|
|
|
// until MetaCache.Enrich has run over them — for the Debian-family buckets the
|
|
|
|
|
// until MetaCache.Enrich has run over them - for the Debian-family buckets the
|
|
|
|
|
// advisory carries no severity at all, so an unenriched Result reads "unknown".
|
|
|
|
|
type Result struct {
|
|
|
|
|
CVEID string
|
|
|
|
@@ -33,7 +33,7 @@ type Result struct {
|
|
|
|
|
//
|
|
|
|
|
// Vulnerable means: no fix has been published, or the installed version sorts
|
|
|
|
|
// strictly before the fixed version under the distribution's own ordering.
|
|
|
|
|
// Equal is NOT vulnerable — that is the backported-fix case, where a
|
|
|
|
|
// Equal is NOT vulnerable - that is the backported-fix case, where a
|
|
|
|
|
// distribution patches in place without changing the upstream version, and
|
|
|
|
|
// treating it as vulnerable reports a patched fleet as exposed.
|
|
|
|
|
func Match(src AdvisorySource, os models.OSRelease, pkgs []models.InstalledPackage) ([]Result, error) {
|
|
|
|
@@ -47,7 +47,7 @@ func Match(src AdvisorySource, os models.OSRelease, pkgs []models.InstalledPacka
|
|
|
|
|
var advisoryCount, skipped, unactionable, noFix int
|
|
|
|
|
|
|
|
|
|
// Advisories are keyed on the SOURCE package, and several hundred binary
|
|
|
|
|
// packages on a host resolve to the same few hundred sources — linux-modules,
|
|
|
|
|
// packages on a host resolve to the same few hundred sources - linux-modules,
|
|
|
|
|
// linux-image and linux-headers all ask about "linux", whose advisory list is
|
|
|
|
|
// thousands long. Without this the same bolt read is repeated once per binary
|
|
|
|
|
// package, which is most of what made a single Ubuntu host take minutes.
|
|
|
|
@@ -78,7 +78,7 @@ func Match(src AdvisorySource, os models.OSRelease, pkgs []models.InstalledPacka
|
|
|
|
|
|
|
|
|
|
for _, a := range advs {
|
|
|
|
|
// No published fix. Whether that is a finding depends entirely on the
|
|
|
|
|
// status the vendor attached to it — see actionable().
|
|
|
|
|
// status the vendor attached to it - see actionable().
|
|
|
|
|
if a.FixedVersion == "" {
|
|
|
|
|
if !actionable(a.Status) {
|
|
|
|
|
unactionable++
|
|
|
|
@@ -97,7 +97,7 @@ func Match(src AdvisorySource, os models.OSRelease, pkgs []models.InstalledPacka
|
|
|
|
|
if err != nil {
|
|
|
|
|
// Skip this one advisory rather than failing the whole server:
|
|
|
|
|
// one unparseable version must not blind us to every other CVE
|
|
|
|
|
// on the host. Log it — a silent skip is a silent false
|
|
|
|
|
// on the host. Log it - a silent skip is a silent false
|
|
|
|
|
// negative, which is the direction that hurts.
|
|
|
|
|
log.Printf("vulndb: compare %s %s vs %s: %v", p.Name, p.Version, a.FixedVersion, err)
|
|
|
|
|
skipped++
|
|
|
|
@@ -127,8 +127,8 @@ func Match(src AdvisorySource, os models.OSRelease, pkgs []models.InstalledPacka
|
|
|
|
|
// necessary: Ubuntu encodes the ABI in the binary name, so an upgrade INSTALLS
|
|
|
|
|
// linux-headers-6.8.0-137 beside linux-headers-6.8.0-124 rather than replacing
|
|
|
|
|
// it, and the old one lingers until an autoremove. Matched per binary package,
|
|
|
|
|
// a fully patched host reports every superseded ABI package as vulnerable —
|
|
|
|
|
// which is the noise this exists to stop — and reports it twice over, once for
|
|
|
|
|
// a fully patched host reports every superseded ABI package as vulnerable -
|
|
|
|
|
// which is the noise this exists to stop - and reports it twice over, once for
|
|
|
|
|
// linux-headers-6.8.0-124 and again for its -generic sibling.
|
|
|
|
|
//
|
|
|
|
|
// The version, not the name, decides. There is no kernel special case here: a
|
|
|
|
@@ -176,7 +176,7 @@ func newestPerSource(family string, pkgs []models.InstalledPackage) []models.Ins
|
|
|
|
|
// actionable decides whether an advisory with no fixed version is a finding.
|
|
|
|
|
//
|
|
|
|
|
// trivy-db fills Status only when FixedVersion is empty, and Ubuntu publishes a
|
|
|
|
|
// status for every CVE against every source package it ships — the vast
|
|
|
|
|
// status for every CVE against every source package it ships - the vast
|
|
|
|
|
// majority being "under_investigation" (the tracker's needs-triage), meaning
|
|
|
|
|
// nobody has yet established that the package is affected at all. Reporting
|
|
|
|
|
// those produced ~24,000 findings for a single 797-package host, which is not a
|
|
|
|
@@ -185,11 +185,11 @@ func newestPerSource(family string, pkgs []models.InstalledPackage) []models.Ins
|
|
|
|
|
//
|
|
|
|
|
// What survives is what the vendor has confirmed: affected, will_not_fix,
|
|
|
|
|
// fix_deferred, end_of_life. Those are exactly the findings the CLAUDE.md rule
|
|
|
|
|
// is about — an empty fixed_in that means "no fix exists", the one most in need
|
|
|
|
|
// is about - an empty fixed_in that means "no fix exists", the one most in need
|
|
|
|
|
// of acceptance rather than patching.
|
|
|
|
|
// Only the two statuses that positively say "this is not a finding" are
|
|
|
|
|
// dropped. "unknown" is kept: a feed that sets no status at all must not become
|
|
|
|
|
// a silent false negative, and it is not what generates the noise — Ubuntu
|
|
|
|
|
// a silent false negative, and it is not what generates the noise - Ubuntu
|
|
|
|
|
// states under_investigation explicitly.
|
|
|
|
|
func actionable(status string) bool {
|
|
|
|
|
switch status {
|
|
|
|
|