1// +build !scanner
2
3package oval
4
5import (
6	"fmt"
7	"strconv"
8	"strings"
9
10	"github.com/future-architect/vuls/config"
11	"github.com/future-architect/vuls/models"
12	"github.com/future-architect/vuls/util"
13	"github.com/kotakanbe/goval-dictionary/db"
14	ovalmodels "github.com/kotakanbe/goval-dictionary/models"
15)
16
17// RedHatBase is the base struct for RedHat and CentOS
18type RedHatBase struct {
19	Base
20}
21
22// FillWithOval returns scan result after updating CVE info by OVAL
23func (o RedHatBase) FillWithOval(driver db.DB, r *models.ScanResult) (nCVEs int, err error) {
24	var relatedDefs ovalResult
25	if config.Conf.OvalDict.IsFetchViaHTTP() {
26		if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
27			return 0, err
28		}
29	} else {
30		if relatedDefs, err = getDefsByPackNameFromOvalDB(driver, r); err != nil {
31			return 0, err
32		}
33	}
34
35	for _, defPacks := range relatedDefs.entries {
36		nCVEs += o.update(r, defPacks)
37	}
38
39	for _, vuln := range r.ScannedCves {
40		switch models.NewCveContentType(o.family) {
41		case models.RedHat:
42			if cont, ok := vuln.CveContents[models.RedHat]; ok {
43				cont.SourceLink = "https://access.redhat.com/security/cve/" + cont.CveID
44				vuln.CveContents[models.RedHat] = cont
45			}
46		case models.Oracle:
47			if cont, ok := vuln.CveContents[models.Oracle]; ok {
48				cont.SourceLink = fmt.Sprintf("https://linux.oracle.com/cve/%s.html", cont.CveID)
49				vuln.CveContents[models.Oracle] = cont
50			}
51		}
52	}
53
54	return nCVEs, nil
55}
56
57var kernelRelatedPackNames = map[string]bool{
58	"kernel":                  true,
59	"kernel-aarch64":          true,
60	"kernel-abi-whitelists":   true,
61	"kernel-bootwrapper":      true,
62	"kernel-debug":            true,
63	"kernel-debug-devel":      true,
64	"kernel-devel":            true,
65	"kernel-doc":              true,
66	"kernel-headers":          true,
67	"kernel-kdump":            true,
68	"kernel-kdump-devel":      true,
69	"kernel-rt":               true,
70	"kernel-rt-debug":         true,
71	"kernel-rt-debug-devel":   true,
72	"kernel-rt-debug-kvm":     true,
73	"kernel-rt-devel":         true,
74	"kernel-rt-doc":           true,
75	"kernel-rt-kvm":           true,
76	"kernel-rt-trace":         true,
77	"kernel-rt-trace-devel":   true,
78	"kernel-rt-trace-kvm":     true,
79	"kernel-rt-virt":          true,
80	"kernel-rt-virt-devel":    true,
81	"kernel-tools":            true,
82	"kernel-tools-libs":       true,
83	"kernel-tools-libs-devel": true,
84	"perf":                    true,
85	"python-perf":             true,
86}
87
88func (o RedHatBase) update(r *models.ScanResult, defPacks defPacks) (nCVEs int) {
89	ctype := models.NewCveContentType(o.family)
90	for _, cve := range defPacks.def.Advisory.Cves {
91		ovalContent := *o.convertToModel(cve.CveID, &defPacks.def)
92		vinfo, ok := r.ScannedCves[cve.CveID]
93		if !ok {
94			util.Log.Debugf("%s is newly detected by OVAL", cve.CveID)
95			vinfo = models.VulnInfo{
96				CveID:       cve.CveID,
97				Confidences: models.Confidences{models.OvalMatch},
98				CveContents: models.NewCveContents(ovalContent),
99			}
100			nCVEs++
101		} else {
102			cveContents := vinfo.CveContents
103			if v, ok := vinfo.CveContents[ctype]; ok {
104				if v.LastModified.After(ovalContent.LastModified) {
105					util.Log.Debugf("%s, OvalID: %d ignored: ",
106						cve.CveID, defPacks.def.ID)
107				} else {
108					util.Log.Debugf("%s OVAL will be overwritten", cve.CveID)
109				}
110			} else {
111				util.Log.Debugf("%s also detected by OVAL", cve.CveID)
112				cveContents = models.CveContents{}
113			}
114
115			vinfo.Confidences.AppendIfMissing(models.OvalMatch)
116			cveContents[ctype] = ovalContent
117			vinfo.CveContents = cveContents
118		}
119
120		vinfo.DistroAdvisories.AppendIfMissing(
121			o.convertToDistroAdvisory(&defPacks.def))
122
123		// uniq(vinfo.PackNames + defPacks.actuallyAffectedPackNames)
124		for _, pack := range vinfo.AffectedPackages {
125			if stat, ok := defPacks.binpkgFixstat[pack.Name]; !ok {
126				defPacks.binpkgFixstat[pack.Name] = fixStat{
127					notFixedYet: pack.NotFixedYet,
128					fixedIn:     pack.FixedIn,
129				}
130			} else if stat.notFixedYet {
131				defPacks.binpkgFixstat[pack.Name] = fixStat{
132					notFixedYet: true,
133					fixedIn:     pack.FixedIn,
134				}
135			}
136		}
137		vinfo.AffectedPackages = defPacks.toPackStatuses()
138		vinfo.AffectedPackages.Sort()
139		r.ScannedCves[cve.CveID] = vinfo
140	}
141	return
142}
143
144func (o RedHatBase) convertToDistroAdvisory(def *ovalmodels.Definition) *models.DistroAdvisory {
145	advisoryID := def.Title
146	if (o.family == config.RedHat || o.family == config.CentOS) && len(advisoryID) > 0 {
147		ss := strings.Fields(def.Title)
148		advisoryID = strings.TrimSuffix(ss[0], ":")
149	}
150	return &models.DistroAdvisory{
151		AdvisoryID:  advisoryID,
152		Severity:    def.Advisory.Severity,
153		Issued:      def.Advisory.Issued,
154		Updated:     def.Advisory.Updated,
155		Description: def.Description,
156	}
157}
158
159func (o RedHatBase) convertToModel(cveID string, def *ovalmodels.Definition) *models.CveContent {
160	for _, cve := range def.Advisory.Cves {
161		if cve.CveID != cveID {
162			continue
163		}
164		var refs []models.Reference
165		for _, r := range def.References {
166			refs = append(refs, models.Reference{
167				Link:   r.RefURL,
168				Source: r.Source,
169				RefID:  r.RefID,
170			})
171		}
172
173		score2, vec2 := o.parseCvss2(cve.Cvss2)
174		score3, vec3 := o.parseCvss3(cve.Cvss3)
175
176		severity := def.Advisory.Severity
177		if cve.Impact != "" {
178			severity = cve.Impact
179		}
180
181		sev2, sev3 := "", ""
182		if score2 == 0 {
183			sev2 = severity
184		}
185		if score3 == 0 {
186			sev3 = severity
187		}
188
189		// CWE-ID in RedHat OVAL may have multiple cweIDs separated by space
190		cwes := strings.Fields(cve.Cwe)
191
192		return &models.CveContent{
193			Type:          models.NewCveContentType(o.family),
194			CveID:         cve.CveID,
195			Title:         def.Title,
196			Summary:       def.Description,
197			Cvss2Score:    score2,
198			Cvss2Vector:   vec2,
199			Cvss2Severity: sev2,
200			Cvss3Score:    score3,
201			Cvss3Vector:   vec3,
202			Cvss3Severity: sev3,
203			References:    refs,
204			CweIDs:        cwes,
205			Published:     def.Advisory.Issued,
206			LastModified:  def.Advisory.Updated,
207		}
208	}
209	return nil
210}
211
212// ParseCvss2 divide CVSSv2 string into score and vector
213// 5/AV:N/AC:L/Au:N/C:N/I:N/A:P
214func (o RedHatBase) parseCvss2(scoreVector string) (score float64, vector string) {
215	var err error
216	ss := strings.Split(scoreVector, "/")
217	if 1 < len(ss) {
218		if score, err = strconv.ParseFloat(ss[0], 64); err != nil {
219			return 0, ""
220		}
221		return score, strings.Join(ss[1:], "/")
222	}
223	return 0, ""
224}
225
226// ParseCvss3 divide CVSSv3 string into score and vector
227// 5.6/CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L
228func (o RedHatBase) parseCvss3(scoreVector string) (score float64, vector string) {
229	var err error
230	for _, s := range []string{
231		"/CVSS:3.0/",
232		"/CVSS:3.1/",
233	} {
234		ss := strings.Split(scoreVector, s)
235		if 1 < len(ss) {
236			if score, err = strconv.ParseFloat(ss[0], 64); err != nil {
237				return 0, ""
238			}
239			return score, strings.TrimPrefix(s, "/") + ss[1]
240		}
241	}
242	return 0, ""
243}
244
245// RedHat is the interface for RedhatBase OVAL
246type RedHat struct {
247	RedHatBase
248}
249
250// NewRedhat creates OVAL client for Redhat
251func NewRedhat() RedHat {
252	return RedHat{
253		RedHatBase{
254			Base{
255				family: config.RedHat,
256			},
257		},
258	}
259}
260
261// CentOS is the interface for CentOS OVAL
262type CentOS struct {
263	RedHatBase
264}
265
266// NewCentOS creates OVAL client for CentOS
267func NewCentOS() CentOS {
268	return CentOS{
269		RedHatBase{
270			Base{
271				family: config.CentOS,
272			},
273		},
274	}
275}
276
277// Oracle is the interface for Oracle OVAL
278type Oracle struct {
279	RedHatBase
280}
281
282// NewOracle creates OVAL client for Oracle
283func NewOracle() Oracle {
284	return Oracle{
285		RedHatBase{
286			Base{
287				family: config.Oracle,
288			},
289		},
290	}
291}
292
293// Amazon is the interface for RedhatBase OVAL
294type Amazon struct {
295	// Base
296	RedHatBase
297}
298
299// NewAmazon creates OVAL client for Amazon Linux
300func NewAmazon() Amazon {
301	return Amazon{
302		RedHatBase{
303			Base{
304				family: config.Amazon,
305			},
306		},
307	}
308}
309