vuln-list-update/debian/tracker/annotation.go
Teppei Fukuda cccad02d55
fix(debian): handle severity properly (#109)
* test(debian): add a failure case

* fix(debian): handle severity
2021-09-28 09:48:35 +03:00

261 lines
7.8 KiB
Go

package tracker
import (
"fmt"
"regexp"
"strconv"
"strings"
"sync"
"github.com/aquasecurity/vuln-list-update/utils"
)
var (
// ref. https://salsa.debian.org/security-tracker-team/security-tracker/-/blob/50ca55fb66ec7592f9bc1053a11dbf0bd50ee425/lib/python/sectracker/parsers.py#L185-186
flagRegexp = `(?P<type>RESERVED|REJECTED)`
// ref. https://salsa.debian.org/security-tracker-team/security-tracker/-/blob/50ca55fb66ec7592f9bc1053a11dbf0bd50ee425/lib/python/sectracker/parsers.py#L187-188
stringRegexp = `(?P<type>NOT-FOR-US|NOTE|TODO):\s+(?P<description>\S.*)`
// e.g.
// [stretch] - apache2 2.4.25-3+deb9u10
// - libredwg <itp> (bug #595191)
// ref. https://salsa.debian.org/security-tracker-team/security-tracker/-/blob/50ca55fb66ec7592f9bc1053a11dbf0bd50ee425/lib/python/sectracker/parsers.py#L120-121
pkgVersionRegexp = `(?:\[(?P<release>[a-z]+)\]\s)?-\s(?P<package>[A-Za-z0-9:.+-]+)\s*` +
`(?:\s(?P<version>[A-Za-z0-9:.+~-]+)\s*)?(?:\s\((?P<inner>.*)\))?`
// ref. https://salsa.debian.org/security-tracker-team/security-tracker/-/blob/50ca55fb66ec7592f9bc1053a11dbf0bd50ee425/lib/python/sectracker/parsers.py#L142-143
pkgPseudoRegexp = `(?:\[(?P<release>[a-z]+)\]\s)?-\s(?P<package>[A-Za-z0-9:.+-]+)` +
`\s+<(?P<kind>[a-z-]+)>\s*(?:\s\((?P<inner>.*)\))?`
// ref. https://salsa.debian.org/security-tracker-team/security-tracker/-/blob/50ca55fb66ec7592f9bc1053a11dbf0bd50ee425/lib/python/sectracker/parsers.py#L175
xrefRegexp = `\{(?P<xref>.*)\}`
// inner annotations, like (bug #1345; low)
// ref. https://salsa.debian.org/security-tracker-team/security-tracker/-/blob/50ca55fb66ec7592f9bc1053a11dbf0bd50ee425/lib/python/sectracker/parsers.py#L85-99
severityRegexp = regexp.MustCompile(`(unimportant|low|medium|high)`)
bugNoRegexp = regexp.MustCompile(`bug #(?P<bugno>\d+)`)
// ref. https://salsa.debian.org/security-tracker-team/security-tracker/-/blob/50ca55fb66ec7592f9bc1053a11dbf0bd50ee425/lib/python/sectracker/parsers.py#L140-141
pseudoFreeText = []string{"no-dsa", "not-affected", "end-of-life", "ignored", "postponed"}
pseudoStruct = []string{"unfixed", "removed", "itp", "undetermined"}
)
type Annotation struct {
Original string `json:",omitempty"`
Line int `json:",omitempty"`
Type string `json:",omitempty"`
Release string `json:",omitempty"`
Package string `json:",omitempty"`
Kind string `json:",omitempty"`
Version string `json:",omitempty"`
Description string `json:",omitempty"`
Bugs []string `json:",omitempty"`
// inner annotations
Severity string `json:",omitempty"`
BugNo int `json:",omitempty"`
}
type annotationParser interface {
Match(line string) []string
Apply(match []string, ann *Annotation)
}
type annotationDispatcher struct {
parsers []annotationParser
}
func newAnnotationDispatcher() annotationDispatcher {
return annotationDispatcher{
parsers: []annotationParser{
newFlagAnnotation(),
newStringAnnotation(),
newPkgVersionAnnotation(),
newPkgPseudoAnnotation(),
newXrefAnnotation(),
},
}
}
func (d annotationDispatcher) parseAnnotation(line string, lineno int) *Annotation {
var once sync.Once
var ann *Annotation
for _, p := range d.parsers {
match := p.Match(line)
if len(match) != 0 {
once.Do(func() {
ann = &Annotation{
Original: strings.TrimSpace(line),
Line: lineno,
}
})
p.Apply(match, ann)
}
}
return ann
}
func newAnnotationRegexp(s string) *regexp.Regexp {
str := fmt.Sprintf("^\\s+%s\\s*$", s)
return regexp.MustCompile(str)
}
// Parser for reserved/rejected vulnerabilities
type flagAnnotation struct {
regex *regexp.Regexp
}
func newFlagAnnotation() flagAnnotation {
return flagAnnotation{regex: newAnnotationRegexp(flagRegexp)}
}
func (a flagAnnotation) Match(line string) []string {
return a.regex.FindStringSubmatch(line)
}
func (a flagAnnotation) Apply(match []string, ann *Annotation) {
ann.Type = match[a.regex.SubexpIndex("type")]
}
// Parser for unaffected vulnerabilities
// e.g. NOT-FOR-US: Nightscout Web Monitor
// ref. https://salsa.debian.org/security-tracker-team/security-tracker/-/blob/50ca55fb66ec7592f9bc1053a11dbf0bd50ee425/lib/python/sectracker/parsers.py#L187-188
type stringAnnotation struct {
regex *regexp.Regexp
}
func newStringAnnotation() stringAnnotation {
return stringAnnotation{regex: newAnnotationRegexp(stringRegexp)}
}
func (a stringAnnotation) Match(line string) []string {
return a.regex.FindStringSubmatch(line)
}
func (a stringAnnotation) Apply(match []string, ann *Annotation) {
ann.Type = match[a.regex.SubexpIndex("type")]
ann.Description = match[a.regex.SubexpIndex("description")]
}
// Parser for fixed vulnerabilities
// e.g. [jessie] - suricata 2.0.7-2+deb8u4
// ref. https://salsa.debian.org/security-tracker-team/security-tracker/-/blob/50ca55fb66ec7592f9bc1053a11dbf0bd50ee425/lib/python/sectracker/parsers.py#L120-138
type pkgVersionAnnotation struct {
regex *regexp.Regexp
}
func newPkgVersionAnnotation() pkgVersionAnnotation {
return pkgVersionAnnotation{regex: newAnnotationRegexp(pkgVersionRegexp)}
}
func (a pkgVersionAnnotation) Match(line string) []string {
return a.regex.FindStringSubmatch(line)
}
func (a pkgVersionAnnotation) Apply(match []string, ann *Annotation) {
release := match[a.regex.SubexpIndex("release")]
pkg := match[a.regex.SubexpIndex("package")]
version := match[a.regex.SubexpIndex("version")]
inner := match[a.regex.SubexpIndex("inner")]
severity, bugno := parseInner(inner)
kind := "fixed"
if version == "" {
kind = "unfixed"
}
ann.Type = "package"
ann.Release = release
ann.Package = pkg
ann.Kind = kind
ann.Version = version
ann.Severity = severity
ann.BugNo = bugno
}
func parseInner(inner string) (string, int) {
if inner == "" {
return "", 0
}
var severity string
var bugno int
// e.g. (bug #1345; low)
for _, ann := range strings.Split(inner, ";") {
// Parse severity
s := severityRegexp.FindString(ann)
if s != "" {
severity = s
continue
}
// Parse bug number
match := bugNoRegexp.FindStringSubmatch(ann)
if len(match) > 0 {
str := match[bugNoRegexp.SubexpIndex("bugno")]
bugno, _ = strconv.Atoi(str)
}
}
return severity, bugno
}
// Parser for unfixed vulnerabilities
// e.g. [bullseye] - putty <no-dsa> (Minor issue)
// ref. https://salsa.debian.org/security-tracker-team/security-tracker/-/blob/50ca55fb66ec7592f9bc1053a11dbf0bd50ee425/lib/python/sectracker/parsers.py#L144
type pkgPseudoAnnotation struct {
regex *regexp.Regexp
}
func newPkgPseudoAnnotation() pkgPseudoAnnotation {
return pkgPseudoAnnotation{regex: newAnnotationRegexp(pkgPseudoRegexp)}
}
func (a pkgPseudoAnnotation) Match(line string) []string {
return a.regex.FindStringSubmatch(line)
}
func (a pkgPseudoAnnotation) Apply(match []string, ann *Annotation) {
release := match[a.regex.SubexpIndex("release")]
pkg := match[a.regex.SubexpIndex("package")]
kind := match[a.regex.SubexpIndex("kind")]
inner := match[a.regex.SubexpIndex("inner")]
ann.Type = "package"
ann.Release = release
ann.Package = pkg
ann.Kind = kind
if utils.StringInSlice(kind, pseudoFreeText) {
ann.Description = inner
} else if utils.StringInSlice(kind, pseudoStruct) {
severity, bugno := parseInner(inner)
ann.Severity = severity
ann.BugNo = bugno
}
}
// Parser for cross-reference
// e.g. {CVE-2021-29970 CVE-2021-29976 CVE-2021-30547}
// ref. https://salsa.debian.org/security-tracker-team/security-tracker/-/blob/50ca55fb66ec7592f9bc1053a11dbf0bd50ee425/lib/python/sectracker/parsers.py#L175-182
type xrefAnnotation struct {
regex *regexp.Regexp
}
func newXrefAnnotation() xrefAnnotation {
return xrefAnnotation{regex: newAnnotationRegexp(xrefRegexp)}
}
func (a xrefAnnotation) Match(line string) []string {
return a.regex.FindStringSubmatch(line)
}
func (a xrefAnnotation) Apply(match []string, ann *Annotation) {
xref := match[a.regex.SubexpIndex("xref")]
ann.Type = "xref"
ann.Bugs = strings.Fields(xref)
}