From ad5a6f82ec98d81af81c6c4ca5aa64a44d0959f4 Mon Sep 17 00:00:00 2001 From: Maceo Thompson Date: Fri, 22 Mar 2024 11:35:41 -0600 Subject: [PATCH] internal/openvex: add handler updates golang/go#62486 Change-Id: Ib1cd9281cf33fb84a8a3c0f3e7523cfb8d93e677 Reviewed-on: https://go-review.googlesource.com/c/vuln/+/575858 Reviewed-by: Zvonimir Pavlinovic LUCI-TryBot-Result: Go LUCI --- cmd/govulncheck/testdata/common/config.json | 4 + .../testfiles/binary-call/binary_vex.ct | 11 +++ .../testfiles/source-call/source_call_vex.ct | 11 +++ internal/openvex/handler.go | 98 +++++++++++++++++++ internal/openvex/handler_test.go | 70 +++++++++++++ internal/scan/flags.go | 16 +-- internal/scan/run.go | 3 + 7 files changed, 206 insertions(+), 7 deletions(-) create mode 100644 cmd/govulncheck/testdata/common/testfiles/binary-call/binary_vex.ct create mode 100644 cmd/govulncheck/testdata/common/testfiles/source-call/source_call_vex.ct create mode 100644 internal/openvex/handler.go create mode 100644 internal/openvex/handler_test.go diff --git a/cmd/govulncheck/testdata/common/config.json b/cmd/govulncheck/testdata/common/config.json index b4eb324..911f4e3 100644 --- a/cmd/govulncheck/testdata/common/config.json +++ b/cmd/govulncheck/testdata/common/config.json @@ -35,6 +35,10 @@ { "pattern": "\"go_version\": \"go[^\\s\"]*\"", "replace": "\"go_version\": \"go1.18\"" + }, + { + "pattern": "\"timestamp\": (.*),", + "replace": "\"timestamp\": \"2024-01-01T00:00:00\"," } ] } diff --git a/cmd/govulncheck/testdata/common/testfiles/binary-call/binary_vex.ct b/cmd/govulncheck/testdata/common/testfiles/binary-call/binary_vex.ct new file mode 100644 index 0000000..b936d3d --- /dev/null +++ b/cmd/govulncheck/testdata/common/testfiles/binary-call/binary_vex.ct @@ -0,0 +1,11 @@ +##### +# Test basic binary scanning with vex output +$ govulncheck -format openvex -mode binary ${common_vuln_binary} +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "@id": "govulncheckVEX", + "author": "Unknown Author", + "timestamp": "2024-01-01T00:00:00", + "version": 1, + "tooling": "https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck" +} diff --git a/cmd/govulncheck/testdata/common/testfiles/source-call/source_call_vex.ct b/cmd/govulncheck/testdata/common/testfiles/source-call/source_call_vex.ct new file mode 100644 index 0000000..c747ab9 --- /dev/null +++ b/cmd/govulncheck/testdata/common/testfiles/source-call/source_call_vex.ct @@ -0,0 +1,11 @@ +##### +# Test vex json output +$ govulncheck -C ${moddir}/vuln -format openvex ./... +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "@id": "govulncheckVEX", + "author": "Unknown Author", + "timestamp": "2024-01-01T00:00:00", + "version": 1, + "tooling": "https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck" +} diff --git a/internal/openvex/handler.go b/internal/openvex/handler.go new file mode 100644 index 0000000..cc223bf --- /dev/null +++ b/internal/openvex/handler.go @@ -0,0 +1,98 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package openvex + +import ( + "encoding/json" + "io" + "time" + + "golang.org/x/vuln/internal/govulncheck" + "golang.org/x/vuln/internal/osv" +) + +type findingLevel int + +const ( + invalid findingLevel = iota + required + imported + called +) + +type handler struct { + w io.Writer + cfg *govulncheck.Config + osvs map[string]*osv.Entry + levels map[string]findingLevel +} + +func NewHandler(w io.Writer) *handler { + return &handler{ + w: w, + osvs: make(map[string]*osv.Entry), + levels: make(map[string]findingLevel), + } +} + +func (h *handler) Config(cfg *govulncheck.Config) error { + h.cfg = cfg + return nil +} + +func (h *handler) Progress(progress *govulncheck.Progress) error { + return nil +} + +func (h *handler) OSV(e *osv.Entry) error { + h.osvs[e.ID] = e + return nil +} + +// foundAtLevel returns the level at which a specific finding is present in the +// scanned product. +func foundAtLevel(f *govulncheck.Finding) findingLevel { + frame := f.Trace[0] + if frame.Function != "" { + return called + } + if frame.Package != "" { + return imported + } + return required +} + +func (h *handler) Finding(f *govulncheck.Finding) error { + fLevel := foundAtLevel(f) + if fLevel > h.levels[f.OSV] { + h.levels[f.OSV] = fLevel + } + return nil +} + +// Flush is used to print the vex json to w. +// This is needed as vex is not streamed. +func (h *handler) Flush() error { + doc := toVex() + out, err := json.MarshalIndent(doc, "", " ") + if err != nil { + return err + } + _, err = h.w.Write(out) + return err +} + +func toVex() Document { + doc := Document{ + ID: "govulncheckVEX", // TODO: create hash from document for ID + Context: ContextURI, + Author: DefaultAuthor, + Timestamp: time.Now().UTC(), + Version: 1, + Tooling: Tooling, + //TODO: Add statements + } + return doc +} diff --git a/internal/openvex/handler_test.go b/internal/openvex/handler_test.go new file mode 100644 index 0000000..82f12ba --- /dev/null +++ b/internal/openvex/handler_test.go @@ -0,0 +1,70 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package openvex + +import ( + "testing" + + "golang.org/x/vuln/internal/govulncheck" +) + +func TestFinding(t *testing.T) { + const id1 = "ID1" + tests := []struct { + name string + id string + findings []*govulncheck.Finding + want findingLevel + }{ + { + name: "multiple", + id: id1, + findings: []*govulncheck.Finding{ + { + OSV: id1, + Trace: []*govulncheck.Frame{ + { + Module: "mod", + Package: "pkg", + }, + }, + }, + { + OSV: id1, + Trace: []*govulncheck.Frame{ + { + Module: "mod", + Package: "pkg", + Function: "func", + }, + }, + }, + { + OSV: id1, + Trace: []*govulncheck.Frame{ + { + Module: "mod", + }, + }, + }, + }, + want: called, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + h := NewHandler(nil) + for _, f := range tt.findings { + if err := h.Finding(f); err != nil { + t.Errorf("handler.Finding() error = %v", err) + } + } + got := h.levels[tt.id] + if got != tt.want { + t.Errorf("want %v; got %v", tt.want, got) + } + }) + } +} diff --git a/internal/scan/flags.go b/internal/scan/flags.go index 210e1b9..0f3c677 100644 --- a/internal/scan/flags.go +++ b/internal/scan/flags.go @@ -236,16 +236,18 @@ func (v ShowFlag) Update(h *TextHandler) { type FormatFlag string const ( - formatUnset = "" - formatJSON = "json" - formatText = "text" - formatSarif = "sarif" + formatUnset = "" + formatJSON = "json" + formatText = "text" + formatSarif = "sarif" + formatOpenVEX = "openvex" ) var supportedFormats = map[string]bool{ - formatJSON: true, - formatText: true, - formatSarif: true, + formatJSON: true, + formatText: true, + formatSarif: true, + formatOpenVEX: true, } func (f *FormatFlag) Get() interface{} { return *f } diff --git a/internal/scan/run.go b/internal/scan/run.go index 5440204..9fef5d7 100644 --- a/internal/scan/run.go +++ b/internal/scan/run.go @@ -18,6 +18,7 @@ import ( "golang.org/x/telemetry/counter" "golang.org/x/vuln/internal/client" "golang.org/x/vuln/internal/govulncheck" + "golang.org/x/vuln/internal/openvex" "golang.org/x/vuln/internal/sarif" ) @@ -42,6 +43,8 @@ func RunGovulncheck(ctx context.Context, env []string, r io.Reader, stdout io.Wr handler = govulncheck.NewJSONHandler(stdout) case formatSarif: handler = sarif.NewHandler(stdout) + case formatOpenVEX: + handler = openvex.NewHandler(stdout) default: th := NewTextHandler(stdout) cfg.show.Update(th)