From 210e3dc9907d73feb5ccbfe1abef7bcf75ca9eec Mon Sep 17 00:00:00 2001 From: Kota Kanbe Date: Mon, 8 May 2017 22:15:12 +0900 Subject: [PATCH] Change ScanResult.Packages structure to Map --- cache/bolt_test.go | 4 +- cache/db.go | 12 +----- commands/cmdutil.go | 21 ---------- commands/util.go | 24 ++++++++++-- commands/util_test.go | 24 ++++++------ models/models.go | 78 ++++++++++++++----------------------- models/models_test.go | 47 ++-------------------- oval/oval.go | 14 +++---- oval/redhat.go | 6 +-- scan/debian.go | 91 ++++++++++++++++++++----------------------- scan/debian_test.go | 4 +- scan/freebsd.go | 23 +++++------ scan/freebsd_test.go | 12 +++--- scan/redhat.go | 45 +++++++++++---------- scan/redhat_test.go | 88 +++++++++++++++++++++-------------------- 15 files changed, 209 insertions(+), 284 deletions(-) delete mode 100644 commands/cmdutil.go diff --git a/cache/bolt_test.go b/cache/bolt_test.go index d13dd947..95a9815a 100644 --- a/cache/bolt_test.go +++ b/cache/bolt_test.go @@ -37,8 +37,8 @@ var meta = Meta{ Family: "ubuntu", Release: "16.04", }, - Packs: []models.Package{ - { + Packs: models.Packages{ + "apt": { Name: "apt", Version: "1", }, diff --git a/cache/db.go b/cache/db.go index ded0668f..ce837774 100644 --- a/cache/db.go +++ b/cache/db.go @@ -45,16 +45,6 @@ type Cache interface { type Meta struct { Name string Distro config.Distro - Packs []models.Package + Packs models.Packages CreatedAt time.Time } - -// FindPack search a Package -func (m Meta) FindPack(name string) (pack models.Package, found bool) { - for _, p := range m.Packs { - if name == p.Name { - return p, true - } - } - return pack, false -} diff --git a/commands/cmdutil.go b/commands/cmdutil.go deleted file mode 100644 index 9a58019b..00000000 --- a/commands/cmdutil.go +++ /dev/null @@ -1,21 +0,0 @@ -package commands - -import ( - "fmt" - - "github.com/howeyc/gopass" -) - -func getPasswd(prompt string) (string, error) { - for { - fmt.Print(prompt) - pass, err := gopass.GetPasswdMasked() - if err != nil { - return "", fmt.Errorf("Failed to read password") - } - if 0 < len(pass) { - return string(pass[:]), nil - } - } - -} diff --git a/commands/util.go b/commands/util.go index 3d2b583b..8f90624c 100644 --- a/commands/util.go +++ b/commands/util.go @@ -33,6 +33,7 @@ import ( "github.com/future-architect/vuls/models" "github.com/future-architect/vuls/report" "github.com/future-architect/vuls/util" + "github.com/howeyc/gopass" ) // jsonDirPattern is file name pattern of JSON directory @@ -190,11 +191,14 @@ func diff(curResults, preResults models.ScanResults) (diffed models.ScanResults, new, updated := getDiffCves(previous, current) current.ScannedCves = append(new, updated...) - current.Packages = models.Packages{} + packages := models.Packages{} for _, s := range current.ScannedCves { - current.Packages = append(current.Packages, s.Packages...) + for _, pack := range s.Packages { + p := current.Packages[pack.Name] + packages[pack.Name] = p + } } - current.Packages = current.Packages.UniqByName() + current.Packages = packages } diffed = append(diffed, current) @@ -320,3 +324,17 @@ func needToRefreshCve(r models.ScanResult) bool { } return true } + +func getPasswd(prompt string) (string, error) { + for { + fmt.Print(prompt) + pass, err := gopass.GetPasswdMasked() + if err != nil { + return "", fmt.Errorf("Failed to read password") + } + if 0 < len(pass) { + return string(pass[:]), nil + } + } + +} diff --git a/commands/util_test.go b/commands/util_test.go index ee47986e..6732d716 100644 --- a/commands/util_test.go +++ b/commands/util_test.go @@ -201,7 +201,7 @@ func TestDiff(t *testing.T) { { CveID: "CVE-2012-6702", Packages: models.Packages{ - { + "libexpat1": { Name: "libexpat1", Version: "2.1.0-7", Release: "", @@ -216,7 +216,7 @@ func TestDiff(t *testing.T) { { CveID: "CVE-2014-9761", Packages: models.Packages{ - { + "libc-bin": { Name: "libc-bin", Version: "2.21-0ubuntu5", Release: "", @@ -229,7 +229,7 @@ func TestDiff(t *testing.T) { CpeNames: []string{}, }, }, - Packages: []models.Package{}, + Packages: models.Packages{}, Errors: []string{}, Optional: [][]interface{}{}, }, @@ -244,7 +244,7 @@ func TestDiff(t *testing.T) { { CveID: "CVE-2012-6702", Packages: models.Packages{ - { + "libexpat1": { Name: "libexpat1", Version: "2.1.0-7", Release: "", @@ -259,7 +259,7 @@ func TestDiff(t *testing.T) { { CveID: "CVE-2014-9761", Packages: models.Packages{ - { + "libc-bin": { Name: "libc-bin", Version: "2.21-0ubuntu5", Release: "", @@ -272,7 +272,7 @@ func TestDiff(t *testing.T) { CpeNames: []string{}, }, }, - Packages: []models.Package{}, + Packages: models.Packages{}, Errors: []string{}, Optional: [][]interface{}{}, }, @@ -282,7 +282,7 @@ func TestDiff(t *testing.T) { ServerName: "u16", Family: "ubuntu", Release: "16.04", - Packages: []models.Package{}, + Packages: models.Packages{}, Errors: []string{}, Optional: [][]interface{}{}, }, @@ -298,7 +298,7 @@ func TestDiff(t *testing.T) { { CveID: "CVE-2016-6662", Packages: models.Packages{ - { + "mysql-libs": { Name: "mysql-libs", Version: "5.1.73", Release: "7.el6", @@ -331,7 +331,7 @@ func TestDiff(t *testing.T) { { CveID: "CVE-2016-6662", Packages: models.Packages{ - { + "mysql-libs": { Name: "mysql-libs", Version: "5.1.73", Release: "7.el6", @@ -345,7 +345,7 @@ func TestDiff(t *testing.T) { }, }, Packages: models.Packages{ - models.Package{ + "mysql-libs": { Name: "mysql-libs", Version: "5.1.73", Release: "7.el6", @@ -368,14 +368,14 @@ func TestDiff(t *testing.T) { if !reflect.DeepEqual(actual.ScannedCves, tt.out.ScannedCves) { h := pp.Sprint(actual.ScannedCves) x := pp.Sprint(tt.out.ScannedCves) - t.Errorf("[%d] actual: \n %s \n expected: \n %s", i, h, x) + t.Errorf("[%d] cves actual: \n %s \n expected: \n %s", i, h, x) } for j := range tt.out.Packages { if !reflect.DeepEqual(tt.out.Packages[j], actual.Packages[j]) { h := pp.Sprint(tt.out.Packages[j]) x := pp.Sprint(actual.Packages[j]) - t.Errorf("[%d] actual: \n %s \n expected: \n %s", i, x, h) + t.Errorf("[%d] packages actual: \n %s \n expected: \n %s", i, x, h) } } } diff --git a/models/models.go b/models/models.go index ead677bc..c6923949 100644 --- a/models/models.go +++ b/models/models.go @@ -448,7 +448,7 @@ type CveContents map[CveContentType]CveContent // NewCveContents create CveContents func NewCveContents(conts ...CveContent) CveContents { - m := make(map[CveContentType]CveContent) + m := map[CveContentType]CveContent{} for _, cont := range conts { m[cont.Type] = cont } @@ -547,69 +547,51 @@ type Reference struct { Link string } -// Packages is slice of Package -type Packages []Package +// Packages is Map of Package +// { "package-name": Package } +type Packages map[string]Package -// Exists returns true if exists the name -func (ps Packages) Exists(name string) bool { - for _, p := range ps { - if p.Name == name { - return true - } +// NewPackages create Packages +func NewPackages(packs ...Package) Packages { + m := Packages{} + for _, pack := range packs { + m[pack.Name] = pack } - return false -} - -// UniqByName be uniq by name. -func (ps Packages) UniqByName() (distincted Packages) { - set := make(map[string]Package) - for _, p := range ps { - set[p.Name] = p - } - for _, v := range set { - distincted = append(distincted, v) - } - return -} - -// FindByName search Package by name -func (ps Packages) FindByName(name string) (result Package, found bool) { - for _, p := range ps { - if p.Name == name { - return p, true - } - } - return Package{}, false + return m } // MergeNewVersion merges candidate version information to the receiver struct func (ps Packages) MergeNewVersion(as Packages) { for _, a := range as { - for i, p := range ps { - if p.Name == a.Name { - ps[i].NewVersion = a.NewVersion - ps[i].NewRelease = a.NewRelease - } + if pack, ok := ps[a.Name]; ok { + pack.NewVersion = a.NewVersion + pack.NewRelease = a.NewRelease + ps[a.Name] = pack } } } -func (ps Packages) countUpdatablePacks() int { - count := 0 - set := make(map[string]bool) - for _, p := range ps { - if len(p.NewVersion) != 0 && !set[p.Name] { - count++ - set[p.Name] = true - } +// Merge returns merged map (immutable) +func (ps Packages) Merge(other Packages) Packages { + merged := map[string]Package{} + for k, v := range ps { + merged[k] = v } - return count + for k, v := range other { + merged[k] = v + } + return merged } // FormatUpdatablePacksSummary returns a summary of updatable packages func (ps Packages) FormatUpdatablePacksSummary() string { - return fmt.Sprintf("%d updatable packages", - ps.countUpdatablePacks()) + nUpdatable := 0 + for _, p := range ps { + if p.NewVersion != "" { + nUpdatable++ + } + } + return fmt.Sprintf("%d updatable packages", nUpdatable) } // Package has installed packages. diff --git a/models/models_test.go b/models/models_test.go index 2626d7ab..3a4b428b 100644 --- a/models/models_test.go +++ b/models/models_test.go @@ -19,52 +19,11 @@ package models import ( "reflect" - "sort" "testing" "github.com/k0kubun/pp" ) -func TestPackagesUniqByName(t *testing.T) { - var test = struct { - in Packages - out Packages - }{ - Packages{ - { - Name: "hoge", - }, - { - Name: "fuga", - }, - { - Name: "hoge", - }, - }, - Packages{ - { - Name: "hoge", - }, - { - Name: "fuga", - }, - }, - } - - actual := test.in.UniqByName() - sort.Slice(actual, func(i, j int) bool { - return actual[i].Name < actual[j].Name - }) - sort.Slice(test.out, func(i, j int) bool { - return test.out[i].Name < test.out[j].Name - }) - for i, ePack := range test.out { - if actual[i].Name != ePack.Name { - t.Errorf("expected %#v, actual %#v", ePack.Name, actual[i].Name) - } - } -} - func TestMergeNewVersion(t *testing.T) { var test = struct { a Packages @@ -72,19 +31,19 @@ func TestMergeNewVersion(t *testing.T) { expected Packages }{ Packages{ - { + "hoge": { Name: "hoge", }, }, Packages{ - { + "hoge": { Name: "hoge", NewVersion: "1.0.0", NewRelease: "release1", }, }, Packages{ - { + "hoge": { Name: "hoge", NewVersion: "1.0.0", NewRelease: "release1", diff --git a/oval/oval.go b/oval/oval.go index d386b6b3..557f464c 100644 --- a/oval/oval.go +++ b/oval/oval.go @@ -11,15 +11,11 @@ type Client interface { } func getPackages(r *models.ScanResult, d *ovalmodels.Definition) models.Packages { - var packages models.Packages - for _, pack := range d.AffectedPacks { - for _, p := range r.Packages { - if pack.Name == p.Name { - p.Changelog = models.Changelog{} - packages = append(packages, p) - break - } - } + packages := models.Packages{} + for _, affectedPack := range d.AffectedPacks { + pack, _ := r.Packages[affectedPack.Name] + // pack.Changelog = models.Changelog{} + packages[affectedPack.Name] = pack } return packages } diff --git a/oval/redhat.go b/oval/redhat.go index 295d1746..cf301599 100644 --- a/oval/redhat.go +++ b/oval/redhat.go @@ -61,7 +61,7 @@ func (o Redhat) fillOvalInfo(r *models.ScanResult, definition *ovalmodels.Defini ovalContent := *o.convertToModel(cve.CveID, definition) vinfo, ok := r.ScannedCves.Get(cve.CveID) if !ok { - util.Log.Infof("%s is newly detected by OVAL", definition.Debian.CveID) + util.Log.Infof("%s is newly detected by OVAL", cve.CveID) vinfo = models.VulnInfo{ CveID: cve.CveID, Confidence: models.OvalMatch, @@ -70,9 +70,9 @@ func (o Redhat) fillOvalInfo(r *models.ScanResult, definition *ovalmodels.Defini } } else { if _, ok := vinfo.CveContents.Get(models.RedHat); !ok { - util.Log.Infof("%s is also detected by OVAL", definition.Debian.CveID) + util.Log.Infof("%s is also detected by OVAL", cve.CveID) } else { - util.Log.Infof("%s will be updated by OVAL", definition.Debian.CveID) + util.Log.Infof("%s will be updated by OVAL", cve.CveID) } if vinfo.Confidence.Score < models.OvalMatch.Score { vinfo.Confidence = models.OvalMatch diff --git a/scan/debian.go b/scan/debian.go index d9843cb6..5db8cbac 100644 --- a/scan/debian.go +++ b/scan/debian.go @@ -181,7 +181,10 @@ func (o *debian) scanPackages() error { return nil } -func (o *debian) scanInstalledPackages() (installed models.Packages, upgradable models.Packages, err error) { +func (o *debian) scanInstalledPackages() (models.Packages, models.Packages, error) { + installed := models.Packages{} + upgradable := models.Packages{} + r := o.exec("dpkg-query -W", noSudo) if !r.isSuccess() { return nil, nil, fmt.Errorf("Failed to SSH: %s", r) @@ -198,10 +201,10 @@ func (o *debian) scanInstalledPackages() (installed models.Packages, upgradable return nil, nil, fmt.Errorf( "Debian: Failed to parse package line: %s", line) } - installed = append(installed, models.Package{ + installed[name] = models.Package{ Name: name, Version: version, - }) + } } } @@ -212,20 +215,20 @@ func (o *debian) scanInstalledPackages() (installed models.Packages, upgradable for _, name := range upgradableNames { for _, pack := range installed { if pack.Name == name { - upgradable = append(upgradable, pack) + upgradable[name] = pack break } } } // Fill the candidate versions of upgradable packages - upgradable, err = o.fillCandidateVersion(upgradable) + err = o.fillCandidateVersion(upgradable) if err != nil { return nil, nil, fmt.Errorf("Failed to fill candidate versions. err: %s", err) } installed.MergeNewVersion(upgradable) - return + return installed, upgradable, nil } var packageLinePattern = regexp.MustCompile(`^([^\t']+)\t(.+)$`) @@ -254,7 +257,7 @@ func (o *debian) aptGetUpdate() error { return nil } -func (o *debian) scanUnsecurePackages(upgradable []models.Package) ([]models.VulnInfo, error) { +func (o *debian) scanUnsecurePackages(upgradable models.Packages) ([]models.VulnInfo, error) { o.aptGetUpdate() @@ -315,28 +318,28 @@ func (o *debian) ensureChangelogCache(current cache.Meta) (*cache.Meta, error) { return &cached, nil } -func (o *debian) fillCandidateVersion(before models.Packages) (filled []models.Package, err error) { +func (o *debian) fillCandidateVersion(packages models.Packages) (err error) { names := []string{} - for _, p := range before { - names = append(names, p.Name) + for name := range packages { + names = append(names, name) } cmd := fmt.Sprintf("LANGUAGE=en_US.UTF-8 apt-cache policy %s", strings.Join(names, " ")) r := o.exec(cmd, noSudo) if !r.isSuccess() { - return nil, fmt.Errorf("Failed to SSH: %s", r) + return fmt.Errorf("Failed to SSH: %s", r) } packChangelog := o.splitAptCachePolicy(r.Stdout) for k, v := range packChangelog { ver, err := o.parseAptCachePolicy(v, k) if err != nil { - return nil, fmt.Errorf("Failed to parse %s", err) + return fmt.Errorf("Failed to parse %s", err) } - p, found := before.FindByName(k) - if !found { - return nil, fmt.Errorf("Not found: %s", k) + pack, ok := packages[k] + if !ok { + return fmt.Errorf("Not found: %s", k) } - p.NewVersion = ver.Candidate - filled = append(filled, p) + pack.NewVersion = ver.Candidate + packages[k] = pack } return } @@ -394,7 +397,7 @@ func (o *debian) parseAptGetUpgrade(stdout string) (upgradableNames []string, er return } -func (o *debian) scanVulnInfos(upgradablePacks []models.Package, meta *cache.Meta) (models.VulnInfos, error) { +func (o *debian) scanVulnInfos(upgradablePacks models.Packages, meta *cache.Meta) (models.VulnInfos, error) { resChan := make(chan struct { models.Package DetectedCveIDs @@ -412,7 +415,7 @@ func (o *debian) scanVulnInfos(upgradablePacks []models.Package, meta *cache.Met }() timeout := time.After(30 * 60 * time.Second) - concurrency := 10 + concurrency := 1 tasks := util.GenWorkers(concurrency) for range upgradablePacks { tasks <- func() { @@ -446,18 +449,23 @@ func (o *debian) scanVulnInfos(upgradablePacks []models.Package, meta *cache.Met } // { DetectedCveID{} : [package] } - cvePackages := make(map[DetectedCveID][]models.Package) + cvePackages := make(map[DetectedCveID]models.Packages) errs := []error{} for i := 0; i < len(upgradablePacks); i++ { select { case pair := <-resChan: - pack := pair.Package - cveIDs := pair.DetectedCveIDs - for _, cveID := range cveIDs { - cvePackages[cveID] = appendPackIfMissing(cvePackages[cveID], pack) + cves := pair.DetectedCveIDs + for _, cve := range cves { + packs, ok := cvePackages[cve] + if ok { + packs[cve.CveID] = pair.Package + } else { + packs = models.Packages{} + } + cvePackages[cve] = packs } o.log.Infof("(%d/%d) Scanned %s-%s : %s", - i+1, len(upgradablePacks), pair.Name, pair.Package.Version, cveIDs) + i+1, len(upgradablePacks), pair.Name, pair.Package.Version, cves) case err := <-errChan: errs = append(errs, err) case <-timeout: @@ -492,7 +500,7 @@ func (o *debian) scanVulnInfos(upgradablePacks []models.Package, meta *cache.Met } func (o *debian) getChangelogCache(meta *cache.Meta, pack models.Package) string { - cachedPack, found := meta.FindPack(pack.Name) + cachedPack, found := meta.Packs[pack.Name] if !found { o.log.Debugf("Not found: %s", pack.Name) return "" @@ -602,14 +610,12 @@ func (o *debian) getCveIDsFromChangelog( // Only logging the error. o.log.Error(err) - for i, p := range o.Packages { - if p.Name == name { - o.Packages[i].Changelog = models.Changelog{ - Contents: "", - Method: models.FailedToFindVersionInChangelog, - } - } + pack := o.Packages[name] + pack.Changelog = models.Changelog{ + Contents: "", + Method: models.FailedToFindVersionInChangelog, } + o.Packages[name] = pack // If the version is not in changelog, return entire changelog to put into cache return []DetectedCveID{}, models.Changelog{ @@ -666,11 +672,9 @@ func (o *debian) parseChangelog(changelog, name, ver string, confidence models.C Method: string(confidence.DetectionMethod), } - for i, p := range o.Packages { - if p.Name == name { - o.Packages[i].Changelog = clog - } - } + pack := o.Packages[name] + pack.Changelog = clog + o.Packages[name] = pack cves := []DetectedCveID{} for _, id := range cveIDs { @@ -729,14 +733,3 @@ func (o *debian) parseAptCachePolicy(stdout, name string) (packCandidateVer, err } return ver, fmt.Errorf("Unknown Format: %s", stdout) } - -func appendPackIfMissing(slice []models.Package, s models.Package) []models.Package { - for _, ele := range slice { - if ele.Name == s.Name && - ele.Version == s.Version && - ele.Release == s.Release { - return slice - } - } - return append(slice, s) -} diff --git a/scan/debian_test.go b/scan/debian_test.go index 6a5f81c6..04429aa8 100644 --- a/scan/debian_test.go +++ b/scan/debian_test.go @@ -624,7 +624,9 @@ func TestGetChangelogCache(t *testing.T) { Family: "ubuntu", Release: "16.04", }, - Packs: []models.Package{pack}, + Packs: models.Packages{ + "apt": pack, + }, } const path = "/tmp/vuls-test-cache-11111111.db" diff --git a/scan/freebsd.go b/scan/freebsd.go index 817e9bd1..9be3aad3 100644 --- a/scan/freebsd.go +++ b/scan/freebsd.go @@ -71,7 +71,7 @@ func (o *bsd) checkDependencies() error { func (o *bsd) scanPackages() error { var err error - var packs []models.Package + var packs models.Packages if packs, err = o.scanInstalledPackages(); err != nil { o.log.Errorf("Failed to scan installed packages") return err @@ -87,7 +87,7 @@ func (o *bsd) scanPackages() error { return nil } -func (o *bsd) scanInstalledPackages() ([]models.Package, error) { +func (o *bsd) scanInstalledPackages() (models.Packages, error) { cmd := util.PrependProxyEnv("pkg version -v") r := o.exec(cmd, noSudo) if !r.isSuccess() { @@ -121,7 +121,7 @@ func (o *bsd) scanUnsecurePackages() (vulnInfos []models.VulnInfo, err error) { if len(cveIDs) == 0 { continue } - pack, found := o.Packages.FindByName(name) + pack, found := o.Packages[name] if !found { return nil, fmt.Errorf("Vulnerable package: %s is not found", name) } @@ -143,9 +143,9 @@ func (o *bsd) scanUnsecurePackages() (vulnInfos []models.VulnInfo, err error) { } for k := range cveIDAdtMap { - packs := []models.Package{} + packs := models.Packages{} for _, r := range cveIDAdtMap[k] { - packs = append(packs, r.pack) + packs[r.pack.Name] = r.pack } disAdvs := []models.DistroAdvisory{} @@ -165,7 +165,8 @@ func (o *bsd) scanUnsecurePackages() (vulnInfos []models.VulnInfo, err error) { return } -func (o *bsd) parsePkgVersion(stdout string) (packs []models.Package) { +func (o *bsd) parsePkgVersion(stdout string) models.Packages { + packs := models.Packages{} lines := strings.Split(stdout, "\n") for _, l := range lines { fields := strings.Fields(l) @@ -180,20 +181,20 @@ func (o *bsd) parsePkgVersion(stdout string) (packs []models.Package) { switch fields[1] { case "?", "=": - packs = append(packs, models.Package{ + packs[name] = models.Package{ Name: name, Version: ver, - }) + } case "<": candidate := strings.TrimSuffix(fields[6], ")") - packs = append(packs, models.Package{ + packs[name] = models.Package{ Name: name, Version: ver, NewVersion: candidate, - }) + } } } - return + return packs } type vulnIDCveIDs struct { diff --git a/scan/freebsd_test.go b/scan/freebsd_test.go index ee2cdc81..9ad62ef2 100644 --- a/scan/freebsd_test.go +++ b/scan/freebsd_test.go @@ -12,7 +12,7 @@ import ( func TestParsePkgVersion(t *testing.T) { var tests = []struct { in string - expected []models.Package + expected models.Packages }{ { `Updating FreeBSD repository catalogue... @@ -23,22 +23,22 @@ gettext-0.18.3.1 < needs updating (remote has 0.19.7) tcl84-8.4.20_2,1 = up-to-date with remote teTeX-base-3.0_25 ? orphaned: print/teTeX-base`, - []models.Package{ - { + models.Packages{ + "bash": { Name: "bash", Version: "4.2.45", NewVersion: "4.3.42_1", }, - { + "gettext": { Name: "gettext", Version: "0.18.3.1", NewVersion: "0.19.7", }, - { + "tcl84": { Name: "tcl84", Version: "8.4.20_2,1", }, - { + "teTeX-base": { Name: "teTeX-base", Version: "3.0_25", }, diff --git a/scan/redhat.go b/scan/redhat.go index 0d49a71a..3411ac11 100644 --- a/scan/redhat.go +++ b/scan/redhat.go @@ -231,7 +231,7 @@ func (o *redhat) scanPackages() error { o.log.Errorf("Failed to scan installed packages") return err } - o.setPackages(packs) + o.setPackages(models.NewPackages(packs...)) var vinfos []models.VulnInfo if vinfos, err = o.scanVulnInfos(); err != nil { @@ -242,7 +242,7 @@ func (o *redhat) scanPackages() error { return nil } -func (o *redhat) scanInstalledPackages() (installedPackages models.Packages, err error) { +func (o *redhat) scanInstalledPackages() (installed []models.Package, err error) { cmd := "rpm -qa --queryformat '%{NAME}\t%{EPOCHNUM}\t%{VERSION}\t%{RELEASE}\n'" r := o.exec(cmd, noSudo) if r.isSuccess() { @@ -255,13 +255,13 @@ func (o *redhat) scanInstalledPackages() (installedPackages models.Packages, err if pack, err = o.parseScannedPackagesLine(line); err != nil { return } - installedPackages = append(installedPackages, pack) + installed = append(installed, pack) } } return } - return installedPackages, fmt.Errorf( + return nil, fmt.Errorf( "Scan packages failed. status: %d, stdout: %s, stderr: %s", r.ExitStatus, r.Stdout, r.Stderr) } @@ -341,22 +341,23 @@ func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (models.VulnInfos, er } for name, clog := range rpm2changelog { - for i, p := range o.Packages { - n := fmt.Sprintf("%s-%s-%s", - p.Name, p.NewVersion, p.NewRelease) + for _, p := range o.Packages { + n := fmt.Sprintf("%s-%s-%s", p.Name, p.NewVersion, p.NewRelease) if name == n { - o.Packages[i].Changelog = models.Changelog{ + p.Changelog = models.Changelog{ Contents: *clog, Method: models.ChangelogExactMatchStr, } + o.Packages[p.Name] = p break } } } var results []PackageCveIDs - for i, pack := range packages { - changelog := o.getChangelogCVELines(rpm2changelog, pack) + i := 0 + for name := range packages { + changelog := o.getChangelogCVELines(rpm2changelog, packages[name]) // Collect unique set of CVE-ID in each changelog uniqueCveIDMap := make(map[string]bool) @@ -374,7 +375,7 @@ func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (models.VulnInfos, er cveIDs = append(cveIDs, k) } p := PackageCveIDs{ - Package: pack, + Package: packages[name], CveIDs: cveIDs, } results = append(results, p) @@ -388,6 +389,7 @@ func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (models.VulnInfos, er p.Package.NewVersion, p.Package.NewRelease, p.CveIDs) + i++ } // transform datastructure @@ -415,7 +417,7 @@ func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (models.VulnInfos, er // Amazon, RHEL do not use this method, so VendorAdvisory do not set. vinfos = append(vinfos, models.VulnInfo{ CveID: k, - Packages: v, + Packages: models.NewPackages(v...), Confidence: models.ChangelogExactMatch, }) } @@ -423,7 +425,8 @@ func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (models.VulnInfos, er } // parseYumCheckUpdateLines parse yum check-update to get package name, candidate version -func (o *redhat) parseYumCheckUpdateLines(stdout string) (results models.Packages, err error) { +func (o *redhat) parseYumCheckUpdateLines(stdout string) (models.Packages, error) { + results := models.Packages{} needToParse := false lines := strings.Split(stdout, "\n") for _, line := range lines { @@ -443,20 +446,20 @@ func (o *redhat) parseYumCheckUpdateLines(stdout string) (results models.Package return results, err } - installed, found := o.Packages.FindByName(candidate.Name) + installed, found := o.Packages[candidate.Name] if !found { o.log.Warnf("Not found the package in rpm -qa. candidate: %s-%s-%s", candidate.Name, candidate.Version, candidate.Release) - results = append(results, candidate) + results[candidate.Name] = candidate continue } installed.NewVersion = candidate.NewVersion installed.NewRelease = candidate.NewRelease installed.Repository = candidate.Repository - results = append(results, installed) + results[installed.Name] = installed } } - return + return results, nil } func (o *redhat) parseYumCheckUpdateLine(line string) (models.Package, error) { @@ -686,16 +689,16 @@ func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (models.VulnInfos, // set candidate version info o.Packages.MergeNewVersion(updatable) - dict := map[string][]models.Package{} + dict := make(map[string]models.Packages) for _, advIDPackNames := range advIDPackNamesList { packages := models.Packages{} for _, packName := range advIDPackNames.PackNames { - pack, found := updatable.FindByName(packName) + pack, found := updatable[packName] if !found { return nil, fmt.Errorf( "Package not found. pack: %#v", packName) } - packages = append(packages, pack) + packages[pack.Name] = pack continue } dict[advIDPackNames.AdvisoryID] = packages @@ -729,7 +732,7 @@ func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (models.VulnInfos, vinfos[i].DistroAdvisories = advAppended packs := dict[advIDCveIDs.DistroAdvisory.AdvisoryID] - vinfos[i].Packages = append(vinfos[i].Packages, packs...) + vinfos[i].Packages = vinfos[i].Packages.Merge(packs) found = true break } diff --git a/scan/redhat_test.go b/scan/redhat_test.go index 70017135..b506b23a 100644 --- a/scan/redhat_test.go +++ b/scan/redhat_test.go @@ -440,11 +440,13 @@ Description : kernel-uek for _, tt := range tests { actual, _ := r.parseYumUpdateinfo(tt.in) for i, advisoryCveIDs := range actual { - if !reflect.DeepEqual(tt.out[i], advisoryCveIDs) { - e := pp.Sprintf("%v", tt.out[i]) - a := pp.Sprintf("%v", advisoryCveIDs) + if tt.out[i].DistroAdvisory != advisoryCveIDs.DistroAdvisory { t.Errorf("[%d] Alas is not same. \nexpected: %s\nactual: %s", - i, e, a) + i, tt.out[i].DistroAdvisory, advisoryCveIDs.DistroAdvisory) + } + if !reflect.DeepEqual(tt.out[i].CveIDs, advisoryCveIDs.CveIDs) { + t.Errorf("[%d] Alas is not same. \nexpected: %s\nactual: %s", + i, tt.out[i].CveIDs, advisoryCveIDs.CveIDs) } } } @@ -562,14 +564,14 @@ Description : The Berkeley Internet Name Domain (BIND) is an implementation of } for _, tt := range tests { actual, _ := r.parseYumUpdateinfo(tt.in) - for j, advisoryCveIDs := range actual { - sort.Strings(tt.out[j].CveIDs) + for i, advisoryCveIDs := range actual { + sort.Strings(tt.out[i].CveIDs) sort.Strings(advisoryCveIDs.CveIDs) - if !reflect.DeepEqual(tt.out[j], advisoryCveIDs) { - e := pp.Sprintf("%v", tt.out[j]) + if !reflect.DeepEqual(tt.out[i], advisoryCveIDs) { + e := pp.Sprintf("%v", tt.out[i]) a := pp.Sprintf("%v", advisoryCveIDs) t.Errorf("[%d] Alas is not same. \nexpected: %s\nactual: %s", - j, e, a) + i, e, a) } } } @@ -688,46 +690,46 @@ bind-utils.x86_64 30:9.3.6-25.P1.el5_11.8 updates pytalloc.x86_64 2.0.7-2.el6 @CentOS 6.5/6.5 ` - r.Packages = []models.Package{ - { + r.setPackages(models.NewPackages( + models.Package{ Name: "audit-libs", Version: "2.3.6", Release: "4.el6", }, - { + models.Package{ Name: "bash", Version: "4.1.1", Release: "33", }, - { + models.Package{ Name: "python-libs", Version: "2.6.0", Release: "1.1-0", }, - { + models.Package{ Name: "python-ordereddict", Version: "1.0", Release: "1", }, - { + models.Package{ Name: "bind-utils", Version: "1.0", Release: "1", }, - { + models.Package{ Name: "pytalloc", Version: "2.0.1", Release: "0", }, - } + )) var tests = []struct { in string out models.Packages }{ { stdout, - models.Packages{ - { + models.NewPackages( + models.Package{ Name: "audit-libs", Version: "2.3.6", Release: "4.el6", @@ -735,7 +737,7 @@ pytalloc.x86_64 2.0.7-2.el6 @CentOS 6.5/6.5 NewRelease: "5.el6", Repository: "base", }, - { + models.Package{ Name: "bash", Version: "4.1.1", Release: "33", @@ -743,7 +745,7 @@ pytalloc.x86_64 2.0.7-2.el6 @CentOS 6.5/6.5 NewRelease: "33.el6_7.1", Repository: "updates", }, - { + models.Package{ Name: "python-libs", Version: "2.6.0", Release: "1.1-0", @@ -751,7 +753,7 @@ pytalloc.x86_64 2.0.7-2.el6 @CentOS 6.5/6.5 NewRelease: "64.el6", Repository: "rhui-REGION-rhel-server-releases", }, - { + models.Package{ Name: "python-ordereddict", Version: "1.0", Release: "1", @@ -759,7 +761,7 @@ pytalloc.x86_64 2.0.7-2.el6 @CentOS 6.5/6.5 NewRelease: "3.el6ev", Repository: "installed", }, - { + models.Package{ Name: "bind-utils", Version: "1.0", Release: "1", @@ -767,7 +769,7 @@ pytalloc.x86_64 2.0.7-2.el6 @CentOS 6.5/6.5 NewRelease: "25.P1.el5_11.8", Repository: "updates", }, - { + models.Package{ Name: "pytalloc", Version: "2.0.1", Release: "0", @@ -775,7 +777,7 @@ pytalloc.x86_64 2.0.7-2.el6 @CentOS 6.5/6.5 NewRelease: "2.el6", Repository: "@CentOS 6.5/6.5", }, - }, + ), }, } @@ -785,11 +787,11 @@ pytalloc.x86_64 2.0.7-2.el6 @CentOS 6.5/6.5 t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in) return } - for i, ePack := range tt.out { - if !reflect.DeepEqual(ePack, packages[i]) { + for name, ePack := range tt.out { + if !reflect.DeepEqual(ePack, packages[name]) { e := pp.Sprintf("%v", ePack) - a := pp.Sprintf("%v", packages[i]) - t.Errorf("[%d] expected %s, actual %s", i, e, a) + a := pp.Sprintf("%v", packages[name]) + t.Errorf("expected %s, actual %s", e, a) } } } @@ -805,31 +807,31 @@ bind-libs.x86_64 32:9.8.2-0.37.rc1.45.amzn1 amzn-main java-1.7.0-openjdk.x86_64 1.7.0.95-2.6.4.0.65.amzn1 amzn-main if-not-architecture 100-200 amzn-main ` - r.Packages = []models.Package{ - { + r.Packages = models.NewPackages( + models.Package{ Name: "bind-libs", Version: "9.8.0", Release: "0.33.rc1.45.amzn1", }, - { + models.Package{ Name: "java-1.7.0-openjdk", Version: "1.7.0.0", Release: "2.6.4.0.0.amzn1", }, - { + models.Package{ Name: "if-not-architecture", Version: "10", Release: "20", }, - } + ) var tests = []struct { in string out models.Packages }{ { stdout, - models.Packages{ - { + models.NewPackages( + models.Package{ Name: "bind-libs", Version: "9.8.0", Release: "0.33.rc1.45.amzn1", @@ -837,7 +839,7 @@ if-not-architecture 100-200 amzn-main NewRelease: "0.37.rc1.45.amzn1", Repository: "amzn-main", }, - { + models.Package{ Name: "java-1.7.0-openjdk", Version: "1.7.0.0", Release: "2.6.4.0.0.amzn1", @@ -845,7 +847,7 @@ if-not-architecture 100-200 amzn-main NewRelease: "2.6.4.0.65.amzn1", Repository: "amzn-main", }, - { + models.Package{ Name: "if-not-architecture", Version: "10", Release: "20", @@ -853,7 +855,7 @@ if-not-architecture 100-200 amzn-main NewRelease: "200", Repository: "amzn-main", }, - }, + ), }, } @@ -863,11 +865,11 @@ if-not-architecture 100-200 amzn-main t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in) return } - for i, ePack := range tt.out { - if !reflect.DeepEqual(ePack, packages[i]) { + for name, ePack := range tt.out { + if !reflect.DeepEqual(ePack, packages[name]) { e := pp.Sprintf("%v", ePack) - a := pp.Sprintf("%v", packages[i]) - t.Errorf("[%d] expected %s, actual %s", i, e, a) + a := pp.Sprintf("%v", packages[name]) + t.Errorf("[%s] expected %s, actual %s", name, e, a) } } }