From cb42892716dbdbcccf5cecda1e509c53e37678bf Mon Sep 17 00:00:00 2001 From: liang chenye Date: Tue, 12 Jul 2016 18:52:08 +0800 Subject: [PATCH] add FixedInVersions to check affected packages Signed-off-by: liang chenye --- api/v1/README.md | 10 +- api/v1/models.go | 27 +- database/models.go | 9 +- database/pgsql/complex_test.go | 8 +- database/pgsql/feature.go | 8 +- database/pgsql/layer_test.go | 2 +- database/pgsql/notification_test.go | 12 +- database/pgsql/testdata/data.sql | 4 +- database/pgsql/vulnerability.go | 25 +- database/pgsql/vulnerability_test.go | 24 +- updater/fetchers/debian/debian.go | 2 +- updater/fetchers/debian/debian_test.go | 12 +- updater/fetchers/nodejs/nodejs.go | 37 +-- updater/fetchers/nodejs/nodejs_test.go | 6 +- updater/fetchers/nodejs/nodejs_version.go | 76 ------ .../fetchers/nodejs/nodejs_version_test.go | 53 ---- updater/fetchers/rhel/rhel.go | 6 +- updater/fetchers/rhel/rhel_test.go | 10 +- updater/fetchers/ubuntu/ubuntu.go | 2 +- updater/fetchers/ubuntu/ubuntu_test.go | 6 +- utils/types/fixedin_versions.go | 237 ++++++++++++++++++ utils/types/fixedin_versions_test.go | 69 +++++ 22 files changed, 397 insertions(+), 248 deletions(-) delete mode 100644 updater/fetchers/nodejs/nodejs_version.go delete mode 100644 updater/fetchers/nodejs/nodejs_version_test.go create mode 100644 utils/types/fixedin_versions.go create mode 100644 utils/types/fixedin_versions_test.go diff --git a/api/v1/README.md b/api/v1/README.md index d9ab6ace..e69cb984 100644 --- a/api/v1/README.md +++ b/api/v1/README.md @@ -144,7 +144,7 @@ Server: clair "Description": "The parse_datetime function in GNU coreutils allows remote attackers to cause a denial of service (crash) or possibly execute arbitrary code via a crafted date string, as demonstrated by the \"--date=TZ=\"123\"345\" @1\" string to the touch or date command.", "Link": "https://security-tracker.debian.org/tracker/CVE-2014-9471", "Severity": "Low", - "FixedBy": "9.23-5" + "FixedBy": ">= 9.23-5" } ] } @@ -289,7 +289,7 @@ POST http://localhost:6060/v1/namespaces/debian%3A8/vulnerabilities HTTP/1.1 { "Name": "coreutils", "NamespaceName": "debian:8", - "Version": "8.23-1" + "FixedInVersions": ">= 8.23-1" } ] } @@ -322,7 +322,7 @@ Server: clair { "Name": "coreutils", "NamespaceName": "debian:8", - "Version": "8.23-1" + "FixedInVersions": ">= 8.23-1" } ] } @@ -373,7 +373,7 @@ Server: clair { "Name": "coreutils", "NamespaceName": "debian:8", - "Version": "8.23-1" + "FixedInVersions": ">= 8.23-1" } ] } @@ -592,7 +592,7 @@ Server: clair { "Name": "grep", "NamespaceName": "debian:8", - "Version": "2.25" + "FixedInVersions": ">= 2.25" } ] }, diff --git a/api/v1/models.go b/api/v1/models.go index 0d6a6383..9424fb49 100644 --- a/api/v1/models.go +++ b/api/v1/models.go @@ -77,7 +77,7 @@ func LayerFromDatabaseModel(dbLayer database.Layer, withFeatures, withVulnerabil Metadata: dbVuln.Metadata, } - if dbVuln.FixedBy != types.MaxVersion { + if dbVuln.FixedBy.String() != types.NewFixedInVersionsFromOV(types.OpGreaterEqual, types.MaxVersion).String() { vuln.FixedBy = dbVuln.FixedBy.String() } feature.Vulnerabilities = append(feature.Vulnerabilities, vuln) @@ -154,31 +154,32 @@ type Feature struct { Name string `json:"Name,omitempty"` NamespaceName string `json:"NamespaceName,omitempty"` Version string `json:"Version,omitempty"` + FixedInVersions string `json:"FixedInVersions,omitempty"` Vulnerabilities []Vulnerability `json:"Vulnerabilities,omitempty"` AddedBy string `json:"AddedBy,omitempty"` } func FeatureFromDatabaseModel(dbFeatureVersion database.FeatureVersion) Feature { - versionStr := dbFeatureVersion.Version.String() - if versionStr == types.MaxVersion.String() { - versionStr = "None" + fixedInVersionsStr := dbFeatureVersion.FixedInVersions.String() + if fixedInVersionsStr == types.NewFixedInVersionsFromOV(types.OpGreaterEqual, types.MaxVersion).String() { + fixedInVersionsStr = "None" } return Feature{ - Name: dbFeatureVersion.Feature.Name, - NamespaceName: dbFeatureVersion.Feature.Namespace.Name, - Version: versionStr, - AddedBy: dbFeatureVersion.AddedBy.Name, + Name: dbFeatureVersion.Feature.Name, + NamespaceName: dbFeatureVersion.Feature.Namespace.Name, + FixedInVersions: fixedInVersionsStr, + AddedBy: dbFeatureVersion.AddedBy.Name, } } func (f Feature) DatabaseModel() (database.FeatureVersion, error) { - var version types.Version - if f.Version == "None" { - version = types.MaxVersion + var fivs types.FixedInVersions + if f.FixedInVersions == "None" { + fivs = types.NewFixedInVersionsFromOV(types.OpGreaterEqual, types.MaxVersion) } else { var err error - version, err = types.NewVersion(f.Version) + fivs, err = types.NewFixedInVersions(f.FixedInVersions) if err != nil { return database.FeatureVersion{}, err } @@ -189,7 +190,7 @@ func (f Feature) DatabaseModel() (database.FeatureVersion, error) { Name: f.Name, Namespace: database.Namespace{Name: f.NamespaceName}, }, - Version: version, + FixedInVersions: fivs, }, nil } diff --git a/database/models.go b/database/models.go index a44291b8..e88d2360 100644 --- a/database/models.go +++ b/database/models.go @@ -53,9 +53,10 @@ type Feature struct { type FeatureVersion struct { Model - Feature Feature - Version types.Version - AffectedBy []Vulnerability + Feature Feature + Version types.Version + FixedInVersions types.FixedInVersions + AffectedBy []Vulnerability // For output purposes. Only make sense when the feature version is in the context of an image. AddedBy Layer @@ -78,7 +79,7 @@ type Vulnerability struct { // For output purposes. Only make sense when the vulnerability // is already about a specific Feature/FeatureVersion. - FixedBy types.Version `json:",omitempty"` + FixedBy types.FixedInVersions `json:",omitempty"` } type MetadataMap map[string]interface{} diff --git a/database/pgsql/complex_test.go b/database/pgsql/complex_test.go index 46ba504a..a475a99f 100644 --- a/database/pgsql/complex_test.go +++ b/database/pgsql/complex_test.go @@ -85,8 +85,8 @@ func TestRaceAffects(t *testing.T) { Namespace: feature.Namespace, FixedIn: []database.FeatureVersion{ { - Feature: feature, - Version: types.NewVersionUnsafe(strconv.Itoa(version)), + Feature: feature, + FixedInVersions: types.NewFixedInVersionsUnsafe(">=" + strconv.Itoa(version)), }, }, Severity: types.Unknown, @@ -149,7 +149,9 @@ func TestRaceAffects(t *testing.T) { // Get expected affects. for i := numVulnerabilities; i > featureVersionVersion; i-- { for _, vulnerability := range vulnerabilities[i] { - expectedAffectedNames = append(expectedAffectedNames, vulnerability.Name) + if vulnerability.FixedIn[0].FixedInVersions.Affected(featureVersion.Version) { + expectedAffectedNames = append(expectedAffectedNames, vulnerability.Name) + } } } diff --git a/database/pgsql/feature.go b/database/pgsql/feature.go index a2f2abe8..679b7152 100644 --- a/database/pgsql/feature.go +++ b/database/pgsql/feature.go @@ -194,7 +194,7 @@ func (pgSQL *pgSQL) insertFeatureVersions(featureVersions []database.FeatureVers type vulnerabilityAffectsFeatureVersion struct { vulnerabilityID int fixedInID int - fixedInVersion types.Version + fixedInVersions types.FixedInVersions } func linkFeatureVersionToVulnerabilities(tx *sql.Tx, featureVersion database.FeatureVersion) error { @@ -210,14 +210,12 @@ func linkFeatureVersionToVulnerabilities(tx *sql.Tx, featureVersion database.Fea for rows.Next() { var affect vulnerabilityAffectsFeatureVersion - err := rows.Scan(&affect.fixedInID, &affect.vulnerabilityID, &affect.fixedInVersion) + err := rows.Scan(&affect.fixedInID, &affect.vulnerabilityID, &affect.fixedInVersions) if err != nil { return handleError("searchVulnerabilityFixedInFeature.Scan()", err) } - if featureVersion.Version.Compare(affect.fixedInVersion) < 0 { - // The version of the FeatureVersion we are inserting is lower than the fixed version on this - // Vulnerability, thus, this FeatureVersion is affected by it. + if affect.fixedInVersions.Affected(featureVersion.Version) { affects = append(affects, affect) } } diff --git a/database/pgsql/layer_test.go b/database/pgsql/layer_test.go index c45cbbed..fc6c900f 100644 --- a/database/pgsql/layer_test.go +++ b/database/pgsql/layer_test.go @@ -93,7 +93,7 @@ func TestFindLayer(t *testing.T) { assert.Equal(t, types.High, featureVersion.AffectedBy[0].Severity) assert.Equal(t, "A vulnerability affecting OpenSSL < 2.0 on Debian 7.0", featureVersion.AffectedBy[0].Description) assert.Equal(t, "http://google.com/#q=CVE-OPENSSL-1-DEB7", featureVersion.AffectedBy[0].Link) - assert.Equal(t, types.NewVersionUnsafe("2.0"), featureVersion.AffectedBy[0].FixedBy) + assert.Equal(t, types.NewFixedInVersionsUnsafe(">= 2.0").String(), featureVersion.AffectedBy[0].FixedBy.String()) } default: t.Errorf("unexpected package %s for layer-1", featureVersion.Feature.Name) diff --git a/database/pgsql/notification_test.go b/database/pgsql/notification_test.go index 3f90f349..e8fb3705 100644 --- a/database/pgsql/notification_test.go +++ b/database/pgsql/notification_test.go @@ -104,8 +104,8 @@ func TestNotification(t *testing.T) { Severity: "Unknown", FixedIn: []database.FeatureVersion{ { - Feature: f1, - Version: types.NewVersionUnsafe("1.0"), + Feature: f1, + FixedInVersions: types.NewFixedInVersionsUnsafe(">= 1.0"), }, }, } @@ -164,12 +164,12 @@ func TestNotification(t *testing.T) { v1b.Severity = types.High v1b.FixedIn = []database.FeatureVersion{ { - Feature: f1, - Version: types.MinVersion, + Feature: f1, + FixedInVersions: types.NewFixedInVersionsFromOV(types.OpGreaterEqual, types.MinVersion), }, { - Feature: f2, - Version: types.MaxVersion, + Feature: f2, + FixedInVersions: types.NewFixedInVersionsFromOV(types.OpGreaterEqual, types.MaxVersion), }, } diff --git a/database/pgsql/testdata/data.sql b/database/pgsql/testdata/data.sql index 7a48ef64..e91e42e8 100644 --- a/database/pgsql/testdata/data.sql +++ b/database/pgsql/testdata/data.sql @@ -48,8 +48,8 @@ INSERT INTO vulnerability (id, namespace_id, name, description, link, severity) (2, 1, 'CVE-NOPE', 'A vulnerability affecting nothing', '', 'Unknown'); INSERT INTO vulnerability_fixedin_feature (id, vulnerability_id, feature_id, version) VALUES - (1, 1, 2, '2.0'), - (2, 1, 4, '1.9-abc'); + (1, 1, 2, '>= 2.0'), + (2, 1, 4, '>= 1.9-abc'); INSERT INTO vulnerability_affects_featureversion (id, vulnerability_id, featureversion_id, fixedin_id) VALUES (1, 1, 2, 1); -- CVE-OPENSSL-1-DEB7 affects Debian:7 OpenSSL 1.0 diff --git a/database/pgsql/vulnerability.go b/database/pgsql/vulnerability.go index 74ee9828..21e9c7f5 100644 --- a/database/pgsql/vulnerability.go +++ b/database/pgsql/vulnerability.go @@ -140,11 +140,11 @@ func scanVulnerability(queryer Queryer, queryName string, vulnerabilityRow *sql. for rows.Next() { var featureVersionID zero.Int - var featureVersionVersion zero.String + var featureVersionFixedInVersions zero.String var featureVersionFeatureName zero.String err := rows.Scan( - &featureVersionVersion, + &featureVersionFixedInVersions, &featureVersionID, &featureVersionFeatureName, ) @@ -163,7 +163,7 @@ func scanVulnerability(queryer Queryer, queryName string, vulnerabilityRow *sql. Namespace: vulnerability.Namespace, Name: featureVersionFeatureName.String, }, - Version: types.NewVersionUnsafe(featureVersionVersion.String), + FixedInVersions: types.NewFixedInVersionsUnsafe(featureVersionFixedInVersions.String), } vulnerability.FixedIn = append(vulnerability.FixedIn, featureVersion) } @@ -274,7 +274,7 @@ func (pgSQL *pgSQL) insertVulnerability(vulnerability database.Vulnerability, on // for diffing existing vulnerabilities. var fixedIn []database.FeatureVersion for _, fv := range vulnerability.FixedIn { - if fv.Version != types.MinVersion { + if fv.FixedInVersions.String() != types.NewFixedInVersionsFromOV(types.OpGreaterEqual, types.MinVersion).String() { fixedIn = append(fixedIn, fv) } } @@ -350,7 +350,7 @@ func applyFixedInDiff(currentList, diff []database.FeatureVersion) ([]database.F different := false for _, name := range addedNames { - if diffMap[name].Version == types.MinVersion { + if diffMap[name].FixedInVersions.String() == types.NewFixedInVersionsFromOV(types.OpGreaterEqual, types.MinVersion).String() { // MinVersion only makes sense when a Feature is already fixed in some version, // in which case we would be in the "inBothNames". continue @@ -363,7 +363,7 @@ func applyFixedInDiff(currentList, diff []database.FeatureVersion) ([]database.F for _, name := range inBothNames { fv := diffMap[name] - if fv.Version == types.MinVersion { + if fv.FixedInVersions.String() == types.NewFixedInVersionsFromOV(types.OpGreaterEqual, types.MinVersion).String() { // MinVersion means that the Feature doesn't affect the Vulnerability anymore. delete(currentMap, name) different = true @@ -438,7 +438,7 @@ func (pgSQL *pgSQL) insertVulnerabilityFixedInFeatureVersions(tx *sql.Tx, vulner err = tx.QueryRow( insertVulnerabilityFixedInFeature, vulnerabilityID, fv.Feature.ID, - &fv.Version, + &fv.FixedInVersions, ).Scan(&fixedInID) if err != nil { @@ -446,7 +446,7 @@ func (pgSQL *pgSQL) insertVulnerabilityFixedInFeatureVersions(tx *sql.Tx, vulner } // Insert Vulnerability_Affects_FeatureVersion. - err = linkVulnerabilityToFeatureVersions(tx, fixedInID, vulnerabilityID, fv.Feature.ID, fv.Version) + err = linkVulnerabilityToFeatureVersions(tx, fixedInID, vulnerabilityID, fv.Feature.ID, fv.FixedInVersions) if err != nil { return err } @@ -455,7 +455,7 @@ func (pgSQL *pgSQL) insertVulnerabilityFixedInFeatureVersions(tx *sql.Tx, vulner return nil } -func linkVulnerabilityToFeatureVersions(tx *sql.Tx, fixedInID, vulnerabilityID, featureID int, fixedInVersion types.Version) error { +func linkVulnerabilityToFeatureVersions(tx *sql.Tx, fixedInID, vulnerabilityID, featureID int, fixedInVersions types.FixedInVersions) error { // Find every FeatureVersions of the Feature that the vulnerability affects. // TODO(Quentin-M): LIMIT rows, err := tx.Query(searchFeatureVersionByFeature, featureID) @@ -472,10 +472,7 @@ func linkVulnerabilityToFeatureVersions(tx *sql.Tx, fixedInID, vulnerabilityID, if err != nil { return handleError("searchFeatureVersionByFeature.Scan()", err) } - - if affected.Version.Compare(fixedInVersion) < 0 { - // The version of the FeatureVersion is lower than the fixed version of this vulnerability, - // thus, this FeatureVersion is affected by it. + if fixedInVersions.Affected(affected.Version) { affecteds = append(affecteds, affected) } } @@ -527,7 +524,7 @@ func (pgSQL *pgSQL) DeleteVulnerabilityFix(vulnerabilityNamespace, vulnerability Name: vulnerabilityNamespace, }, }, - Version: types.MinVersion, + FixedInVersions: types.NewFixedInVersionsFromOV(types.OpGreaterEqual, types.MinVersion), }, }, } diff --git a/database/pgsql/vulnerability_test.go b/database/pgsql/vulnerability_test.go index d20c2e35..de182587 100644 --- a/database/pgsql/vulnerability_test.go +++ b/database/pgsql/vulnerability_test.go @@ -46,12 +46,12 @@ func TestFindVulnerability(t *testing.T) { Namespace: database.Namespace{Name: "debian:7"}, FixedIn: []database.FeatureVersion{ { - Feature: database.Feature{Name: "openssl"}, - Version: types.NewVersionUnsafe("2.0"), + Feature: database.Feature{Name: "openssl"}, + FixedInVersions: types.NewFixedInVersionsUnsafe(">= 2.0"), }, { - Feature: database.Feature{Name: "libssl"}, - Version: types.NewVersionUnsafe("1.9-abc"), + Feature: database.Feature{Name: "libssl"}, + FixedInVersions: types.NewFixedInVersionsUnsafe(">= 1.9-abc"), }, }, } @@ -114,50 +114,50 @@ func TestInsertVulnerability(t *testing.T) { Name: "TestInsertVulnerabilityFeatureVersion1", Namespace: n1, }, - Version: types.NewVersionUnsafe("1.0"), + FixedInVersions: types.NewFixedInVersionsUnsafe(">= 1.0"), } f2 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion1", Namespace: n2, }, - Version: types.NewVersionUnsafe("1.0"), + FixedInVersions: types.NewFixedInVersionsUnsafe(">= 1.0"), } f3 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion2", }, - Version: types.MaxVersion, + FixedInVersions: types.NewFixedInVersionsFromOV(types.OpGreaterEqual, types.MaxVersion), } f4 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion2", }, - Version: types.NewVersionUnsafe("1.4"), + FixedInVersions: types.NewFixedInVersionsUnsafe(">= 1.4"), } f5 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion3", }, - Version: types.NewVersionUnsafe("1.5"), + FixedInVersions: types.NewFixedInVersionsUnsafe(">= 1.5"), } f6 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion4", }, - Version: types.NewVersionUnsafe("0.1"), + FixedInVersions: types.NewFixedInVersionsUnsafe(">= 0.1"), } f7 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion5", }, - Version: types.MaxVersion, + FixedInVersions: types.NewFixedInVersionsFromOV(types.OpGreaterEqual, types.MaxVersion), } f8 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion5", }, - Version: types.MinVersion, + FixedInVersions: types.NewFixedInVersionsFromOV(types.OpGreaterEqual, types.MinVersion), } // Insert invalid vulnerabilities. diff --git a/updater/fetchers/debian/debian.go b/updater/fetchers/debian/debian.go index 356613eb..84e9e5a5 100644 --- a/updater/fetchers/debian/debian.go +++ b/updater/fetchers/debian/debian.go @@ -196,7 +196,7 @@ func parseDebianJSON(data *jsonData) (vulnerabilities []database.Vulnerability, Name: "debian:" + database.DebianReleasesMapping[releaseName], }, }, - Version: version, + FixedInVersions: types.NewFixedInVersionsFromOV(types.OpGreaterEqual, version), } vulnerability.FixedIn = append(vulnerability.FixedIn, pkg) diff --git a/updater/fetchers/debian/debian_test.go b/updater/fetchers/debian/debian_test.go index 88000af1..40cea819 100644 --- a/updater/fetchers/debian/debian_test.go +++ b/updater/fetchers/debian/debian_test.go @@ -45,7 +45,7 @@ func TestDebianParser(t *testing.T) { Namespace: database.Namespace{Name: "debian:8"}, Name: "aptdaemon", }, - Version: types.MaxVersion, + FixedInVersions: types.NewFixedInVersionsFromOV(types.OpGreaterEqual, types.MaxVersion), }, { Feature: database.Feature{ @@ -53,7 +53,7 @@ func TestDebianParser(t *testing.T) { Name: "aptdaemon", }, - Version: types.NewVersionUnsafe("1.1.1+bzr982-1"), + FixedInVersions: types.NewFixedInVersionsUnsafe(">= 1.1.1+bzr982-1"), }, } @@ -71,21 +71,21 @@ func TestDebianParser(t *testing.T) { Namespace: database.Namespace{Name: "debian:8"}, Name: "aptdaemon", }, - Version: types.NewVersionUnsafe("0.7.0"), + FixedInVersions: types.NewFixedInVersionsUnsafe(">= 0.7.0"), }, { Feature: database.Feature{ Namespace: database.Namespace{Name: "debian:unstable"}, Name: "aptdaemon", }, - Version: types.NewVersionUnsafe("0.7.0"), + FixedInVersions: types.NewFixedInVersionsUnsafe(">= 0.7.0"), }, { Feature: database.Feature{ Namespace: database.Namespace{Name: "debian:8"}, Name: "asterisk", }, - Version: types.NewVersionUnsafe("0.5.56"), + FixedInVersions: types.NewFixedInVersionsUnsafe(">= 0.5.56"), }, } @@ -103,7 +103,7 @@ func TestDebianParser(t *testing.T) { Namespace: database.Namespace{Name: "debian:8"}, Name: "asterisk", }, - Version: types.MinVersion, + FixedInVersions: types.NewFixedInVersionsFromOV(types.OpGreaterEqual, types.MinVersion), }, } diff --git a/updater/fetchers/nodejs/nodejs.go b/updater/fetchers/nodejs/nodejs.go index 7bd8362e..88dcdb9e 100644 --- a/updater/fetchers/nodejs/nodejs.go +++ b/updater/fetchers/nodejs/nodejs.go @@ -31,8 +31,6 @@ const ( cveURLPrefix = "http://cve.mitre.org/cgi-bin/cvename.cgi?name=" updaterFlag = "nodejsUpdater" defaultNodejsVersion = "all" - //Add a suffix when an advisory is fixed `after` a certain version. - defaultVersionSuffix = "-1" ) var log = capnslog.NewPackageLogger("github.com/coreos/clair", "updater/fetchers/nodejs") @@ -131,8 +129,10 @@ func parseNodejsAdvisories(advisories []nodejsAdvisory, latestUpdate string) (vu }, }, } - if version, err := getAdvisoryVersion(advisory.PatchedVersions); err == nil { - pkg.Version = version + if fivs, err := types.NewFixedInVersions(advisory.PatchedVersions); err == nil { + pkg.FixedInVersions = fivs + } else { + log.Warningf("could not parse nodejs patched version: '%s'.", err) } vulnerability.FixedIn = append(vulnerability.FixedIn, pkg) @@ -149,34 +149,5 @@ func parseNodejsAdvisories(advisories []nodejsAdvisory, latestUpdate string) (vu return } -// getAdvisoryVersion parses a string containing one or multiple version ranges -// and returns upper-bound. By nature, this simplification may lead to false-positives -func getAdvisoryVersion(fullVersion string) (types.Version, error) { - fixedVersion := types.MinVersion - - for _, version := range strings.Split(fullVersion, "||") { - ovs := getOperVersions(version) - for _, ov := range ovs { - if ov.Oper == ">" { - if curVersion, err := types.NewVersion(ov.Version + defaultVersionSuffix); err != nil { - log.Warningf("could not parse package version '%s': %s. skipping", curVersion, err.Error()) - } else if curVersion.Compare(fixedVersion) > 0 { - fixedVersion = curVersion - } - } else if ov.Oper == ">=" { - if curVersion, err := types.NewVersion(ov.Version); err != nil { - log.Warningf("could not parse package version '%s': %s. skipping", curVersion, err.Error()) - } else if curVersion.Compare(fixedVersion) > 0 { - fixedVersion = curVersion - } - } - } - } - if fixedVersion != types.MinVersion { - return fixedVersion, nil - } - return types.MaxVersion, cerrors.ErrNotFound -} - // Clean deletes any allocated resources. func (fetcher *NodejsFetcher) Clean() {} diff --git a/updater/fetchers/nodejs/nodejs_test.go b/updater/fetchers/nodejs/nodejs_test.go index 16c8edb7..7bca1049 100644 --- a/updater/fetchers/nodejs/nodejs_test.go +++ b/updater/fetchers/nodejs/nodejs_test.go @@ -50,14 +50,14 @@ func TestNodejsParser(t *testing.T) { Namespace: database.Namespace{Name: "nodejs:" + defaultNodejsVersion}, Name: "ldapauth", }, - Version: types.NewVersionUnsafe("2.2.4" + defaultVersionSuffix), + FixedInVersions: types.NewFixedInVersionsUnsafe("> 2.2.4"), }, { Feature: database.Feature{ Namespace: database.Namespace{Name: "nodejs:" + defaultNodejsVersion}, Name: "ldapauth-fork", }, - Version: types.NewVersionUnsafe("2.3.3"), + FixedInVersions: types.NewFixedInVersionsUnsafe(">= 2.3.3"), }, } for _, expectedFeatureVersion := range expectedFeatureVersions { @@ -73,7 +73,7 @@ func TestNodejsParser(t *testing.T) { Namespace: database.Namespace{Name: "nodejs:" + defaultNodejsVersion}, Name: "hawk", }, - Version: types.NewVersionUnsafe("4.1.1"), + FixedInVersions: types.NewFixedInVersionsUnsafe(">=3.1.3 < 4.0.0 || >=4.1.1"), }, } for _, expectedFeatureVersion := range expectedFeatureVersions { diff --git a/updater/fetchers/nodejs/nodejs_version.go b/updater/fetchers/nodejs/nodejs_version.go deleted file mode 100644 index bf87da7e..00000000 --- a/updater/fetchers/nodejs/nodejs_version.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2016 clair authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package nodejs - -import ( - "strings" - "unicode" -) - -type operVersion struct { - Oper string - Version string -} - -type ovState string - -const ( - ovStateInit ovState = "init" - ovStateOper ovState = "operation" - ovStateVersion ovState = "version" -) - -func isOper(ch rune) bool { - return ch == '>' || ch == '<' || ch == '=' -} - -func getOperVersions(content string) (ovs []operVersion) { - state := ovStateInit - begin := 0 - var ov operVersion - for i, ch := range content { - if unicode.IsSpace(ch) { - continue - } - switch state { - case ovStateInit: - if isOper(ch) { - state = ovStateOper - begin = i - } else { - return nil - } - case ovStateOper: - if !isOper(ch) { - state = ovStateVersion - ov.Oper = strings.TrimSpace(content[begin:i]) - begin = i - } - case ovStateVersion: - if isOper(ch) { - state = ovStateOper - ov.Version = strings.TrimSpace(content[begin:i]) - ovs = append(ovs, ov) - begin = i - } - } - } - if state == ovStateVersion { - ov.Version = strings.TrimSpace(content[begin:len(content)]) - ovs = append(ovs, ov) - } - - return -} diff --git a/updater/fetchers/nodejs/nodejs_version_test.go b/updater/fetchers/nodejs/nodejs_version_test.go deleted file mode 100644 index 0559b107..00000000 --- a/updater/fetchers/nodejs/nodejs_version_test.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2016 clair authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package nodejs - -import ( - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNodeVersion(t *testing.T) { - invalid_version := "3.1.3 < 4.0.0 || >= " - versions := strings.Split(invalid_version, "||") - for _, version := range versions { - ovs := getOperVersions(version) - assert.Len(t, ovs, 0) - } - - valid_version := ">=3.1.3 < 4.0.0 || >=4.1.1" - versions = strings.Split(valid_version, "||") - for _, version := range versions { - if strings.Contains(version, "4.1.1") { - ovs := getOperVersions(version) - assert.Len(t, ovs, 1) - assert.Equal(t, ">=", ovs[0].Oper) - assert.Equal(t, "4.1.1", ovs[0].Version) - } else { - ovs := getOperVersions(version) - assert.Len(t, ovs, 2) - - for _, ov := range ovs { - if ov.Oper == ">=" { - assert.Equal(t, "3.1.3", ov.Version) - } else if ov.Oper == "<" { - assert.Equal(t, "4.0.0", ov.Version) - } - } - } - } -} diff --git a/updater/fetchers/rhel/rhel.go b/updater/fetchers/rhel/rhel.go index 8d31f309..6f589954 100644 --- a/updater/fetchers/rhel/rhel.go +++ b/updater/fetchers/rhel/rhel.go @@ -283,11 +283,13 @@ func toFeatureVersions(criteria criteria) []database.FeatureVersion { } } else if strings.Contains(c.Comment, " is earlier than ") { const prefixLen = len(" is earlier than ") + var version types.Version featureVersion.Feature.Name = strings.TrimSpace(c.Comment[:strings.Index(c.Comment, " is earlier than ")]) - featureVersion.Version, err = types.NewVersion(c.Comment[strings.Index(c.Comment, " is earlier than ")+prefixLen:]) + version, err = types.NewVersion(c.Comment[strings.Index(c.Comment, " is earlier than ")+prefixLen:]) if err != nil { log.Warningf("could not parse package version '%s': %s. skipping", c.Comment[strings.Index(c.Comment, " is earlier than ")+prefixLen:], err.Error()) } + featureVersion.FixedInVersions = types.NewFixedInVersionsFromOV(types.OpGreaterEqual, version) } } @@ -297,7 +299,7 @@ func toFeatureVersions(criteria criteria) []database.FeatureVersion { continue } - if featureVersion.Feature.Namespace.Name != "" && featureVersion.Feature.Name != "" && featureVersion.Version.String() != "" { + if featureVersion.Feature.Namespace.Name != "" && featureVersion.Feature.Name != "" && featureVersion.FixedInVersions.String() != "" { featureVersionParameters[featureVersion.Feature.Namespace.Name+":"+featureVersion.Feature.Name] = featureVersion } else { log.Warningf("could not determine a valid package from criterions: %v", criterions) diff --git a/updater/fetchers/rhel/rhel_test.go b/updater/fetchers/rhel/rhel_test.go index b7fb9818..7d621167 100644 --- a/updater/fetchers/rhel/rhel_test.go +++ b/updater/fetchers/rhel/rhel_test.go @@ -45,21 +45,21 @@ func TestRHELParser(t *testing.T) { Namespace: database.Namespace{Name: "centos:7"}, Name: "xerces-c", }, - Version: types.NewVersionUnsafe("3.1.1-7.el7_1"), + FixedInVersions: types.NewFixedInVersionsUnsafe(">= 3.1.1-7.el7_1"), }, { Feature: database.Feature{ Namespace: database.Namespace{Name: "centos:7"}, Name: "xerces-c-devel", }, - Version: types.NewVersionUnsafe("3.1.1-7.el7_1"), + FixedInVersions: types.NewFixedInVersionsUnsafe(">= 3.1.1-7.el7_1"), }, { Feature: database.Feature{ Namespace: database.Namespace{Name: "centos:7"}, Name: "xerces-c-doc", }, - Version: types.NewVersionUnsafe("3.1.1-7.el7_1"), + FixedInVersions: types.NewFixedInVersionsUnsafe(">= 3.1.1-7.el7_1"), }, } @@ -84,14 +84,14 @@ func TestRHELParser(t *testing.T) { Namespace: database.Namespace{Name: "centos:6"}, Name: "firefox", }, - Version: types.NewVersionUnsafe("38.1.0-1.el6_6"), + FixedInVersions: types.NewFixedInVersionsUnsafe(">= 38.1.0-1.el6_6"), }, { Feature: database.Feature{ Namespace: database.Namespace{Name: "centos:7"}, Name: "firefox", }, - Version: types.NewVersionUnsafe("38.1.0-1.el7_1"), + FixedInVersions: types.NewFixedInVersionsUnsafe(">= 38.1.0-1.el7_1"), }, } diff --git a/updater/fetchers/ubuntu/ubuntu.go b/updater/fetchers/ubuntu/ubuntu.go index d5436906..bfd9b92c 100644 --- a/updater/fetchers/ubuntu/ubuntu.go +++ b/updater/fetchers/ubuntu/ubuntu.go @@ -368,7 +368,7 @@ func parseUbuntuCVE(fileContent io.Reader) (vulnerability database.Vulnerability Namespace: database.Namespace{Name: "ubuntu:" + database.UbuntuReleasesMapping[md["release"]]}, Name: md["package"], }, - Version: version, + FixedInVersions: types.NewFixedInVersionsFromOV(types.OpGreaterEqual, version), } vulnerability.FixedIn = append(vulnerability.FixedIn, featureVersion) } diff --git a/updater/fetchers/ubuntu/ubuntu_test.go b/updater/fetchers/ubuntu/ubuntu_test.go index d76d457e..5a7aafef 100644 --- a/updater/fetchers/ubuntu/ubuntu_test.go +++ b/updater/fetchers/ubuntu/ubuntu_test.go @@ -48,21 +48,21 @@ func TestUbuntuParser(t *testing.T) { Namespace: database.Namespace{Name: "ubuntu:14.04"}, Name: "libmspack", }, - Version: types.MaxVersion, + FixedInVersions: types.NewFixedInVersionsFromOV(types.OpGreaterEqual, types.MaxVersion), }, { Feature: database.Feature{ Namespace: database.Namespace{Name: "ubuntu:15.04"}, Name: "libmspack", }, - Version: types.NewVersionUnsafe("0.4-3"), + FixedInVersions: types.NewFixedInVersionsUnsafe(">= 0.4-3"), }, { Feature: database.Feature{ Namespace: database.Namespace{Name: "ubuntu:15.10"}, Name: "libmspack-anotherpkg", }, - Version: types.NewVersionUnsafe("0.1"), + FixedInVersions: types.NewFixedInVersionsUnsafe(">= 0.1"), }, } diff --git a/utils/types/fixedin_versions.go b/utils/types/fixedin_versions.go new file mode 100644 index 00000000..181d36cb --- /dev/null +++ b/utils/types/fixedin_versions.go @@ -0,0 +1,237 @@ +// Copyright 2016 clair authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "database/sql/driver" + "encoding/json" + "errors" + "fmt" + "strings" + "unicode" +) + +type Operator string + +const ( + OpNotEqual Operator = "!=" + OpLessThan Operator = "<" + OpLessEqual Operator = "<=" + OpEqualTo Operator = "==" + OpGreaterEqual Operator = ">=" + OpGreaterThan Operator = ">" +) + +type FixedInVersions struct { + fivs [][]operVersion +} + +type operVersion struct { + oper Operator + version Version +} + +type ovState string + +const ( + ovStateInit ovState = "init" + ovStateOper ovState = "operation" + ovStateVersion ovState = "version" +) + +func isOperChar(ch rune) bool { + return ch == '>' || ch == '<' || ch == '=' +} + +func getOperator(str string) (oper Operator, error error) { + switch str { + case "!=": + case "<": + case "<=": + case "==": + case ">=": + case ">": + default: + return oper, fmt.Errorf("Invalid operator: '%s'", str) + } + + return Operator(str), nil +} + +func getFixedinVersion(content string) (ovs []operVersion, err error) { + state := ovStateInit + begin := 0 + var ov operVersion + for i, ch := range content { + if unicode.IsSpace(ch) { + continue + } + switch state { + case ovStateInit: + if isOperChar(ch) { + state = ovStateOper + } else { + // Default to '>=' + ov.oper = OpGreaterEqual + state = ovStateVersion + } + begin = i + case ovStateOper: + if !isOperChar(ch) { + state = ovStateVersion + if ov.oper, err = getOperator(strings.TrimSpace(content[begin:i])); err != nil { + return nil, err + } + begin = i + } + case ovStateVersion: + if isOperChar(ch) { + state = ovStateOper + if ov.version, err = NewVersion(strings.TrimSpace(content[begin:i])); err != nil { + return nil, err + } + ovs = append(ovs, ov) + begin = i + } + } + } + if state == ovStateVersion { + if ov.version, err = NewVersion(strings.TrimSpace(content[begin:len(content)])); err != nil { + return nil, err + } + ovs = append(ovs, ov) + } + + if len(ovs) == 0 { + err = fmt.Errorf("Failed to parse '%s'", content) + } + + return +} + +func (ov operVersion) patched(version Version) bool { + val := version.Compare(ov.version) + switch ov.oper { + case OpNotEqual: + return val != 0 + case OpLessThan: + return val < 0 + case OpLessEqual: + return val <= 0 + case OpEqualTo: + return val == 0 + case OpGreaterEqual: + return val >= 0 + case OpGreaterThan: + return val > 0 + } + + //Cannot get here + return false +} + +// String returns the string representation of a FixedInVersions +func (fivs FixedInVersions) String() (s string) { + firstFiv := false + for _, fiv := range fivs.fivs { + if !firstFiv { + firstFiv = true + } else { + s += " || " + } + + firstOV := false + for _, ov := range fiv { + if !firstOV { + firstOV = true + } else { + s += " " + } + s += string(ov.oper) + ov.version.String() + } + } + + return +} + +func (fivs FixedInVersions) MarshalJSON() ([]byte, error) { + return json.Marshal(fivs.String()) +} + +func (fivs *FixedInVersions) UnmarshalJSON(b []byte) (err error) { + var str string + json.Unmarshal(b, &str) + vp := NewFixedInVersionsUnsafe(str) + *fivs = vp + return +} + +func (fivs *FixedInVersions) Scan(value interface{}) (err error) { + val, ok := value.([]byte) + if !ok { + return errors.New("could not scan a Version from a non-string input") + } + *fivs, err = NewFixedInVersions(string(val)) + return +} + +func (fivs *FixedInVersions) Value() (driver.Value, error) { + return fivs.String(), nil +} + +func (fivs FixedInVersions) Affected(version Version) bool { + for _, fiv := range fivs.fivs { + affected := false + for _, ov := range fiv { + if !ov.patched(version) { + affected = true + break + } + } + if !affected { + return false + } + } + + return true +} + +func NewFixedInVersionsFromOV(oper Operator, version Version) FixedInVersions { + var fivs FixedInVersions + var fiv []operVersion + + fiv = append(fiv, operVersion{oper, version}) + fivs.fivs = append(fivs.fivs, fiv) + + return fivs +} + +func NewFixedInVersions(str string) (FixedInVersions, error) { + var fivs FixedInVersions + for _, ovsStr := range strings.Split(str, "||") { + if fiv, err := getFixedinVersion(ovsStr); err == nil { + fivs.fivs = append(fivs.fivs, fiv) + } else { + return fivs, err + } + } + + return fivs, nil +} + +func NewFixedInVersionsUnsafe(str string) FixedInVersions { + fivs, _ := NewFixedInVersions(str) + return fivs +} diff --git a/utils/types/fixedin_versions_test.go b/utils/types/fixedin_versions_test.go new file mode 100644 index 00000000..0df3ba86 --- /dev/null +++ b/utils/types/fixedin_versions_test.go @@ -0,0 +1,69 @@ +// Copyright 2016 clair authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFixedInVersions(t *testing.T) { + invalid_version := "3.1.3 < 4.0.0 || >= " + fivs, err := NewFixedInVersions(invalid_version) + assert.Error(t, err, "Failed to parse '%s'", ">=") + + invalid_version = "3.1.3 < ab.0.0 || >= " + fivs, err = NewFixedInVersions(invalid_version) + assert.Error(t, err, "Failed to parse '%s'", ">=") + + valid_version := "3.1.3" + fivs, err = NewFixedInVersions(valid_version) + assert.Nil(t, err) + + valid_version = ">=3.1.3 <4.0.0 || >=4.1.1" + fivs, err = NewFixedInVersions(valid_version) + assert.Nil(t, err) + assert.Equal(t, strings.Replace(fivs.String(), " ", "", -1), strings.Replace(valid_version, " ", "", -1)) + + for _, fiv := range fivs.fivs { + if len(fiv) == 1 { + assert.Equal(t, OpGreaterEqual, fiv[0].oper) + assert.Equal(t, NewVersionUnsafe("4.1.1"), fiv[0].version) + } else { + for _, ov := range fiv { + if ov.oper == OpGreaterEqual { + assert.Equal(t, NewVersionUnsafe("3.1.3"), ov.version) + } else if ov.oper == OpLessThan { + assert.Equal(t, NewVersionUnsafe("4.0.0"), ov.version) + } + } + } + } + + cases := []struct { + version string + expected bool + }{ + {"4.2", false}, + {"4.0.0", true}, + {"3.1.3", false}, + {"3.1.2", true}, + } + for _, c := range cases { + assert.Equal(t, fivs.Affected(NewVersionUnsafe(c.version)), c.expected) + } +}