2019-04-30 07:02:09 +03:00
package ubuntu
import (
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"time"
2019-08-19 11:47:18 +03:00
"github.com/aquasecurity/vuln-list-update/git"
2019-04-30 07:02:09 +03:00
"github.com/araddon/dateparse"
"golang.org/x/xerrors"
2019-08-19 11:47:18 +03:00
"github.com/aquasecurity/vuln-list-update/utils"
2019-04-30 07:02:09 +03:00
)
const (
repoURL = "https://git.launchpad.net/ubuntu-cve-tracker"
ubuntuDir = "ubuntu"
)
2020-08-17 14:51:04 +03:00
var (
statuses = [ ] string {
"released" ,
"needed" ,
"ignored" ,
"DNE" ,
"not-affected" ,
"needs-triage" ,
"deferred" ,
"pending" ,
}
)
2019-04-30 07:02:09 +03:00
type Vulnerability struct {
PublicDateAtUSN time . Time
CRD time . Time
Candidate string
PublicDate time . Time
References [ ] string
Description string
UbuntuDescription string
Notes [ ] string
Bugs [ ] string
Priority string
DiscoveredBy string
AssignedTo string
Patches map [ Package ] Statuses
UpstreamLinks map [ Package ] [ ] string
}
type Package string
type Release string
type Statuses map [ Release ] Status
type Status struct {
Status string
Note string
}
func Update ( ) error {
2019-10-08 03:43:09 +03:00
gc := git . Config { }
2019-04-30 07:02:09 +03:00
dir := filepath . Join ( utils . CacheDir ( ) , "ubuntu-cve-tracker" )
2020-12-17 18:05:35 +03:00
if _ , err := gc . CloneOrPull ( repoURL , dir , "master" ) ; err != nil {
2019-04-30 07:02:09 +03:00
return xerrors . Errorf ( "failed to clone or pull: %w" , err )
}
2020-08-17 14:51:04 +03:00
log . Println ( "Walking Ubuntu..." )
2019-04-30 07:02:09 +03:00
for _ , target := range [ ] string { "active" , "retired" } {
if err := walkDir ( filepath . Join ( dir , target ) ) ; err != nil {
return err
}
}
return nil
}
func walkDir ( root string ) error {
err := filepath . Walk ( root , func ( path string , info os . FileInfo , err error ) error {
2019-10-12 09:54:08 +03:00
if err != nil {
return xerrors . Errorf ( "file walk error: %w" , err )
}
2019-04-30 07:02:09 +03:00
if info . IsDir ( ) {
return nil
}
base := filepath . Base ( path )
if ! strings . HasPrefix ( base , "CVE-" ) {
return nil
}
f , err := os . Open ( path )
if err != nil {
return xerrors . Errorf ( "error in file open: %w" , err )
}
vuln , err := parse ( f )
if err != nil {
return xerrors . Errorf ( "error in parse: %w" , err )
}
if err = utils . SaveCVEPerYear ( ubuntuDir , vuln . Candidate , vuln ) ; err != nil {
return xerrors . Errorf ( "error in save: %w" , err )
}
return nil
} )
if err != nil {
return xerrors . Errorf ( "error in walk: %w" , err )
}
return nil
}
func parse ( r io . Reader ) ( vuln * Vulnerability , err error ) {
vuln = & Vulnerability { }
vuln . Patches = map [ Package ] Statuses { }
vuln . UpstreamLinks = map [ Package ] [ ] string { }
all , err := ioutil . ReadAll ( r )
if err != nil {
return nil , err
}
lines := strings . Split ( string ( all ) , "\n" )
for i := 0 ; i < len ( lines ) ; i ++ {
line := lines [ i ]
// Skip
if strings . HasPrefix ( line , "#" ) || line == "" {
continue
}
// Parse PublicDateAtUSN
if strings . HasPrefix ( line , "PublicDateAtUSN:" ) {
line = strings . TrimPrefix ( line , "PublicDateAtUSN:" )
line = strings . TrimSpace ( line )
vuln . PublicDateAtUSN , _ = dateparse . ParseAny ( line )
continue
}
// Parse CRD
if strings . HasPrefix ( line , "CRD:" ) {
line = strings . TrimPrefix ( line , "CRD:" )
line = strings . TrimSpace ( line )
vuln . CRD , _ = dateparse . ParseAny ( line )
continue
}
// Parse Candidate
if strings . HasPrefix ( line , "Candidate:" ) {
line = strings . TrimPrefix ( line , "Candidate:" )
vuln . Candidate = strings . TrimSpace ( line )
continue
}
// Parse PublicDate
if strings . HasPrefix ( line , "PublicDate:" ) {
line = strings . TrimPrefix ( line , "PublicDate:" )
line = strings . TrimSpace ( line )
vuln . PublicDate , _ = dateparse . ParseAny ( line )
continue
}
// Parse References
if strings . HasPrefix ( line , "References:" ) {
for strings . HasPrefix ( lines [ i + 1 ] , " " ) {
i ++
line = strings . TrimSpace ( lines [ i ] )
vuln . References = append ( vuln . References , line )
}
continue
}
// Parse Description
if strings . HasPrefix ( line , "Description:" ) {
var description [ ] string
for strings . HasPrefix ( lines [ i + 1 ] , " " ) {
i ++
line = strings . TrimSpace ( lines [ i ] )
description = append ( description , line )
}
vuln . Description = strings . Join ( description , " " )
continue
}
// Parse Ubuntu Description
if strings . HasPrefix ( line , "Ubuntu-Description:" ) {
var description [ ] string
for strings . HasPrefix ( lines [ i + 1 ] , " " ) {
i ++
line = strings . TrimSpace ( lines [ i ] )
description = append ( description , line )
}
vuln . UbuntuDescription = strings . Join ( description , " " )
continue
}
// Parse Notes
if strings . HasPrefix ( line , "Notes:" ) {
for strings . HasPrefix ( lines [ i + 1 ] , " " ) {
i ++
line = strings . TrimSpace ( lines [ i ] )
note := [ ] string { line }
for strings . HasPrefix ( lines [ i + 1 ] , " " ) {
i ++
l := strings . TrimSpace ( lines [ i ] )
note = append ( note , l )
}
vuln . Notes = append ( vuln . Notes , strings . Join ( note , " " ) )
}
continue
}
// Parse Bugs
if strings . HasPrefix ( line , "Bugs:" ) {
for strings . HasPrefix ( lines [ i + 1 ] , " " ) {
i ++
line = strings . TrimSpace ( lines [ i ] )
vuln . Bugs = append ( vuln . Bugs , line )
}
continue
}
// Parse Priority
if strings . HasPrefix ( line , "Priority:" ) {
line = strings . TrimPrefix ( line , "Priority:" )
vuln . Priority = strings . TrimSpace ( line )
continue
}
// Parse Discovered-by
if strings . HasPrefix ( line , "Discovered-by:" ) {
line = strings . TrimPrefix ( line , "Discovered-by:" )
vuln . DiscoveredBy = strings . TrimSpace ( line )
continue
}
// Parse Assigned-to
if strings . HasPrefix ( line , "Assigned-to:" ) {
line = strings . TrimPrefix ( line , "Assigned-to:" )
vuln . AssignedTo = strings . TrimSpace ( line )
continue
}
// Parse Patches
2020-08-17 14:51:04 +03:00
// e.g. trusty/esm_vnc4: needs-triage
s := strings . SplitN ( line , ":" , 2 )
if len ( s ) < 2 {
continue
}
2019-04-30 07:02:09 +03:00
2020-08-17 14:51:04 +03:00
status := strings . TrimSpace ( s [ 1 ] )
2019-04-30 07:02:09 +03:00
2020-08-17 14:51:04 +03:00
// Some advisories have status with "Patches_" prefix and it should be skipped
// e.g. Patches_qtwebkit-opensource-src: needs-triage
if isPatch ( status ) && ! strings . HasPrefix ( s [ 0 ] , "Patches_" ) {
pkgRel := strings . SplitN ( s [ 0 ] , "_" , 2 )
release := Release ( pkgRel [ 0 ] )
pkgName := Package ( strings . Trim ( pkgRel [ 1 ] , ":" ) )
2019-04-30 07:02:09 +03:00
2020-08-17 14:51:04 +03:00
fields := strings . Fields ( status )
status := Status {
Status : fields [ 0 ] ,
}
if len ( fields ) > 1 {
note := strings . Join ( fields [ 1 : ] , " " )
status . Note = strings . Trim ( note , "()" )
}
2019-04-30 07:02:09 +03:00
2020-08-17 14:51:04 +03:00
if existingStatuses , ok := vuln . Patches [ pkgName ] ; ok {
existingStatuses [ release ] = status
vuln . Patches [ pkgName ] = existingStatuses
} else {
statuses := Statuses { }
2019-04-30 07:02:09 +03:00
statuses [ release ] = status
2020-08-17 14:51:04 +03:00
vuln . Patches [ pkgName ] = statuses
}
}
// Parse UpstreamLinks
if strings . HasPrefix ( line , "Patches_" ) {
suffix := strings . TrimPrefix ( line , "Patches" )
var upstreamLinks [ ] string
j := i
for j < len ( lines ) && lines [ j + 1 ] != "" {
j ++
line = strings . TrimSpace ( lines [ j ] )
if ! strings . HasPrefix ( line , "upstream:" ) {
break
}
line = strings . TrimPrefix ( line , "upstream:" )
upstreamLinks = append ( upstreamLinks , strings . TrimSpace ( line ) )
2019-04-30 07:02:09 +03:00
}
pkg := Package ( strings . Trim ( suffix , "_: " ) )
if len ( upstreamLinks ) > 0 {
vuln . UpstreamLinks [ pkg ] = upstreamLinks
}
continue
}
}
return vuln , nil
}
2020-08-17 14:51:04 +03:00
func isPatch ( s string ) bool {
for _ , status := range statuses {
if strings . HasPrefix ( s , status ) {
return true
}
}
return false
}