diff --git a/oval/debian.go b/oval/debian.go
index cd07d0ac..5137467b 100644
--- a/oval/debian.go
+++ b/oval/debian.go
@@ -14,9 +14,12 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
+
package oval
import (
+ "sort"
+
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
@@ -30,19 +33,19 @@ type DebianBase struct {
// FillWithOval returns scan result after updating CVE info by OVAL
func (o DebianBase) FillWithOval(r *models.ScanResult) (err error) {
- var defs []ovalmodels.Definition
+ var relatedDefs ovalResult
if o.isFetchViaHTTP() {
- if defs, err = getDefsByPackNameViaHTTP(r); err != nil {
+ if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
return err
}
} else {
- if defs, err = getDefsByPackNameFromOvalDB(o.family, r.Release, r.Packages); err != nil {
+ if relatedDefs, err = getDefsByPackNameFromOvalDB(o.family, r.Release, r.Packages); err != nil {
return err
}
}
- for _, def := range defs {
- o.update(r, &def)
+ for _, defPacks := range relatedDefs.entries {
+ o.update(r, defPacks)
}
for _, vuln := range r.ScannedCves {
@@ -62,25 +65,26 @@ func (o DebianBase) FillWithOval(r *models.ScanResult) (err error) {
return nil
}
-func (o DebianBase) update(r *models.ScanResult, definition *ovalmodels.Definition) {
- ovalContent := *o.convertToModel(definition)
+func (o DebianBase) update(r *models.ScanResult, defPacks defPacks) {
+ ovalContent := *o.convertToModel(&defPacks.def)
ovalContent.Type = models.NewCveContentType(o.family)
- vinfo, ok := r.ScannedCves[definition.Debian.CveID]
+ vinfo, ok := r.ScannedCves[defPacks.def.Debian.CveID]
if !ok {
- util.Log.Debugf("%s is newly detected by OVAL", definition.Debian.CveID)
+ util.Log.Debugf("%s is newly detected by OVAL", defPacks.def.Debian.CveID)
vinfo = models.VulnInfo{
- CveID: definition.Debian.CveID,
- Confidence: models.OvalMatch,
- PackageNames: getPackages(r, definition),
- CveContents: models.NewCveContents(ovalContent),
+ CveID: defPacks.def.Debian.CveID,
+ Confidence: models.OvalMatch,
+ CveContents: models.NewCveContents(ovalContent),
}
} else {
cveContents := vinfo.CveContents
ctype := models.NewCveContentType(o.family)
if _, ok := vinfo.CveContents[ctype]; ok {
- util.Log.Debugf("%s will be updated by OVAL", definition.Debian.CveID)
+ util.Log.Debugf("%s OVAL will be overwritten",
+ defPacks.def.Debian.CveID)
} else {
- util.Log.Debugf("%s is also detected by OVAL", definition.Debian.CveID)
+ util.Log.Debugf("%s is also detected by OVAL",
+ defPacks.def.Debian.CveID)
cveContents = models.CveContents{}
}
if vinfo.Confidence.Score < models.OvalMatch.Score {
@@ -89,7 +93,14 @@ func (o DebianBase) update(r *models.ScanResult, definition *ovalmodels.Definiti
cveContents[ctype] = ovalContent
vinfo.CveContents = cveContents
}
- r.ScannedCves[definition.Debian.CveID] = vinfo
+
+ // uniq(vinfo.PackNames + defPacks.actuallyAffectedPackNames)
+ for _, name := range vinfo.PackageNames {
+ defPacks.actuallyAffectedPackNames[name] = true
+ }
+ vinfo.PackageNames = defPacks.packNames()
+ sort.Strings(vinfo.PackageNames)
+ r.ScannedCves[defPacks.def.Debian.CveID] = vinfo
}
func (o DebianBase) convertToModel(def *ovalmodels.Definition) *models.CveContent {
diff --git a/oval/debian_test.go b/oval/debian_test.go
new file mode 100644
index 00000000..d3630acc
--- /dev/null
+++ b/oval/debian_test.go
@@ -0,0 +1,75 @@
+/* Vuls - Vulnerability Scanner
+Copyright (C) 2016 Future Architect, Inc. Japan.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+package oval
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/future-architect/vuls/config"
+ "github.com/future-architect/vuls/models"
+ "github.com/future-architect/vuls/util"
+ ovalmodels "github.com/kotakanbe/goval-dictionary/models"
+)
+
+func TestPackNamesOfUpdateDebian(t *testing.T) {
+ var tests = []struct {
+ in models.ScanResult
+ defPacks defPacks
+ out models.ScanResult
+ }{
+ {
+ in: models.ScanResult{
+ ScannedCves: models.VulnInfos{
+ "CVE-2000-1000": models.VulnInfo{
+ PackageNames: []string{"packA"},
+ },
+ },
+ },
+ defPacks: defPacks{
+ def: ovalmodels.Definition{
+ Debian: ovalmodels.Debian{
+ CveID: "CVE-2000-1000",
+ },
+ },
+ actuallyAffectedPackNames: map[string]bool{
+ "packB": true,
+ },
+ },
+ out: models.ScanResult{
+ ScannedCves: models.VulnInfos{
+ "CVE-2000-1000": models.VulnInfo{
+ PackageNames: []string{
+ "packA",
+ "packB",
+ },
+ },
+ },
+ },
+ },
+ }
+
+ util.Log = util.NewCustomLogger(config.ServerInfo{})
+ for i, tt := range tests {
+ Debian{}.update(&tt.in, tt.defPacks)
+ e := tt.out.ScannedCves["CVE-2000-1000"].PackageNames
+ a := tt.in.ScannedCves["CVE-2000-1000"].PackageNames
+ if !reflect.DeepEqual(a, e) {
+ t.Errorf("[%d] expected: %v\n actual: %v\n", i, e, a)
+ }
+ }
+}
diff --git a/oval/redhat.go b/oval/redhat.go
index 46e8153c..6927db39 100644
--- a/oval/redhat.go
+++ b/oval/redhat.go
@@ -19,6 +19,7 @@ package oval
import (
"fmt"
+ "sort"
"strconv"
"strings"
@@ -35,23 +36,22 @@ type RedHatBase struct {
// FillWithOval returns scan result after updating CVE info by OVAL
func (o RedHatBase) FillWithOval(r *models.ScanResult) (err error) {
- var defs []ovalmodels.Definition
+ var relatedDefs ovalResult
if o.isFetchViaHTTP() {
- if defs, err = getDefsByPackNameViaHTTP(r); err != nil {
+ if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
return err
}
} else {
- if defs, err = getDefsByPackNameFromOvalDB(
+ if relatedDefs, err = getDefsByPackNameFromOvalDB(
o.family, r.Release, r.Packages); err != nil {
return err
}
}
- for _, def := range defs {
- o.update(r, &def)
+ for _, defPacks := range relatedDefs.entries {
+ o.update(r, defPacks)
}
- // TODO merge to VulnInfo.VendorLinks
for _, vuln := range r.ScannedCves {
switch models.NewCveContentType(o.family) {
case models.RedHat:
@@ -69,23 +69,22 @@ func (o RedHatBase) FillWithOval(r *models.ScanResult) (err error) {
return nil
}
-func (o RedHatBase) update(r *models.ScanResult, definition *ovalmodels.Definition) {
+func (o RedHatBase) update(r *models.ScanResult, defPacks defPacks) {
ctype := models.NewCveContentType(o.family)
- for _, cve := range definition.Advisory.Cves {
- ovalContent := *o.convertToModel(cve.CveID, definition)
+ for _, cve := range defPacks.def.Advisory.Cves {
+ ovalContent := *o.convertToModel(cve.CveID, &defPacks.def)
vinfo, ok := r.ScannedCves[cve.CveID]
if !ok {
util.Log.Debugf("%s is newly detected by OVAL", cve.CveID)
vinfo = models.VulnInfo{
- CveID: cve.CveID,
- Confidence: models.OvalMatch,
- PackageNames: getPackages(r, definition),
- CveContents: models.NewCveContents(ovalContent),
+ CveID: cve.CveID,
+ Confidence: models.OvalMatch,
+ CveContents: models.NewCveContents(ovalContent),
}
} else {
cveContents := vinfo.CveContents
if _, ok := vinfo.CveContents[ctype]; ok {
- util.Log.Debugf("%s will be updated by OVAL", cve.CveID)
+ util.Log.Debugf("%s OVAL will be overwritten", cve.CveID)
} else {
util.Log.Debugf("%s also detected by OVAL", cve.CveID)
cveContents = models.CveContents{}
@@ -97,6 +96,13 @@ func (o RedHatBase) update(r *models.ScanResult, definition *ovalmodels.Definiti
cveContents[ctype] = ovalContent
vinfo.CveContents = cveContents
}
+
+ // uniq(vinfo.PackNames + defPacks.actuallyAffectedPackNames)
+ for _, name := range vinfo.PackageNames {
+ defPacks.actuallyAffectedPackNames[name] = true
+ }
+ vinfo.PackageNames = defPacks.packNames()
+ sort.Strings(vinfo.PackageNames)
r.ScannedCves[cve.CveID] = vinfo
}
}
diff --git a/oval/redhat_test.go b/oval/redhat_test.go
index 79b90eab..a69bf838 100644
--- a/oval/redhat_test.go
+++ b/oval/redhat_test.go
@@ -16,7 +16,15 @@ along with this program. If not, see .
*/
package oval
-import "testing"
+import (
+ "reflect"
+ "testing"
+
+ "github.com/future-architect/vuls/config"
+ "github.com/future-architect/vuls/models"
+ "github.com/future-architect/vuls/util"
+ ovalmodels "github.com/kotakanbe/goval-dictionary/models"
+)
func TestParseCvss2(t *testing.T) {
type out struct {
@@ -83,3 +91,55 @@ func TestParseCvss3(t *testing.T) {
}
}
}
+
+func TestPackNamesOfUpdate(t *testing.T) {
+ var tests = []struct {
+ in models.ScanResult
+ defPacks defPacks
+ out models.ScanResult
+ }{
+ {
+ in: models.ScanResult{
+ ScannedCves: models.VulnInfos{
+ "CVE-2000-1000": models.VulnInfo{
+ PackageNames: []string{"packA"},
+ },
+ },
+ },
+ defPacks: defPacks{
+ def: ovalmodels.Definition{
+ Advisory: ovalmodels.Advisory{
+ Cves: []ovalmodels.Cve{
+ {
+ CveID: "CVE-2000-1000",
+ },
+ },
+ },
+ },
+ actuallyAffectedPackNames: map[string]bool{
+ "packB": true,
+ },
+ },
+ out: models.ScanResult{
+ ScannedCves: models.VulnInfos{
+ "CVE-2000-1000": models.VulnInfo{
+ PackageNames: []string{
+ "packA",
+ "packB",
+ },
+ },
+ },
+ },
+ },
+ }
+
+ util.Log = util.NewCustomLogger(config.ServerInfo{})
+ for i, tt := range tests {
+ RedHat{}.update(&tt.in, tt.defPacks)
+ e := tt.out.ScannedCves["CVE-2000-1000"].PackageNames
+ a := tt.in.ScannedCves["CVE-2000-1000"].PackageNames
+ if !reflect.DeepEqual(a, e) {
+ t.Errorf("[%d] expected: %v\n actual: %v\n", i, e, a)
+ }
+ }
+}
diff --git a/oval/util.go b/oval/util.go
index c84cade1..ffdce94e 100644
--- a/oval/util.go
+++ b/oval/util.go
@@ -35,6 +35,36 @@ import (
"github.com/parnurzeal/gorequest"
)
+type ovalResult struct {
+ entries []defPacks
+}
+
+type defPacks struct {
+ def ovalmodels.Definition
+ actuallyAffectedPackNames map[string]bool
+}
+
+func (e defPacks) packNames() (names []string) {
+ for k := range e.actuallyAffectedPackNames {
+ names = append(names, k)
+ }
+ return
+}
+
+func (e *ovalResult) upsert(def ovalmodels.Definition, packName string) (upserted bool) {
+ for i, entry := range e.entries {
+ if entry.def.DefinitionID == def.DefinitionID {
+ e.entries[i].actuallyAffectedPackNames[packName] = true
+ return true
+ }
+ }
+ e.entries = append(e.entries, defPacks{
+ def: def,
+ actuallyAffectedPackNames: map[string]bool{packName: true},
+ })
+ return false
+}
+
type request struct {
pack models.Package
}
@@ -46,7 +76,7 @@ type response struct {
// getDefsByPackNameViaHTTP fetches OVAL information via HTTP
func getDefsByPackNameViaHTTP(r *models.ScanResult) (
- relatedDefs []ovalmodels.Definition, err error) {
+ relatedDefs ovalResult, err error) {
reqChan := make(chan request, len(r.Packages))
resChan := make(chan response, len(r.Packages))
@@ -102,18 +132,18 @@ func getDefsByPackNameViaHTTP(r *models.ScanResult) (
util.Log.Debugf("%#v\n%#v", *res.pack, p)
}
} else if less {
- relatedDefs = append(relatedDefs, def)
+ relatedDefs.upsert(def, p.Name)
}
}
}
case err := <-errChan:
errs = append(errs, err)
case <-timeout:
- return nil, fmt.Errorf("Timeout Fetching OVAL")
+ return relatedDefs, fmt.Errorf("Timeout Fetching OVAL")
}
}
if len(errs) != 0 {
- return nil, fmt.Errorf("Failed to fetch OVAL. err: %v", errs)
+ return relatedDefs, fmt.Errorf("Failed to fetch OVAL. err: %v", errs)
}
return
}
@@ -161,15 +191,8 @@ func httpGet(url string, pack *models.Package, resChan chan<- response, errChan
}
}
-func getPackages(r *models.ScanResult, d *ovalmodels.Definition) (names []string) {
- for _, affectedPack := range d.AffectedPacks {
- names = append(names, affectedPack.Name)
- }
- return
-}
-
func getDefsByPackNameFromOvalDB(family, osRelease string,
- packs models.Packages) (relatedDefs []ovalmodels.Definition, err error) {
+ packs models.Packages) (relatedDefs ovalResult, err error) {
ovallog.Initialize(config.Conf.LogDir)
path := config.Conf.OvalDBURL
@@ -191,7 +214,7 @@ func getDefsByPackNameFromOvalDB(family, osRelease string,
for _, pack := range packs {
definitions, err := ovaldb.GetByPackName(osRelease, pack.Name)
if err != nil {
- return nil, fmt.Errorf("Failed to get %s OVAL info by package name: %v", family, err)
+ return relatedDefs, fmt.Errorf("Failed to get %s OVAL info by package name: %v", family, err)
}
for _, def := range definitions {
for _, p := range def.AffectedPacks {
@@ -204,7 +227,7 @@ func getDefsByPackNameFromOvalDB(family, osRelease string,
util.Log.Debugf("%#v\n%#v", pack, p)
}
} else if less {
- relatedDefs = append(relatedDefs, def)
+ relatedDefs.upsert(def, pack.Name)
}
}
}
diff --git a/oval/util_test.go b/oval/util_test.go
new file mode 100644
index 00000000..48ef395b
--- /dev/null
+++ b/oval/util_test.go
@@ -0,0 +1,98 @@
+package oval
+
+import (
+ "reflect"
+ "testing"
+
+ ovalmodels "github.com/kotakanbe/goval-dictionary/models"
+)
+
+func TestUpsert(t *testing.T) {
+ var tests = []struct {
+ res ovalResult
+ def ovalmodels.Definition
+ packName string
+ upserted bool
+ out ovalResult
+ }{
+ //insert
+ {
+ res: ovalResult{},
+ def: ovalmodels.Definition{
+ DefinitionID: "1111",
+ },
+ packName: "pack1",
+ upserted: false,
+ out: ovalResult{
+ []defPacks{
+ {
+ def: ovalmodels.Definition{
+ DefinitionID: "1111",
+ },
+ actuallyAffectedPackNames: map[string]bool{
+ "pack1": true,
+ },
+ },
+ },
+ },
+ },
+ //update
+ {
+ res: ovalResult{
+ []defPacks{
+ {
+ def: ovalmodels.Definition{
+ DefinitionID: "1111",
+ },
+ actuallyAffectedPackNames: map[string]bool{
+ "pack1": true,
+ },
+ },
+ {
+ def: ovalmodels.Definition{
+ DefinitionID: "2222",
+ },
+ actuallyAffectedPackNames: map[string]bool{
+ "pack3": true,
+ },
+ },
+ },
+ },
+ def: ovalmodels.Definition{
+ DefinitionID: "1111",
+ },
+ packName: "pack2",
+ upserted: true,
+ out: ovalResult{
+ []defPacks{
+ {
+ def: ovalmodels.Definition{
+ DefinitionID: "1111",
+ },
+ actuallyAffectedPackNames: map[string]bool{
+ "pack1": true,
+ "pack2": true,
+ },
+ },
+ {
+ def: ovalmodels.Definition{
+ DefinitionID: "2222",
+ },
+ actuallyAffectedPackNames: map[string]bool{
+ "pack3": true,
+ },
+ },
+ },
+ },
+ },
+ }
+ for i, tt := range tests {
+ upserted := tt.res.upsert(tt.def, tt.packName)
+ if tt.upserted != upserted {
+ t.Errorf("[%d]\nexpected: %t\n actual: %t\n", i, tt.upserted, upserted)
+ }
+ if !reflect.DeepEqual(tt.out, tt.res) {
+ t.Errorf("[%d]\nexpected: %v\n actual: %v\n", i, tt.out, tt.res)
+ }
+ }
+}
diff --git a/report/localfile.go b/report/localfile.go
index 79f192a1..13e9b98f 100644
--- a/report/localfile.go
+++ b/report/localfile.go
@@ -58,8 +58,14 @@ func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
}
var b []byte
- if b, err = json.Marshal(r); err != nil {
- return fmt.Errorf("Failed to Marshal to JSON: %s", err)
+ if c.Conf.Debug {
+ if b, err = json.MarshalIndent(r, "", " "); err != nil {
+ return fmt.Errorf("Failed to Marshal to JSON: %s", err)
+ }
+ } else {
+ if b, err = json.Marshal(r); err != nil {
+ return fmt.Errorf("Failed to Marshal to JSON: %s", err)
+ }
}
if err := writeFile(p, b, 0600); err != nil {
return fmt.Errorf("Failed to write JSON. path: %s, err: %s", p, err)
diff --git a/report/util.go b/report/util.go
index f9085377..5cbf43c5 100644
--- a/report/util.go
+++ b/report/util.go
@@ -218,7 +218,6 @@ No CVE-IDs are found in updatable packages.
packsVer := []string{}
sort.Strings(vuln.PackageNames)
for _, name := range vuln.PackageNames {
- // packages detected by OVAL may not be actually installed
if pack, ok := r.Packages[name]; ok {
packsVer = append(packsVer, pack.FormatVersionFromTo())
}