-
Notifications
You must be signed in to change notification settings - Fork 79
/
rpm.go
167 lines (155 loc) · 5.24 KB
/
rpm.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package ovalutil
import (
"context"
"regexp"
"github.com/quay/goval-parser/oval"
"github.com/rs/zerolog"
"github.com/quay/claircore"
)
var moduleCommentRegex *regexp.Regexp
func init() {
moduleCommentRegex = regexp.MustCompile(`(Module )(.*)( is enabled)`)
}
// ProtoVulnsFunc allows a caller to create prototype vulnerabilities that will be
// copied and further defined for every applicable oval.Criterion discovered.
//
// This allows the caller to use oval.Definition fields and closure syntax when
// defining how a vulnerability should be parsed
type ProtoVulnsFunc func(def oval.Definition) ([]*claircore.Vulnerability, error)
// RPMDefsToVulns iterates over the definitions in an oval root and assumes RPMInfo objects and states.
//
// Each Criterion encountered with an EVR string will be translated into a claircore.Vulnerability
func RPMDefsToVulns(ctx context.Context, root *oval.Root, protoVulns ProtoVulnsFunc) ([]*claircore.Vulnerability, error) {
log := zerolog.Ctx(ctx).With().
Str("component", "ovalutil/RPMDefsToVulns").
Logger()
ctx = log.WithContext(ctx)
vulns := make([]*claircore.Vulnerability, 0, 10000)
pkgcache := map[string]*claircore.Package{}
cris := []*oval.Criterion{}
for _, def := range root.Definitions.Definitions {
// create our prototype vulnerability
protoVulns, err := protoVulns(def)
if err != nil {
log.Debug().
Err(err).
Str("def_id", def.ID).
Msg("could not create prototype vulnerabilities")
continue
}
// recursively collect criterions for this definition
cris := cris[:0]
walkCriterion(ctx, &def.Criteria, &cris)
// unpack criterions into vulnerabilities
enabledModules := getEnabledModules(cris)
if len(enabledModules) == 0 {
// add default empty module
enabledModules = append(enabledModules, "")
}
for _, criterion := range cris {
// lookup test
_, index, err := root.Tests.Lookup(criterion.TestRef)
if err != nil {
log.Debug().Str("test_ref", criterion.TestRef).Msg("test ref lookup failure. moving to next criterion")
continue
}
test := root.Tests.RPMInfoTests[index]
if len(test.ObjectRefs) == 1 && len(test.StateRefs) == 0 {
// We always take an object reference to imply the existence of
// that object, so just skip tests with a single object reference
// and no associated state object.
continue
}
if len(test.ObjectRefs) != len(test.StateRefs) {
log.Debug().Str("test_ref", criterion.TestRef).Msg("object refs and state refs are not in pairs. moving to next criterion")
continue
}
// look at each object,state pair the test references
// and create a vuln if an evr tag is found
for i := 0; i < len(test.ObjectRefs); i++ {
objRef := test.ObjectRefs[i].ObjectRef
stateRef := test.StateRefs[i].StateRef
_, objIndex, err := root.Objects.Lookup(objRef)
if err != nil {
log.Debug().Str("object_ref", objRef).Msg("failed object lookup. moving to next object,state pair")
continue
}
_, stateIndex, err := root.States.Lookup(stateRef)
if err != nil {
log.Debug().Str("state_ref", stateRef).Msg("failed state lookup. moving to next object,state pair")
continue
}
object := root.Objects.RPMInfoObjects[objIndex]
state := root.States.RPMInfoStates[stateIndex]
// if EVR tag not present this is not a linux package
// see oval definitions for more details
if state.EVR == nil {
continue
}
for _, module := range enabledModules {
for _, protoVuln := range protoVulns {
vuln := *protoVuln
vuln.FixedInVersion = state.EVR.Body
pkgCacheKey := object.Name + module
if pkg, ok := pkgcache[pkgCacheKey]; !ok {
p := &claircore.Package{
Name: object.Name,
Module: module,
Kind: claircore.BINARY,
}
pkgcache[pkgCacheKey] = p
vuln.Package = p
} else {
vuln.Package = pkg
}
vuln.FixedInVersion = state.EVR.Body
if state.Arch != nil {
vuln.ArchOperation = mapArchOp(state.Arch.Operation)
vuln.Package.Arch = state.Arch.Body
}
vulns = append(vulns, &vuln)
}
}
}
}
}
return vulns, nil
}
func mapArchOp(op oval.Operation) claircore.ArchOp {
switch op {
case oval.OpEquals:
return claircore.OpEquals
case oval.OpNotEquals:
return claircore.OpNotEquals
case oval.OpPatternMatch:
return claircore.OpPatternMatch
default:
}
return claircore.ArchOp(0)
}
// walkCriterion recursively extracts Criterions from a root Crteria node in a depth
// first manor.
//
// a pointer to a slice header is modified in place when appending
func walkCriterion(ctx context.Context, node *oval.Criteria, cris *[]*oval.Criterion) {
// recursive to leafs
for _, criteria := range node.Criterias {
walkCriterion(ctx, &criteria, cris)
}
// search for criterions at current node
for _, criterion := range node.Criterions {
c := criterion
*cris = append(*cris, &c)
}
}
func getEnabledModules(cris []*oval.Criterion) []string {
enabledModules := []string{}
for _, criterion := range cris {
matches := moduleCommentRegex.FindStringSubmatch(criterion.Comment)
if matches != nil && len(matches) > 2 && matches[2] != "" {
moduleNameStream := matches[2]
enabledModules = append(enabledModules, moduleNameStream)
}
}
return enabledModules
}