github.com/quay/claircore@v1.5.28/pkg/ovalutil/dpkg.go (about) 1 package ovalutil 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "regexp" 8 "strings" 9 10 "github.com/quay/goval-parser/oval" 11 "github.com/quay/zlog" 12 "github.com/rs/zerolog" 13 14 "github.com/quay/claircore" 15 ) 16 17 // PackageExpansionFunc allows a caller to expand the inserted vulns. For example 18 // when the OVAL DB reports vulnerabilities from the source package only (Debian). Or 19 // the name field has a var_ref indicating a variable lookup is needed (Ubuntu). 20 type PackageExpansionFunc func(def oval.Definition, name *oval.DpkgName) []string 21 22 // DpkgDefsToVulns iterates over the definitions in an oval root and assumes DpkgInfo objects and states. 23 // 24 // Each Criterion encountered with an EVR string will be translated into a claircore.Vulnerability 25 func DpkgDefsToVulns(ctx context.Context, root *oval.Root, protoVulns ProtoVulnsFunc, expansionFunc PackageExpansionFunc) ([]*claircore.Vulnerability, error) { 26 ctx = zlog.ContextWithValues(ctx, "component", "ovalutil/DpkgDefsToVulns") 27 vulns := make([]*claircore.Vulnerability, 0, 10000) 28 pkgcache := map[string]*claircore.Package{} 29 cris := []*oval.Criterion{} 30 var stats struct { 31 Test, Obj, State int 32 } 33 badvers := make(map[string]string) 34 35 for _, def := range root.Definitions.Definitions { 36 // create our prototype vulnerability 37 protoVulns, err := protoVulns(def) 38 if err != nil { 39 zlog.Debug(ctx). 40 Err(err). 41 Str("def_id", def.ID). 42 Msg("could not create prototype vulnerabilities") 43 continue 44 } 45 // recursively collect criterions for this definition 46 cris := cris[:0] 47 walkCriterion(ctx, &def.Criteria, &cris) 48 // unpack criterions into vulnerabilities 49 for _, criterion := range cris { 50 test, err := TestLookup(root, criterion.TestRef, func(kind string) bool { 51 return kind == "dpkginfo_test" 52 }) 53 switch { 54 case errors.Is(err, nil): 55 case errors.Is(err, errTestSkip): 56 continue 57 default: 58 stats.Test++ 59 continue 60 } 61 62 objRefs := test.ObjectRef() 63 stateRefs := test.StateRef() 64 65 // from the dpkginfo_test specification found here: https://oval.mitre.org/language/version5.7/ovaldefinition/documentation/linux-definitions-schema.html 66 // The required object element references a dpkginfo_object and the optional state element specifies the data to check. 67 // The evaluation of the test is guided by the check attribute that is inherited from the TestType. 68 // 69 // thus we *should* only need to care about a single dpkginfo_object and optionally a state object providing the package's fixed-in version. 70 71 objRef := objRefs[0].ObjectRef 72 object, err := dpkgObjectLookup(root, objRef) 73 switch { 74 case errors.Is(err, nil): 75 case errors.Is(err, errObjectSkip): 76 // We only handle dpkginfo_objects. 77 continue 78 default: 79 if err != nil { 80 stats.Obj++ 81 continue 82 } 83 } 84 85 var state *oval.DpkgInfoState 86 if len(stateRefs) > 0 { 87 stateRef := stateRefs[0].StateRef 88 state, err = dpkgStateLookup(root, stateRef) 89 if err != nil { 90 stats.State++ 91 continue 92 } 93 // if EVR tag not present this is not a linux package 94 // see oval definitions for more details 95 if state.EVR == nil { 96 continue 97 } 98 } 99 100 for _, protoVuln := range protoVulns { 101 name := object.Name 102 var ns []string 103 ns = append(ns, expansionFunc(def, name)...) 104 for _, n := range ns { 105 vuln := *protoVuln 106 if state != nil { 107 // Ubuntu has issues with whitespace, so fix it for them. 108 v := strings.TrimSpace(state.EVR.Body) 109 if !validVersion.MatchString(v) { 110 badvers[n] = v 111 continue 112 } 113 vuln.FixedInVersion = state.EVR.Body 114 if state.Arch != nil { 115 vuln.ArchOperation = mapArchOp(state.Arch.Operation) 116 vuln.Package.Arch = state.Arch.Body 117 } 118 } 119 if pkg, ok := pkgcache[n]; !ok { 120 p := &claircore.Package{ 121 Name: n, 122 Kind: claircore.BINARY, 123 } 124 pkgcache[n] = p 125 vuln.Package = p 126 } else { 127 vuln.Package = pkg 128 } 129 vulns = append(vulns, &vuln) 130 } 131 } 132 } 133 } 134 zlog.Debug(ctx). 135 Int("test", stats.Test). 136 Int("object", stats.Obj). 137 Int("state", stats.State). 138 Msg("ref lookup failures") 139 if ev := zlog.Debug(ctx); ev.Enabled() { 140 d := zerolog.Dict() 141 for k, v := range badvers { 142 d.Str(k, v) 143 } 144 ev. 145 Dict("package-version", d). 146 Msg("bogus versions") 147 } 148 return vulns, nil 149 } 150 151 // ValidVersion is a regexp that allows all valid Debian version strings. 152 // It's more permissive than the actual algorithm; see also deb-version(5). 153 // 154 // Notably, this allows underscores in the upstream part and doesn't enforce that parts start 155 // with a numeric. 156 var validVersion = regexp.MustCompile(`\A([0-9]+:)?[-_A-Za-z0-9.+:~]+(-[A-Za-z0-9+.~]+)?\z`) 157 158 func dpkgStateLookup(root *oval.Root, ref string) (*oval.DpkgInfoState, error) { 159 kind, i, err := root.States.Lookup(ref) 160 if err != nil { 161 return nil, err 162 } 163 if kind != "dpkginfo_state" { 164 return nil, fmt.Errorf("oval: got kind %q: %w", kind, errStateSkip) 165 } 166 return &root.States.DpkgInfoStates[i], nil 167 } 168 169 func dpkgObjectLookup(root *oval.Root, ref string) (*oval.DpkgInfoObject, error) { 170 kind, i, err := root.Objects.Lookup(ref) 171 if err != nil { 172 return nil, err 173 } 174 if kind != "dpkginfo_object" { 175 return nil, fmt.Errorf("oval: got kind %q: %w", kind, errObjectSkip) 176 } 177 return &root.Objects.DpkgInfoObjects[i], nil 178 }