From 0b161fc3cdeb4b02c0296420e13a11818ed986c9 Mon Sep 17 00:00:00 2001 From: Sakib Sajal Date: Thu, 17 Feb 2022 11:22:54 -0500 Subject: [PATCH] feat(wrlinux): Add Wind River Linux vulnerability data (#177) Signed-off-by: Sakib Sajal --- .github/workflows/update.yml | 4 + README.md | 2 +- main.go | 8 +- wrlinux/testdata/golden | 18 +++ wrlinux/wrlinux.go | 273 +++++++++++++++++++++++++++++++++++ wrlinux/wrlinux_test.go | 82 +++++++++++ 6 files changed, 385 insertions(+), 2 deletions(-) create mode 100644 wrlinux/testdata/golden create mode 100755 wrlinux/wrlinux.go create mode 100644 wrlinux/wrlinux_test.go diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml index 4a0285fa..4c033acd 100644 --- a/.github/workflows/update.yml +++ b/.github/workflows/update.yml @@ -99,6 +99,10 @@ jobs: name: CBL-Mariner Vulnerability Data run: ./vuln-list-update -target mariner + - if: always() + name: WindRiver CVE Tracker + run: ./vuln-list-update -target wrlinux + - if: always() name: OSV Database run: ./vuln-list-update -target osv diff --git a/README.md b/README.md index fb30634b..b9793d87 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ https://github.com/aquasecurity/vuln-list/ $ vuln-list-update -h Usage of vuln-list-update: -target string - update target (nvd, alpine, alpine-unfixed, redhat, redhat-oval, debian, debian-oval, ubuntu, amazon, oracle-oval, suse-cvrf, photon, arch-linux, ghsa, glad, cwe, osv, go-vulndb, mariner, kevc) + update target (nvd, alpine, alpine-unfixed, redhat, redhat-oval, debian, debian-oval, ubuntu, amazon, oracle-oval, suse-cvrf, photon, arch-linux, ghsa, glad, cwe, osv, go-vulndb, mariner, kevc, wrlinux) -years string update years (only redhat) ``` diff --git a/main.go b/main.go index 03f4e6eb..94671693 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,7 @@ import ( susecvrf "github.com/aquasecurity/vuln-list-update/suse/cvrf" "github.com/aquasecurity/vuln-list-update/ubuntu" "github.com/aquasecurity/vuln-list-update/utils" + "github.com/aquasecurity/vuln-list-update/wrlinux" ) const ( @@ -48,7 +49,7 @@ const ( var ( target = flag.String("target", "", "update target (nvd, alpine, alpine-unfixed, redhat, redhat-oval, "+ - "debian, debian-oval, ubuntu, amazon, oracle-oval, suse-cvrf, photon, arch-linux, ghsa, glad, cwe, osv, go-vulndb, mariner, kevc)") + "debian, debian-oval, ubuntu, amazon, oracle-oval, suse-cvrf, photon, arch-linux, ghsa, glad, cwe, osv, go-vulndb, mariner, kevc, wrlinux)") years = flag.String("years", "", "update years (only redhat)") targetUri = flag.String("target-uri", "", "alternative repository URI (only glad)") targetBranch = flag.String("target-branch", "", "alternative repository branch (only glad)") @@ -228,6 +229,11 @@ func run() error { return xerrors.Errorf("Known Exploited Vulnerability Catalog update error: %w", err) } commitMsg = "Known Exploited Vulnerability Catalog" + case "wrlinux": + if err := wrlinux.Update(); err != nil { + return xerrors.Errorf("WRLinux update error: %w", err) + } + commitMsg = "Wind River CVE Tracker" default: return xerrors.New("unknown target") } diff --git a/wrlinux/testdata/golden b/wrlinux/testdata/golden new file mode 100644 index 00000000..12342529 --- /dev/null +++ b/wrlinux/testdata/golden @@ -0,0 +1,18 @@ +Candidate: CVE-2020-24241 +PublicDate: 2020-08-25 +Description: + In Netwide Assembler (NASM) 2.15rc10, there is heap use-after-free + in saa_wbytes in nasmlib/saa.c. +Notes: +Priority: medium +Bugs: + LINCD-2974 + LIN1019-5289 + LIN1018-6614 + LIN10-7689 + +Patches_nasm: +10.20.6.0_nasm: not-affected +10.19.45.1_nasm: pending +10.18.44.1_nasm: ignored +10.17.41.1_nasm: released (10.17.41.22) diff --git a/wrlinux/wrlinux.go b/wrlinux/wrlinux.go new file mode 100755 index 00000000..7d2ec914 --- /dev/null +++ b/wrlinux/wrlinux.go @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2022 Wind River Systems, Inc. + * + * The right to copy, distribute, modify, or otherwise make use + * of this software may be licensed only pursuant to the terms + * of an applicable Wind River license agreement. + */ + +package wrlinux + +import ( + "io" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" + "time" + + "github.com/aquasecurity/vuln-list-update/git" + "github.com/araddon/dateparse" + "golang.org/x/xerrors" + "github.com/aquasecurity/vuln-list-update/utils" +) + +const ( + cveTrackerDir = "windriver-cve-tracker" + windriverDir = "wrlinux" +) + +var ( + repoURLs = []string{ + "https://distro.windriver.com/git/windriver-cve-tracker.git", + } + targets = []string{ + "active", + } + statuses = []string{ + "released", + "pending", + "not-affected", + "ignored", + } +) + +type Vulnerability struct { + Candidate string + PublicDate time.Time + Description string + References []string + Notes []string + Priority string + Bugs []string + Patches map[Package]Statuses +} + +type Package string + +type Release string + +type Statuses map[Release]Status + +type Status struct { + Status string + Note string +} + +func Update() error { + var err error + gc := git.Config{} + dir := filepath.Join(utils.CacheDir(), cveTrackerDir) + for _, url := range repoURLs { + _, err = gc.CloneOrPull(url, dir, "master", false) + if err == nil { + break + } + log.Printf("failed to clone or pull: %s: %v", url, err) + log.Printf("removing %s directory", cveTrackerDir) + if err := os.RemoveAll(dir); err != nil { + return xerrors.Errorf("failed to remove %s directory: %w", cveTrackerDir, err) + } + } + if err != nil { + return xerrors.Errorf("failed to clone or pull: %w", err) + } + + dst := filepath.Join(utils.VulnListDir(), windriverDir) + log.Printf("removing windriver directory %s", dst) + if err := os.RemoveAll(dst); err != nil { + return xerrors.Errorf("failed to remove windriver directory: %w", err) + } + + log.Println("walking windriver-cve-tracker ...") + for _, target := range targets { + if err := walkDir(filepath.Join(dir, target)); err != nil { + return err + } + } + + return nil +} + +func walkDir(root string) error { + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil { + return xerrors.Errorf("file walk error: %w", err) + } + if info.IsDir() { + return nil + } + + base := filepath.Base(path) + if !strings.HasPrefix(base, "CVE-") { + return nil + } + + f, err := os.Open(path) + if err != nil { + return xerrors.Errorf("error in file open: %w", err) + } + + vuln, err := parse(f) + if err != nil { + return xerrors.Errorf("error in parse: %w", err) + } + + if err = utils.SaveCVEPerYear(filepath.Join(utils.VulnListDir(), windriverDir), vuln.Candidate, vuln); err != nil { + return xerrors.Errorf("error in save: %w", err) + } + + return nil + }) + + if err != nil { + return xerrors.Errorf("error in walk: %w", err) + } + return nil +} + +func parse(r io.Reader) (vuln *Vulnerability, err error) { + vuln = &Vulnerability{} + vuln.Patches = map[Package]Statuses{} + + all, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + lines := strings.Split(string(all), "\n") + + for i := 0; i < len(lines); i++ { + line := lines[i] + + // Skip + if strings.HasPrefix(line, "#") || line == "" { + continue + } + + // Parse Candidate + if strings.HasPrefix(line, "Candidate:") { + line = strings.TrimPrefix(line, "Candidate:") + vuln.Candidate = strings.TrimSpace(line) + continue + } + + // Parse PublicDate + if strings.HasPrefix(line, "PublicDate:") { + line = strings.TrimPrefix(line, "PublicDate:") + line = strings.TrimSpace(line) + vuln.PublicDate, _ = dateparse.ParseAny(line) + continue + } + + // Parse Description + if strings.HasPrefix(line, "Description:") { + var description []string + for strings.HasPrefix(lines[i+1], " ") { + i++ + line = strings.TrimSpace(lines[i]) + description = append(description, line) + } + vuln.Description = strings.Join(description, " ") + continue + } + + // Parse References + if strings.HasPrefix(line, "References:") { + for strings.HasPrefix(lines[i+1], " ") { + i++ + line = strings.TrimSpace(lines[i]) + vuln.References = append(vuln.References, line) + } + continue + } + + // Parse Notes + if strings.HasPrefix(line, "Notes:") { + for strings.HasPrefix(lines[i+1], " ") { + i++ + line = strings.TrimSpace(lines[i]) + note := []string{line} + for strings.HasPrefix(lines[i+1], " ") { + i++ + l := strings.TrimSpace(lines[i]) + note = append(note, l) + } + vuln.Notes = append(vuln.Notes, strings.Join(note, " ")) + } + continue + } + + // Parse Priority + if strings.HasPrefix(line, "Priority:") { + line = strings.TrimPrefix(line, "Priority:") + vuln.Priority = strings.TrimSpace(line) + continue + } + + // Parse Bugs + if strings.HasPrefix(line, "Bugs:") { + for strings.HasPrefix(lines[i+1], " ") { + i++ + line = strings.TrimSpace(lines[i]) + vuln.Bugs = append(vuln.Bugs, line) + } + continue + } + + // Parse Patches + // e.g. trusty/esm_vnc4: needs-triage + s := strings.SplitN(line, ":", 2) + if len(s) < 2 { + continue + } + + status := strings.TrimSpace(s[1]) + + // Some advisories have status with "Patches_" prefix and it should be skipped + // e.g. Patches_qtwebkit-opensource-src: needs-triage + if isPatch(status) && !strings.HasPrefix(s[0], "Patches_") { + pkgRel := strings.SplitN(s[0], "_", 2) + release := Release(pkgRel[0]) + pkgName := Package(strings.Trim(pkgRel[1], ":")) + + fields := strings.Fields(status) + status := Status{ + Status: fields[0], + } + if len(fields) > 1 { + note := strings.Join(fields[1:], " ") + status.Note = strings.Trim(note, "()") + } + + if existingStatuses, ok := vuln.Patches[pkgName]; ok { + existingStatuses[release] = status + vuln.Patches[pkgName] = existingStatuses + } else { + statuses := Statuses{} + statuses[release] = status + vuln.Patches[pkgName] = statuses + } + } + } + return vuln, nil +} + +func isPatch(s string) bool { + for _, status := range statuses { + if strings.HasPrefix(s, status) { + return true + } + } + return false +} diff --git a/wrlinux/wrlinux_test.go b/wrlinux/wrlinux_test.go new file mode 100644 index 00000000..7574f40c --- /dev/null +++ b/wrlinux/wrlinux_test.go @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2022 Wind River Systems, Inc. + * + * The right to copy, distribute, modify, or otherwise make use + * of this software may be licensed only pursuant to the terms + * of an applicable Wind River license agreement. + */ + +package wrlinux + +import ( + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_parse(t *testing.T) { + type args struct { + filePath string + } + testCases := []struct { + name string + args args + want *Vulnerability + wantErr error + }{ + { + name: "perfect data", + args: args{ + filePath: "./testdata/golden", + }, + want: &Vulnerability{ + Candidate: "CVE-2020-24241", + PublicDate: time.Date(2020, 8, 25, 0, 0, 0, 0, time.UTC), + Description: "In Netwide Assembler (NASM) 2.15rc10, there is heap use-after-free in saa_wbytes in nasmlib/saa.c.", + References: []string{}, + Notes: []string{}, + Priority: "medium", + Bugs: []string{ + "LINCD-2974", + "LIN1019-5289", + "LIN1018-6614", + "LIN10-7689", + }, + Patches: map[Package]Statuses{ + Package("nasm"): { + "10.17.41.1": { + "Status": "released", + "Note": "10.17.41.22" + }, + "10.18.44.1": { + "Status": "ignored", + "Note": "" + }, + 10.19.45.1": { + "Status": "pending", + "Note": "" + }, + "10.20.6.0": { + "Status": "not-affected", + "Note": "" + }, + }, + }, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + f, err := os.Open(tc.args.filePath) + require.NoError(t, err) + defer f.Close() + + got, gotErr := parse(f) + assert.Equal(t, tc.wantErr, gotErr) + assert.Equal(t, tc.want, got) + }) + } +}