-
Notifications
You must be signed in to change notification settings - Fork 79
/
dpkg.go
178 lines (164 loc) · 5.22 KB
/
dpkg.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
168
169
170
171
172
173
174
175
176
177
178
package ovalutil
import (
"context"
"errors"
"fmt"
"regexp"
"strings"
"github.com/quay/goval-parser/oval"
"github.com/quay/zlog"
"github.com/rs/zerolog"
"github.com/quay/claircore"
)
// PackageExpansionFunc allows a caller to expand the inserted vulns. For example
// when the OVAL DB reports vulnerabilities from the source package only (Debian). Or
// the name field has a var_ref indicating a variable lookup is needed (Ubuntu).
type PackageExpansionFunc func(def oval.Definition, name *oval.DpkgName) []string
// DpkgDefsToVulns iterates over the definitions in an oval root and assumes DpkgInfo objects and states.
//
// Each Criterion encountered with an EVR string will be translated into a claircore.Vulnerability
func DpkgDefsToVulns(ctx context.Context, root *oval.Root, protoVulns ProtoVulnsFunc, expansionFunc PackageExpansionFunc) ([]*claircore.Vulnerability, error) {
ctx = zlog.ContextWithValues(ctx, "component", "ovalutil/DpkgDefsToVulns")
vulns := make([]*claircore.Vulnerability, 0, 10000)
pkgcache := map[string]*claircore.Package{}
cris := []*oval.Criterion{}
var stats struct {
Test, Obj, State int
}
badvers := make(map[string]string)
for _, def := range root.Definitions.Definitions {
// create our prototype vulnerability
protoVulns, err := protoVulns(def)
if err != nil {
zlog.Debug(ctx).
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
for _, criterion := range cris {
test, err := TestLookup(root, criterion.TestRef, func(kind string) bool {
return kind == "dpkginfo_test"
})
switch {
case errors.Is(err, nil):
case errors.Is(err, errTestSkip):
continue
default:
stats.Test++
continue
}
objRefs := test.ObjectRef()
stateRefs := test.StateRef()
// from the dpkginfo_test specification found here: https://oval.mitre.org/language/version5.7/ovaldefinition/documentation/linux-definitions-schema.html
// The required object element references a dpkginfo_object and the optional state element specifies the data to check.
// The evaluation of the test is guided by the check attribute that is inherited from the TestType.
//
// thus we *should* only need to care about a single dpkginfo_object and optionally a state object providing the package's fixed-in version.
objRef := objRefs[0].ObjectRef
object, err := dpkgObjectLookup(root, objRef)
switch {
case errors.Is(err, nil):
case errors.Is(err, errObjectSkip):
// We only handle dpkginfo_objects.
continue
default:
if err != nil {
stats.Obj++
continue
}
}
var state *oval.DpkgInfoState
if len(stateRefs) > 0 {
stateRef := stateRefs[0].StateRef
state, err = dpkgStateLookup(root, stateRef)
if err != nil {
stats.State++
continue
}
// if EVR tag not present this is not a linux package
// see oval definitions for more details
if state.EVR == nil {
continue
}
}
for _, protoVuln := range protoVulns {
name := object.Name
var ns []string
ns = append(ns, expansionFunc(def, name)...)
for _, n := range ns {
vuln := *protoVuln
if state != nil {
// Ubuntu has issues with whitespace, so fix it for them.
v := strings.TrimSpace(state.EVR.Body)
if !validVersion.MatchString(v) {
badvers[n] = v
continue
}
vuln.FixedInVersion = state.EVR.Body
if state.Arch != nil {
vuln.ArchOperation = mapArchOp(state.Arch.Operation)
vuln.Package.Arch = state.Arch.Body
}
}
if pkg, ok := pkgcache[n]; !ok {
p := &claircore.Package{
Name: n,
Kind: claircore.BINARY,
}
pkgcache[n] = p
vuln.Package = p
} else {
vuln.Package = pkg
}
vulns = append(vulns, &vuln)
}
}
}
}
zlog.Debug(ctx).
Int("test", stats.Test).
Int("object", stats.Obj).
Int("state", stats.State).
Msg("ref lookup failures")
if ev := zlog.Debug(ctx); ev.Enabled() {
d := zerolog.Dict()
for k, v := range badvers {
d.Str(k, v)
}
ev.
Dict("package-version", d).
Msg("bogus versions")
}
return vulns, nil
}
// ValidVersion is a regexp that allows all valid Debian version strings.
// It's more permissive than the actual algorithm; see also deb-version(5).
//
// Notably, this allows underscores in the upstream part and doesn't enforce that parts start
// with a numeric.
var validVersion = regexp.MustCompile(`\A([0-9]+:)?[-_A-Za-z0-9.+:~]+(-[A-Za-z0-9+.~]+)?\z`)
func dpkgStateLookup(root *oval.Root, ref string) (*oval.DpkgInfoState, error) {
kind, i, err := root.States.Lookup(ref)
if err != nil {
return nil, err
}
if kind != "dpkginfo_state" {
return nil, fmt.Errorf("oval: got kind %q: %w", kind, errStateSkip)
}
return &root.States.DpkgInfoStates[i], nil
}
func dpkgObjectLookup(root *oval.Root, ref string) (*oval.DpkgInfoObject, error) {
kind, i, err := root.Objects.Lookup(ref)
if err != nil {
return nil, err
}
if kind != "dpkginfo_object" {
return nil, fmt.Errorf("oval: got kind %q: %w", kind, errObjectSkip)
}
return &root.Objects.DpkgInfoObjects[i], nil
}