From 635109f094c343c9f3f7b451c87abce8a08e6da2 Mon Sep 17 00:00:00 2001 From: MaineK00n Date: Tue, 17 Dec 2024 17:17:13 +0900 Subject: [PATCH] fix(detector/gost/ubuntu): detection logic when esm etc. are mixed (#2090) --- gost/ubuntu.go | 181 +++++++++++++++++++++++++----------------- gost/ubuntu_test.go | 188 +++++++++++++++++++++++++++++++++++--------- 2 files changed, 261 insertions(+), 108 deletions(-) diff --git a/gost/ubuntu.go b/gost/ubuntu.go index c44c81cc51..baca79d531 100644 --- a/gost/ubuntu.go +++ b/gost/ubuntu.go @@ -86,66 +86,84 @@ func (ubu Ubuntu) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error } } - fixedCVEs, err := ubu.detectCVEsWithFixState(r, true) + cves, err := ubu.detectCVEs(r) if err != nil { - return 0, xerrors.Errorf("Failed to detect fixed CVEs. err: %w", err) + return 0, xerrors.Errorf("Failed to detect CVEs. err: %w", err) } - unfixedCVEs, err := ubu.detectCVEsWithFixState(r, false) - if err != nil { - return 0, xerrors.Errorf("Failed to detect unfixed CVEs. err: %w", err) - } - - return len(unique(append(fixedCVEs, unfixedCVEs...))), nil + return len(cves), nil } -func (ubu Ubuntu) detectCVEsWithFixState(r *models.ScanResult, fixed bool) ([]string, error) { - detects := map[string]cveContent{} +func (ubu Ubuntu) detectCVEs(r *models.ScanResult) ([]string, error) { + detects := make(map[string]cveContent) if ubu.driver == nil { + pm := make(map[string]map[string]map[string]gostmodels.UbuntuCVE) + urlPrefix, err := util.URLPathJoin(ubu.baseURL, "ubuntu", strings.Replace(r.Release, ".", "", 1), "pkgs") if err != nil { return nil, xerrors.Errorf("Failed to join URLPath. err: %w", err) } - s := "fixed-cves" - if !fixed { - s = "unfixed-cves" - } - responses, err := getCvesWithFixStateViaHTTP(r, urlPrefix, s) - if err != nil { - return nil, xerrors.Errorf("Failed to get fixed CVEs via HTTP. err: %w", err) - } - for _, res := range responses { - if !res.request.isSrcPack { - continue + for _, state := range []string{"fixed-cves", "unfixed-cves"} { + responses, err := getCvesWithFixStateViaHTTP(r, urlPrefix, state) + if err != nil { + return nil, xerrors.Errorf("Failed to get %s CVEs via HTTP. err: %w", func() string { + switch state { + case "fixed-cves": + return "fixed" + case "unfixed-cves": + return "unfixed" + default: + return "" + } + }(), err) } + for _, res := range responses { + if !res.request.isSrcPack { + continue + } - // To detect vulnerabilities in running kernels only, skip if the kernel is not running. - if models.IsKernelSourcePackage(constant.Ubuntu, res.request.packName) && !slices.ContainsFunc(r.SrcPackages[res.request.packName].BinaryNames, func(bn string) bool { - switch bn { - case fmt.Sprintf("linux-image-%s", r.RunningKernel.Release), fmt.Sprintf("linux-image-unsigned-%s", r.RunningKernel.Release), fmt.Sprintf("linux-signed-image-%s", r.RunningKernel.Release), fmt.Sprintf("linux-image-uc-%s", r.RunningKernel.Release), - fmt.Sprintf("linux-buildinfo-%s", r.RunningKernel.Release), fmt.Sprintf("linux-cloud-tools-%s", r.RunningKernel.Release), fmt.Sprintf("linux-headers-%s", r.RunningKernel.Release), fmt.Sprintf("linux-lib-rust-%s", r.RunningKernel.Release), fmt.Sprintf("linux-modules-%s", r.RunningKernel.Release), fmt.Sprintf("linux-modules-extra-%s", r.RunningKernel.Release), fmt.Sprintf("linux-modules-ipu6-%s", r.RunningKernel.Release), fmt.Sprintf("linux-modules-ivsc-%s", r.RunningKernel.Release), fmt.Sprintf("linux-modules-iwlwifi-%s", r.RunningKernel.Release), fmt.Sprintf("linux-tools-%s", r.RunningKernel.Release): - return true - default: - if (strings.HasPrefix(bn, "linux-modules-nvidia-") || strings.HasPrefix(bn, "linux-objects-nvidia-") || strings.HasPrefix(bn, "linux-signatures-nvidia-")) && strings.HasSuffix(bn, r.RunningKernel.Release) { + // To detect vulnerabilities in running kernels only, skip if the kernel is not running. + if models.IsKernelSourcePackage(constant.Ubuntu, res.request.packName) && !slices.ContainsFunc(r.SrcPackages[res.request.packName].BinaryNames, func(bn string) bool { + switch bn { + case fmt.Sprintf("linux-image-%s", r.RunningKernel.Release), fmt.Sprintf("linux-image-unsigned-%s", r.RunningKernel.Release), fmt.Sprintf("linux-signed-image-%s", r.RunningKernel.Release), fmt.Sprintf("linux-image-uc-%s", r.RunningKernel.Release), + fmt.Sprintf("linux-buildinfo-%s", r.RunningKernel.Release), fmt.Sprintf("linux-cloud-tools-%s", r.RunningKernel.Release), fmt.Sprintf("linux-headers-%s", r.RunningKernel.Release), fmt.Sprintf("linux-lib-rust-%s", r.RunningKernel.Release), fmt.Sprintf("linux-modules-%s", r.RunningKernel.Release), fmt.Sprintf("linux-modules-extra-%s", r.RunningKernel.Release), fmt.Sprintf("linux-modules-ipu6-%s", r.RunningKernel.Release), fmt.Sprintf("linux-modules-ivsc-%s", r.RunningKernel.Release), fmt.Sprintf("linux-modules-iwlwifi-%s", r.RunningKernel.Release), fmt.Sprintf("linux-tools-%s", r.RunningKernel.Release): return true + default: + if (strings.HasPrefix(bn, "linux-modules-nvidia-") || strings.HasPrefix(bn, "linux-objects-nvidia-") || strings.HasPrefix(bn, "linux-signatures-nvidia-")) && strings.HasSuffix(bn, r.RunningKernel.Release) { + return true + } + return false } - return false + }) { + continue } - }) { - continue + + var cs map[string]gostmodels.UbuntuCVE + if err := json.Unmarshal([]byte(res.json), &cs); err != nil { + return nil, xerrors.Errorf("Failed to unmarshal json. err: %w", err) + } + + base, ok := pm[res.request.packName] + if !ok { + base = make(map[string]map[string]gostmodels.UbuntuCVE) + } + base[state] = cs + pm[res.request.packName] = base } + } - cs := map[string]gostmodels.UbuntuCVE{} - if err := json.Unmarshal([]byte(res.json), &cs); err != nil { - return nil, xerrors.Errorf("Failed to unmarshal json. err: %w", err) + for name, res := range pm { + cs, err := ubu.detect(res["fixed-cves"], res["unfixed-cves"], r.SrcPackages[name]) + if err != nil { + return nil, xerrors.Errorf("Failed to detect affected CVEs. release: %s, package: %s, err: %w", strings.Replace(r.Release, ".", "", 1), name, err) } - for _, content := range ubu.detect(cs, fixed, models.SrcPackage{Name: res.request.packName, Version: r.SrcPackages[res.request.packName].Version, BinaryNames: r.SrcPackages[res.request.packName].BinaryNames}) { - c, ok := detects[content.cveContent.CveID] + for _, c := range cs { + base, ok := detects[c.cveContent.CveID] if ok { - content.fixStatuses = append(content.fixStatuses, c.fixStatuses...) + c.fixStatuses = append(c.fixStatuses, base.fixStatuses...) } - detects[content.cveContent.CveID] = content + detects[c.cveContent.CveID] = c } } } else { @@ -166,26 +184,31 @@ func (ubu Ubuntu) detectCVEsWithFixState(r *models.ScanResult, fixed bool) ([]st continue } - var f func(string, string) (map[string]gostmodels.UbuntuCVE, error) = ubu.driver.GetFixedCvesUbuntu - if !fixed { - f = ubu.driver.GetUnfixedCvesUbuntu - } - n := p.Name if models.IsKernelSourcePackage(constant.Ubuntu, p.Name) { n = models.RenameKernelSourcePackageName(constant.Ubuntu, p.Name) } - cs, err := f(strings.Replace(r.Release, ".", "", 1), n) + fixed, err := ubu.driver.GetFixedCvesUbuntu(strings.Replace(r.Release, ".", "", 1), n) + if err != nil { + return nil, xerrors.Errorf("Failed to get fixed CVEs. release: %s, package: %s, err: %w", strings.Replace(r.Release, ".", "", 1), n, err) + } + + unfixed, err := ubu.driver.GetUnfixedCvesUbuntu(strings.Replace(r.Release, ".", "", 1), n) if err != nil { - return nil, xerrors.Errorf("Failed to get CVEs. release: %s, src package: %s, err: %w", major(r.Release), p.Name, err) + return nil, xerrors.Errorf("Failed to get unfixed CVEs. release: %s, package: %s, err: %w", strings.Replace(r.Release, ".", "", 1), n, err) } - for _, content := range ubu.detect(cs, fixed, p) { - c, ok := detects[content.cveContent.CveID] + + cs, err := ubu.detect(fixed, unfixed, p) + if err != nil { + return nil, xerrors.Errorf("Failed to detect affected CVEs. release: %s, package: %s, err: %w", strings.Replace(r.Release, ".", "", 1), n, err) + } + for _, c := range cs { + base, ok := detects[c.cveContent.CveID] if ok { - content.fixStatuses = append(content.fixStatuses, c.fixStatuses...) + c.fixStatuses = append(c.fixStatuses, base.fixStatuses...) } - detects[content.cveContent.CveID] = content + detects[c.cveContent.CveID] = c } } } @@ -216,14 +239,38 @@ func (ubu Ubuntu) detectCVEsWithFixState(r *models.ScanResult, fixed bool) ([]st return slices.Collect(maps.Keys(detects)), nil } -func (ubu Ubuntu) detect(cves map[string]gostmodels.UbuntuCVE, fixed bool, srcPkg models.SrcPackage) []cveContent { - var contents []cveContent - for _, cve := range cves { - c := cveContent{ - cveContent: *(Ubuntu{}).ConvertToModel(&cve), +func (ubu Ubuntu) detect(fixed, unfixed map[string]gostmodels.UbuntuCVE, srcPkg models.SrcPackage) ([]cveContent, error) { + m := make(map[string]cveContent) + for _, cve := range unfixed { + cont := ubu.ConvertToModel(&cve) + if cont == nil { + return nil, xerrors.Errorf("Failed to convert to model. cve: %#v", cve) } + m[cont.CveID] = cveContent{ + cveContent: *cont, + fixStatuses: func() models.PackageFixStatuses { + fs := make(models.PackageFixStatuses, 0, len(srcPkg.BinaryNames)) + for _, bn := range srcPkg.BinaryNames { + fs = append(fs, models.PackageFixStatus{ + Name: bn, + FixState: "open", + NotFixedYet: true, + }) + } + return fs + }(), + } + } + for _, cve := range fixed { + cont := ubu.ConvertToModel(&cve) + if cont == nil { + return nil, xerrors.Errorf("Failed to convert to model. cve: %#v", cve) + } + + delete(m, cont.CveID) - if fixed { + fs := func() models.PackageFixStatuses { + var fs models.PackageFixStatuses for _, p := range cve.Patches { for _, rp := range p.ReleasePatches { affected, err := ubu.isGostDefAffected(srcPkg.Version, rp.Note) @@ -234,7 +281,7 @@ func (ubu Ubuntu) detect(cves map[string]gostmodels.UbuntuCVE, fixed bool, srcPk if affected { for _, bn := range srcPkg.BinaryNames { - c.fixStatuses = append(c.fixStatuses, models.PackageFixStatus{ + fs = append(fs, models.PackageFixStatus{ Name: bn, FixedIn: rp.Note, }) @@ -242,22 +289,16 @@ func (ubu Ubuntu) detect(cves map[string]gostmodels.UbuntuCVE, fixed bool, srcPk } } } - } else { - for _, bn := range srcPkg.BinaryNames { - c.fixStatuses = append(c.fixStatuses, models.PackageFixStatus{ - Name: bn, - FixState: "open", - NotFixedYet: true, - }) + return fs + }() + if len(fs) > 0 { + m[cont.CveID] = cveContent{ + cveContent: *cont, + fixStatuses: fs, } } - - if len(c.fixStatuses) > 0 { - c.fixStatuses.Sort() - contents = append(contents, c) - } } - return contents + return slices.Collect(maps.Values(m)), nil } func (ubu Ubuntu) isGostDefAffected(versionRelease, gostVersion string) (affected bool, err error) { diff --git a/gost/ubuntu_test.go b/gost/ubuntu_test.go index a2f3199985..51ccd12f0a 100644 --- a/gost/ubuntu_test.go +++ b/gost/ubuntu_test.go @@ -1,9 +1,7 @@ package gost import ( - "cmp" "reflect" - "slices" "testing" "time" @@ -119,21 +117,22 @@ func TestUbuntuConvertToModel(t *testing.T) { } } -func Test_detect(t *testing.T) { +func TestUbuntu_detect(t *testing.T) { type args struct { - cves map[string]gostmodels.UbuntuCVE - fixed bool - srcPkg models.SrcPackage + fixed map[string]gostmodels.UbuntuCVE + unfixed map[string]gostmodels.UbuntuCVE + srcPkg models.SrcPackage } tests := []struct { - name string - args args - want []cveContent + name string + args args + want []cveContent + wantErr bool }{ { name: "fixed", args: args{ - cves: map[string]gostmodels.UbuntuCVE{ + fixed: map[string]gostmodels.UbuntuCVE{ "CVE-0000-0000": { Candidate: "CVE-0000-0000", Patches: []gostmodels.UbuntuPatch{ @@ -153,51 +152,71 @@ func Test_detect(t *testing.T) { }, }, }, - fixed: true, - srcPkg: models.SrcPackage{Name: "pkg", Version: "0.0.0-1", BinaryNames: []string{"pkg"}}, + srcPkg: models.SrcPackage{ + Name: "pkg", + Version: "0.0.0-1", + BinaryNames: []string{"pkg"}, + }, }, want: []cveContent{ { - cveContent: models.CveContent{Type: models.UbuntuAPI, CveID: "CVE-0000-0001", SourceLink: "https://ubuntu.com/security/CVE-0000-0001", References: []models.Reference{}}, - fixStatuses: models.PackageFixStatuses{{ - Name: "pkg", - FixedIn: "0.0.0-2", - }}, + cveContent: models.CveContent{ + Type: models.UbuntuAPI, + CveID: "CVE-0000-0001", + SourceLink: "https://ubuntu.com/security/CVE-0000-0001", + References: []models.Reference{}, + }, + fixStatuses: models.PackageFixStatuses{ + { + Name: "pkg", + FixedIn: "0.0.0-2", + }, + }, }, }, }, { name: "unfixed", args: args{ - cves: map[string]gostmodels.UbuntuCVE{ + unfixed: map[string]gostmodels.UbuntuCVE{ "CVE-0000-0000": { Candidate: "CVE-0000-0000", Patches: []gostmodels.UbuntuPatch{ { PackageName: "pkg", - ReleasePatches: []gostmodels.UbuntuReleasePatch{{ReleaseName: "jammy", Status: "open"}}, + ReleasePatches: []gostmodels.UbuntuReleasePatch{{ReleaseName: "jammy", Status: "needed"}}, }, }, }, }, - fixed: false, - srcPkg: models.SrcPackage{Name: "pkg", Version: "0.0.0-1", BinaryNames: []string{"pkg"}}, + srcPkg: models.SrcPackage{ + Name: "pkg", + Version: "0.0.0-1", + BinaryNames: []string{"pkg"}, + }, }, want: []cveContent{ { - cveContent: models.CveContent{Type: models.UbuntuAPI, CveID: "CVE-0000-0000", SourceLink: "https://ubuntu.com/security/CVE-0000-0000", References: []models.Reference{}}, - fixStatuses: models.PackageFixStatuses{{ - Name: "pkg", - FixState: "open", - NotFixedYet: true, - }}, + cveContent: models.CveContent{ + Type: models.UbuntuAPI, + CveID: "CVE-0000-0000", + SourceLink: "https://ubuntu.com/security/CVE-0000-0000", + References: []models.Reference{}, + }, + fixStatuses: models.PackageFixStatuses{ + { + Name: "pkg", + FixState: "open", + NotFixedYet: true, + }, + }, }, }, }, { name: "linux-signed", args: args{ - cves: map[string]gostmodels.UbuntuCVE{ + fixed: map[string]gostmodels.UbuntuCVE{ "CVE-0000-0000": { Candidate: "CVE-0000-0000", Patches: []gostmodels.UbuntuPatch{ @@ -217,12 +236,20 @@ func Test_detect(t *testing.T) { }, }, }, - fixed: true, - srcPkg: models.SrcPackage{Name: "linux-signed", Version: "0.0.0-1", BinaryNames: []string{"linux-image-generic", "linux-headers-generic"}}, + srcPkg: models.SrcPackage{ + Name: "linux-signed", + Version: "0.0.0-1", + BinaryNames: []string{"linux-image-generic", "linux-headers-generic"}, + }, }, want: []cveContent{ { - cveContent: models.CveContent{Type: models.UbuntuAPI, CveID: "CVE-0000-0001", SourceLink: "https://ubuntu.com/security/CVE-0000-0001", References: []models.Reference{}}, + cveContent: models.CveContent{ + Type: models.UbuntuAPI, + CveID: "CVE-0000-0001", + SourceLink: "https://ubuntu.com/security/CVE-0000-0001", + References: []models.Reference{}, + }, fixStatuses: models.PackageFixStatuses{ { Name: "linux-image-generic", @@ -239,7 +266,7 @@ func Test_detect(t *testing.T) { { name: "linux-meta", args: args{ - cves: map[string]gostmodels.UbuntuCVE{ + fixed: map[string]gostmodels.UbuntuCVE{ "CVE-0000-0000": { Candidate: "CVE-0000-0000", Patches: []gostmodels.UbuntuPatch{ @@ -259,20 +286,105 @@ func Test_detect(t *testing.T) { }, }, }, - fixed: true, - srcPkg: models.SrcPackage{Name: "linux-meta", Version: "0.0.0.1", BinaryNames: []string{"linux-image-generic", "linux-headers-generic"}}, + srcPkg: models.SrcPackage{ + Name: "linux-meta", + Version: "0.0.0.1", + BinaryNames: []string{"linux-image-generic", "linux-headers-generic"}, + }, + }, + want: nil, + }, + { + name: "fixed and unfixed, installed < fixed", + args: args{ + fixed: map[string]gostmodels.UbuntuCVE{ + "CVE-0000-0000": { + Candidate: "CVE-0000-0000", + Patches: []gostmodels.UbuntuPatch{ + { + PackageName: "pkg", + ReleasePatches: []gostmodels.UbuntuReleasePatch{{ReleaseName: "esm-apps/focal", Status: "released", Note: "0.0.0-1"}}, + }, + }, + }, + }, + unfixed: map[string]gostmodels.UbuntuCVE{ + "CVE-0000-0000": { + Candidate: "CVE-0000-0000", + Patches: []gostmodels.UbuntuPatch{ + { + PackageName: "pkg", + ReleasePatches: []gostmodels.UbuntuReleasePatch{{ReleaseName: "focal", Status: "needed"}}, + }, + }, + }, + }, + srcPkg: models.SrcPackage{ + Name: "pkg", + Version: "0.0.0-0", + BinaryNames: []string{"pkg"}, + }, + }, + want: []cveContent{ + { + cveContent: models.CveContent{ + Type: models.UbuntuAPI, + CveID: "CVE-0000-0000", + SourceLink: "https://ubuntu.com/security/CVE-0000-0000", + References: []models.Reference{}, + }, + fixStatuses: models.PackageFixStatuses{ + { + Name: "pkg", + FixedIn: "0.0.0-1", + }, + }, + }, + }, + }, + { + name: "fixed and unfixed, installed > fixed", + args: args{ + fixed: map[string]gostmodels.UbuntuCVE{ + "CVE-0000-0000": { + Candidate: "CVE-0000-0000", + Patches: []gostmodels.UbuntuPatch{ + { + PackageName: "pkg", + ReleasePatches: []gostmodels.UbuntuReleasePatch{{ReleaseName: "esm-apps/focal", Status: "released", Note: "0.0.0-1"}}, + }, + }, + }, + }, + unfixed: map[string]gostmodels.UbuntuCVE{ + "CVE-0000-0000": { + Candidate: "CVE-0000-0000", + Patches: []gostmodels.UbuntuPatch{ + { + PackageName: "pkg", + ReleasePatches: []gostmodels.UbuntuReleasePatch{{ReleaseName: "focal", Status: "needed"}}, + }, + }, + }, + }, + srcPkg: models.SrcPackage{ + Name: "pkg", + Version: "0.0.0-2", + BinaryNames: []string{"pkg"}, + }, }, want: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := (Ubuntu{}).detect(tt.args.cves, tt.args.fixed, tt.args.srcPkg) - for i := range got { - slices.SortFunc(got[i].fixStatuses, func(i, j models.PackageFixStatus) int { return cmp.Compare(j.Name, i.Name) }) + got, err := (Ubuntu{}).detect(tt.args.fixed, tt.args.unfixed, tt.args.srcPkg) + if (err != nil) != tt.wantErr { + t.Errorf("Ubuntu.detect() error = %v, wantErr %v", err, tt.wantErr) + return } if !reflect.DeepEqual(got, tt.want) { - t.Errorf("detect() = %#v, want %#v", got, tt.want) + t.Errorf("Ubuntu.detect() = %v, want %v", got, tt.want) } }) }