Skip to content
10 changes: 10 additions & 0 deletions cmd/grype/cli/commands/internal/dbsearch/vulnerabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ type VulnerabilityInfo struct {

// EPSS is a list of Exploit Prediction Scoring System (EPSS) scores for the vulnerability
EPSS []EPSS `json:"epss,omitempty"`

// CWEs is a list of Common Weakness Enumeration (CWE) identifiers for the vulnerability
CWEs []CWE `json:"cwes,omitempty"`
}

// OperatingSystem represents specific release of an operating system.
Expand Down Expand Up @@ -88,6 +91,13 @@ type EPSS struct {
Date string `json:"date"`
}

type CWE struct {
Cve string `json:"cve"`
CWE string `json:"cwe"`
Source string `json:"source"`
Type string `json:"type"`
}

type CVSSSeverity struct {
// Vector is the CVSS assessment as a parameterized string
Vector string `json:"vector"`
Expand Down
13 changes: 13 additions & 0 deletions grype/db/v6/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func Models() []any {
&KnownExploitedVulnerabilityHandle{},
&EpssHandle{},
&EpssMetadata{},
&CWEHandle{},
}
}

Expand Down Expand Up @@ -799,3 +800,15 @@ type EpssHandle struct {
Percentile float64 `gorm:"column:percentile;not null"`
Date time.Time `gorm:"-"` // note we do not store the date in this table since it is expected to be the same for all records, that is what EpssMetadata is for
}

type CWEHandle struct {
ID int64 `gorm:"primaryKey"`
Cve string `gorm:"column:cve;not null;index:cwes_cve_idx,collate:NOCASE"`
CWE string `gorm:"column:cwe;not null;"`
Source string `gorm:"column:source;"`
Type string `gorm:"column:type;"`
}

func (c CWEHandle) String() string {
return fmt.Sprintf("CWE(%s: %s, source=%s, type=%s)", c.Cve, c.CWE, c.Source, c.Type)
}
56 changes: 56 additions & 0 deletions grype/db/v6/vulnerability_decorator_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,34 @@ import (
type VulnerabilityDecoratorStoreWriter interface {
AddKnownExploitedVulnerabilities(...*KnownExploitedVulnerabilityHandle) error
AddEpss(...*EpssHandle) error
AddCWE(...*CWEHandle) error
}

type VulnerabilityDecoratorStoreReader interface {
GetKnownExploitedVulnerabilities(cve string) ([]KnownExploitedVulnerabilityHandle, error)
GetEpss(cve string) ([]EpssHandle, error)
GetCWEs(cve string) ([]CWEHandle, error)
}

type vulnerabilityDecoratorStore struct {
db *gorm.DB
blobStore *blobStore
kevEnabled bool
epssEnabled bool
cweEnabled bool
epssDate *time.Time
}

func newVulnerabilityDecoratorStore(db *gorm.DB, bs *blobStore, dbVersion schemaver.SchemaVer) *vulnerabilityDecoratorStore {
minSupportedKEVClientVersion := schemaver.New(6, 0, 1)
minSupportedEPSSClientVersion := schemaver.New(6, 0, 2)
minSupportedCWEClientVersion := schemaver.New(6, 0, 0)
return &vulnerabilityDecoratorStore{
db: db,
blobStore: bs,
kevEnabled: dbVersion.GreaterOrEqualTo(minSupportedKEVClientVersion),
epssEnabled: dbVersion.GreaterOrEqualTo(minSupportedEPSSClientVersion),
cweEnabled: dbVersion.GreaterOrEqualTo(minSupportedCWEClientVersion),
}
}

Expand All @@ -60,6 +65,22 @@ func (s *vulnerabilityDecoratorStore) AddEpss(epss ...*EpssHandle) error {
return nil
}

func (s *vulnerabilityDecoratorStore) AddCWE(cwe ...*CWEHandle) error {
if !s.cweEnabled {
// when populating a new DB any capability issues found should result in halting
return ErrDBCapabilityNotSupported
}

for i := range cwe {
c := cwe[i]

if err := s.db.Create(c).Error; err != nil {
return fmt.Errorf("unable to create CWE: %w", err)
}
}
return nil
}

func (s *vulnerabilityDecoratorStore) setEPSSMetadata(date time.Time) error {
if !s.epssEnabled {
// when populating a new DB any capability issues found should result in halting
Expand Down Expand Up @@ -145,6 +166,41 @@ func (s *vulnerabilityDecoratorStore) GetEpss(cve string) ([]EpssHandle, error)
return models, nil
}

func (s *vulnerabilityDecoratorStore) GetCWEs(cve string) ([]CWEHandle, error) {
if !s.cweEnabled {
// capability incompatibilities should gracefully degrade, returning no data or errors
return nil, nil
}

fields := logger.Fields{
"cve": cve,
}
start := time.Now()
var count int
defer func() {
fields["duration"] = time.Since(start)
fields["records"] = count
log.WithFields(fields).Trace("fetched CWE records")
}()

var models []CWEHandle
var results []*CWEHandle

if err := s.db.Where("cve = ? collate nocase", cve).FindInBatches(&results, batchSize, func(_ *gorm.DB, _ int) error {
for _, r := range results {
models = append(models, *r)
}

count += len(results)

return nil
}).Error; err != nil {
return models, fmt.Errorf("unable to fetch CWE records: %w", err)
}

return models, nil
}

func (s *vulnerabilityDecoratorStore) AddKnownExploitedVulnerabilities(kevs ...*KnownExploitedVulnerabilityHandle) error {
if !s.kevEnabled {
// when populating a new DB any capability issues found should result in halting
Expand Down
31 changes: 29 additions & 2 deletions grype/db/v6/vulnerability_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,36 @@
log.WithFields("id", vuln.Name, "vulnerability", vuln.String(), "error", err).Debug("unable to fetch epss from vulnerability")
}

return newVulnerabilityMetadata(vuln, namespace, kevs, epss)
cwes, err := vp.fetchCWE(cves)
if err != nil {
log.WithFields("id", vuln.Name, "vulnerability", vuln.String(), "error", err).Debug("unable to fetch cwes from vulnerability")
}

return newVulnerabilityMetadata(vuln, namespace, kevs, epss, cwes)
}

func (vp vulnerabilityProvider) fetchCWE(cves []string) ([]vulnerability.CWE, error) {
var out []vulnerability.CWE
var errs error
for _, cve := range cves {
entries, err := vp.reader.GetCWEs(cve)
if err != nil {
errs = multierror.Append(errs, err)
continue
}
for _, entry := range entries {
out = append(out, vulnerability.CWE{
CVE: entry.Cve,
CWE: entry.CWE,
Source: entry.Source,
Type: entry.Type,
})
}
}
return out, errs
}

func newVulnerabilityMetadata(vuln *VulnerabilityHandle, namespace string, kevs []vulnerability.KnownExploited, epss []vulnerability.EPSS) (*vulnerability.Metadata, error) {
func newVulnerabilityMetadata(vuln *VulnerabilityHandle, namespace string, kevs []vulnerability.KnownExploited, epss []vulnerability.EPSS, CWEs []vulnerability.CWE) (*vulnerability.Metadata, error) {

Check failure on line 110 in grype/db/v6/vulnerability_provider.go

View workflow job for this annotation

GitHub Actions / Static analysis

captLocal: `CWEs' should not be capitalized (gocritic)
if vuln == nil {
return nil, nil
}
Expand All @@ -101,6 +127,7 @@
Cvss: cvss,
KnownExploited: kevs,
EPSS: epss,
CWEs: CWEs,
}, nil
}

Expand Down
22 changes: 22 additions & 0 deletions grype/presenter/models/vulnerability_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type VulnerabilityMetadata struct {
Cvss []Cvss `json:"cvss"`
KnownExploited []KnownExploited `json:"knownExploited,omitempty"`
EPSS []EPSS `json:"epss,omitempty"`
CWEs []CWE `json:"cwes,omitempty"`
}

type KnownExploited struct {
Expand All @@ -38,6 +39,13 @@ type EPSS struct {
Date string `json:"date"`
}

type CWE struct {
Cve string `json:"cve"`
CWE string `json:"cwe,omitempty"`
Source string `json:"source,omitempty"`
Type string `json:"type,omitempty"`
}

func NewVulnerabilityMetadata(id, namespace string, metadata *vulnerability.Metadata) VulnerabilityMetadata {
if metadata == nil {
return VulnerabilityMetadata{
Expand All @@ -61,6 +69,7 @@ func NewVulnerabilityMetadata(id, namespace string, metadata *vulnerability.Meta
Cvss: toCVSS(metadata),
KnownExploited: toKnownExploited(metadata.KnownExploited),
EPSS: toEPSS(metadata.EPSS),
CWEs: toCWE(metadata.CWEs),
}
}

Expand Down Expand Up @@ -102,3 +111,16 @@ func toEPSS(epss []vulnerability.EPSS) []EPSS {
}
return result
}

func toCWE(cwes []vulnerability.CWE) []CWE {
result := make([]CWE, len(cwes))
for idx, e := range cwes {
result[idx] = CWE{
Cve: e.CVE,
CWE: e.CWE,
Source: e.Source,
Type: e.Type,
}
}
return result
}
8 changes: 8 additions & 0 deletions grype/vulnerability/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Metadata struct {
Cvss []Cvss
KnownExploited []KnownExploited
EPSS []EPSS
CWEs []CWE

// calculated as-needed
risk float64
Expand Down Expand Up @@ -156,3 +157,10 @@ type EPSS struct {
Percentile float64
Date time.Time
}

type CWE struct {
CVE string
CWE string
Source string
Type string
}
Loading