From 6b0fd5601b4a69285e464ec05957800bf63e4d0e Mon Sep 17 00:00:00 2001 From: Zvonimir Pavlinovic Date: Mon, 22 Apr 2024 21:57:18 +0000 Subject: [PATCH] internal/sarif,cmd/govulncheck: publicize sarif Also, add and improve documentation. Fixes golang/go#61347 Change-Id: Ia631615b40b9d23be6efa615b573be6c465fa36b Reviewed-on: https://go-review.googlesource.com/c/vuln/+/580955 LUCI-TryBot-Result: Go LUCI TryBot-Result: Gopher Robot Reviewed-by: Ian Cottrell Run-TryBot: Zvonimir Pavlinovic --- cmd/govulncheck/doc.go | 23 ++++-- .../testdata/common/testfiles/usage/usage.ct | 2 +- internal/sarif/sarif.go | 82 +++++++++++++------ internal/scan/flags.go | 2 +- 4 files changed, 73 insertions(+), 36 deletions(-) diff --git a/cmd/govulncheck/doc.go b/cmd/govulncheck/doc.go index 18229fa..dbfb957 100644 --- a/cmd/govulncheck/doc.go +++ b/cmd/govulncheck/doc.go @@ -9,10 +9,10 @@ only those that could affect the application. By default, govulncheck makes requests to the Go vulnerability database at https://vuln.go.dev. Requests to the vulnerability database contain only module -paths, not code or other properties of your program. See -https://vuln.go.dev/privacy.html for more. Use the -db flag to specify a -different database, which must implement the specification at -https://go.dev/security/vuln/database. +paths with vulnerabilities already known to the database, not code or other +properties of your program. See https://vuln.go.dev/privacy.html for more. +Use the -db flag to specify a different database, which must implement the +specification at https://go.dev/security/vuln/database. Govulncheck looks for vulnerabilities in Go programs using a specific build configuration. For analyzing source code, that configuration is the Go version @@ -59,12 +59,21 @@ information needed to analyze the binary. This will produce a blob, typically mu smaller than the binary, that can also be passed to govulncheck as an argument with '-mode binary'. The users should not rely on the contents or representation of the blob. -Govulncheck exits successfully (exit code 0) if there are no vulnerabilities, -and exits unsuccessfully if there are. It also exits successfully if the -json flag -(or '-format json') is provided, regardless of the number of detected vulnerabilities. +# Integrations Govulncheck supports streaming JSON. For more details, please see [golang.org/x/vuln/internal/govulncheck]. +Govulncheck also supports Static Analysis Results Interchange Format (SARIF) output +format, following the specification at https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=sarif. +For more details, please see [golang.org/x/vuln/internal/sarif]. + +# Exit codes + +Govulncheck exits successfully (exit code 0) if there are no vulnerabilities, +and exits unsuccessfully if there are. It also exits successfully if the +'format -json' ('-json') or '-format sarif' is provided, regardless of the number +of detected vulnerabilities. + # Limitations Govulncheck has these limitations: diff --git a/cmd/govulncheck/testdata/common/testfiles/usage/usage.ct b/cmd/govulncheck/testdata/common/testfiles/usage/usage.ct index 26bea5a..7d5a143 100644 --- a/cmd/govulncheck/testdata/common/testfiles/usage/usage.ct +++ b/cmd/govulncheck/testdata/common/testfiles/usage/usage.ct @@ -14,7 +14,7 @@ Usage: vulnerability database url (default "https://vuln.go.dev") -format value specify format output - The supported values are 'text' and 'json' (default 'text') + The supported values are 'text', 'json', and 'sarif' (default 'text') -json output JSON (Go compatible legacy flag, see format flag) -mode value diff --git a/internal/sarif/sarif.go b/internal/sarif/sarif.go index fb1c5b7..b434f1d 100644 --- a/internal/sarif/sarif.go +++ b/internal/sarif/sarif.go @@ -8,10 +8,41 @@ // The implementation covers the subset of the specification available // at https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=sarif. // -// If govulncheck is used in source mode, the locations will include a -// physical location implemented as a path relative to either the source -// module (%SRCROOT%), Go root (%GOROOT%), or Go module cache (%GOMODCACHE%) -// URI base id. +// The sarif encoding models govulncheck findings as Results. Each +// Result encodes findings for a unique OSV entry at the most precise +// detected level only. CodeFlows summarize call stacks, similar to govulncheck +// textual output, while Stacks contain call stack information verbatim. +// +// The result Levels are defined by the govulncheck.ScanLevel and the most +// precise level at which the finding was detected. Result error is produced +// when the finding level matches the user desired level of scan precision; +// all other finding levels are then classified as progressively weaker. +// For instance, if the user specified symbol scan level and govulncheck +// detected a use of a vulnerable symbol, then the Result will have error +// Level. If the symbol was not used but its package was imported, then the +// Result Level is warning, and so on. +// +// Each Result is attached to the first line of the go.mod file. Other +// ArtifactLocations are paths relative to their enclosing modules. +// Similar to JSON output format, this makes govulncheck sarif locations +// portable. +// +// The relative paths in PhysicalLocations also come with a URIBaseID offset. +// Paths for the source module analyzed, the Go standard library, and third-party +// dependencies are relative to %SRCROOT%, %GOROOT%, and %GOMODCACHE% offsets, +// resp. We note that the URIBaseID offsets are not explicitly defined in +// the sarif output. It is the clients responsibility to set them to resolve +// paths at their local machines. +// +// All paths use "/" delimiter for portability. +// +// Properties field of a Tool.Driver is a govulncheck.Config used for the +// invocation of govulncheck producing the Results. Properties field of +// a Rule contains information on CVE and GHSA aliases for the corresponding +// rule OSV. Clients can use this information to, say, supress and filter +// vulnerabilities. +// +// Please see the definition of types below for more information. package sarif import "golang.org/x/vuln/internal/govulncheck" @@ -34,7 +65,7 @@ type Log struct { type Run struct { Tool Tool `json:"tool,omitempty"` // Results contain govulncheck findings. There should be exactly one - // Result per a detected OSV. + // Result per a detected use of an OSV. Results []Result `json:"results,omitempty"` } @@ -45,11 +76,11 @@ type Tool struct { // Driver provides details about the govulncheck binary being executed. type Driver struct { - // Name should be "govulncheck" + // Name is "govulncheck" Name string `json:"name,omitempty"` - // Version should be the govulncheck version + // Version is the govulncheck version Version string `json:"semanticVersion,omitempty"` - // InformationURI should point to the description of govulncheck tool + // InformationURI points to the description of govulncheck tool InformationURI string `json:"informationUri,omitempty"` // Properties are govulncheck run metadata, such as vuln db, Go version, etc. Properties govulncheck.Config `json:"properties,omitempty"` @@ -66,9 +97,9 @@ type Rule struct { FullDescription Description `json:"fullDescription,omitempty"` Help Description `json:"help,omitempty"` HelpURI string `json:"helpUri,omitempty"` - // Properties should contain OSV.Aliases (CVEs and GHSAs) as tags. + // Properties contain OSV.Aliases (CVEs and GHSAs) as tags. // Consumers of govulncheck SARIF can use these tags to filter - // results based on, say, CVEs. + // results. Properties RuleTags `json:"properties,omitempty"` } @@ -85,27 +116,28 @@ type Description struct { // Result is a set of govulncheck findings for an OSV. For call stack // mode, it will contain call stacks for the OSV. There is exactly -// one Result per detected OSV. Only findings at the lowest possible -// level appear in the Result. For instance, if there are findings -// with call stacks for an OSV, those findings will be in the Result, -// but not the “imports” and “requires” findings for the same OSV. +// one Result per detected OSV. Only findings at the most precise +// detected level appear in the Result. For instance, if there are +// symbol findings for an OSV, those findings will be in the Result, +// but not the package and module level findings for the same OSV. type Result struct { // RuleID is the Rule.ID/OSV producing the finding. RuleID string `json:"ruleId,omitempty"` - // Level is one of "error", "warning", "note", and "none". + // Level is one of "error", "warning", and "note". Level string `json:"level,omitempty"` // Message explains the overall findings. Message Description `json:"message,omitempty"` - // Locations to which the findings are associated. + // Locations to which the findings are associated. Always + // a single location pointing to the first line of the go.mod + // file. The path to the file is "go.mod". Locations []Location `json:"locations,omitempty"` - // CodeFlows can encode call stacks produced by govulncheck. + // CodeFlows summarize call stacks produced by govulncheck. CodeFlows []CodeFlow `json:"codeFlows,omitempty"` - // Stacks can encode call stacks produced by govulncheck. + // Stacks encode call stacks produced by govulncheck. Stacks []Stack `json:"stacks,omitempty"` - // TODO: support Fixes when integration points to the same } -// CodeFlow describes a detected offending flow of information in terms of +// CodeFlow summarizes a detected offending flow of information in terms of // code locations. More precisely, it can contain several related information // flows, keeping them together. In govulncheck, those can be all call stacks // for, say, a particular symbol or package. @@ -128,8 +160,6 @@ type ThreadFlowLocation struct { Module string `json:"module,omitempty"` // Location also contains a Message field. Location Location `json:"location,omitempty"` - // Can also contain Stack field that encodes a call stack - // leading to this thread flow location. } // Stack is a sequence of frames and can encode a govulncheck call stack. @@ -167,12 +197,10 @@ const ( // ArtifactLocation is a path to an offending file. type ArtifactLocation struct { - // URI is a path to the artifact. If URIBaseID is empty, then - // URI is absolute and it needs to start with, say, "file://." + // URI is a path relative to URIBaseID. URI string `json:"uri,omitempty"` - // URIBaseID is offset for URI. An example is %SRCROOT%, used by - // Github Code Scanning to point to the root of the target repo. - // Its value must be defined in URIBaseIDs of a Run. + // URIBaseID is offset for URI, one of %SRCROOT%, %GOROOT%, + // and %GOMODCACHE%. URIBaseID string `json:"uriBaseId,omitempty"` } diff --git a/internal/scan/flags.go b/internal/scan/flags.go index 482620f..210e1b9 100644 --- a/internal/scan/flags.go +++ b/internal/scan/flags.go @@ -42,7 +42,7 @@ func parseFlags(cfg *config, stderr io.Writer, args []string) error { flags.Var(&modeFlag, "mode", "supports 'source', 'binary', and 'extract' (default 'source')") flags.Var(&cfg.tags, "tags", "comma-separated `list` of build tags") flags.Var(&cfg.show, "show", "enable display of additional information specified by the comma separated `list`\nThe supported values are 'traces','color', 'version', and 'verbose'") - flags.Var(&cfg.format, "format", "specify format output\nThe supported values are 'text' and 'json' (default 'text')") + flags.Var(&cfg.format, "format", "specify format output\nThe supported values are 'text', 'json', and 'sarif' (default 'text')") flags.BoolVar(&version, "version", false, "print the version information") flags.Var(&scanFlag, "scan", "set the scanning level desired, one of 'module', 'package', or 'symbol' (default 'symbol')")