diff --git a/changes/44800-ollama-resolved-in-version b/changes/44800-ollama-resolved-in-version new file mode 100644 index 00000000000..a447dfd2dcc --- /dev/null +++ b/changes/44800-ollama-resolved-in-version @@ -0,0 +1 @@ +- Fixed missing `resolved_in_version` for CVE-2025-63389 on Ollama (resolved in v0.12.4), which was absent because the NVD record only provides a `versionEndIncluding` constraint. diff --git a/server/vulnerabilities/nvd/cpe_matching_rules.go b/server/vulnerabilities/nvd/cpe_matching_rules.go index 87eac7b3580..27196075168 100644 --- a/server/vulnerabilities/nvd/cpe_matching_rules.go +++ b/server/vulnerabilities/nvd/cpe_matching_rules.go @@ -5,9 +5,48 @@ import ( "strconv" "strings" + feednvd "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools/cvefeed/nvd" "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools/wfn" ) +// resolvedVersionOverride supplies a known upstream fix version for a CVE whose NVD record only +// provides a versionEndIncluding constraint (so the resolved version can't be derived from the +// feed). Like GetKnownNVDBugRules, this works around bad/incomplete data in the NVD dataset, but +// it is consulted only as a fallback - the live NVD feed always takes precedence (see +// getMatchingVersionEndExcluding). See https://github.com/fleetdm/fleet/issues/44800. +type resolvedVersionOverride struct { + vendor string + product string + resolvedInVersion string +} + +// knownResolvedVersionOverrides maps a CVE to its known upstream fix version. Add an entry here +// only when NVD reports the CVE without a versionEndExcluding (so resolved_in_version comes back +// empty) but the fix version is known. Remove the entry once NVD publishes the correct data. +var knownResolvedVersionOverrides = map[string]resolvedVersionOverride{ + // CVE-2025-63389: NVD lists ollama as vulnerable through (and including) v0.12.3 via + // versionEndIncluding with no versionEndExcluding. The fix shipped in the next release, v0.12.4. + "CVE-2025-63389": {vendor: "ollama", product: "ollama", resolvedInVersion: "0.12.4"}, +} + +// findResolvedVersionOverride returns the known resolved version for the given CVE and host +// software, if one is configured and applicable, or an empty string otherwise. The caller must +// already have confirmed that the host is affected by the CVE; the version guard here only avoids +// reporting a resolved version for a host that is already at or above the fix version. +func findResolvedVersionOverride(cve string, hostSoftwareMeta *wfn.Attributes) string { + override, ok := knownResolvedVersionOverrides[cve] + if !ok || hostSoftwareMeta == nil { + return "" + } + if hostSoftwareMeta.Vendor != override.vendor || hostSoftwareMeta.Product != override.product { + return "" + } + if feednvd.SmartVerCmp(wfn.StripSlashes(hostSoftwareMeta.Version), override.resolvedInVersion) != -1 { + return "" + } + return override.resolvedInVersion +} + type CPEMatchingRules []CPEMatchingRule // GetKnownNVDBugRules returns a list of CPEMatchingRules used for diff --git a/server/vulnerabilities/nvd/cve.go b/server/vulnerabilities/nvd/cve.go index f2005718b24..03d5bf70add 100644 --- a/server/vulnerabilities/nvd/cve.go +++ b/server/vulnerabilities/nvd/cve.go @@ -688,10 +688,29 @@ func expandCPEAliases(cpeItem *wfn.Attributes) []*wfn.Attributes { return cpeItems } -// Returns the versionEndExcluding string for the given CVE and host software meta -// data, if it exists in the NVD feed. This effectively gives us the version of the -// software it needs to upgrade to in order to address the CVE. +// getMatchingVersionEndExcluding returns the version of the software it needs to upgrade to in +// order to address the CVE. It is derived from the NVD feed's versionEndExcluding when available; +// otherwise, as a fallback, it consults knownResolvedVersionOverrides for CVEs whose NVD record +// only provides versionEndIncluding. Authoritative NVD data always takes precedence over the +// fallback. The caller must already have confirmed that the host is affected by the CVE. func getMatchingVersionEndExcluding(ctx context.Context, cve string, hostSoftwareMeta *wfn.Attributes, dict cvefeed.Dictionary, logger *slog.Logger) (string, error) { + resolved, err := versionEndExcludingFromFeed(ctx, cve, hostSoftwareMeta, dict, logger) + if err != nil { + return "", err + } + if resolved != "" { + return resolved, nil + } + + // The NVD feed yielded no resolved version (e.g. the record only has versionEndIncluding). + // Fall back to a known upstream fix version, if one is configured for this CVE. + // See https://github.com/fleetdm/fleet/issues/44800. + return findResolvedVersionOverride(cve, hostSoftwareMeta), nil +} + +// versionEndExcludingFromFeed returns the versionEndExcluding string for the given CVE and host +// software meta data, if it exists in the NVD feed. +func versionEndExcludingFromFeed(ctx context.Context, cve string, hostSoftwareMeta *wfn.Attributes, dict cvefeed.Dictionary, logger *slog.Logger) (string, error) { vuln, ok := dict[cve].(*feednvd.Vuln) if !ok { return "", nil diff --git a/server/vulnerabilities/nvd/cve_test.go b/server/vulnerabilities/nvd/cve_test.go index 4b5f6dbb9b8..43da1f7d06a 100644 --- a/server/vulnerabilities/nvd/cve_test.go +++ b/server/vulnerabilities/nvd/cve_test.go @@ -1123,6 +1123,52 @@ func TestGetMatchingVersionEndExcluding(t *testing.T) { want: "6.72.10.07", wantErr: false, }, + { + // NVD only provides versionEndIncluding for this CVE, so the resolved version comes + // from knownResolvedVersionOverrides. See #44800. + name: "versionEndIncluding-only CVE uses resolved version override", + cve: "CVE-2025-63389", + meta: &wfn.Attributes{ + Vendor: "ollama", + Product: "ollama", + Version: "0.12.3", + }, + want: "0.12.4", + wantErr: false, + }, + { + name: "resolved version override applies to older vulnerable versions", + cve: "CVE-2025-63389", + meta: &wfn.Attributes{ + Vendor: "ollama", + Product: "ollama", + Version: "0.12.0", + }, + want: "0.12.4", + wantErr: false, + }, + { + name: "resolved version override does not apply at or above the fix version", + cve: "CVE-2025-63389", + meta: &wfn.Attributes{ + Vendor: "ollama", + Product: "ollama", + Version: "0.12.4", + }, + want: "", + wantErr: false, + }, + { + name: "resolved version override does not apply to other products", + cve: "CVE-2025-63389", + meta: &wfn.Attributes{ + Vendor: "notollama", + Product: "notollama", + Version: "0.12.0", + }, + want: "", + wantErr: false, + }, } for _, tt := range tests {