func parseRHSA(ovalReader io.Reader) (vulnerabilities []database.Vulnerability, err error) { // Decode the XML. var ov oval err = xml.NewDecoder(ovalReader).Decode(&ov) if err != nil { log.Errorf("could not decode RHEL's XML: %s", err) err = cerrors.ErrCouldNotParse return } // Iterate over the definitions and collect any vulnerabilities that affect // at least one package. for _, definition := range ov.Definitions { pkgs := toFeatureVersions(definition.Criteria) if len(pkgs) > 0 { vulnerability := database.Vulnerability{ Name: name(definition), Link: link(definition), Severity: priority(definition), Description: description(definition), } for _, p := range pkgs { vulnerability.FixedIn = append(vulnerability.FixedIn, p) } vulnerabilities = append(vulnerabilities, vulnerability) } } return }
// Parse an Oval file. func (f *OvalFetcher) ParseOval(ovalReader io.Reader) (vulnerabilities []database.Vulnerability, err error) { var ov oval err = xml.NewDecoder(ovalReader).Decode(&ov) if err != nil { log.Errorf("could not decode %s's XML: %s", f.OsInfo.DistName(), err) return vulnerabilities, cerrors.ErrCouldNotParse } for _, definition := range ov.Definitions { pkgs := f.ToFeatureVersions(f.Possibilities(definition.Criteria)) if len(pkgs) > 0 { vulnerability := database.Vulnerability{ Name: name(definition), Link: link(definition, f.OsInfo.SecToken()), Severity: priority(definition), Description: description(definition), } vulnerability.FixedIn = append(vulnerability.FixedIn, pkgs...) vulnerabilities = append(vulnerabilities, vulnerability) } } return }
// mergeVulnerability updates the target vulnerability structure using the specified one. func mergeVulnerability(target, source *database.Vulnerability) { if source.Link != "" { target.Link = source.Link } if source.Description != "" { target.Description = source.Description } if source.Priority.Compare(target.Priority) > 0 { target.Priority = source.Priority } for _, node := range source.FixedInNodes { if !utils.Contains(node, target.FixedInNodes) { target.FixedInNodes = append(target.FixedInNodes, node) } } }
func parse34YAML(r io.Reader) (vulns []database.Vulnerability, err error) { var rBytes []byte rBytes, err = ioutil.ReadAll(r) if err != nil { return } var file secdb34File err = yaml.Unmarshal(rBytes, &file) if err != nil { return } for _, pack := range file.Packages { pkg := pack.Pkg for versionStr, vulnStrs := range pkg.Fixes { version, err := types.NewVersion(versionStr) if err != nil { log.Warningf("could not parse package version '%s': %s. skipping", versionStr, err.Error()) continue } for _, vulnStr := range vulnStrs { var vuln database.Vulnerability vuln.Severity = types.Unknown vuln.Name = vulnStr vuln.Link = nvdURLPrefix + vulnStr vuln.FixedIn = []database.FeatureVersion{ { Feature: database.Feature{ Namespace: database.Namespace{Name: "alpine:" + file.Distro}, Name: pkg.Name, }, Version: version, }, } vulns = append(vulns, vuln) } } } return }
// Fills Vulnerability.LayersIntroducingVulnerability. // limit -1: won't do anything // limit 0: will just get the startID of the second page func (pgSQL *pgSQL) loadLayerIntroducingVulnerability(vulnerability *database.Vulnerability, limit, startID int) (int, error) { tf := time.Now() if vulnerability == nil { return -1, nil } // A startID equals to -1 means that we reached the end already. if startID == -1 || limit == -1 { return -1, nil } // We do `defer observeQueryTime` here because we don't want to observe invalid calls. defer observeQueryTime("loadLayerIntroducingVulnerability", "all", tf) // Query with limit + 1, the last item will be used to know the next starting ID. rows, err := pgSQL.Query(searchNotificationLayerIntroducingVulnerability, vulnerability.ID, startID, limit+1) if err != nil { return 0, handleError("searchNotificationLayerIntroducingVulnerability", err) } defer rows.Close() var layers []database.Layer for rows.Next() { var layer database.Layer if err := rows.Scan(&layer.ID, &layer.Name); err != nil { return -1, handleError("searchNotificationLayerIntroducingVulnerability.Scan()", err) } layers = append(layers, layer) } if err = rows.Err(); err != nil { return -1, handleError("searchNotificationLayerIntroducingVulnerability.Rows()", err) } size := limit if len(layers) < limit { size = len(layers) } vulnerability.LayersIntroducingVulnerability = layers[:size] nextID := -1 if len(layers) > limit { nextID = layers[limit].ID } return nextID, nil }
func (pgSQL *pgSQL) insertVulnerability(vulnerability database.Vulnerability, onlyFixedIn, generateNotification bool) error { tf := time.Now() // Verify parameters if vulnerability.Name == "" || vulnerability.Namespace.Name == "" { return cerrors.NewBadRequestError("insertVulnerability needs at least the Name and the Namespace") } if !onlyFixedIn && !vulnerability.Severity.IsValid() { msg := fmt.Sprintf("could not insert a vulnerability that has an invalid Severity: %s", vulnerability.Severity) log.Warning(msg) return cerrors.NewBadRequestError(msg) } for i := 0; i < len(vulnerability.FixedIn); i++ { fifv := &vulnerability.FixedIn[i] if fifv.Feature.Namespace.Name == "" { // As there is no Namespace on that FixedIn FeatureVersion, set it to the Vulnerability's // Namespace. fifv.Feature.Namespace.Name = vulnerability.Namespace.Name } else if fifv.Feature.Namespace.Name != vulnerability.Namespace.Name { msg := "could not insert an invalid vulnerability that contains FixedIn FeatureVersion that are not in the same namespace as the Vulnerability" log.Warning(msg) return cerrors.NewBadRequestError(msg) } } // We do `defer observeQueryTime` here because we don't want to observe invalid vulnerabilities. defer observeQueryTime("insertVulnerability", "all", tf) // Begin transaction. tx, err := pgSQL.Begin() if err != nil { tx.Rollback() return handleError("insertVulnerability.Begin()", err) } // Find existing vulnerability and its Vulnerability_FixedIn_Features (for update). existingVulnerability, err := findVulnerability(tx, vulnerability.Namespace.Name, vulnerability.Name, true) if err != nil && err != cerrors.ErrNotFound { tx.Rollback() return err } if onlyFixedIn { // Because this call tries to update FixedIn FeatureVersion, import all other data from the // existing one. if existingVulnerability.ID == 0 { return cerrors.ErrNotFound } fixedIn := vulnerability.FixedIn vulnerability = existingVulnerability vulnerability.FixedIn = fixedIn } if existingVulnerability.ID != 0 { updateMetadata := vulnerability.Description != existingVulnerability.Description || vulnerability.Link != existingVulnerability.Link || vulnerability.Severity != existingVulnerability.Severity || !reflect.DeepEqual(castMetadata(vulnerability.Metadata), existingVulnerability.Metadata) // Construct the entire list of FixedIn FeatureVersion, by using the // the FixedIn list of the old vulnerability. // // TODO(Quentin-M): We could use !updateFixedIn to just copy FixedIn/Affects rows from the // existing vulnerability in order to make metadata updates much faster. var updateFixedIn bool vulnerability.FixedIn, updateFixedIn = applyFixedInDiff(existingVulnerability.FixedIn, vulnerability.FixedIn) if !updateMetadata && !updateFixedIn { tx.Commit() return nil } // Mark the old vulnerability as non latest. _, err = tx.Exec(removeVulnerability, vulnerability.Namespace.Name, vulnerability.Name) if err != nil { tx.Rollback() return handleError("removeVulnerability", err) } } else { // The vulnerability is new, we don't want to have any types.MinVersion as they are only used // for diffing existing vulnerabilities. var fixedIn []database.FeatureVersion for _, fv := range vulnerability.FixedIn { if fv.Version != types.MinVersion { fixedIn = append(fixedIn, fv) } } vulnerability.FixedIn = fixedIn } // Find or insert Vulnerability's Namespace. namespaceID, err := pgSQL.insertNamespace(vulnerability.Namespace) if err != nil { return err } // Insert vulnerability. err = tx.QueryRow( insertVulnerability, namespaceID, vulnerability.Name, vulnerability.Description, vulnerability.Link, &vulnerability.Severity, &vulnerability.Metadata, ).Scan(&vulnerability.ID) if err != nil { tx.Rollback() return handleError("insertVulnerability", err) } // Update Vulnerability_FixedIn_Feature and Vulnerability_Affects_FeatureVersion now. err = pgSQL.insertVulnerabilityFixedInFeatureVersions(tx, vulnerability.ID, vulnerability.FixedIn) if err != nil { tx.Rollback() return err } // Create a notification. if generateNotification { err = createNotification(tx, existingVulnerability.ID, vulnerability.ID) if err != nil { return err } } // Commit transaction. err = tx.Commit() if err != nil { tx.Rollback() return handleError("insertVulnerability.Commit()", err) } return nil }
func scanVulnerability(queryer Queryer, queryName string, vulnerabilityRow *sql.Row) (database.Vulnerability, error) { var vulnerability database.Vulnerability err := vulnerabilityRow.Scan( &vulnerability.ID, &vulnerability.Name, &vulnerability.Namespace.ID, &vulnerability.Namespace.Name, &vulnerability.Description, &vulnerability.Link, &vulnerability.Severity, &vulnerability.Metadata, ) if err != nil { return vulnerability, handleError(queryName+".Scan()", err) } if vulnerability.ID == 0 { return vulnerability, cerrors.ErrNotFound } // Query the FixedIn FeatureVersion now. rows, err := queryer.Query(searchVulnerabilityFixedIn, vulnerability.ID) if err != nil { return vulnerability, handleError("searchVulnerabilityFixedIn.Scan()", err) } defer rows.Close() for rows.Next() { var featureVersionID zero.Int var featureVersionVersion zero.String var featureVersionFeatureName zero.String err := rows.Scan( &featureVersionVersion, &featureVersionID, &featureVersionFeatureName, ) if err != nil { return vulnerability, handleError("searchVulnerabilityFixedIn.Scan()", err) } if !featureVersionID.IsZero() { // Note that the ID we fill in featureVersion is actually a Feature ID, and not // a FeatureVersion ID. featureVersion := database.FeatureVersion{ Model: database.Model{ID: int(featureVersionID.Int64)}, Feature: database.Feature{ Model: database.Model{ID: int(featureVersionID.Int64)}, Namespace: vulnerability.Namespace, Name: featureVersionFeatureName.String, }, Version: types.NewVersionUnsafe(featureVersionVersion.String), } vulnerability.FixedIn = append(vulnerability.FixedIn, featureVersion) } } if err := rows.Err(); err != nil { return vulnerability, handleError("searchVulnerabilityFixedIn.Rows()", err) } return vulnerability, nil }
func TestInsertVulnerability(t *testing.T) { datastore, err := openDatabaseForTest("InsertVulnerability", false) if err != nil { t.Error(err) return } defer datastore.Close() // Create some data. n1 := database.Namespace{Name: "TestInsertVulnerabilityNamespace1"} n2 := database.Namespace{Name: "TestInsertVulnerabilityNamespace2"} f1 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion1", Namespace: n1, }, Version: types.NewVersionUnsafe("1.0"), } f2 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion1", Namespace: n2, }, Version: types.NewVersionUnsafe("1.0"), } f3 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion2", }, Version: types.MaxVersion, } f4 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion2", }, Version: types.NewVersionUnsafe("1.4"), } f5 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion3", }, Version: types.NewVersionUnsafe("1.5"), } f6 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion4", }, Version: types.NewVersionUnsafe("0.1"), } f7 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion5", }, Version: types.MaxVersion, } f8 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion5", }, Version: types.MinVersion, } // Insert invalid vulnerabilities. for _, vulnerability := range []database.Vulnerability{ { Name: "", Namespace: n1, FixedIn: []database.FeatureVersion{f1}, Severity: types.Unknown, }, { Name: "TestInsertVulnerability0", Namespace: database.Namespace{}, FixedIn: []database.FeatureVersion{f1}, Severity: types.Unknown, }, { Name: "TestInsertVulnerability0-", Namespace: database.Namespace{}, FixedIn: []database.FeatureVersion{f1}, }, { Name: "TestInsertVulnerability0", Namespace: n1, FixedIn: []database.FeatureVersion{f1}, Severity: types.Priority(""), }, { Name: "TestInsertVulnerability0", Namespace: n1, FixedIn: []database.FeatureVersion{f2}, Severity: types.Unknown, }, } { err := datastore.InsertVulnerabilities([]database.Vulnerability{vulnerability}, true) assert.Error(t, err) } // Insert a simple vulnerability and find it. v1meta := make(map[string]interface{}) v1meta["TestInsertVulnerabilityMetadata1"] = "TestInsertVulnerabilityMetadataValue1" v1meta["TestInsertVulnerabilityMetadata2"] = struct { Test string }{ Test: "TestInsertVulnerabilityMetadataValue1", } v1 := database.Vulnerability{ Name: "TestInsertVulnerability1", Namespace: n1, FixedIn: []database.FeatureVersion{f1, f3, f6, f7}, Severity: types.Low, Description: "TestInsertVulnerabilityDescription1", Link: "TestInsertVulnerabilityLink1", Metadata: v1meta, } err = datastore.InsertVulnerabilities([]database.Vulnerability{v1}, true) if assert.Nil(t, err) { v1f, err := datastore.FindVulnerability(n1.Name, v1.Name) if assert.Nil(t, err) { equalsVuln(t, &v1, &v1f) } } // Update vulnerability. v1.Description = "TestInsertVulnerabilityLink2" v1.Link = "TestInsertVulnerabilityLink2" v1.Severity = types.High // Update f3 in f4, add fixed in f5, add fixed in f6 which already exists, removes fixed in f7 by // adding f8 which is f7 but with MinVersion. v1.FixedIn = []database.FeatureVersion{f4, f5, f6, f8} err = datastore.InsertVulnerabilities([]database.Vulnerability{v1}, true) if assert.Nil(t, err) { v1f, err := datastore.FindVulnerability(n1.Name, v1.Name) if assert.Nil(t, err) { // We already had f1 before the update. // Add it to the struct for comparison. v1.FixedIn = append(v1.FixedIn, f1) // Removes f8 from the struct for comparison as it was just here to cancel f7. for i := 0; i < len(v1.FixedIn); i++ { if v1.FixedIn[i].Feature.Name == f8.Feature.Name { v1.FixedIn = append(v1.FixedIn[:i], v1.FixedIn[i+1:]...) } } equalsVuln(t, &v1, &v1f) } } }
// Fills Vulnerability.LayersIntroducingVulnerability. // limit -1: won't do anything // limit 0: will just get the startID of the second page func (pgSQL *pgSQL) loadLayerIntroducingVulnerability(vulnerability *database.Vulnerability, limit, startID int) (int, error) { tf := time.Now() if vulnerability == nil { return -1, nil } // A startID equals to -1 means that we reached the end already. if startID == -1 || limit == -1 { return -1, nil } // Create a transaction to disable hash joins as our experience shows that // PostgreSQL plans in certain cases a sequential scan and a hash on // Layer_diff_FeatureVersion for the condition `ldfv.layer_id >= $2 AND // ldfv.modification = 'add'` before realizing a hash inner join with // Vulnerability_Affects_FeatureVersion. By disabling explictly hash joins, // we force PostgreSQL to perform a bitmap index scan with // `ldfv.featureversion_id = fv.id` on Layer_diff_FeatureVersion, followed by // a bitmap heap scan on `ldfv.layer_id >= $2 AND ldfv.modification = 'add'`, // thus avoiding a sequential scan on the biggest database table and // allowing a small nested loop join instead. tx, err := pgSQL.Begin() if err != nil { return -1, handleError("searchNotificationLayerIntroducingVulnerability.Begin()", err) } defer tx.Commit() _, err = tx.Exec(disableHashJoin) if err != nil { log.Warningf("searchNotificationLayerIntroducingVulnerability: could not disable hash join: %s", err) } // We do `defer observeQueryTime` here because we don't want to observe invalid calls. defer observeQueryTime("loadLayerIntroducingVulnerability", "all", tf) // Query with limit + 1, the last item will be used to know the next starting ID. rows, err := tx.Query(searchNotificationLayerIntroducingVulnerability, vulnerability.ID, startID, limit+1) if err != nil { return 0, handleError("searchNotificationLayerIntroducingVulnerability", err) } defer rows.Close() var layers []database.Layer for rows.Next() { var layer database.Layer if err := rows.Scan(&layer.ID, &layer.Name); err != nil { return -1, handleError("searchNotificationLayerIntroducingVulnerability.Scan()", err) } layers = append(layers, layer) } if err = rows.Err(); err != nil { return -1, handleError("searchNotificationLayerIntroducingVulnerability.Rows()", err) } size := limit if len(layers) < limit { size = len(layers) } vulnerability.LayersIntroducingVulnerability = layers[:size] nextID := -1 if len(layers) > limit { nextID = layers[limit].ID } return nextID, nil }