blob: 7b25c53d204b94558a8cc9e98a47ee243b221ae3 [file] [log] [blame]
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package database
import (
"fmt"
"golang.org/x/vulndb/internal/osv"
"golang.org/x/vulndb/internal/version"
)
// New creates a new database from the given entries.
// Errors if there are multiple entries with the same ID.
func New(entries ...osv.Entry) (*Database, error) {
db := &Database{
DB: DBMeta{},
Modules: make(ModulesIndex),
Vulns: make(VulnsIndex),
Entries: make([]osv.Entry, 0, len(entries)),
}
for _, entry := range entries {
if err := db.Add(entry); err != nil {
return nil, err
}
}
return db, nil
}
// Add adds new entries to a database, erroring if any of the entries
// is already in the database.
func (db *Database) Add(entries ...osv.Entry) error {
for _, entry := range entries {
if err := db.Vulns.add(entry); err != nil {
return err
}
// Only add the entry once we are sure it won't
// cause an error.
db.Entries = append(db.Entries, entry)
db.Modules.add(entry)
db.DB.add(entry)
}
return nil
}
func (dbi *DBMeta) add(entry osv.Entry) {
if entry.Modified.After(dbi.Modified.Time) {
dbi.Modified = entry.Modified
}
}
func (m *ModulesIndex) add(entry osv.Entry) {
for _, affected := range entry.Affected {
modulePath := affected.Module.Path
if _, ok := (*m)[modulePath]; !ok {
(*m)[modulePath] = &Module{
Path: modulePath,
Vulns: []ModuleVuln{},
}
}
module := (*m)[modulePath]
module.Vulns = append(module.Vulns, ModuleVuln{
ID: entry.ID,
Modified: entry.Modified,
Fixed: latestFixedVersion(affected.Ranges),
})
}
}
func (v *VulnsIndex) add(entry osv.Entry) error {
if _, ok := (*v)[entry.ID]; ok {
return fmt.Errorf("id %q appears twice in database", entry.ID)
}
(*v)[entry.ID] = &Vuln{
ID: entry.ID,
Modified: entry.Modified,
Aliases: entry.Aliases,
}
return nil
}
func latestFixedVersion(ranges []osv.Range) string {
var latestFixed string
for _, r := range ranges {
if r.Type == osv.RangeTypeSemver {
for _, e := range r.Events {
if fixed := e.Fixed; fixed != "" && version.Before(latestFixed, fixed) {
latestFixed = fixed
}
}
// If the vulnerability was re-introduced after the latest fix
// we found, there is no latest fix for this range.
for _, e := range r.Events {
if introduced := e.Introduced; introduced != "" && introduced != "0" && version.Before(latestFixed, introduced) {
latestFixed = ""
break
}
}
}
}
return string(latestFixed)
}