From 14fa3e2cbeb2eb9d410f2ca79779dbb659eb9ae1 Mon Sep 17 00:00:00 2001 From: Tim Lee Date: Tue, 28 May 2024 13:40:32 -0600 Subject: [PATCH] Ubuntu Kernel Vulns Part 1: Generate OVAL JSON (#19210) --- .../oval/input/ubuntu_result.go | 5 + .../oval/input/unix_uname_state.go | 7 + .../oval/input/unix_uname_test_object.go | 11 ++ .../oval/input/variable_object.go | 7 + .../oval/input/variable_state.go | 7 + .../oval/input/variable_test_object.go | 16 +++ server/vulnerabilities/oval/mappers.go | 32 +++++ .../oval/parsed/ind_variabletest.go | 9 ++ .../oval/parsed/ubuntu_result.go | 6 + .../oval/parsed/unix_unameState.go | 6 + .../oval/parsed/unix_unameTest.go | 5 + server/vulnerabilities/oval/parser.go | 131 ++++++++++++++++-- server/vulnerabilities/oval/parser_test.go | 65 ++++++++- 13 files changed, 288 insertions(+), 19 deletions(-) create mode 100644 server/vulnerabilities/oval/input/unix_uname_state.go create mode 100644 server/vulnerabilities/oval/input/unix_uname_test_object.go create mode 100644 server/vulnerabilities/oval/input/variable_object.go create mode 100644 server/vulnerabilities/oval/input/variable_state.go create mode 100644 server/vulnerabilities/oval/input/variable_test_object.go create mode 100644 server/vulnerabilities/oval/parsed/ind_variabletest.go create mode 100644 server/vulnerabilities/oval/parsed/unix_unameState.go create mode 100644 server/vulnerabilities/oval/parsed/unix_unameTest.go diff --git a/server/vulnerabilities/oval/input/ubuntu_result.go b/server/vulnerabilities/oval/input/ubuntu_result.go index e503fba3cc..d59a534a53 100644 --- a/server/vulnerabilities/oval/input/ubuntu_result.go +++ b/server/vulnerabilities/oval/input/ubuntu_result.go @@ -7,5 +7,10 @@ type UbuntuResultXML struct { DpkgInfoTests []DpkgInfoTestXML DpkgInfoStates []DpkgInfoStateXML DpkgInfoObjects []PackageInfoTestObjectXML + UnameTests []UnixUnameTestXML + UnameStates []UnixUnameStateXML + VariableTests []VariableTestXML + VariableObjects map[int]VariableObjectXML + VariableStates []VariableStateXML Variables map[string]ConstantVariableXML } diff --git a/server/vulnerabilities/oval/input/unix_uname_state.go b/server/vulnerabilities/oval/input/unix_uname_state.go new file mode 100644 index 0000000000..afbf680abe --- /dev/null +++ b/server/vulnerabilities/oval/input/unix_uname_state.go @@ -0,0 +1,7 @@ +package oval_input + +// UnixUnameStateXML see https://oval.mitre.org/language/version5.10.1/ovaldefinition/documentation/unix-definitions-schema.html#uname_state +type UnixUnameStateXML struct { + Id string `xml:"id,attr"` + OSRelease *SimpleTypeXML `xml:"os_release"` +} diff --git a/server/vulnerabilities/oval/input/unix_uname_test_object.go b/server/vulnerabilities/oval/input/unix_uname_test_object.go new file mode 100644 index 0000000000..0c8330254f --- /dev/null +++ b/server/vulnerabilities/oval/input/unix_uname_test_object.go @@ -0,0 +1,11 @@ +package oval_input + +type unixUnameTestStateXML struct { + Id string `xml:"state_ref,attr"` +} + +// https://oval.mitre.org/language/version5.10.1/ovaldefinition/documentation/unix-definitions-schema.html#uname_state +type UnixUnameTestXML struct { + Id string `xml:"id,attr"` + States []unixUnameTestStateXML `xml:"state"` +} diff --git a/server/vulnerabilities/oval/input/variable_object.go b/server/vulnerabilities/oval/input/variable_object.go new file mode 100644 index 0000000000..b5a098e252 --- /dev/null +++ b/server/vulnerabilities/oval/input/variable_object.go @@ -0,0 +1,7 @@ +package oval_input + +// https://oval.mitre.org/language/version5.10.1/ovaldefinition/documentation/independent-definitions-schema.html#variable_object +type VariableObjectXML struct { + Id string `xml:"id,attr"` + RefID string `xml:"var_ref"` +} diff --git a/server/vulnerabilities/oval/input/variable_state.go b/server/vulnerabilities/oval/input/variable_state.go new file mode 100644 index 0000000000..c8c6533c6e --- /dev/null +++ b/server/vulnerabilities/oval/input/variable_state.go @@ -0,0 +1,7 @@ +package oval_input + +// https://oval.mitre.org/language/version5.10.1/ovaldefinition/documentation/independent-definitions-schema.html#variable_state +type VariableStateXML struct { + Id string `xml:"id,attr"` + Value SimpleTypeXML `xml:"value"` +} diff --git a/server/vulnerabilities/oval/input/variable_test_object.go b/server/vulnerabilities/oval/input/variable_test_object.go new file mode 100644 index 0000000000..c46434fde6 --- /dev/null +++ b/server/vulnerabilities/oval/input/variable_test_object.go @@ -0,0 +1,16 @@ +package oval_input + +type variableObjectXML struct { + Id string `xml:"object_ref"` +} + +type variableStateXML struct { + Id string `xml:"state_ref,attr"` +} + +// https://oval.mitre.org/language/version5.10.1/ovaldefinition/documentation/independent-definitions-schema.html#variable_test +type VariableTestXML struct { + Id string `xml:"id,attr"` + Object variableObjectXML `xml:"object"` + States []variableStateXML `xml:"state"` +} diff --git a/server/vulnerabilities/oval/mappers.go b/server/vulnerabilities/oval/mappers.go index e39f461e85..f7f9e57750 100644 --- a/server/vulnerabilities/oval/mappers.go +++ b/server/vulnerabilities/oval/mappers.go @@ -277,6 +277,17 @@ func mapDpkgInfoTest(i oval_input.DpkgInfoTestXML) (int, *oval_parsed.DpkgInfoTe return id, &tst, nil } +func mapUnixUnameTest(i oval_input.UnixUnameTestXML) (int, *oval_parsed.UnixUnameTest, error) { + id, err := extractId(i.Id) + if err != nil { + return 0, nil, err + } + + tst := oval_parsed.UnixUnameTest{} + + return id, &tst, nil +} + // mapDpkgInfoState maps a DpkgInfoStateXML into an EVR string. The state of an object defines // the different information that can be used to evaluate the specified DPKG package. All Ubuntu // OVAL definitions seem to only use Evr strings to define object state, that's why only Evr support @@ -296,3 +307,24 @@ func mapDpkgInfoState(sta oval_input.DpkgInfoStateXML) (*oval_parsed.ObjectState r := oval_parsed.NewObjectStateEvrString(sta.Evr.Op, sta.Evr.Value) return &r, nil } + +func mapUnameState(sta oval_input.UnixUnameStateXML) *oval_parsed.ObjectStateString { + r := oval_parsed.NewObjectStateString(sta.OSRelease.Op, sta.OSRelease.Value) + return &r +} + +func mapVariableTest(i oval_input.VariableTestXML) (int, *oval_parsed.UnixUnameTest, error) { + id, err := extractId(i.Id) + if err != nil { + return 0, nil, err + } + + tst := oval_parsed.UnixUnameTest{} + + return id, &tst, nil +} + +func mapVariableState(sta oval_input.VariableStateXML) *oval_parsed.ObjectStateString { + r := oval_parsed.NewObjectStateString(sta.Value.Op, sta.Value.Value) + return &r +} diff --git a/server/vulnerabilities/oval/parsed/ind_variabletest.go b/server/vulnerabilities/oval/parsed/ind_variabletest.go new file mode 100644 index 0000000000..b7b2b7f306 --- /dev/null +++ b/server/vulnerabilities/oval/parsed/ind_variabletest.go @@ -0,0 +1,9 @@ +package oval_parsed + +type VariableTest struct { + Objects []string + States []ObjectStateEvrString + StateOperator OperatorType + ObjectMatch ObjectMatchType + StateMatch StateMatchType +} diff --git a/server/vulnerabilities/oval/parsed/ubuntu_result.go b/server/vulnerabilities/oval/parsed/ubuntu_result.go index e777a91833..b7169dc7b0 100644 --- a/server/vulnerabilities/oval/parsed/ubuntu_result.go +++ b/server/vulnerabilities/oval/parsed/ubuntu_result.go @@ -7,6 +7,7 @@ import ( type UbuntuResult struct { Definitions []Definition PackageTests map[int]*DpkgInfoTest + UnameTests map[int]*UnixUnameTest } // NewUbuntuResult is the result of parsing an OVAL file that targets an Ubuntu distro. @@ -14,6 +15,7 @@ type UbuntuResult struct { func NewUbuntuResult() *UbuntuResult { return &UbuntuResult{ PackageTests: make(map[int]*DpkgInfoTest), + UnameTests: make(map[int]*UnixUnameTest), } } @@ -27,6 +29,10 @@ func (r *UbuntuResult) AddPackageTest(id int, tst *DpkgInfoTest) { r.PackageTests[id] = tst } +func (r *UbuntuResult) AddUnameTest(id int, tst *UnixUnameTest) { + r.UnameTests[id] = tst +} + func (r UbuntuResult) Eval(ver fleet.OSVersion, software []fleet.Software) ([]fleet.SoftwareVulnerability, error) { // Test Id => Matching software pkgTstResults := make(map[int][]fleet.Software) diff --git a/server/vulnerabilities/oval/parsed/unix_unameState.go b/server/vulnerabilities/oval/parsed/unix_unameState.go new file mode 100644 index 0000000000..f3f9ae0083 --- /dev/null +++ b/server/vulnerabilities/oval/parsed/unix_unameState.go @@ -0,0 +1,6 @@ +package oval_parsed + +type UnixUnameState struct { + States []ObjectStateString + StateMatch StateMatchType +} diff --git a/server/vulnerabilities/oval/parsed/unix_unameTest.go b/server/vulnerabilities/oval/parsed/unix_unameTest.go new file mode 100644 index 0000000000..3a7d358466 --- /dev/null +++ b/server/vulnerabilities/oval/parsed/unix_unameTest.go @@ -0,0 +1,5 @@ +package oval_parsed + +type UnixUnameTest struct { + States []ObjectStateString +} diff --git a/server/vulnerabilities/oval/parser.go b/server/vulnerabilities/oval/parser.go index 62e9e8f6af..5f1b937bb8 100644 --- a/server/vulnerabilities/oval/parser.go +++ b/server/vulnerabilities/oval/parser.go @@ -254,17 +254,17 @@ func mapToRhelResult(xmlResult *oval_input.RhelResultXML) (*oval_parsed.RhelResu func processUbuntuDef(r io.Reader) ([]byte, error) { xmlResult, err := parseUbuntuXML(r) if err != nil { - return nil, err + return nil, fmt.Errorf("parsing ubuntu xml: %w", err) } result, err := mapToUbuntuResult(xmlResult) if err != nil { - return nil, err + return nil, fmt.Errorf("mapping ubuntu result: %w", err) } payload, err := json.Marshal(result) if err != nil { - return nil, err + return nil, fmt.Errorf("marshalling ubuntu result: %w", err) } return payload, nil @@ -272,7 +272,8 @@ func processUbuntuDef(r io.Reader) ([]byte, error) { func parseUbuntuXML(reader io.Reader) (*oval_input.UbuntuResultXML, error) { r := &oval_input.UbuntuResultXML{ - Variables: make(map[string]oval_input.ConstantVariableXML), + Variables: make(map[string]oval_input.ConstantVariableXML), + VariableObjects: make(map[int]oval_input.VariableObjectXML), } d := xml.NewDecoder(reader) @@ -290,35 +291,76 @@ func parseUbuntuXML(reader io.Reader) (*oval_input.UbuntuResultXML, error) { if t.Name.Local == "definition" { def := oval_input.DefinitionXML{} if err = d.DecodeElement(&def, &t); err != nil { - return nil, err + return nil, fmt.Errorf("decoding definition: %w", err) } r.Definitions = append(r.Definitions, def) } if t.Name.Local == "dpkginfo_test" { tst := oval_input.DpkgInfoTestXML{} if err = d.DecodeElement(&tst, &t); err != nil { - return nil, err + return nil, fmt.Errorf("decoding dpkginfo_test: %w", err) } r.DpkgInfoTests = append(r.DpkgInfoTests, tst) } if t.Name.Local == "dpkginfo_state" { sta := oval_input.DpkgInfoStateXML{} if err = d.DecodeElement(&sta, &t); err != nil { - return nil, err + return nil, fmt.Errorf("decoding dpkginfo_state: %w", err) } r.DpkgInfoStates = append(r.DpkgInfoStates, sta) } if t.Name.Local == "dpkginfo_object" { obj := oval_input.PackageInfoTestObjectXML{} if err = d.DecodeElement(&obj, &t); err != nil { - return nil, err + return nil, fmt.Errorf("decoding dpkginfo_object: %w", err) } r.DpkgInfoObjects = append(r.DpkgInfoObjects, obj) } + if t.Name.Local == "uname_test" { + tst := oval_input.UnixUnameTestXML{} + if err = d.DecodeElement(&tst, &t); err != nil { + return nil, fmt.Errorf("decoding uname_test: %w", err) + } + r.UnameTests = append(r.UnameTests, tst) + } + if t.Name.Local == "uname_state" { + sta := oval_input.UnixUnameStateXML{} + if err = d.DecodeElement(&sta, &t); err != nil { + return nil, fmt.Errorf("decoding uname_state: %w", err) + } + r.UnameStates = append(r.UnameStates, sta) + } + if t.Name.Local == "variable_test" { + tst := oval_input.VariableTestXML{} + if err = d.DecodeElement(&tst, &t); err != nil { + return nil, fmt.Errorf("decoding variable_test: %w", err) + } + r.VariableTests = append(r.VariableTests, tst) + } + if t.Name.Local == "variable_object" { + obj := oval_input.VariableObjectXML{} + if err = d.DecodeElement(&obj, &t); err != nil { + return nil, fmt.Errorf("decoding variable_object: %w", err) + } + + id, err := extractId(obj.Id) + if err != nil { + return nil, fmt.Errorf("extracting id: %w", err) + } + r.VariableObjects[id] = obj + } + + if t.Name.Local == "variable_state" { + sta := oval_input.VariableStateXML{} + if err = d.DecodeElement(&sta, &t); err != nil { + return nil, fmt.Errorf("decoding variable_state: %w", err) + } + r.VariableStates = append(r.VariableStates, sta) + } if t.Name.Local == "constant_variable" { cVar := oval_input.ConstantVariableXML{} if err = d.DecodeElement(&cVar, &t); err != nil { - return nil, err + return nil, fmt.Errorf("decoding constant_variable: %w", err) } r.Variables[cVar.Id] = cVar } @@ -331,12 +373,14 @@ func mapToUbuntuResult(xmlResult *oval_input.UbuntuResultXML) (*oval_parsed.Ubun staToTst := make(map[string][]int) objToTst := make(map[string][]int) + ustaToTst := make(map[string][]int) + vstaToTst := make(map[string][]int) for _, d := range xmlResult.Definitions { if len(d.Vulnerabilities) > 0 { def, err := mapDefinition(d) if err != nil { - return nil, err + return nil, fmt.Errorf("mapping definition: %w", err) } r.AddDefinition(*def) } @@ -345,7 +389,7 @@ func mapToUbuntuResult(xmlResult *oval_input.UbuntuResultXML) (*oval_parsed.Ubun for _, t := range xmlResult.DpkgInfoTests { id, tst, err := mapDpkgInfoTest(t) if err != nil { - return nil, err + return nil, fmt.Errorf("mapping dpkg info test: %w", err) } objToTst[t.Object.Id] = append(objToTst[t.Object.Id], id) @@ -358,7 +402,7 @@ func mapToUbuntuResult(xmlResult *oval_input.UbuntuResultXML) (*oval_parsed.Ubun for _, o := range xmlResult.DpkgInfoObjects { obj, err := mapPackageInfoTestObject(o, xmlResult.Variables) if err != nil { - return nil, err + return nil, fmt.Errorf("mapping dpkg info object: %w", err) } for _, tId := range objToTst[o.Id] { @@ -374,7 +418,7 @@ func mapToUbuntuResult(xmlResult *oval_input.UbuntuResultXML) (*oval_parsed.Ubun for _, s := range xmlResult.DpkgInfoStates { sta, err := mapDpkgInfoState(s) if err != nil { - return nil, err + return nil, fmt.Errorf("mapping dpkg info state: %w", err) } for _, tId := range staToTst[s.Id] { t, ok := r.PackageTests[tId] @@ -385,5 +429,66 @@ func mapToUbuntuResult(xmlResult *oval_input.UbuntuResultXML) (*oval_parsed.Ubun } } } + + for _, t := range xmlResult.UnameTests { + id, tst, err := mapUnixUnameTest(t) + if err != nil { + return nil, fmt.Errorf("mapping uname test: %w", err) + } + + for _, sta := range t.States { + ustaToTst[sta.Id] = append(ustaToTst[sta.Id], id) + } + r.AddUnameTest(id, tst) + } + + for _, s := range xmlResult.UnameStates { + sta := mapUnameState(s) + for _, tId := range ustaToTst[s.Id] { + t, ok := r.UnameTests[tId] + if ok { + t.States = append(t.States, *sta) + } else { + return nil, fmt.Errorf("test not found: %d", tId) + } + } + } + + for _, t := range xmlResult.VariableTests { + id, tst, err := mapVariableTest(t) + if err != nil { + return nil, fmt.Errorf("mapping variable test: %w", err) + } + + // Skip tests that are not used in any uname test + // (there should be none) + if obj, ok := xmlResult.VariableObjects[id]; ok { + id, err := extractId(obj.RefID) + if err != nil { + return nil, fmt.Errorf("extracting variable object id: %w", err) + } + if _, ok := r.UnameTests[id]; !ok { + continue + } + } + + for _, sta := range t.States { + vstaToTst[sta.Id] = append(vstaToTst[sta.Id], id) + } + r.AddUnameTest(id, tst) + } + + for _, s := range xmlResult.VariableStates { + sta := mapVariableState(s) + for _, tId := range vstaToTst[s.Id] { + t, ok := r.UnameTests[tId] + if ok { + t.States = append(t.States, *sta) + } else { + return nil, fmt.Errorf("test not found: %d", tId) + } + } + } + return r, nil } diff --git a/server/vulnerabilities/oval/parser_test.go b/server/vulnerabilities/oval/parser_test.go index 79c9e3ec57..ee3597752a 100644 --- a/server/vulnerabilities/oval/parser_test.go +++ b/server/vulnerabilities/oval/parser_test.go @@ -59,6 +59,38 @@ func TestOvalParser(t *testing.T) { + + + USN-5544-1 -- Linux kernel vulnerabilities + + Ubuntu 22.04 LTS + + + + + + + Some long description + + High + + CVE-2022-1652 + CVE-2022-1679 + CVE-2022-28893 + CVE-2022-34918 + + + + + + + + + + + + + @@ -68,7 +100,15 @@ func TestOvalParser(t *testing.T) { + + + + + + + + @@ -76,6 +116,10 @@ func TestOvalParser(t *testing.T) { + + + oval:com.ubuntu.jammy:var:554410000000 + @@ -84,6 +128,12 @@ func TestOvalParser(t *testing.T) { 1:9.18.1-1ubuntu1.1 + + 5.15.0-\d+(-generic|-generic-64k|-generic-lpae|-lowlatency|-lowlatency-64k) + + + 0:5.15.0-43 + @@ -351,14 +401,8 @@ func TestOvalParser(t *testing.T) { require.NoError(t, err) var expectedVulns []string - var expectedTestIds []int for _, d := range xmlResult.Definitions { - for _, c := range d.Criteria.Criteriums { - tstId, err := extractId(c.TestId) - require.NoError(t, err) - expectedTestIds = append(expectedTestIds, tstId) - } for _, v := range d.Vulnerabilities { expectedVulns = append(expectedVulns, v.Id) } @@ -373,6 +417,8 @@ func TestOvalParser(t *testing.T) { } require.Equal(t, expectedVulns, actualVulns) + + expectedTestIds := []int{540210000000, 542910000000, 554410000000, 554410000010} require.ElementsMatch(t, expectedTestIds, actualTestIds) require.Len(t, result.PackageTests, 2) @@ -399,6 +445,13 @@ func TestOvalParser(t *testing.T) { "bind9-dnsutils", "bind9-host", }) + + require.Len(t, result.UnameTests, 2) + matchState := []oval_parsed.ObjectStateString{"pattern match|5.15.0-\\d+(-generic|-generic-64k|-generic-lpae|-lowlatency|-lowlatency-64k)"} + require.ElementsMatch(t, result.UnameTests[554410000000].States, matchState) + + variableState := []oval_parsed.ObjectStateString{"less than|0:5.15.0-43"} + require.ElementsMatch(t, result.UnameTests[554410000010].States, variableState) }) t.Run("#parseRhelXML", func(t *testing.T) {