Initialize module and dependencies
This commit is contained in:
261
vendor/golang.org/x/vuln/internal/openvex/handler.go
generated
vendored
Normal file
261
vendor/golang.org/x/vuln/internal/openvex/handler.go
generated
vendored
Normal file
@@ -0,0 +1,261 @@
|
||||
// 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 (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"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
|
||||
sbom *govulncheck.SBOM
|
||||
osvs map[string]*osv.Entry
|
||||
// findings contains same-level findings for an
|
||||
// OSV at the most precise level of granularity
|
||||
// available. This means, for instance, that if
|
||||
// an osv is indeed called, then all findings for
|
||||
// the osv will have call stack info.
|
||||
findings map[string][]*govulncheck.Finding
|
||||
}
|
||||
|
||||
func NewHandler(w io.Writer) *handler {
|
||||
return &handler{
|
||||
w: w,
|
||||
osvs: make(map[string]*osv.Entry),
|
||||
findings: make(map[string][]*govulncheck.Finding),
|
||||
}
|
||||
}
|
||||
|
||||
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) SBOM(s *govulncheck.SBOM) error {
|
||||
h.sbom = s
|
||||
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
|
||||
}
|
||||
|
||||
// moreSpecific favors a call finding over a non-call
|
||||
// finding and a package finding over a module finding.
|
||||
func moreSpecific(f1, f2 *govulncheck.Finding) int {
|
||||
if len(f1.Trace) > 1 && len(f2.Trace) > 1 {
|
||||
// Both are call stack findings.
|
||||
return 0
|
||||
}
|
||||
if len(f1.Trace) > 1 {
|
||||
return -1
|
||||
}
|
||||
if len(f2.Trace) > 1 {
|
||||
return 1
|
||||
}
|
||||
|
||||
fr1, fr2 := f1.Trace[0], f2.Trace[0]
|
||||
if fr1.Function != "" && fr2.Function == "" {
|
||||
return -1
|
||||
}
|
||||
if fr1.Function == "" && fr2.Function != "" {
|
||||
return 1
|
||||
}
|
||||
if fr1.Package != "" && fr2.Package == "" {
|
||||
return -1
|
||||
}
|
||||
if fr1.Package == "" && fr2.Package != "" {
|
||||
return -1
|
||||
}
|
||||
return 0 // findings always have module info
|
||||
}
|
||||
|
||||
func (h *handler) Finding(f *govulncheck.Finding) error {
|
||||
fs := h.findings[f.OSV]
|
||||
if len(fs) == 0 {
|
||||
fs = []*govulncheck.Finding{f}
|
||||
} else {
|
||||
if ms := moreSpecific(f, fs[0]); ms == -1 {
|
||||
// The new finding is more specific, so we need
|
||||
// to erase existing findings and add the new one.
|
||||
fs = []*govulncheck.Finding{f}
|
||||
} else if ms == 0 {
|
||||
// The new finding is at the same level of precision.
|
||||
fs = append(fs, f)
|
||||
}
|
||||
// Otherwise, the new finding is at a less precise level.
|
||||
}
|
||||
h.findings[f.OSV] = fs
|
||||
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(h)
|
||||
out, err := json.MarshalIndent(doc, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = h.w.Write(out)
|
||||
return err
|
||||
}
|
||||
|
||||
func toVex(h *handler) Document {
|
||||
doc := Document{
|
||||
Context: ContextURI,
|
||||
Author: DefaultAuthor,
|
||||
Timestamp: time.Now().UTC(),
|
||||
Version: 1,
|
||||
Tooling: Tooling,
|
||||
Statements: statements(h),
|
||||
}
|
||||
|
||||
id := hashVex(doc)
|
||||
doc.ID = "govulncheck/vex:" + id
|
||||
return doc
|
||||
}
|
||||
|
||||
// Given a slice of findings, returns those findings as a set of subcomponents
|
||||
// that are unique per the vulnerable artifact's PURL.
|
||||
func subcomponentSet(findings []*govulncheck.Finding) []Component {
|
||||
var scs []Component
|
||||
seen := make(map[string]bool)
|
||||
for _, f := range findings {
|
||||
purl := purlFromFinding(f)
|
||||
if !seen[purl] {
|
||||
scs = append(scs, Component{
|
||||
ID: purlFromFinding(f),
|
||||
})
|
||||
seen[purl] = true
|
||||
}
|
||||
}
|
||||
return scs
|
||||
}
|
||||
|
||||
// statements combines all OSVs found by govulncheck and generates the list of
|
||||
// vex statements with the proper affected level and justification to match the
|
||||
// openVex specification.
|
||||
func statements(h *handler) []Statement {
|
||||
var scanLevel findingLevel
|
||||
switch h.cfg.ScanLevel {
|
||||
case govulncheck.ScanLevelModule:
|
||||
scanLevel = required
|
||||
case govulncheck.ScanLevelPackage:
|
||||
scanLevel = imported
|
||||
case govulncheck.ScanLevelSymbol:
|
||||
scanLevel = called
|
||||
}
|
||||
|
||||
var statements []Statement
|
||||
for id, osv := range h.osvs {
|
||||
// if there are no findings emitted for a given OSV that means that
|
||||
// the vulnerable module is not required at a vulnerable version.
|
||||
if len(h.findings[id]) == 0 {
|
||||
continue
|
||||
}
|
||||
description := osv.Summary
|
||||
if description == "" {
|
||||
description = osv.Details
|
||||
}
|
||||
|
||||
s := Statement{
|
||||
Vulnerability: Vulnerability{
|
||||
ID: fmt.Sprintf("https://pkg.go.dev/vuln/%s", id),
|
||||
Name: id,
|
||||
Description: description,
|
||||
Aliases: osv.Aliases,
|
||||
},
|
||||
Products: []Product{
|
||||
{
|
||||
Component: Component{ID: DefaultPID},
|
||||
Subcomponents: subcomponentSet(h.findings[id]),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Findings are guaranteed to be at the same level, so we can just check the first element
|
||||
fLevel := foundAtLevel(h.findings[id][0])
|
||||
if fLevel >= scanLevel {
|
||||
s.Status = StatusAffected
|
||||
} else {
|
||||
s.Status = StatusNotAffected
|
||||
s.ImpactStatement = Impact
|
||||
s.Justification = JustificationNotPresent
|
||||
// We only reach this case if running in symbol mode
|
||||
if fLevel == imported {
|
||||
s.Justification = JustificationNotExecuted
|
||||
}
|
||||
}
|
||||
statements = append(statements, s)
|
||||
}
|
||||
|
||||
slices.SortFunc(statements, func(a, b Statement) int {
|
||||
if a.Vulnerability.ID > b.Vulnerability.ID {
|
||||
return 1
|
||||
}
|
||||
if a.Vulnerability.ID < b.Vulnerability.ID {
|
||||
return -1
|
||||
}
|
||||
// this should never happen in practice, since statements are being
|
||||
// populated from a map with the vulnerability IDs as keys
|
||||
return 0
|
||||
})
|
||||
return statements
|
||||
}
|
||||
|
||||
func hashVex(doc Document) string {
|
||||
// json.Marshal should never error here (because of the structure of Document).
|
||||
// If an error does occur, it won't be a jsonerror, but instead a panic
|
||||
d := Document{
|
||||
Context: doc.Context,
|
||||
ID: doc.ID,
|
||||
Author: doc.Author,
|
||||
Version: doc.Version,
|
||||
Tooling: doc.Tooling,
|
||||
Statements: doc.Statements,
|
||||
}
|
||||
out, err := json.Marshal(d)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return fmt.Sprintf("%x", sha256.Sum256(out))
|
||||
}
|
||||
46
vendor/golang.org/x/vuln/internal/openvex/purl.go
generated
vendored
Normal file
46
vendor/golang.org/x/vuln/internal/openvex/purl.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// 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 (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/vuln/internal/govulncheck"
|
||||
)
|
||||
|
||||
// The PURL is printed as: pkg:golang/MODULE_PATH@VERSION
|
||||
// Conceptually there is no namespace and the name is entirely defined by
|
||||
// the module path. See https://github.com/package-url/purl-spec/issues/63
|
||||
// for further disucssion.
|
||||
|
||||
const suffix = "pkg:golang/"
|
||||
|
||||
type purl struct {
|
||||
name string
|
||||
version string
|
||||
}
|
||||
|
||||
func (p *purl) String() string {
|
||||
var b strings.Builder
|
||||
b.WriteString(suffix)
|
||||
b.WriteString(url.PathEscape(p.name))
|
||||
if p.version != "" {
|
||||
b.WriteString("@")
|
||||
b.WriteString(p.version)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// purlFromFinding takes a govulncheck finding and generates a purl to the
|
||||
// vulnerable dependency.
|
||||
func purlFromFinding(f *govulncheck.Finding) string {
|
||||
purl := purl{
|
||||
name: f.Trace[0].Module,
|
||||
version: f.Trace[0].Version,
|
||||
}
|
||||
|
||||
return purl.String()
|
||||
}
|
||||
113
vendor/golang.org/x/vuln/internal/openvex/vex.go
generated
vendored
Normal file
113
vendor/golang.org/x/vuln/internal/openvex/vex.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
// 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 vex defines the Vulnerability EXchange Format (VEX) types
|
||||
// supported by govulncheck.
|
||||
//
|
||||
// These types match the OpenVEX standard. See https://github.com/openvex for
|
||||
// more information on VEX and OpenVEX.
|
||||
//
|
||||
// This is intended to be the minimimal amount of information required to output
|
||||
// a complete VEX document according to the specification.
|
||||
package openvex
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
ContextURI = "https://openvex.dev/ns/v0.2.0"
|
||||
Tooling = "https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck"
|
||||
Impact = "Govulncheck determined that the vulnerable code isn't called"
|
||||
|
||||
DefaultAuthor = "Unknown Author"
|
||||
DefaultPID = "Unknown Product"
|
||||
|
||||
// The following are defined by the VEX standard.
|
||||
StatusAffected = "affected"
|
||||
StatusNotAffected = "not_affected"
|
||||
|
||||
// The following are defined by the VEX standard.
|
||||
JustificationNotExecuted = "vulnerable_code_not_in_execute_path"
|
||||
JustificationNotPresent = "vulnerable_code_not_present"
|
||||
)
|
||||
|
||||
// Document is the top-level struct for a VEX document.
|
||||
type Document struct {
|
||||
// Context is an IRI pointing to the version of openVEX being used by the doc
|
||||
// For govulncheck, it will always be https://openvex.dev/ns/v0.2.0
|
||||
Context string `json:"@context,omitempty"`
|
||||
|
||||
// ID is the identifying string for the VEX document.
|
||||
// govulncheck/vex-[content-based-hash]
|
||||
ID string `json:"@id,omitempty"`
|
||||
|
||||
// Author is the identifier for the author of the VEX statement.
|
||||
// Govulncheck will leave this field default (Unknown author) to be filled in by the user.
|
||||
Author string `json:"author,omitempty"`
|
||||
|
||||
// Timestamp defines the time at which the document was issued.
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
|
||||
// Version is the document version. For govulncheck's output, this will always be 1.
|
||||
Version int `json:"version,omitempty"`
|
||||
|
||||
// Tooling expresses how the VEX document and contained VEX statements were
|
||||
// generated. In this case, it will always be:
|
||||
// "https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck"
|
||||
Tooling string `json:"tooling,omitempty"`
|
||||
|
||||
// Statements are all statements for a given govulncheck output.
|
||||
// Each OSV emitted by govulncheck will have a corresponding statement.
|
||||
Statements []Statement `json:"statements,omitempty"`
|
||||
}
|
||||
|
||||
// Statement conveys a single status for a single vulnerability for one or more products.
|
||||
type Statement struct {
|
||||
// Vulnerability is the vuln being referenced by the statement.
|
||||
Vulnerability Vulnerability `json:"vulnerability,omitempty"`
|
||||
|
||||
// Products are the products associated with the given vulnerability in the statement.
|
||||
Products []Product `json:"products,omitempty"`
|
||||
|
||||
// The status of the vulnerability. Will be either not_affected or affected for govulncheck.
|
||||
Status string `json:"status,omitempty"`
|
||||
|
||||
// If the status is not_affected, this must be filled. The official VEX justification that
|
||||
// best matches govulncheck's vuln filtering is "vulnerable_code_not_in_execute_path"
|
||||
Justification string `json:"justification,omitempty"`
|
||||
|
||||
// If the status is not_affected, this must be filled. For govulncheck, this will always be:
|
||||
// "Govulncheck determined that the vulnerable code isn't called"
|
||||
ImpactStatement string `json:"impact_statement,omitempty"`
|
||||
}
|
||||
|
||||
// Vulnerability captures a vulnerability and its identifiers/aliases.
|
||||
type Vulnerability struct {
|
||||
// ID is a URI that in govulncheck's case points to the govulndb link for the vulnerability.
|
||||
// I.E. https://pkg.go.dev/vuln/GO-2024-2497
|
||||
ID string `json:"@id,omitempty"`
|
||||
|
||||
// Name is the main identifier for the vulnerability (GO-YYYY-XXXX)
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// Description is a short text description of the vulnerability.
|
||||
// It will be populated from the 'summary' field of the vuln's OSV if it exists,
|
||||
// and the 'description' field of the osv if a summary isn't present.
|
||||
Description string `json:"description,omitempty"`
|
||||
|
||||
// Aliases a list of identifiers that other systems are using to track the vulnerability.
|
||||
// I.E. GHSA or CVE ids.
|
||||
Aliases []string `json:"aliases,omitempty"`
|
||||
}
|
||||
|
||||
// Product identifies the products associated with the given vuln.
|
||||
type Product struct {
|
||||
// The main product ID will remian default for now.
|
||||
Component
|
||||
// The subcomponent ID will be a PURL to the vulnerable dependency.
|
||||
Subcomponents []Component `json:"subcomponents,omitempty"`
|
||||
}
|
||||
|
||||
type Component struct {
|
||||
ID string `json:"@id,omitempty"`
|
||||
}
|
||||
Reference in New Issue
Block a user