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