pax_global_header00006660000000000000000000000064144674545150014531gustar00rootroot0000000000000052 comment=da4b74a5408a0116e9a2dde953659a7b0956dc56 vuln-1.0.1/000077500000000000000000000000001446745451500125145ustar00rootroot00000000000000vuln-1.0.1/.gitignore000066400000000000000000000000771446745451500145100ustar00rootroot00000000000000**/.terraform/* .terraform.lock.hcl terraform/terraform.tfvars vuln-1.0.1/CONTRIBUTING.md000066400000000000000000000017431446745451500147520ustar00rootroot00000000000000# Contributing to the Go Vulnerability Database Go is an open source project. It is the work of hundreds of contributors. We appreciate your help! ## Reporting a vulnerability To report a new *public* vulnerability, [open an issue](https://github.com/golang/vulndb/issues/new), send a GitHub PR, or mail a Gerrit CL. Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html) before sending patches. ## Contributor License Agreement Contributions to this project must be accompanied by a Contributor License Agreement (CLA). You (or your employer) retain the copyright to your contribution; this simply gives us permission to use and redistribute your contributions as part of the project. Head over to to see your current agreements on file or to sign a new one. You generally only need to submit a CLA once, so if you've already submitted one (even if it was for a different project), you probably don't need to do it again. vuln-1.0.1/LICENSE000066400000000000000000000027071446745451500135270ustar00rootroot00000000000000Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. vuln-1.0.1/PATENTS000066400000000000000000000024271446745451500135620ustar00rootroot00000000000000Additional IP Rights Grant (Patents) "This implementation" means the copyrightable works distributed by Google as part of the Go project. Google hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, transfer and otherwise run, modify and propagate the contents of this implementation of Go, where such license applies only to those patent claims, both currently owned or controlled by Google and acquired in the future, licensable by Google that are necessarily infringed by this implementation of Go. This grant does not include claims that would be infringed only as a consequence of further modification of this implementation. If you or your agent or exclusive licensee institute or order or agree to the institution of patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that this implementation of Go or any code incorporated within this implementation of Go constitutes direct or contributory patent infringement, or inducement of patent infringement, then any patent rights granted to you under this License for this implementation of Go shall terminate as of the date such litigation is filed. vuln-1.0.1/README.md000066400000000000000000000030331446745451500137720ustar00rootroot00000000000000# Go Vulnerability Management [![Go Reference](https://pkg.go.dev/badge/golang.org/x/vuln.svg)](https://pkg.go.dev/golang.org/x/vuln) Go's support for vulnerability management includes tooling for analyzing your codebase and binaries to surface known vulnerabilities in your dependencies. This tooling is backed by the Go vulnerability database, which is curated by the Go security team. Go’s tooling reduces noise in your results by only surfacing vulnerabilities in functions that your code is actually calling. You can install the latest version of govulncheck using [go install](https://pkg.go.dev/cmd/go#hdr-Compile_and_install_packages_and_dependencies) ``` go install golang.org/x/vuln/cmd/govulncheck@latest ``` Then, run govulncheck inside your module: ``` govulncheck ./... ``` See [the govulncheck tutorial](https://go.dev/doc/tutorial/govulncheck) to get started, and [https://go.dev/security/vuln](https://go.dev/security/vuln) for more information about Go's support for vulnerability management. The API documentation can be found at [https://pkg.go.dev/golang.org/x/vuln/scan](https://pkg.go.dev/golang.org/x/vuln/scan). ## Privacy Policy The privacy policy for `govulncheck` can be found at [https://vuln.go.dev/privacy](https://vuln.go.dev/privacy). ## License Unless otherwise noted, the Go source files are distributed under the BSD-style license found in the LICENSE file. Database entries available at https://vuln.go.dev are distributed under the terms of the [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) license. vuln-1.0.1/all_test.go000066400000000000000000000066501446745451500146610ustar00rootroot00000000000000// Copyright 2021 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. //go:build go1.17 && !windows // +build go1.17,!windows package main import ( "bufio" "context" "io/fs" "os" "os/exec" "regexp" "strings" "testing" "golang.org/x/mod/modfile" "golang.org/x/vuln/internal/testenv" "golang.org/x/vuln/scan" "mvdan.cc/unparam/check" ) // excluded contains the set of modules that x/vuln should not depend on. var excluded = map[string]bool{ "golang.org/x/exp": true, } var goHeader = regexp.MustCompile(`^// Copyright 20\d\d 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\.`) func TestBashChecks(t *testing.T) { skipIfShort(t) bash, err := exec.LookPath("bash") if err != nil { t.Skipf("skipping: %v", err) } var cmd *exec.Cmd if os.Getenv("GO_BUILDER_NAME") != "" { cmd = exec.Command(bash, "./checks.bash", "trybots") } else { cmd = exec.Command(bash, "./checks.bash") } cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { t.Fatal(err) } } func TestDependencies(t *testing.T) { dat, err := os.ReadFile("go.mod") if err != nil { t.Fatal(err) } f, err := modfile.Parse("go.mod", dat, nil) if err != nil { t.Fatalf("modfile.Parse: %v", err) } for _, r := range f.Require { // This is used by staticcheck. if strings.HasPrefix(r.Mod.Path, "golang.org/x/exp/typeparams") { continue } for ex := range excluded { if strings.HasPrefix(r.Mod.Path, ex) { t.Errorf("go.mod contains %q as a dependency, which should not happen", r.Mod.Path) } } } } func TestGovulncheck(t *testing.T) { skipIfShort(t) testenv.NeedsGoBuild(t) ctx := context.Background() cmd := scan.Command(ctx, "./...") err := cmd.Start() if err == nil { err = cmd.Wait() } switch err := err.(type) { case nil: case interface{ ExitCode() int }: if err.ExitCode() != 0 { t.Error("govulncheck found problems") } default: t.Error(err) } } func TestStaticCheck(t *testing.T) { skipIfShort(t) rungo(t, "run", "honnef.co/go/tools/cmd/staticcheck@v0.4.3", "./...") } func TestUnparam(t *testing.T) { testenv.NeedsGoBuild(t) warns, err := check.UnusedParams(false, false, false, "./...") if err != nil { t.Fatalf("check.UnusedParams: %v", err) } for _, warn := range warns { t.Errorf(warn) } } func TestVet(t *testing.T) { rungo(t, "vet", "-all", "./...") } func TestMisspell(t *testing.T) { skipIfShort(t) rungo(t, "run", "github.com/client9/misspell/cmd/misspell@v0.3.4", "-error", ".") } func TestHeaders(t *testing.T) { sfs := os.DirFS(".") fs.WalkDir(sfs, ".", func(path string, d fs.DirEntry, _ error) error { if d.IsDir() { if d.Name() == "testdata" { return fs.SkipDir } return nil } if !strings.HasSuffix(path, ".go") { return nil } f, err := sfs.Open(path) if err != nil { return err } defer f.Close() if !goHeader.MatchReader(bufio.NewReader(f)) { t.Errorf("%v: incorrect go header", path) } return nil }) } func rungo(t *testing.T, args ...string) { t.Helper() testenv.NeedsGoBuild(t) cmd := exec.Command("go", args...) if output, err := cmd.CombinedOutput(); err != nil { t.Log("\n" + string(output)) t.Error("command had non zero exit code") } } func skipIfShort(t *testing.T) { if testing.Short() { t.Skipf("skipping: short mode") } } vuln-1.0.1/checks.bash000077500000000000000000000030611446745451500146160ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2021 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. # This file will be run by `go test`. # See all_test.go in this directory. # Ensure that installed go binaries are on the path. # This bash expression follows the algorithm described at the top of # `go install help`: first try $GOBIN, then $GOPATH/bin, then $HOME/go/bin. go_install_dir=${GOBIN:-${GOPATH:-$HOME/go}/bin} PATH=$PATH:$go_install_dir source devtools/lib.sh # check_shellcheck runs shellcheck on .bash and .sh files. check_shellcheck() { if ! [ -x "$(command -v shellcheck)" ]; then echo "Please install shellcheck. See https://github.com/koalaman/shellcheck#installing." fi runcmd shellcheck -x checks.bash runcmd shellcheck ./**/*.sh } go_modtidy() { runcmd go mod tidy } # runchecks runs all checks and is intended to run as a precommit hook. runchecks() { trybots "$@" # These checks only run locally due to a limitation with TryBots. check_shellcheck } # trybots runs checks supported by TryBots. trybots() { go_modtidy } usage() { cat < k8s.txt k8s k8s.txt update_status $? "kubernetes(source)" popd || exit # Clone scanner to a dedicated directory. dir="$GOPATH/src/scanner" if [ -d "$dir" ]; then echo "Destination scanner already exists. Using the existing code." else git clone https://github.com/stackrox/scanner.git "${dir}" fi pushd "$dir" || exit # Use scanner at specific commit and tag version for reproducibility. git checkout 29b8761da747 go build -trimpath -ldflags="-X github.com/stackrox/scanner/pkg/version.Version=2.26-29-g29b8761da7-dirty" -o image/scanner/bin/scanner ./cmd/clair govulncheck -mode=binary --json ./image/scanner/bin/scanner &> scan.txt stackrox-scanner scan.txt update_status $? "stackrox-scanner(binary)" popd || exit if [ ${#failed[@]} -ne 0 ]; then echo "FAIL: integration run failed for the following projects: ${failed[*]}" exit 1 fi echo PASS vuln-1.0.1/cmd/govulncheck/integration/integration_test.sh000077500000000000000000000011651446745451500240360ustar00rootroot00000000000000#!/bin/bash # Copyright 2022 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. # Runs the integration tests for whole program analysis. # Assumes this is run from vuln/cmd/govulncheck/integration echo "Building govulncheck docker image" # The building context is vuln/ so we can have the current # version of both govulncheck and its vuln dependencies docker build -f Dockerfile -t govulncheck-integration ../../../ echo "Running govulncheck integration tests in the docker image" docker run govulncheck-integration ./integration_run.sh vuln-1.0.1/cmd/govulncheck/integration/internal/000077500000000000000000000000001446745451500217265ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/integration/internal/integration/000077500000000000000000000000001446745451500242515ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/integration/internal/integration/test.go000066400000000000000000000030251446745451500255570ustar00rootroot00000000000000// Copyright 2022 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 integration import ( "bytes" "encoding/json" "fmt" "log" "os" "strings" "github.com/google/go-cmp/cmp" "golang.org/x/vuln/internal/govulncheck" ) // CompareNonStdVulns compares vulnerable packages in out and want. // For out, it only considers vulnerabilities outside of the standard // library. Assumes the same for want. func CompareNonStdVulns(out string, want map[string]bool) error { outJson, err := os.ReadFile(out) if err != nil { return fmt.Errorf("failed to read: %v", out) } calledVulnPkgs := make(map[string]bool) dec := json.NewDecoder(bytes.NewReader(outJson)) for dec.More() { msg := govulncheck.Message{} // decode the next message in the stream if err := dec.Decode(&msg); err != nil { log.Fatalf("failed to load json: %v", err) } if msg.Finding != nil { if msg.Finding.Trace[0].Function == "" { // No symbol means the vulnerability is // imported but not called. continue } // collect only called non-std packages pkgPath := msg.Finding.Trace[0].Package if !isStd(pkgPath) { calledVulnPkgs[pkgPath] = true } } } if diff := cmp.Diff(want, calledVulnPkgs); diff != "" { return fmt.Errorf("reachable vulnerable packages mismatch (-want, +got):\n%s", diff) } return nil } // isStd returns true iff pkg is a standard library package. func isStd(pkg string) bool { return !strings.Contains(pkg, ".") } vuln-1.0.1/cmd/govulncheck/integration/k8s/000077500000000000000000000000001446745451500206175ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/integration/k8s/k8s.go000066400000000000000000000025671446745451500216650ustar00rootroot00000000000000// Copyright 2022 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 main import ( "log" "os" "golang.org/x/vuln/cmd/govulncheck/integration/internal/integration" ) const usage = `test helper for examining the output of running govulncheck on k8s@v1.15.11. Example usage: ./k8s [path to output file] ` func main() { if len(os.Args) != 2 { log.Fatal("Incorrect number of expected command line arguments", usage) } out := os.Args[1] want := map[string]bool{ "github.com/containernetworking/cni/pkg/invoke": true, "github.com/evanphx/json-patch": true, "github.com/opencontainers/selinux/go-selinux": true, "github.com/prometheus/client_golang/prometheus/promhttp": true, "golang.org/x/crypto/cryptobyte": true, "golang.org/x/crypto/salsa20/salsa": true, "golang.org/x/crypto/ssh": true, "golang.org/x/net/http/httpguts": true, "golang.org/x/net/http2": true, "golang.org/x/net/http2/hpack": true, "golang.org/x/text/encoding/unicode": true, } if err := integration.CompareNonStdVulns(out, want); err != nil { log.Fatal(err) } } vuln-1.0.1/cmd/govulncheck/integration/kokoro/000077500000000000000000000000001446745451500214165ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/integration/kokoro/integration.cfg000066400000000000000000000001741446745451500244240ustar00rootroot00000000000000# Format: //devtools/kokoro/config/proto/build.proto build_file: "vuln/cmd/govulncheck/integration/kokoro/integration.sh" vuln-1.0.1/cmd/govulncheck/integration/kokoro/integration.sh000077500000000000000000000010521446745451500242760ustar00rootroot00000000000000#!/bin/bash # Copyright 2022 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. # Run integration_test.sh on kokoro. # Fail on any error. set -e # Code under repo is checked out to ${KOKORO_ARTIFACTS_DIR}/git. # The main directory name in this path is determined by the scm name specified # in the job configuration, which in this case is "vuln". cd "${KOKORO_ARTIFACTS_DIR}/git/vuln/cmd/govulncheck/integration" # Run integration_test.sh ./integration_test.sh vuln-1.0.1/cmd/govulncheck/integration/stackrox-scanner/000077500000000000000000000000001446745451500233775ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/integration/stackrox-scanner/scanner.go000066400000000000000000000014621446745451500253620ustar00rootroot00000000000000// Copyright 2022 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 main import ( "log" "os" "golang.org/x/vuln/cmd/govulncheck/integration/internal/integration" ) const usage = `test helper for examining the output of running govulncheck on stackrox-io/scanner binary (https://quay.io/repository/stackrox-io/scanner). Example usage: ./stackrox-scanner [path to output file] ` func main() { if len(os.Args) != 2 { log.Fatal("Incorrect number of expected command line arguments", usage) } out := os.Args[1] want := map[string]bool{ "golang.org/x/net/http2": true, "golang.org/x/net/http2/hpack": true, } if err := integration.CompareNonStdVulns(out, want); err != nil { log.Fatal(err) } } vuln-1.0.1/cmd/govulncheck/main.go000066400000000000000000000010351446745451500170410ustar00rootroot00000000000000// Copyright 2022 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 main import ( "context" "fmt" "os" "golang.org/x/vuln/scan" ) func main() { ctx := context.Background() cmd := scan.Command(ctx, os.Args[1:]...) err := cmd.Start() if err == nil { err = cmd.Wait() } switch err := err.(type) { case nil: case interface{ ExitCode() int }: os.Exit(err.ExitCode()) default: fmt.Fprintln(os.Stderr, err) os.Exit(1) } } vuln-1.0.1/cmd/govulncheck/main_command_118_test.go000066400000000000000000000156071446745451500222010ustar00rootroot00000000000000// Copyright 2022 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. // Only run this on Go 1.18 or higher, because govulncheck can't // run on binaries before 1.18. //go:build go1.18 // +build go1.18 package main import ( "bytes" "context" "flag" "fmt" "os" "path/filepath" "regexp" "runtime" "sync" "testing" "unsafe" "github.com/google/go-cmdtest" "golang.org/x/vuln/internal/govulncheck" "golang.org/x/vuln/internal/test" "golang.org/x/vuln/internal/web" "golang.org/x/vuln/scan" ) var update = flag.Bool("update", false, "update test files with results") type fixup struct { pattern string compiled *regexp.Regexp replace string replaceFunc func(b []byte) []byte } var fixups = []fixup{ { // modifies paths to Go files by replacing their directory with "...". // For example,/a/b/c.go becomes .../c.go . // This makes it possible to compare govulncheck output across systems, because // Go filenames include setup-specific paths. pattern: `[^\s"]*\.go[\s":]`, replaceFunc: func(b []byte) []byte { s := string(b) return []byte(fmt.Sprintf(`.../%s%c`, filepath.Base(s[:len(s)-1]), s[len(s)-1])) }, }, { // There was a one-line change in container/heap/heap.go between 1.18 // and 1.19 that makes the stack traces different. Ignore it. pattern: `heap\.go:(\d+)`, replace: `N`, }, { pattern: `Scanning your code and (\d+) packages across (\d+)`, replace: `Scanning your code and P packages across M`, }, { pattern: `Scanner: govulncheck@v.*`, replace: `Scanner: govulncheck@v1.0.0`, }, { pattern: `"([^"]*") is a file`, replace: `govulncheck: myfile is a file`, }, { pattern: `"scanner_version": "[^"]*"`, replace: `"scanner_version": "v0.0.0-00000000000-20000101010101"`, }, { pattern: `file:///(.*)/testdata/vulndb`, replace: `testdata/vulndb`, }, { pattern: `package (.*) is not in (GOROOT|std) (.*)`, replace: `package foo is not in GOROOT (/tmp/foo)`, }, { pattern: `modified (.*)\)`, replace: `modified 01 Jan 21 00:00 UTC)`, }, { pattern: `Go: (go1.[\.\d]*|devel).*`, replace: `Go: go1.18`, }, { pattern: `"go_version": "go[^\s"]*"`, replace: `"go_version": "go1.18"`, }, } func (f *fixup) init() { f.compiled = regexp.MustCompile(f.pattern) } func (f *fixup) apply(data []byte) []byte { if f.replaceFunc != nil { return f.compiled.ReplaceAllFunc(data, f.replaceFunc) } return f.compiled.ReplaceAll(data, []byte(f.replace)) } func init() { for i := range fixups { fixups[i].init() } } func TestCommand(t *testing.T) { if testing.Short() { t.Skip("skipping test that uses internet in short mode") } testDir, err := os.Getwd() if err != nil { t.Fatal(err) } vulndbDir, err := filepath.Abs(filepath.Join(testDir, "testdata", "vulndb-v1")) if err != nil { t.Fatal(err) } govulndbURI, err := web.URLFromFilePath(vulndbDir) if err != nil { t.Fatalf("failed to create make vulndb url: %v", err) } moduleDirs, err := filepath.Glob("testdata/modules/*") if err != nil { t.Fatal(err) } os.Setenv("moddir", filepath.Join(testDir, "testdata", "modules")) for _, md := range moduleDirs { // Skip nogomod module. It has intended build issues. if filepath.Base(md) == "nogomod" { continue } // Build test module binary. binary, cleanup := test.GoBuild(t, md, "", filepath.Base(md) == "strip") t.Cleanup(cleanup) // Set an environment variable to the path to the binary, so tests // can refer to it. varName := filepath.Base(md) + "_binary" os.Setenv(varName, binary) } runTestSuite(t, filepath.Join(testDir, "testdata"), govulndbURI.String(), *update) if runtime.GOOS != "darwin" { // TODO(https://go.dev/issue/61051): binaries are not currently stripped on darwin. // This is expected to change in Go 1.22. runTestSuite(t, filepath.Join(testDir, "testdata/strip"), govulndbURI.String(), *update) } } // Limit the number of concurrent scans. Scanning is implemented using // x/tools/go/ssa, which is known to be memory-hungry // (see https://go.dev/issue/14113), and by default the testing package // allows up to GOMAXPROCS parallel tests at a time. // // For now we arbitrarily limit to ⌈GOMAXPROCS/4⌉, on the theory that many Go // developer and CI machines have at least 8 logical cores and we want most // runs of the test to exercise at least a little concurrency. If that turns // out to still be too high, we may consider reducing it further. // // Since all of the scans run in the same process, we need an especially low // limit on 32-bit platforms: we may run out of virtual address space well // before we run out of system RAM. var ( parallelLimiter chan struct{} parallelLimiterInit sync.Once ) // testSuite creates a cmdtest suite from dir. It also defines // a govulncheck command on the suite that runs govulncheck // against vulnerability database available at vulndbDir. func runTestSuite(t *testing.T, dir string, govulndb string, update bool) { parallelLimiterInit.Do(func() { limit := (runtime.GOMAXPROCS(0) + 3) / 4 if limit > 2 && unsafe.Sizeof(uintptr(0)) < 8 { limit = 2 } parallelLimiter = make(chan struct{}, limit) }) ts, err := cmdtest.Read(dir) if err != nil { t.Fatal(err) } ts.DisableLogging = true ts.Commands["govulncheck"] = func(args []string, inputFile string) ([]byte, error) { parallelLimiter <- struct{}{} defer func() { <-parallelLimiter }() newargs := append([]string{"-db", govulndb}, args...) buf := &bytes.Buffer{} cmd := scan.Command(context.Background(), newargs...) cmd.Stdout = buf cmd.Stderr = buf if inputFile != "" { input, err := os.Open(filepath.Join(dir, inputFile)) if err != nil { return nil, err } defer input.Close() cmd.Stdin = input } // We set GOVERSION to always get the same results regardless of the underlying Go build system. cmd.Env = append(os.Environ(), "GOVERSION=go1.18") if err := cmd.Start(); err != nil { return nil, err } err := cmd.Wait() switch e := err.(type) { case nil: case interface{ ExitCode() int }: err = &cmdtest.ExitCodeErr{Msg: err.Error(), Code: e.ExitCode()} if e.ExitCode() == 0 { err = nil } default: fmt.Fprintln(buf, err) err = &cmdtest.ExitCodeErr{Msg: err.Error(), Code: 1} } sorted := buf if err == nil && isJSONMode(args) { // parse, sort and reprint the output for test stability gather := test.NewMockHandler() if err := govulncheck.HandleJSON(buf, gather); err != nil { return nil, err } sorted = &bytes.Buffer{} h := govulncheck.NewJSONHandler(sorted) if err := gather.Write(h); err != nil { return nil, err } } out := sorted.Bytes() for _, fix := range fixups { out = fix.apply(out) } return out, err } if update { ts.Run(t, true) return } ts.RunParallel(t, false) } func isJSONMode(args []string) bool { for _, arg := range args { if arg == "-json" { return true } } return false } vuln-1.0.1/cmd/govulncheck/static/000077500000000000000000000000001446745451500170565ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/static/govulncheck.css000066400000000000000000000005201446745451500220750ustar00rootroot00000000000000/* * Copyright 2022 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. */ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, 'Helvetica Neue', Arial, sans-serif; } ul { list-style-type: none; } vuln-1.0.1/cmd/govulncheck/testdata/000077500000000000000000000000001446745451500174005ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/binary_fail.ct000066400000000000000000000015411446745451500222100ustar00rootroot00000000000000##### # Test of passing a non-file to -mode=binary $ govulncheck -mode=binary notafile --> FAIL 2 "notafile" is not a file ##### # Test of passing a non-binary file to -mode=binary $ govulncheck -mode=binary ${moddir}/vuln/go.mod --> FAIL 1 Scanning your binary for known vulnerabilities... govulncheck: could not parse provided binary: unrecognized file format ##### # Test of trying to analyze multiple binaries $ govulncheck -mode=binary ${vuln_binary} ${vuln_binary} --> FAIL 2 only 1 binary can be analyzed at a time ##### # Test of trying to run -mode=binary with -tags flag $ govulncheck -tags=foo -mode=binary ${vuln_binary} --> FAIL 2 the -tags flag is not supported in binary mode ##### # Test of trying to run -mode=binary with the -test flag $ govulncheck -test -mode=binary ${vuln_binary} --> FAIL 2 the -test flag is not supported in binary mode vuln-1.0.1/cmd/govulncheck/testdata/binary_json.ct000066400000000000000000000145251446745451500222540ustar00rootroot00000000000000##### # Test basic binary scanning with json output $ govulncheck -json -mode=binary ${vuln_binary} { "config": { "protocol_version": "v1.0.0", "scanner_name": "govulncheck", "scanner_version": "v0.0.0-00000000000-20000101010101", "db": "testdata/vulndb-v1", "db_last_modified": "2023-04-03T15:57:51Z", "scan_level": "symbol" } } { "progress": { "message": "Scanning your binary for known vulnerabilities..." } } { "osv": { "schema_version": "1.3.1", "id": "GO-2021-0265", "modified": "2023-04-03T15:57:51Z", "published": "2022-08-15T18:06:07Z", "aliases": [ "CVE-2021-42248", "CVE-2021-42836", "GHSA-c9gm-7rfj-8w5h", "GHSA-ppj4-34rq-v8j9" ], "details": "A maliciously crafted path can cause Get and other query functions to consume excessive amounts of CPU and time.", "affected": [ { "package": { "name": "github.com/tidwall/gjson", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.9.3" } ] } ], "ecosystem_specific": { "imports": [ { "path": "github.com/tidwall/gjson", "symbols": [ "Get", "GetBytes", "GetMany", "GetManyBytes", "Result.Get", "parseObject", "queryMatches" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/tidwall/gjson/commit/77a57fda87dca6d0d7d4627d512a630f89a91c96" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/237" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/236" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/commit/590010fdac311cc8990ef5c97448d4fec8f29944" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0265" } } } { "finding": { "osv": "GO-2021-0265", "fixed_version": "v1.9.3", "trace": [ { "module": "github.com/tidwall/gjson", "version": "v1.6.5", "package": "github.com/tidwall/gjson", "function": "Get" } ] } } { "finding": { "osv": "GO-2021-0265", "fixed_version": "v1.9.3", "trace": [ { "module": "github.com/tidwall/gjson", "version": "v1.6.5", "package": "github.com/tidwall/gjson", "function": "Get", "receiver": "Result" } ] } } { "osv": { "schema_version": "1.3.1", "id": "GO-2021-0113", "modified": "2023-04-03T15:57:51Z", "published": "2021-10-06T17:51:21Z", "aliases": [ "CVE-2021-38561", "GHSA-ppp9-7jff-5vj2" ], "details": "Due to improper index calculation, an incorrectly formatted language tag can cause Parse to panic via an out of bounds read. If Parse is used to process untrusted user inputs, this may be used as a vector for a denial of service attack.", "affected": [ { "package": { "name": "golang.org/x/text", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "0.3.7" } ] } ], "ecosystem_specific": { "imports": [ { "path": "golang.org/x/text/language", "symbols": [ "MatchStrings", "MustParse", "Parse", "ParseAcceptLanguage" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://go.dev/cl/340830" }, { "type": "FIX", "url": "https://go.googlesource.com/text/+/383b2e75a7a4198c42f8f87833eefb772868a56f" } ], "credits": [ { "name": "Guido Vranken" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0113" } } } { "finding": { "osv": "GO-2021-0113", "fixed_version": "v0.3.7", "trace": [ { "module": "golang.org/x/text", "version": "v0.3.0", "package": "golang.org/x/text/language", "function": "Parse" } ] } } { "osv": { "schema_version": "1.3.1", "id": "GO-2021-0054", "modified": "2023-04-03T15:57:51Z", "published": "2021-04-14T20:04:52Z", "aliases": [ "CVE-2020-36067", "GHSA-p64j-r5f4-pwwx" ], "details": "Due to improper bounds checking, maliciously crafted JSON objects can cause an out-of-bounds panic. If parsing user input, this may be used as a denial of service vector.", "affected": [ { "package": { "name": "github.com/tidwall/gjson", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.6.6" } ] } ], "ecosystem_specific": { "imports": [ { "path": "github.com/tidwall/gjson", "symbols": [ "Result.ForEach", "unwrap" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/tidwall/gjson/commit/bf4efcb3c18d1825b2988603dea5909140a5302b" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/196" } ], "credits": [ { "name": "@toptotu" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0054" } } } { "finding": { "osv": "GO-2021-0054", "fixed_version": "v1.6.6", "trace": [ { "module": "github.com/tidwall/gjson", "version": "v1.6.5", "package": "github.com/tidwall/gjson", "function": "ForEach", "receiver": "Result" } ] } } vuln-1.0.1/cmd/govulncheck/testdata/binary_text.ct000066400000000000000000000031211446745451500222550ustar00rootroot00000000000000##### # Test basic binary scanning with text output $ govulncheck -mode=binary ${vuln_binary} --> FAIL 3 Scanning your binary for known vulnerabilities... Vulnerability #1: GO-2021-0265 A maliciously crafted path can cause Get and other query functions to consume excessive amounts of CPU and time. More info: https://pkg.go.dev/vuln/GO-2021-0265 Module: github.com/tidwall/gjson Found in: github.com/tidwall/gjson@v1.6.5 Fixed in: github.com/tidwall/gjson@v1.9.3 Example traces found: #1: gjson.Get #2: gjson.Result.Get Vulnerability #2: GO-2021-0113 Due to improper index calculation, an incorrectly formatted language tag can cause Parse to panic via an out of bounds read. If Parse is used to process untrusted user inputs, this may be used as a vector for a denial of service attack. More info: https://pkg.go.dev/vuln/GO-2021-0113 Module: golang.org/x/text Found in: golang.org/x/text@v0.3.0 Fixed in: golang.org/x/text@v0.3.7 Example traces found: #1: language.Parse Vulnerability #3: GO-2021-0054 Due to improper bounds checking, maliciously crafted JSON objects can cause an out-of-bounds panic. If parsing user input, this may be used as a denial of service vector. More info: https://pkg.go.dev/vuln/GO-2021-0054 Module: github.com/tidwall/gjson Found in: github.com/tidwall/gjson@v1.6.5 Fixed in: github.com/tidwall/gjson@v1.6.6 Example traces found: #1: gjson.Result.ForEach Your code is affected by 3 vulnerabilities from 2 modules. Share feedback at https://go.dev/s/govulncheck-feedback. vuln-1.0.1/cmd/govulncheck/testdata/binary_vendored_json.ct000066400000000000000000000111231446745451500241310ustar00rootroot00000000000000##### # Test basic binary scanning with json output $ govulncheck -json -mode=binary ${vendored_binary} { "config": { "protocol_version": "v1.0.0", "scanner_name": "govulncheck", "scanner_version": "v0.0.0-00000000000-20000101010101", "db": "testdata/vulndb-v1", "db_last_modified": "2023-04-03T15:57:51Z", "scan_level": "symbol" } } { "progress": { "message": "Scanning your binary for known vulnerabilities..." } } { "osv": { "schema_version": "1.3.1", "id": "GO-2021-0265", "modified": "2023-04-03T15:57:51Z", "published": "2022-08-15T18:06:07Z", "aliases": [ "CVE-2021-42248", "CVE-2021-42836", "GHSA-c9gm-7rfj-8w5h", "GHSA-ppj4-34rq-v8j9" ], "details": "A maliciously crafted path can cause Get and other query functions to consume excessive amounts of CPU and time.", "affected": [ { "package": { "name": "github.com/tidwall/gjson", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.9.3" } ] } ], "ecosystem_specific": { "imports": [ { "path": "github.com/tidwall/gjson", "symbols": [ "Get", "GetBytes", "GetMany", "GetManyBytes", "Result.Get", "parseObject", "queryMatches" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/tidwall/gjson/commit/77a57fda87dca6d0d7d4627d512a630f89a91c96" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/237" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/236" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/commit/590010fdac311cc8990ef5c97448d4fec8f29944" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0265" } } } { "finding": { "osv": "GO-2021-0265", "fixed_version": "v1.9.3", "trace": [ { "module": "github.com/tidwall/gjson", "version": "v1.6.5", "package": "github.com/tidwall/gjson", "function": "Get" } ] } } { "finding": { "osv": "GO-2021-0265", "fixed_version": "v1.9.3", "trace": [ { "module": "github.com/tidwall/gjson", "version": "v1.6.5", "package": "github.com/tidwall/gjson", "function": "Get", "receiver": "Result" } ] } } { "osv": { "schema_version": "1.3.1", "id": "GO-2021-0113", "modified": "2023-04-03T15:57:51Z", "published": "2021-10-06T17:51:21Z", "aliases": [ "CVE-2021-38561", "GHSA-ppp9-7jff-5vj2" ], "details": "Due to improper index calculation, an incorrectly formatted language tag can cause Parse to panic via an out of bounds read. If Parse is used to process untrusted user inputs, this may be used as a vector for a denial of service attack.", "affected": [ { "package": { "name": "golang.org/x/text", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "0.3.7" } ] } ], "ecosystem_specific": { "imports": [ { "path": "golang.org/x/text/language", "symbols": [ "MatchStrings", "MustParse", "Parse", "ParseAcceptLanguage" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://go.dev/cl/340830" }, { "type": "FIX", "url": "https://go.googlesource.com/text/+/383b2e75a7a4198c42f8f87833eefb772868a56f" } ], "credits": [ { "name": "Guido Vranken" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0113" } } } { "finding": { "osv": "GO-2021-0113", "fixed_version": "v0.3.7", "trace": [ { "module": "golang.org/x/text", "version": "v0.3.0", "package": "golang.org/x/text/language", "function": "Parse" } ] } } vuln-1.0.1/cmd/govulncheck/testdata/convert_input.json000066400000000000000000000147641446745451500232060ustar00rootroot00000000000000{ "config": { "protocol_version": "v0.1.0", "scanner_name": "govulncheck", "scanner_version": "v0.0.0-00000000000-20000101010101", "db": "testdata/vulndb-v1", "db_last_modified": "2023-04-03T15:57:51Z", "go_version": "go1.18", "scan_level": "symbol" } } { "progress": { "message": "Scanning your code and P packages across M dependent modules for known vulnerabilities..." } } { "osv": { "schema_version": "1.3.1", "id": "GO-2021-0265", "modified": "2023-04-03T15:57:51Z", "published": "2022-08-15T18:06:07Z", "aliases": [ "CVE-2021-42248", "CVE-2021-42836", "GHSA-c9gm-7rfj-8w5h", "GHSA-ppj4-34rq-v8j9" ], "details": "A maliciously crafted path can cause Get and other query functions to consume excessive amounts of CPU and time.", "affected": [ { "package": { "name": "github.com/tidwall/gjson", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.9.3" } ] } ], "ecosystem_specific": { "imports": [ { "path": "github.com/tidwall/gjson", "symbols": [ "Get", "GetBytes", "GetMany", "GetManyBytes", "Result.Get", "parseObject", "queryMatches" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/tidwall/gjson/commit/77a57fda87dca6d0d7d4627d512a630f89a91c96" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/237" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/236" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/commit/590010fdac311cc8990ef5c97448d4fec8f29944" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0265" } } } { "finding": { "osv": "GO-2021-0265", "fixed_version": "v1.9.3", "trace": [ { "module": "github.com/tidwall/gjson", "version": "v1.6.5", "package": "github.com/tidwall/gjson", "function": "Get", "receiver": "Result" }, { "module": "golang.org/vuln", "package": "golang.org/vuln", "function": "main", "position": { "filename": ".../vuln.go", "offset": 183, "line": 14, "column": 20 } } ] } } { "osv": { "schema_version": "1.3.1", "id": "GO-2021-0113", "modified": "2023-04-03T15:57:51Z", "published": "2021-10-06T17:51:21Z", "aliases": [ "CVE-2021-38561", "GHSA-ppp9-7jff-5vj2" ], "details": "Due to improper index calculation, an incorrectly formatted language tag can cause Parse to panic via an out of bounds read. If Parse is used to process untrusted user inputs, this may be used as a vector for a denial of service attack.", "affected": [ { "package": { "name": "golang.org/x/text", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "0.3.7" } ] } ], "ecosystem_specific": { "imports": [ { "path": "golang.org/x/text/language", "symbols": [ "MatchStrings", "MustParse", "Parse", "ParseAcceptLanguage" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://go.dev/cl/340830" }, { "type": "FIX", "url": "https://go.googlesource.com/text/+/383b2e75a7a4198c42f8f87833eefb772868a56f" } ], "credits": [ { "name": "Guido Vranken" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0113" } } } { "finding": { "osv": "GO-2021-0113", "fixed_version": "v0.3.7", "trace": [ { "module": "golang.org/x/text", "version": "v0.3.0", "package": "golang.org/x/text/language", "function": "Parse" }, { "module": "golang.org/vuln", "package": "golang.org/vuln", "function": "main", "position": { "filename": ".../vuln.go", "offset": 159, "line": 13, "column": 16 } } ] } } { "osv": { "schema_version": "1.3.1", "id": "GO-2021-0054", "modified": "2023-04-03T15:57:51Z", "published": "2021-04-14T20:04:52Z", "aliases": [ "CVE-2020-36067", "GHSA-p64j-r5f4-pwwx" ], "details": "Due to improper bounds checking, maliciously crafted JSON objects can cause an out-of-bounds panic. If parsing user input, this may be used as a denial of service vector.", "affected": [ { "package": { "name": "github.com/tidwall/gjson", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.6.6" } ] } ], "ecosystem_specific": { "imports": [ { "path": "github.com/tidwall/gjson", "symbols": [ "Result.ForEach", "unwrap" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/tidwall/gjson/commit/bf4efcb3c18d1825b2988603dea5909140a5302b" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/196" } ], "credits": [ { "name": "@toptotu" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0054" } } } { "finding": { "osv": "GO-2021-0054", "fixed_version": "v1.6.6", "trace": [ { "module": "github.com/tidwall/gjson", "version": "v1.6.5", "package": "github.com/tidwall/gjson" } ] } } vuln-1.0.1/cmd/govulncheck/testdata/convert_text.ct000066400000000000000000000036111446745451500224550ustar00rootroot00000000000000##### # Test using the conversion from json on stdin to text on stdout $ govulncheck -mode=convert < convert_input.json --> FAIL 3 Scanning your code and P packages across M dependent modules for known vulnerabilities... Vulnerability #1: GO-2021-0265 A maliciously crafted path can cause Get and other query functions to consume excessive amounts of CPU and time. More info: https://pkg.go.dev/vuln/GO-2021-0265 Module: github.com/tidwall/gjson Found in: github.com/tidwall/gjson@v1.6.5 Fixed in: github.com/tidwall/gjson@v1.9.3 Example traces found: #1: .../vuln.go:14:20: vuln.main calls gjson.Result.Get Vulnerability #2: GO-2021-0113 Due to improper index calculation, an incorrectly formatted language tag can cause Parse to panic via an out of bounds read. If Parse is used to process untrusted user inputs, this may be used as a vector for a denial of service attack. More info: https://pkg.go.dev/vuln/GO-2021-0113 Module: golang.org/x/text Found in: golang.org/x/text@v0.3.0 Fixed in: golang.org/x/text@v0.3.7 Example traces found: #1: .../vuln.go:13:16: vuln.main calls language.Parse === Informational === Found 1 vulnerability in packages that you import, but there are no call stacks leading to the use of this vulnerability. You may not need to take any action. See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck for details. Vulnerability #1: GO-2021-0054 Due to improper bounds checking, maliciously crafted JSON objects can cause an out-of-bounds panic. If parsing user input, this may be used as a denial of service vector. More info: https://pkg.go.dev/vuln/GO-2021-0054 Module: github.com/tidwall/gjson Found in: github.com/tidwall/gjson@v1.6.5 Fixed in: github.com/tidwall/gjson@v1.6.6 Your code is affected by 2 vulnerabilities from 2 modules. Share feedback at https://go.dev/s/govulncheck-feedback. vuln-1.0.1/cmd/govulncheck/testdata/modules/000077500000000000000000000000001446745451500210505ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/modules/informational/000077500000000000000000000000001446745451500237125ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/modules/informational/go.mod000066400000000000000000000003471446745451500250240ustar00rootroot00000000000000module golang.org/vuln go 1.18 // This version has a vulnerability that is imported. require github.com/tidwall/gjson v1.9.2 require ( github.com/tidwall/match v1.1.0 // indirect github.com/tidwall/pretty v1.2.0 // indirect ) vuln-1.0.1/cmd/govulncheck/testdata/modules/informational/go.sum000066400000000000000000000007671446745451500250570ustar00rootroot00000000000000github.com/tidwall/gjson v1.9.2 h1:SJQc2IgWWKL5V+YGJrr95hjNXFeZzHT2L9Wv1aAb51Q= github.com/tidwall/gjson v1.9.2/go.mod h1:2tcKM/KQ/GjiTN7mfTL/HdNmef9Q6AZLaSK2RdfvSjw= github.com/tidwall/match v1.1.0 h1:VfI2e2aXLvytih7WUVyO9uvRC+RcXlaTrMbHuQWnFmk= github.com/tidwall/match v1.1.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= vuln-1.0.1/cmd/govulncheck/testdata/modules/informational/vuln.go000066400000000000000000000002031446745451500252200ustar00rootroot00000000000000package main import ( "fmt" "github.com/tidwall/gjson" ) func main() { fmt.Println("hello") gjson.Valid("{hello: world}") } vuln-1.0.1/cmd/govulncheck/testdata/modules/multientry/000077500000000000000000000000001446745451500232645ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/modules/multientry/go.mod000066400000000000000000000001101446745451500243620ustar00rootroot00000000000000module golang.org/multientry go 1.18 require golang.org/x/text v0.3.5 vuln-1.0.1/cmd/govulncheck/testdata/modules/multientry/go.sum000066400000000000000000000004061446745451500244170ustar00rootroot00000000000000golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= vuln-1.0.1/cmd/govulncheck/testdata/modules/multientry/main.go000066400000000000000000000032451446745451500245430ustar00rootroot00000000000000package main import ( "fmt" "os" "golang.org/x/text/language" ) func main() { args := os.Args[1:] // Calls foo which directly calls language.Parse. A() // Also calls foo which directly calls language.Parse. B() // Calls language.Parse directly. // // This will be displayed by govulncheck, since it is the shortest path. C() // Calls foobar which eventually calls language.MustParse (different // symbol, same report) D() // Calls moreFoo which directly calls language.Parse. E(args) // Calls stillMoreFoo which directly calls language.Parse. F(args) } func A() { foo(os.Args[1:]) } func B() { foo(os.Args[1:]) } func C() { _, _ = language.Parse("") } func D() { foobar() } func E(args []string) { moreFoo(args) } func F(args []string) { stillMoreFoo(args) } func foo(args []string) { for _, arg := range args { tag, err := language.Parse(arg) if err != nil { fmt.Printf("%s: error: %v\n", arg, err) } else if tag == language.Und { fmt.Printf("%s: undefined\n", arg) } else { fmt.Printf("%s: tag %s\n", arg, tag) } } } func moreFoo(args []string) { for _, arg := range args { tag, err := language.Parse(arg) if err != nil { fmt.Printf("%s: error: %v\n", arg, err) } else if tag == language.Und { fmt.Printf("%s: undefined\n", arg) } else { fmt.Printf("%s: tag %s\n", arg, tag) } } } func stillMoreFoo(args []string) { for _, arg := range args { tag, err := language.Parse(arg) if err != nil { fmt.Printf("%s: error: %v\n", arg, err) } else if tag == language.Und { fmt.Printf("%s: undefined\n", arg) } else { fmt.Printf("%s: tag %s\n", arg, tag) } } } func foobar() { language.MustParse("") } vuln-1.0.1/cmd/govulncheck/testdata/modules/nogomod/000077500000000000000000000000001446745451500225125ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/modules/nogomod/vuln.go000066400000000000000000000001721446745451500240250ustar00rootroot00000000000000package main import ( "fmt" "golang.org/x/text/language" ) func main() { fmt.Println("hello") language.Parse("") } vuln-1.0.1/cmd/govulncheck/testdata/modules/replace/000077500000000000000000000000001446745451500224635ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/modules/replace/go.mod000066400000000000000000000002031446745451500235640ustar00rootroot00000000000000module golang.org/replace go 1.20 replace golang.org/x/text v0.9.0 => golang.org/x/text v0.3.0 require golang.org/x/text v0.9.0 vuln-1.0.1/cmd/govulncheck/testdata/modules/replace/go.sum000066400000000000000000000002311446745451500236120ustar00rootroot00000000000000golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= vuln-1.0.1/cmd/govulncheck/testdata/modules/replace/main.go000066400000000000000000000001721446745451500237360ustar00rootroot00000000000000package main import ( "fmt" "golang.org/x/text/language" ) func main() { fmt.Println("hello") language.Parse("") } vuln-1.0.1/cmd/govulncheck/testdata/modules/stdlib/000077500000000000000000000000001446745451500223315ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/modules/stdlib/go.mod000066400000000000000000000000421446745451500234330ustar00rootroot00000000000000module golang.org/stdlib go 1.18 vuln-1.0.1/cmd/govulncheck/testdata/modules/stdlib/go.sum000066400000000000000000000000001446745451500234520ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/modules/stdlib/stdlib.go000066400000000000000000000004501446745451500241400ustar00rootroot00000000000000package main import ( "io" "log" "net/http" ) func main() { // Hello world, the web server helloHandler := func(w http.ResponseWriter, req *http.Request) { io.WriteString(w, "Hello, world!\n") } http.HandleFunc("/hello", helloHandler) log.Fatal(http.ListenAndServe(":8080", nil)) } vuln-1.0.1/cmd/govulncheck/testdata/modules/strip/000077500000000000000000000000001446745451500222115ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/modules/strip/go.mod000066400000000000000000000001751446745451500233220ustar00rootroot00000000000000module golang.org/vuln go 1.18 // This version has a vulnerability that is only imported. require golang.org/x/text v0.3.0 vuln-1.0.1/cmd/govulncheck/testdata/modules/strip/go.sum000066400000000000000000000002311446745451500233400ustar00rootroot00000000000000golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= vuln-1.0.1/cmd/govulncheck/testdata/modules/strip/vuln.go000066400000000000000000000001111446745451500235150ustar00rootroot00000000000000package main import ( _ "golang.org/x/text/language" ) func main() {} vuln-1.0.1/cmd/govulncheck/testdata/modules/vendored/000077500000000000000000000000001446745451500226565ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/modules/vendored/go.mod000066400000000000000000000005361446745451500237700ustar00rootroot00000000000000module golang.org/vendored go 1.18 require ( // This version has one vulnerability that is imported, and // one that is called. github.com/tidwall/gjson v1.6.5 // This version has a vulnerability that is called. golang.org/x/text v0.3.0 ) require ( github.com/tidwall/match v1.1.0 // indirect github.com/tidwall/pretty v1.2.0 // indirect ) vuln-1.0.1/cmd/govulncheck/testdata/modules/vendored/go.sum000066400000000000000000000014771446745451500240220ustar00rootroot00000000000000github.com/tidwall/gjson v1.6.5 h1:P/K9r+1pt9AK54uap7HcoIp6T3a7AoMg3v18tUis+Cg= github.com/tidwall/gjson v1.6.5/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.1.0 h1:VfI2e2aXLvytih7WUVyO9uvRC+RcXlaTrMbHuQWnFmk= github.com/tidwall/match v1.1.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= vuln-1.0.1/cmd/govulncheck/testdata/modules/vendored/subdir/000077500000000000000000000000001446745451500241465ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/modules/vendored/subdir/subdir.go000066400000000000000000000001351446745451500257640ustar00rootroot00000000000000package subdir import ( "golang.org/x/text/language" ) func Foo() { language.Parse("") } vuln-1.0.1/cmd/govulncheck/testdata/modules/vendored/vendor/000077500000000000000000000000001446745451500241535ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/modules/vendored/vendor/github.com/000077500000000000000000000000001446745451500262125ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/modules/vendored/vendor/github.com/tidwall/000077500000000000000000000000001446745451500276525ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/modules/vendored/vendor/github.com/tidwall/gjson/000077500000000000000000000000001446745451500307725ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/modules/vendored/vendor/github.com/tidwall/gjson/gjson.go000066400000000000000000000002771446745451500324470ustar00rootroot00000000000000package gjson var prevent_optimization int type Result struct{} func (Result) Get(string) { Get("", "") } func Get(json, path string) Result { prevent_optimization++ return Result{} } vuln-1.0.1/cmd/govulncheck/testdata/modules/vendored/vendor/golang.org/000077500000000000000000000000001446745451500262105ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/modules/vendored/vendor/golang.org/x/000077500000000000000000000000001446745451500264575ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/modules/vendored/vendor/golang.org/x/text/000077500000000000000000000000001446745451500274435ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/modules/vendored/vendor/golang.org/x/text/language/000077500000000000000000000000001446745451500312265ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/modules/vendored/vendor/golang.org/x/text/language/language.go000066400000000000000000000001371446745451500333410ustar00rootroot00000000000000package language var prevent_optimization int func Parse(string) { prevent_optimization++ } vuln-1.0.1/cmd/govulncheck/testdata/modules/vendored/vendor/modules.txt000066400000000000000000000005231446745451500263640ustar00rootroot00000000000000# github.com/tidwall/gjson v1.6.5 ## explicit; go 1.12 github.com/tidwall/gjson # github.com/tidwall/match v1.1.0 ## explicit; go 1.15 github.com/tidwall/match # github.com/tidwall/pretty v1.2.0 ## explicit; go 1.16 github.com/tidwall/pretty # golang.org/x/text v0.3.0 ## explicit golang.org/x/text/internal/tag golang.org/x/text/language vuln-1.0.1/cmd/govulncheck/testdata/modules/vendored/vendored.go000066400000000000000000000003411446745451500250110ustar00rootroot00000000000000package main import ( "encoding/pem" "fmt" "github.com/tidwall/gjson" "golang.org/x/text/language" ) func main() { fmt.Println("hello") language.Parse("") gjson.Result{}.Get("") _, _ = pem.Decode([]byte("test")) } vuln-1.0.1/cmd/govulncheck/testdata/modules/vuln/000077500000000000000000000000001446745451500220345ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/modules/vuln/go.mod000066400000000000000000000005321446745451500231420ustar00rootroot00000000000000module golang.org/vuln go 1.18 require ( // This version has one vulnerability that is imported, and // one that is called. github.com/tidwall/gjson v1.6.5 // This version has a vulnerability that is called. golang.org/x/text v0.3.0 ) require ( github.com/tidwall/match v1.1.0 // indirect github.com/tidwall/pretty v1.2.0 // indirect ) vuln-1.0.1/cmd/govulncheck/testdata/modules/vuln/go.sum000066400000000000000000000014771446745451500232000ustar00rootroot00000000000000github.com/tidwall/gjson v1.6.5 h1:P/K9r+1pt9AK54uap7HcoIp6T3a7AoMg3v18tUis+Cg= github.com/tidwall/gjson v1.6.5/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.1.0 h1:VfI2e2aXLvytih7WUVyO9uvRC+RcXlaTrMbHuQWnFmk= github.com/tidwall/match v1.1.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= vuln-1.0.1/cmd/govulncheck/testdata/modules/vuln/subdir/000077500000000000000000000000001446745451500233245ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/modules/vuln/subdir/subdir.go000066400000000000000000000001351446745451500251420ustar00rootroot00000000000000package subdir import ( "golang.org/x/text/language" ) func Foo() { language.Parse("") } vuln-1.0.1/cmd/govulncheck/testdata/modules/vuln/vuln.go000066400000000000000000000003411446745451500233450ustar00rootroot00000000000000package main import ( "encoding/pem" "fmt" "github.com/tidwall/gjson" "golang.org/x/text/language" ) func main() { fmt.Println("hello") language.Parse("") gjson.Result{}.Get("") _, _ = pem.Decode([]byte("test")) } vuln-1.0.1/cmd/govulncheck/testdata/query_fail.ct000066400000000000000000000002641446745451500220720ustar00rootroot00000000000000##### # Test of query mode with invalid input. $ govulncheck -mode=query -json example.com/module@ --> FAIL 2 invalid query example.com/module@: must be of the form module@version vuln-1.0.1/cmd/govulncheck/testdata/query_json.ct000066400000000000000000000073161446745451500221350ustar00rootroot00000000000000##### # Test of query mode for a third party module. $ govulncheck -mode=query -json github.com/tidwall/gjson@v1.6.5 { "config": { "protocol_version": "v1.0.0", "scanner_name": "govulncheck", "scanner_version": "v0.0.0-00000000000-20000101010101", "db": "testdata/vulndb-v1", "db_last_modified": "2023-04-03T15:57:51Z", "scan_level": "symbol" } } { "progress": { "message": "Looking up vulnerabilities in github.com/tidwall/gjson at v1.6.5..." } } { "osv": { "schema_version": "1.3.1", "id": "GO-2021-0054", "modified": "2023-04-03T15:57:51Z", "published": "2021-04-14T20:04:52Z", "aliases": [ "CVE-2020-36067", "GHSA-p64j-r5f4-pwwx" ], "details": "Due to improper bounds checking, maliciously crafted JSON objects can cause an out-of-bounds panic. If parsing user input, this may be used as a denial of service vector.", "affected": [ { "package": { "name": "github.com/tidwall/gjson", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.6.6" } ] } ], "ecosystem_specific": { "imports": [ { "path": "github.com/tidwall/gjson", "symbols": [ "Result.ForEach", "unwrap" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/tidwall/gjson/commit/bf4efcb3c18d1825b2988603dea5909140a5302b" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/196" } ], "credits": [ { "name": "@toptotu" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0054" } } } { "osv": { "schema_version": "1.3.1", "id": "GO-2021-0265", "modified": "2023-04-03T15:57:51Z", "published": "2022-08-15T18:06:07Z", "aliases": [ "CVE-2021-42248", "CVE-2021-42836", "GHSA-c9gm-7rfj-8w5h", "GHSA-ppj4-34rq-v8j9" ], "details": "A maliciously crafted path can cause Get and other query functions to consume excessive amounts of CPU and time.", "affected": [ { "package": { "name": "github.com/tidwall/gjson", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.9.3" } ] } ], "ecosystem_specific": { "imports": [ { "path": "github.com/tidwall/gjson", "symbols": [ "Get", "GetBytes", "GetMany", "GetManyBytes", "Result.Get", "parseObject", "queryMatches" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/tidwall/gjson/commit/77a57fda87dca6d0d7d4627d512a630f89a91c96" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/237" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/236" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/commit/590010fdac311cc8990ef5c97448d4fec8f29944" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0265" } } } vuln-1.0.1/cmd/govulncheck/testdata/query_multi_json.ct000066400000000000000000000147171446745451500233520ustar00rootroot00000000000000##### # Test of query mode with multiple inputs. $ govulncheck -mode=query -json stdlib@go1.17 github.com/tidwall/gjson@v1.6.5 { "config": { "protocol_version": "v1.0.0", "scanner_name": "govulncheck", "scanner_version": "v0.0.0-00000000000-20000101010101", "db": "testdata/vulndb-v1", "db_last_modified": "2023-04-03T15:57:51Z", "scan_level": "symbol" } } { "progress": { "message": "Looking up vulnerabilities in stdlib at go1.17..." } } { "progress": { "message": "Looking up vulnerabilities in github.com/tidwall/gjson at v1.6.5..." } } { "osv": { "schema_version": "1.3.1", "id": "GO-2022-0969", "modified": "2023-04-03T15:57:51Z", "published": "2022-09-12T20:23:06Z", "aliases": [ "CVE-2022-27664", "GHSA-69cg-p879-7622" ], "details": "HTTP/2 server connections can hang forever waiting for a clean shutdown that was preempted by a fatal error. This condition can be exploited by a malicious client to cause a denial of service.", "affected": [ { "package": { "name": "stdlib", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.18.6" }, { "introduced": "1.19.0" }, { "fixed": "1.19.1" } ] } ], "ecosystem_specific": { "imports": [ { "path": "net/http", "symbols": [ "ListenAndServe", "ListenAndServeTLS", "Serve", "ServeTLS", "Server.ListenAndServe", "Server.ListenAndServeTLS", "Server.Serve", "Server.ServeTLS", "http2Server.ServeConn", "http2serverConn.goAway" ] } ] } }, { "package": { "name": "golang.org/x/net", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "0.0.0-20220906165146-f3363e06e74c" } ] } ], "ecosystem_specific": { "imports": [ { "path": "golang.org/x/net/http2", "symbols": [ "Server.ServeConn", "serverConn.goAway" ] } ] } } ], "references": [ { "type": "WEB", "url": "https://groups.google.com/g/golang-announce/c/x49AQzIVX-s" }, { "type": "REPORT", "url": "https://go.dev/issue/54658" }, { "type": "FIX", "url": "https://go.dev/cl/428735" } ], "credits": [ { "name": "Bahruz Jabiyev, Tommaso Innocenti, Anthony Gavazzi, Steven Sprecher, and Kaan Onarlioglu" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2022-0969" } } } { "osv": { "schema_version": "1.3.1", "id": "GO-2021-0054", "modified": "2023-04-03T15:57:51Z", "published": "2021-04-14T20:04:52Z", "aliases": [ "CVE-2020-36067", "GHSA-p64j-r5f4-pwwx" ], "details": "Due to improper bounds checking, maliciously crafted JSON objects can cause an out-of-bounds panic. If parsing user input, this may be used as a denial of service vector.", "affected": [ { "package": { "name": "github.com/tidwall/gjson", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.6.6" } ] } ], "ecosystem_specific": { "imports": [ { "path": "github.com/tidwall/gjson", "symbols": [ "Result.ForEach", "unwrap" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/tidwall/gjson/commit/bf4efcb3c18d1825b2988603dea5909140a5302b" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/196" } ], "credits": [ { "name": "@toptotu" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0054" } } } { "osv": { "schema_version": "1.3.1", "id": "GO-2021-0265", "modified": "2023-04-03T15:57:51Z", "published": "2022-08-15T18:06:07Z", "aliases": [ "CVE-2021-42248", "CVE-2021-42836", "GHSA-c9gm-7rfj-8w5h", "GHSA-ppj4-34rq-v8j9" ], "details": "A maliciously crafted path can cause Get and other query functions to consume excessive amounts of CPU and time.", "affected": [ { "package": { "name": "github.com/tidwall/gjson", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.9.3" } ] } ], "ecosystem_specific": { "imports": [ { "path": "github.com/tidwall/gjson", "symbols": [ "Get", "GetBytes", "GetMany", "GetManyBytes", "Result.Get", "parseObject", "queryMatches" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/tidwall/gjson/commit/77a57fda87dca6d0d7d4627d512a630f89a91c96" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/237" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/236" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/commit/590010fdac311cc8990ef5c97448d4fec8f29944" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0265" } } } vuln-1.0.1/cmd/govulncheck/testdata/query_stdlib_json.ct000066400000000000000000000061351446745451500234740ustar00rootroot00000000000000##### # Test of query mode with the standard library. $ govulncheck -mode=query -json stdlib@go1.17 { "config": { "protocol_version": "v1.0.0", "scanner_name": "govulncheck", "scanner_version": "v0.0.0-00000000000-20000101010101", "db": "testdata/vulndb-v1", "db_last_modified": "2023-04-03T15:57:51Z", "scan_level": "symbol" } } { "progress": { "message": "Looking up vulnerabilities in stdlib at go1.17..." } } { "osv": { "schema_version": "1.3.1", "id": "GO-2022-0969", "modified": "2023-04-03T15:57:51Z", "published": "2022-09-12T20:23:06Z", "aliases": [ "CVE-2022-27664", "GHSA-69cg-p879-7622" ], "details": "HTTP/2 server connections can hang forever waiting for a clean shutdown that was preempted by a fatal error. This condition can be exploited by a malicious client to cause a denial of service.", "affected": [ { "package": { "name": "stdlib", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.18.6" }, { "introduced": "1.19.0" }, { "fixed": "1.19.1" } ] } ], "ecosystem_specific": { "imports": [ { "path": "net/http", "symbols": [ "ListenAndServe", "ListenAndServeTLS", "Serve", "ServeTLS", "Server.ListenAndServe", "Server.ListenAndServeTLS", "Server.Serve", "Server.ServeTLS", "http2Server.ServeConn", "http2serverConn.goAway" ] } ] } }, { "package": { "name": "golang.org/x/net", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "0.0.0-20220906165146-f3363e06e74c" } ] } ], "ecosystem_specific": { "imports": [ { "path": "golang.org/x/net/http2", "symbols": [ "Server.ServeConn", "serverConn.goAway" ] } ] } } ], "references": [ { "type": "WEB", "url": "https://groups.google.com/g/golang-announce/c/x49AQzIVX-s" }, { "type": "REPORT", "url": "https://go.dev/issue/54658" }, { "type": "FIX", "url": "https://go.dev/cl/428735" } ], "credits": [ { "name": "Bahruz Jabiyev, Tommaso Innocenti, Anthony Gavazzi, Steven Sprecher, and Kaan Onarlioglu" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2022-0969" } } } vuln-1.0.1/cmd/govulncheck/testdata/query_vstdlib_json.ct000066400000000000000000000062001446745451500236530ustar00rootroot00000000000000##### # Test of query mode with the standard library (with a v prefix on the version). $ govulncheck -mode=query -json stdlib@v1.17.0 { "config": { "protocol_version": "v1.0.0", "scanner_name": "govulncheck", "scanner_version": "v0.0.0-00000000000-20000101010101", "db": "testdata/vulndb-v1", "db_last_modified": "2023-04-03T15:57:51Z", "scan_level": "symbol" } } { "progress": { "message": "Looking up vulnerabilities in stdlib at v1.17.0..." } } { "osv": { "schema_version": "1.3.1", "id": "GO-2022-0969", "modified": "2023-04-03T15:57:51Z", "published": "2022-09-12T20:23:06Z", "aliases": [ "CVE-2022-27664", "GHSA-69cg-p879-7622" ], "details": "HTTP/2 server connections can hang forever waiting for a clean shutdown that was preempted by a fatal error. This condition can be exploited by a malicious client to cause a denial of service.", "affected": [ { "package": { "name": "stdlib", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.18.6" }, { "introduced": "1.19.0" }, { "fixed": "1.19.1" } ] } ], "ecosystem_specific": { "imports": [ { "path": "net/http", "symbols": [ "ListenAndServe", "ListenAndServeTLS", "Serve", "ServeTLS", "Server.ListenAndServe", "Server.ListenAndServeTLS", "Server.Serve", "Server.ServeTLS", "http2Server.ServeConn", "http2serverConn.goAway" ] } ] } }, { "package": { "name": "golang.org/x/net", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "0.0.0-20220906165146-f3363e06e74c" } ] } ], "ecosystem_specific": { "imports": [ { "path": "golang.org/x/net/http2", "symbols": [ "Server.ServeConn", "serverConn.goAway" ] } ] } } ], "references": [ { "type": "WEB", "url": "https://groups.google.com/g/golang-announce/c/x49AQzIVX-s" }, { "type": "REPORT", "url": "https://go.dev/issue/54658" }, { "type": "FIX", "url": "https://go.dev/cl/428735" } ], "credits": [ { "name": "Bahruz Jabiyev, Tommaso Innocenti, Anthony Gavazzi, Steven Sprecher, and Kaan Onarlioglu" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2022-0969" } } } vuln-1.0.1/cmd/govulncheck/testdata/source_fail.ct000066400000000000000000000016611446745451500222270ustar00rootroot00000000000000##### # Test of missing go.mod error message. $ govulncheck -C ${moddir}/nogomod . --> FAIL 1 govulncheck: no go.mod file govulncheck only works with Go modules. Try navigating to your module directory. Otherwise, run go mod init to make your project a module. See https://go.dev/doc/modules/managing-dependencies for more information. ##### # Test of handing a binary to source mode $ govulncheck ${vuln_binary} --> FAIL 2 govulncheck: myfile is a file. By default, govulncheck runs source analysis on Go modules. Did you mean to run govulncheck with -mode=binary? For details, run govulncheck -h. ##### # Test of handing an invalid package pattern to source mode $ govulncheck -C ${moddir}/vuln blah --> FAIL 1 govulncheck: loading packages: There are errors with the provided package patterns: -: package foo is not in GOROOT (/tmp/foo) For details on package patterns, see https://pkg.go.dev/cmd/go#hdr-Package_lists_and_patterns. vuln-1.0.1/cmd/govulncheck/testdata/source_informational_text.ct000066400000000000000000000015311446745451500252160ustar00rootroot00000000000000##### # Test souce mode with no callstacks $ govulncheck -C ${moddir}/informational -show=traces . Scanning your code and P packages across M dependent modules for known vulnerabilities... === Informational === Found 1 vulnerability in packages that you import, but there are no call stacks leading to the use of this vulnerability. You may not need to take any action. See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck for details. Vulnerability #1: GO-2021-0265 A maliciously crafted path can cause Get and other query functions to consume excessive amounts of CPU and time. More info: https://pkg.go.dev/vuln/GO-2021-0265 Module: github.com/tidwall/gjson Found in: github.com/tidwall/gjson@v1.9.2 Fixed in: github.com/tidwall/gjson@v1.9.3 No vulnerabilities found. Share feedback at https://go.dev/s/govulncheck-feedback. vuln-1.0.1/cmd/govulncheck/testdata/source_multientry_json.ct000066400000000000000000000076371446745451500245720ustar00rootroot00000000000000##### # Test for multiple call stacks in source mode $ govulncheck -json -C ${moddir}/multientry . { "config": { "protocol_version": "v1.0.0", "scanner_name": "govulncheck", "scanner_version": "v0.0.0-00000000000-20000101010101", "db": "testdata/vulndb-v1", "db_last_modified": "2023-04-03T15:57:51Z", "go_version": "go1.18", "scan_level": "symbol" } } { "progress": { "message": "Scanning your code and P packages across M dependent module for known vulnerabilities..." } } { "osv": { "schema_version": "1.3.1", "id": "GO-2021-0113", "modified": "2023-04-03T15:57:51Z", "published": "2021-10-06T17:51:21Z", "aliases": [ "CVE-2021-38561", "GHSA-ppp9-7jff-5vj2" ], "details": "Due to improper index calculation, an incorrectly formatted language tag can cause Parse to panic via an out of bounds read. If Parse is used to process untrusted user inputs, this may be used as a vector for a denial of service attack.", "affected": [ { "package": { "name": "golang.org/x/text", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "0.3.7" } ] } ], "ecosystem_specific": { "imports": [ { "path": "golang.org/x/text/language", "symbols": [ "MatchStrings", "MustParse", "Parse", "ParseAcceptLanguage" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://go.dev/cl/340830" }, { "type": "FIX", "url": "https://go.googlesource.com/text/+/383b2e75a7a4198c42f8f87833eefb772868a56f" } ], "credits": [ { "name": "Guido Vranken" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0113" } } } { "finding": { "osv": "GO-2021-0113", "fixed_version": "v0.3.7", "trace": [ { "module": "golang.org/x/text", "version": "v0.3.5", "package": "golang.org/x/text/language", "function": "MustParse" }, { "module": "golang.org/multientry", "package": "golang.org/multientry", "function": "foobar", "position": { "filename": ".../main.go", "offset": 1694, "line": 99, "column": 20 } }, { "module": "golang.org/multientry", "package": "golang.org/multientry", "function": "D", "position": { "filename": ".../main.go", "offset": 705, "line": 48, "column": 8 } }, { "module": "golang.org/multientry", "package": "golang.org/multientry", "function": "main", "position": { "filename": ".../main.go", "offset": 441, "line": 26, "column": 3 } } ] } } { "finding": { "osv": "GO-2021-0113", "fixed_version": "v0.3.7", "trace": [ { "module": "golang.org/x/text", "version": "v0.3.5", "package": "golang.org/x/text/language", "function": "Parse" }, { "module": "golang.org/multientry", "package": "golang.org/multientry", "function": "C", "position": { "filename": ".../main.go", "offset": 679, "line": 44, "column": 23 } }, { "module": "golang.org/multientry", "package": "golang.org/multientry", "function": "main", "position": { "filename": ".../main.go", "offset": 340, "line": 22, "column": 3 } } ] } } vuln-1.0.1/cmd/govulncheck/testdata/source_multientry_text.ct000066400000000000000000000042411446745451500245710ustar00rootroot00000000000000##### # Test for multiple call stacks in source mode with expanded traces $ govulncheck -C ${moddir}/multientry . --> FAIL 3 Scanning your code and P packages across M dependent module for known vulnerabilities... Vulnerability #1: GO-2021-0113 Due to improper index calculation, an incorrectly formatted language tag can cause Parse to panic via an out of bounds read. If Parse is used to process untrusted user inputs, this may be used as a vector for a denial of service attack. More info: https://pkg.go.dev/vuln/GO-2021-0113 Module: golang.org/x/text Found in: golang.org/x/text@v0.3.5 Fixed in: golang.org/x/text@v0.3.7 Example traces found: #1: .../main.go:99:20: multientry.foobar calls language.MustParse #2: .../main.go:44:23: multientry.C calls language.Parse Your code is affected by 1 vulnerability from 1 module. Share feedback at https://go.dev/s/govulncheck-feedback. ##### # Test for multple call stacks in source mode with expanded traces $ govulncheck -C ${moddir}/multientry -show=traces ./... --> FAIL 3 Scanning your code and P packages across M dependent module for known vulnerabilities... Vulnerability #1: GO-2021-0113 Due to improper index calculation, an incorrectly formatted language tag can cause Parse to panic via an out of bounds read. If Parse is used to process untrusted user inputs, this may be used as a vector for a denial of service attack. More info: https://pkg.go.dev/vuln/GO-2021-0113 Module: golang.org/x/text Found in: golang.org/x/text@v0.3.5 Fixed in: golang.org/x/text@v0.3.7 Example traces found: #1: for function golang.org/x/text/language.MustParse .../main.go:26:3: golang.org/multientry.main .../main.go:48:8: golang.org/multientry.D .../main.go:99:20: golang.org/multientry.foobar golang.org/x/text/language.MustParse #2: for function golang.org/x/text/language.Parse .../main.go:22:3: golang.org/multientry.main .../main.go:44:23: golang.org/multientry.C golang.org/x/text/language.Parse Your code is affected by 1 vulnerability from 1 module. Share feedback at https://go.dev/s/govulncheck-feedback. vuln-1.0.1/cmd/govulncheck/testdata/source_replace_text.ct000066400000000000000000000015251446745451500237720ustar00rootroot00000000000000##### # Test of source mode on a module with a replace directive. $ govulncheck -C ${moddir}/replace ./... --> FAIL 3 Scanning your code and P packages across M dependent module for known vulnerabilities... Vulnerability #1: GO-2021-0113 Due to improper index calculation, an incorrectly formatted language tag can cause Parse to panic via an out of bounds read. If Parse is used to process untrusted user inputs, this may be used as a vector for a denial of service attack. More info: https://pkg.go.dev/vuln/GO-2021-0113 Module: golang.org/x/text Found in: golang.org/x/text@v0.3.0 Fixed in: golang.org/x/text@v0.3.7 Example traces found: #1: .../main.go:11:16: replace.main calls language.Parse Your code is affected by 1 vulnerability from 1 module. Share feedback at https://go.dev/s/govulncheck-feedback. vuln-1.0.1/cmd/govulncheck/testdata/source_stdlib_text.ct000066400000000000000000000032071446745451500236370ustar00rootroot00000000000000##### # Test finding stdlib vulnerability in source mode $ govulncheck -C ${moddir}/stdlib . --> FAIL 3 Scanning your code and P packages across M dependent modules for known vulnerabilities... Vulnerability #1: GO-2022-0969 HTTP/2 server connections can hang forever waiting for a clean shutdown that was preempted by a fatal error. This condition can be exploited by a malicious client to cause a denial of service. More info: https://pkg.go.dev/vuln/GO-2022-0969 Standard library Found in: net/http@go1.18 Fixed in: net/http@go1.19.1 Example traces found: #1: .../stdlib.go:17:31: stdlib.main calls http.ListenAndServe Your code is affected by 1 vulnerability from the Go standard library. Share feedback at https://go.dev/s/govulncheck-feedback. ##### # Test finding stdlib vulnerability in source mode with expanded traces $ govulncheck -C ${moddir}/stdlib -show=traces . --> FAIL 3 Scanning your code and P packages across M dependent modules for known vulnerabilities... Vulnerability #1: GO-2022-0969 HTTP/2 server connections can hang forever waiting for a clean shutdown that was preempted by a fatal error. This condition can be exploited by a malicious client to cause a denial of service. More info: https://pkg.go.dev/vuln/GO-2022-0969 Standard library Found in: net/http@go1.18 Fixed in: net/http@go1.19.1 Example traces found: #1: for function net/http.ListenAndServe .../stdlib.go:17:31: golang.org/stdlib.main net/http.ListenAndServe Your code is affected by 1 vulnerability from the Go standard library. Share feedback at https://go.dev/s/govulncheck-feedback. vuln-1.0.1/cmd/govulncheck/testdata/source_subdir_text.ct000066400000000000000000000034061446745451500236470ustar00rootroot00000000000000##### # Test govulncheck runs on the subdirectory of a module $ govulncheck -C ${moddir}/vuln/subdir . --> FAIL 3 Scanning your code and P packages across M dependent module for known vulnerabilities... Vulnerability #1: GO-2021-0113 Due to improper index calculation, an incorrectly formatted language tag can cause Parse to panic via an out of bounds read. If Parse is used to process untrusted user inputs, this may be used as a vector for a denial of service attack. More info: https://pkg.go.dev/vuln/GO-2021-0113 Module: golang.org/x/text Found in: golang.org/x/text@v0.3.0 Fixed in: golang.org/x/text@v0.3.7 Example traces found: #1: .../subdir.go:8:16: subdir.Foo calls language.Parse Your code is affected by 1 vulnerability from 1 module. Share feedback at https://go.dev/s/govulncheck-feedback. ##### # Test govulncheck runs on the subdirectory of a module $ govulncheck -C ${moddir}/vuln/subdir -show=traces . --> FAIL 3 Scanning your code and P packages across M dependent module for known vulnerabilities... Vulnerability #1: GO-2021-0113 Due to improper index calculation, an incorrectly formatted language tag can cause Parse to panic via an out of bounds read. If Parse is used to process untrusted user inputs, this may be used as a vector for a denial of service attack. More info: https://pkg.go.dev/vuln/GO-2021-0113 Module: golang.org/x/text Found in: golang.org/x/text@v0.3.0 Fixed in: golang.org/x/text@v0.3.7 Example traces found: #1: for function golang.org/x/text/language.Parse .../subdir.go:8:16: golang.org/vuln/subdir.Foo golang.org/x/text/language.Parse Your code is affected by 1 vulnerability from 1 module. Share feedback at https://go.dev/s/govulncheck-feedback. vuln-1.0.1/cmd/govulncheck/testdata/source_vendored_json.ct000066400000000000000000000151041446745451500241500ustar00rootroot00000000000000##### # $ govulncheck -C ${moddir}/vendored -json ./... { "config": { "protocol_version": "v1.0.0", "scanner_name": "govulncheck", "scanner_version": "v0.0.0-00000000000-20000101010101", "db": "testdata/vulndb-v1", "db_last_modified": "2023-04-03T15:57:51Z", "go_version": "go1.18", "scan_level": "symbol" } } { "progress": { "message": "Scanning your code and P packages across M dependent modules for known vulnerabilities..." } } { "osv": { "schema_version": "1.3.1", "id": "GO-2021-0265", "modified": "2023-04-03T15:57:51Z", "published": "2022-08-15T18:06:07Z", "aliases": [ "CVE-2021-42248", "CVE-2021-42836", "GHSA-c9gm-7rfj-8w5h", "GHSA-ppj4-34rq-v8j9" ], "details": "A maliciously crafted path can cause Get and other query functions to consume excessive amounts of CPU and time.", "affected": [ { "package": { "name": "github.com/tidwall/gjson", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.9.3" } ] } ], "ecosystem_specific": { "imports": [ { "path": "github.com/tidwall/gjson", "symbols": [ "Get", "GetBytes", "GetMany", "GetManyBytes", "Result.Get", "parseObject", "queryMatches" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/tidwall/gjson/commit/77a57fda87dca6d0d7d4627d512a630f89a91c96" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/237" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/236" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/commit/590010fdac311cc8990ef5c97448d4fec8f29944" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0265" } } } { "finding": { "osv": "GO-2021-0265", "fixed_version": "v1.9.3", "trace": [ { "module": "github.com/tidwall/gjson", "version": "v1.6.5", "package": "github.com/tidwall/gjson", "function": "Get", "receiver": "Result" }, { "module": "golang.org/vendored", "package": "golang.org/vendored", "function": "main", "position": { "filename": ".../vendored.go", "offset": 183, "line": 14, "column": 20 } } ] } } { "osv": { "schema_version": "1.3.1", "id": "GO-2021-0113", "modified": "2023-04-03T15:57:51Z", "published": "2021-10-06T17:51:21Z", "aliases": [ "CVE-2021-38561", "GHSA-ppp9-7jff-5vj2" ], "details": "Due to improper index calculation, an incorrectly formatted language tag can cause Parse to panic via an out of bounds read. If Parse is used to process untrusted user inputs, this may be used as a vector for a denial of service attack.", "affected": [ { "package": { "name": "golang.org/x/text", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "0.3.7" } ] } ], "ecosystem_specific": { "imports": [ { "path": "golang.org/x/text/language", "symbols": [ "MatchStrings", "MustParse", "Parse", "ParseAcceptLanguage" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://go.dev/cl/340830" }, { "type": "FIX", "url": "https://go.googlesource.com/text/+/383b2e75a7a4198c42f8f87833eefb772868a56f" } ], "credits": [ { "name": "Guido Vranken" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0113" } } } { "finding": { "osv": "GO-2021-0113", "fixed_version": "v0.3.7", "trace": [ { "module": "golang.org/x/text", "version": "v0.3.0", "package": "golang.org/x/text/language", "function": "Parse" }, { "module": "golang.org/vendored", "package": "golang.org/vendored", "function": "main", "position": { "filename": ".../vendored.go", "offset": 159, "line": 13, "column": 16 } } ] } } { "osv": { "schema_version": "1.3.1", "id": "GO-2021-0054", "modified": "2023-04-03T15:57:51Z", "published": "2021-04-14T20:04:52Z", "aliases": [ "CVE-2020-36067", "GHSA-p64j-r5f4-pwwx" ], "details": "Due to improper bounds checking, maliciously crafted JSON objects can cause an out-of-bounds panic. If parsing user input, this may be used as a denial of service vector.", "affected": [ { "package": { "name": "github.com/tidwall/gjson", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.6.6" } ] } ], "ecosystem_specific": { "imports": [ { "path": "github.com/tidwall/gjson", "symbols": [ "Result.ForEach", "unwrap" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/tidwall/gjson/commit/bf4efcb3c18d1825b2988603dea5909140a5302b" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/196" } ], "credits": [ { "name": "@toptotu" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0054" } } } { "finding": { "osv": "GO-2021-0054", "fixed_version": "v1.6.6", "trace": [ { "module": "github.com/tidwall/gjson", "version": "v1.6.5", "package": "github.com/tidwall/gjson" } ] } } vuln-1.0.1/cmd/govulncheck/testdata/source_vuln_json.ct000066400000000000000000000150501446745451500233260ustar00rootroot00000000000000##### # $ govulncheck -C ${moddir}/vuln -json ./... { "config": { "protocol_version": "v1.0.0", "scanner_name": "govulncheck", "scanner_version": "v0.0.0-00000000000-20000101010101", "db": "testdata/vulndb-v1", "db_last_modified": "2023-04-03T15:57:51Z", "go_version": "go1.18", "scan_level": "symbol" } } { "progress": { "message": "Scanning your code and P packages across M dependent modules for known vulnerabilities..." } } { "osv": { "schema_version": "1.3.1", "id": "GO-2021-0265", "modified": "2023-04-03T15:57:51Z", "published": "2022-08-15T18:06:07Z", "aliases": [ "CVE-2021-42248", "CVE-2021-42836", "GHSA-c9gm-7rfj-8w5h", "GHSA-ppj4-34rq-v8j9" ], "details": "A maliciously crafted path can cause Get and other query functions to consume excessive amounts of CPU and time.", "affected": [ { "package": { "name": "github.com/tidwall/gjson", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.9.3" } ] } ], "ecosystem_specific": { "imports": [ { "path": "github.com/tidwall/gjson", "symbols": [ "Get", "GetBytes", "GetMany", "GetManyBytes", "Result.Get", "parseObject", "queryMatches" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/tidwall/gjson/commit/77a57fda87dca6d0d7d4627d512a630f89a91c96" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/237" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/236" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/commit/590010fdac311cc8990ef5c97448d4fec8f29944" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0265" } } } { "finding": { "osv": "GO-2021-0265", "fixed_version": "v1.9.3", "trace": [ { "module": "github.com/tidwall/gjson", "version": "v1.6.5", "package": "github.com/tidwall/gjson", "function": "Get", "receiver": "Result" }, { "module": "golang.org/vuln", "package": "golang.org/vuln", "function": "main", "position": { "filename": ".../vuln.go", "offset": 183, "line": 14, "column": 20 } } ] } } { "osv": { "schema_version": "1.3.1", "id": "GO-2021-0113", "modified": "2023-04-03T15:57:51Z", "published": "2021-10-06T17:51:21Z", "aliases": [ "CVE-2021-38561", "GHSA-ppp9-7jff-5vj2" ], "details": "Due to improper index calculation, an incorrectly formatted language tag can cause Parse to panic via an out of bounds read. If Parse is used to process untrusted user inputs, this may be used as a vector for a denial of service attack.", "affected": [ { "package": { "name": "golang.org/x/text", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "0.3.7" } ] } ], "ecosystem_specific": { "imports": [ { "path": "golang.org/x/text/language", "symbols": [ "MatchStrings", "MustParse", "Parse", "ParseAcceptLanguage" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://go.dev/cl/340830" }, { "type": "FIX", "url": "https://go.googlesource.com/text/+/383b2e75a7a4198c42f8f87833eefb772868a56f" } ], "credits": [ { "name": "Guido Vranken" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0113" } } } { "finding": { "osv": "GO-2021-0113", "fixed_version": "v0.3.7", "trace": [ { "module": "golang.org/x/text", "version": "v0.3.0", "package": "golang.org/x/text/language", "function": "Parse" }, { "module": "golang.org/vuln", "package": "golang.org/vuln", "function": "main", "position": { "filename": ".../vuln.go", "offset": 159, "line": 13, "column": 16 } } ] } } { "osv": { "schema_version": "1.3.1", "id": "GO-2021-0054", "modified": "2023-04-03T15:57:51Z", "published": "2021-04-14T20:04:52Z", "aliases": [ "CVE-2020-36067", "GHSA-p64j-r5f4-pwwx" ], "details": "Due to improper bounds checking, maliciously crafted JSON objects can cause an out-of-bounds panic. If parsing user input, this may be used as a denial of service vector.", "affected": [ { "package": { "name": "github.com/tidwall/gjson", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.6.6" } ] } ], "ecosystem_specific": { "imports": [ { "path": "github.com/tidwall/gjson", "symbols": [ "Result.ForEach", "unwrap" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/tidwall/gjson/commit/bf4efcb3c18d1825b2988603dea5909140a5302b" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/196" } ], "credits": [ { "name": "@toptotu" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0054" } } } { "finding": { "osv": "GO-2021-0054", "fixed_version": "v1.6.6", "trace": [ { "module": "github.com/tidwall/gjson", "version": "v1.6.5", "package": "github.com/tidwall/gjson" } ] } } vuln-1.0.1/cmd/govulncheck/testdata/source_vuln_text.ct000066400000000000000000000076411446745451500233500ustar00rootroot00000000000000##### # Test of basic govulncheck in source mode $ govulncheck -C ${moddir}/vuln ./... --> FAIL 3 Scanning your code and P packages across M dependent modules for known vulnerabilities... Vulnerability #1: GO-2021-0265 A maliciously crafted path can cause Get and other query functions to consume excessive amounts of CPU and time. More info: https://pkg.go.dev/vuln/GO-2021-0265 Module: github.com/tidwall/gjson Found in: github.com/tidwall/gjson@v1.6.5 Fixed in: github.com/tidwall/gjson@v1.9.3 Example traces found: #1: .../vuln.go:14:20: vuln.main calls gjson.Result.Get Vulnerability #2: GO-2021-0113 Due to improper index calculation, an incorrectly formatted language tag can cause Parse to panic via an out of bounds read. If Parse is used to process untrusted user inputs, this may be used as a vector for a denial of service attack. More info: https://pkg.go.dev/vuln/GO-2021-0113 Module: golang.org/x/text Found in: golang.org/x/text@v0.3.0 Fixed in: golang.org/x/text@v0.3.7 Example traces found: #1: .../vuln.go:13:16: vuln.main calls language.Parse === Informational === Found 1 vulnerability in packages that you import, but there are no call stacks leading to the use of this vulnerability. You may not need to take any action. See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck for details. Vulnerability #1: GO-2021-0054 Due to improper bounds checking, maliciously crafted JSON objects can cause an out-of-bounds panic. If parsing user input, this may be used as a denial of service vector. More info: https://pkg.go.dev/vuln/GO-2021-0054 Module: github.com/tidwall/gjson Found in: github.com/tidwall/gjson@v1.6.5 Fixed in: github.com/tidwall/gjson@v1.6.6 Your code is affected by 2 vulnerabilities from 2 modules. Share feedback at https://go.dev/s/govulncheck-feedback. ##### # Test of basic govulncheck in source mode with expanded traces $ govulncheck -C ${moddir}/vuln -show=traces ./... --> FAIL 3 Scanning your code and P packages across M dependent modules for known vulnerabilities... Vulnerability #1: GO-2021-0265 A maliciously crafted path can cause Get and other query functions to consume excessive amounts of CPU and time. More info: https://pkg.go.dev/vuln/GO-2021-0265 Module: github.com/tidwall/gjson Found in: github.com/tidwall/gjson@v1.6.5 Fixed in: github.com/tidwall/gjson@v1.9.3 Example traces found: #1: for function github.com/tidwall/gjson.Result.Get .../vuln.go:14:20: golang.org/vuln.main github.com/tidwall/gjson.Result.Get Vulnerability #2: GO-2021-0113 Due to improper index calculation, an incorrectly formatted language tag can cause Parse to panic via an out of bounds read. If Parse is used to process untrusted user inputs, this may be used as a vector for a denial of service attack. More info: https://pkg.go.dev/vuln/GO-2021-0113 Module: golang.org/x/text Found in: golang.org/x/text@v0.3.0 Fixed in: golang.org/x/text@v0.3.7 Example traces found: #1: for function golang.org/x/text/language.Parse .../vuln.go:13:16: golang.org/vuln.main golang.org/x/text/language.Parse === Informational === Found 1 vulnerability in packages that you import, but there are no call stacks leading to the use of this vulnerability. You may not need to take any action. See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck for details. Vulnerability #1: GO-2021-0054 Due to improper bounds checking, maliciously crafted JSON objects can cause an out-of-bounds panic. If parsing user input, this may be used as a denial of service vector. More info: https://pkg.go.dev/vuln/GO-2021-0054 Module: github.com/tidwall/gjson Found in: github.com/tidwall/gjson@v1.6.5 Fixed in: github.com/tidwall/gjson@v1.6.6 Your code is affected by 2 vulnerabilities from 2 modules. Share feedback at https://go.dev/s/govulncheck-feedback. vuln-1.0.1/cmd/govulncheck/testdata/strip/000077500000000000000000000000001446745451500205415ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/strip/strip.ct000066400000000000000000000027411446745451500222360ustar00rootroot00000000000000# Test for stripped binaries (see #57764). $ govulncheck -mode=binary ${strip_binary} --> FAIL 3 Scanning your binary for known vulnerabilities... Vulnerability #1: GO-2021-0113 Due to improper index calculation, an incorrectly formatted language tag can cause Parse to panic via an out of bounds read. If Parse is used to process untrusted user inputs, this may be used as a vector for a denial of service attack. More info: https://pkg.go.dev/vuln/GO-2021-0113 Module: golang.org/x/text Found in: golang.org/x/text@v0.3.0 Fixed in: golang.org/x/text@v0.3.7 Example traces found: #1: language.MatchStrings #2: language.MustParse #3: language.Parse #4: language.ParseAcceptLanguage Vulnerability #2: GO-2020-0015 An attacker could provide a single byte to a UTF16 decoder instantiated with UseBOM or ExpectBOM to trigger an infinite loop if the String function on the Decoder is called, or the Decoder is passed to transform.String. If used to parse user supplied input, this may be used as a denial of service vector. More info: https://pkg.go.dev/vuln/GO-2020-0015 Module: golang.org/x/text Found in: golang.org/x/text@v0.3.0 Fixed in: golang.org/x/text@v0.3.3 Example traces found: #1: unicode.bomOverride.Transform #2: unicode.utf16Decoder.Transform #3: transform.String Your code is affected by 2 vulnerabilities from 1 module. Share feedback at https://go.dev/s/govulncheck-feedback. vuln-1.0.1/cmd/govulncheck/testdata/usage.ct000066400000000000000000000024121446745451500210330ustar00rootroot00000000000000##### # Test of govulncheck help output $ govulncheck -h Govulncheck reports known vulnerabilities in dependencies. Usage: govulncheck [flags] [patterns] govulncheck -mode=binary [flags] [binary] -C dir change to dir before running govulncheck -db url vulnerability database url (default "https://vuln.go.dev") -json output JSON -mode string supports source or binary (default "source") -scan string set the scanning level desired, one of module, package or symbol (default "symbol") -show list enable display of additional information specified by the comma separated list The only supported value is 'traces' -tags list comma-separated list of build tags -test analyze test files (only valid for source mode) -version print the version information For details, see https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck. ##### # Not scanning anything. $ govulncheck No vulnerabilities found. Share feedback at https://go.dev/s/govulncheck-feedback. ##### # Reporting version without scanning anything. $ govulncheck -version Go: go1.18 Scanner: govulncheck@v1.0.0 DB: testdata/vulndb-v1 DB updated: 2023-04-03 15:57:51 +0000 UTC No vulnerabilities found. Share feedback at https://go.dev/s/govulncheck-feedback.vuln-1.0.1/cmd/govulncheck/testdata/usage_fail.ct000066400000000000000000000004241446745451500220270ustar00rootroot00000000000000##### # Test of invalid input to -mode $ govulncheck -mode=invalid ./... --> FAIL 2 "invalid" is not a valid mode ##### # Test of trying to run -json with -v flag $ govulncheck -C ${moddir}/vuln -show=traces -json . --> FAIL 2 the -show flag is not supported for JSON output vuln-1.0.1/cmd/govulncheck/testdata/vulndb-v1/000077500000000000000000000000001446745451500212165ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/vulndb-v1/ID/000077500000000000000000000000001446745451500215125ustar00rootroot00000000000000vuln-1.0.1/cmd/govulncheck/testdata/vulndb-v1/ID/GO-2020-0015.json000066400000000000000000000023521446745451500236600ustar00rootroot00000000000000{"schema_version":"1.3.1","id":"GO-2020-0015","modified":"2023-04-03T15:57:51Z","published":"2021-04-14T20:04:52Z","aliases":["CVE-2020-14040","GHSA-5rcv-m4m3-hfh7"],"details":"An attacker could provide a single byte to a UTF16 decoder instantiated with UseBOM or ExpectBOM to trigger an infinite loop if the String function on the Decoder is called, or the Decoder is passed to transform.String. If used to parse user supplied input, this may be used as a denial of service vector.","affected":[{"package":{"name":"golang.org/x/text","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.3.3"}]}],"ecosystem_specific":{"imports":[{"path":"golang.org/x/text/encoding/unicode","symbols":["bomOverride.Transform","utf16Decoder.Transform"]},{"path":"golang.org/x/text/transform","symbols":["String"]}]}}],"references":[{"type":"FIX","url":"https://go.dev/cl/238238"},{"type":"FIX","url":"https://go.googlesource.com/text/+/23ae387dee1f90d29a23c0e87ee0b46038fbed0e"},{"type":"REPORT","url":"https://go.dev/issue/39491"},{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/bXVeAmGOqz0"}],"credits":[{"name":"@abacabadabacaba and Anton Gyllenberg"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2020-0015"}}vuln-1.0.1/cmd/govulncheck/testdata/vulndb-v1/ID/GO-2020-0015.json.gz000066400000000000000000000013351446745451500242770ustar00rootroot00000000000000|Tn6žV(ɾ8zjppCaEd"i{}2ֻ;3YAo{A;i映VXzVB̄`n4BLgjqS-1~ב|BTb^-qjb.0_f /nޕ]m#i; #g `b ڶ>DCK؃!"+S|~ K2Etn[@mՑ8׃n ׶f2jg?d dDGXM,dC|Nx)|l`4{ChVm?N5O@d5dسΧM2|=bOZꈖ: [gȶmE~ K!r7ހSl{Џ{OOqx6N-mwj#6e2-=m࿅nwStJ6]á9T׊ͫ%v]xp1Ņ4sÞ|k·_FZo]}u}&MVˢ\ֹppJM~Ȋ\(漹7̢岩Y xX~|Oa଼K{ûmgUm67#k`%g2<]w/Oҳҗt/#$&u ltưٷeEj ^ު`lu:z2%vuln-1.0.1/cmd/govulncheck/testdata/vulndb-v1/ID/GO-2021-0054.json000066400000000000000000000016021446745451500236610ustar00rootroot00000000000000{"schema_version":"1.3.1","id":"GO-2021-0054","modified":"2023-04-03T15:57:51Z","published":"2021-04-14T20:04:52Z","aliases":["CVE-2020-36067","GHSA-p64j-r5f4-pwwx"],"details":"Due to improper bounds checking, maliciously crafted JSON objects can cause an out-of-bounds panic. If parsing user input, this may be used as a denial of service vector.","affected":[{"package":{"name":"github.com/tidwall/gjson","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.6.6"}]}],"ecosystem_specific":{"imports":[{"path":"github.com/tidwall/gjson","symbols":["Result.ForEach","unwrap"]}]}}],"references":[{"type":"FIX","url":"https://github.com/tidwall/gjson/commit/bf4efcb3c18d1825b2988603dea5909140a5302b"},{"type":"WEB","url":"https://github.com/tidwall/gjson/issues/196"}],"credits":[{"name":"@toptotu"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2021-0054"}}vuln-1.0.1/cmd/govulncheck/testdata/vulndb-v1/ID/GO-2021-0054.json.gz000066400000000000000000000010671446745451500243050ustar00rootroot00000000000000n<_.,9Vɟm$HF0F$xczB ԋ]sx7iėY' ddl8ys^J+91/bxʪ+D`B=Hd$+s^*YDGgi3x+c,^c[ElwFАG98@k&Gc!jTIIElA LXl=5'W1 *u] *)v2Iձ2Ltlio:!%q`eN b;^dmI 7#( *C=^6{{uZA$;8O SUwv0ǧ,ޑKRy N9LZ~:e2Y´?ٿ8CBRmh{62߷cɅ'nPAP{v,dIoR;@UiTq>ۂZQ/DjU^zZECX:+8 ק97yt.K*ag$ڇSA5:xe޺ICt^|i[vuln-1.0.1/cmd/govulncheck/testdata/vulndb-v1/ID/GO-2021-0059.json000066400000000000000000000017231446745451500236720ustar00rootroot00000000000000{"schema_version":"1.3.1","id":"GO-2021-0059","modified":"2023-04-03T15:57:51Z","published":"2021-04-14T20:04:52Z","aliases":["CVE-2020-35380","GHSA-w942-gw6m-p62c"],"details":"Due to improper bounds checking, maliciously crafted JSON objects can cause an out-of-bounds panic. If parsing user input, this may be used as a denial of service vector.","affected":[{"package":{"name":"github.com/tidwall/gjson","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.6.4"}]}],"ecosystem_specific":{"imports":[{"path":"github.com/tidwall/gjson","symbols":["Get","GetBytes","GetMany","GetManyBytes","Result.Array","Result.Get","Result.Map","Result.Value","squash"]}]}}],"references":[{"type":"FIX","url":"https://github.com/tidwall/gjson/commit/f0ee9ebde4b619767ae4ac03e8e42addb530f6bc"},{"type":"WEB","url":"https://github.com/tidwall/gjson/issues/192"}],"credits":[{"name":"@toptotu"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2021-0059"}}vuln-1.0.1/cmd/govulncheck/testdata/vulndb-v1/ID/GO-2021-0059.json.gz000066400000000000000000000011251446745451500243050ustar00rootroot00000000000000S]o6+>۲I)Hh`+j%1H? 9?޸쌸{yG %Yղ#8dz7?y\u"VcWm 9 ĜbbFi2V^6]Fab`aTBYQ2Cɔwj#F)x®[X!;-&.b8eZf'#I¦mK/bGPAHPA'\넫1uyaH$D@\:C  `ܤ. HΨZP*)a&.Z,1je@]svk5b"#w99qG.0AY<)}h{97Ԓ!O ?*ӶJ-r5¥mF^R<[96M\d+x3@{mҴWIKSʇ\euyk|T?UpJ/>~K˺TU~WU]]eI757P+\E؛Ay ZtCK7ч>z ӛ9(vuln-1.0.1/cmd/govulncheck/testdata/vulndb-v1/ID/GO-2021-0265.json000066400000000000000000000020641446745451500236700ustar00rootroot00000000000000{"schema_version":"1.3.1","id":"GO-2021-0265","modified":"2023-04-03T15:57:51Z","published":"2022-08-15T18:06:07Z","aliases":["CVE-2021-42248","CVE-2021-42836","GHSA-c9gm-7rfj-8w5h","GHSA-ppj4-34rq-v8j9"],"details":"A maliciously crafted path can cause Get and other query functions to consume excessive amounts of CPU and time.","affected":[{"package":{"name":"github.com/tidwall/gjson","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.9.3"}]}],"ecosystem_specific":{"imports":[{"path":"github.com/tidwall/gjson","symbols":["Get","GetBytes","GetMany","GetManyBytes","Result.Get","parseObject","queryMatches"]}]}}],"references":[{"type":"FIX","url":"https://github.com/tidwall/gjson/commit/77a57fda87dca6d0d7d4627d512a630f89a91c96"},{"type":"WEB","url":"https://github.com/tidwall/gjson/issues/237"},{"type":"WEB","url":"https://github.com/tidwall/gjson/issues/236"},{"type":"WEB","url":"https://github.com/tidwall/gjson/commit/590010fdac311cc8990ef5c97448d4fec8f29944"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2021-0265"}}vuln-1.0.1/cmd/govulncheck/testdata/vulndb-v1/ID/GO-2021-0265.json.gz000066400000000000000000000011251446745451500243040ustar00rootroot00000000000000Tn8^c, Lh`4yiёHEEx_s#X^c˞[e4TIJ@?qxJ**1>M*ݰi?Zb~GEEͧ(fBW~iD,w>/rY]'i] k: \5+3@xϤCA:j™&  aZjˀAsC8ZHXk,1\RmgwɗSvcf+t]Gи4бf|f5ZXq"ڣ5?W?_oڹVa#!7m\,˥`E.8[*r.\dQ Eʈٳ=//}v@I]mVRQ)O,)ʌy" ed`mų|_ &Có8~ 784vuln-1.0.1/cmd/govulncheck/testdata/vulndb-v1/ID/GO-2022-0969.json000066400000000000000000000026721446745451500237110ustar00rootroot00000000000000{"schema_version":"1.3.1","id":"GO-2022-0969","modified":"2023-04-03T15:57:51Z","published":"2022-09-12T20:23:06Z","aliases":["CVE-2022-27664","GHSA-69cg-p879-7622"],"details":"HTTP/2 server connections can hang forever waiting for a clean shutdown that was preempted by a fatal error. This condition can be exploited by a malicious client to cause a denial of service.","affected":[{"package":{"name":"stdlib","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.18.6"},{"introduced":"1.19.0"},{"fixed":"1.19.1"}]}],"ecosystem_specific":{"imports":[{"path":"net/http","symbols":["ListenAndServe","ListenAndServeTLS","Serve","ServeTLS","Server.ListenAndServe","Server.ListenAndServeTLS","Server.Serve","Server.ServeTLS","http2Server.ServeConn","http2serverConn.goAway"]}]}},{"package":{"name":"golang.org/x/net","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.0.0-20220906165146-f3363e06e74c"}]}],"ecosystem_specific":{"imports":[{"path":"golang.org/x/net/http2","symbols":["Server.ServeConn","serverConn.goAway"]}]}}],"references":[{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/x49AQzIVX-s"},{"type":"REPORT","url":"https://go.dev/issue/54658"},{"type":"FIX","url":"https://go.dev/cl/428735"}],"credits":[{"name":"Bahruz Jabiyev, Tommaso Innocenti, Anthony Gavazzi, Steven Sprecher, and Kaan Onarlioglu"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2022-0969"}}vuln-1.0.1/cmd/govulncheck/testdata/vulndb-v1/ID/GO-2022-0969.json.gz000066400000000000000000000013531446745451500243230ustar00rootroot00000000000000TOo۸*;Kr[Z]b x$)w_PvqGΛ:-u}GIOx J4tQ, T&yOtu6+grVI׾P2uL@A%ё||XE1n>b!/y簉"R9(z%ə##˄њF;&Pujc) Qzy:3dBjWQ3ߢgXoSŶ{Frn ;*VWFPI!Pg0#"-Q1S _$|Hz?!(#(J-D@¸ԅ #1}ȫ@ޑޚjc)iz'W-YD[Ć֬=o?&/wUP )Tkt1=>g#IVd .vuln-1.0.1/cmd/govulncheck/testdata/vulndb-v1/index/vulns.json000066400000000000000000000012521446745451500243670ustar00rootroot00000000000000[{"id":"GO-2020-0015","modified":"2023-04-03T15:57:51Z","aliases":["CVE-2020-14040","GHSA-5rcv-m4m3-hfh7"]},{"id":"GO-2021-0054","modified":"2023-04-03T15:57:51Z","aliases":["CVE-2020-36067","GHSA-p64j-r5f4-pwwx"]},{"id":"GO-2021-0059","modified":"2023-04-03T15:57:51Z","aliases":["CVE-2020-35380","GHSA-w942-gw6m-p62c"]},{"id":"GO-2021-0113","modified":"2023-04-03T15:57:51Z","aliases":["CVE-2021-38561","GHSA-ppp9-7jff-5vj2"]},{"id":"GO-2021-0265","modified":"2023-04-03T15:57:51Z","aliases":["CVE-2021-42248","CVE-2021-42836","GHSA-c9gm-7rfj-8w5h","GHSA-ppj4-34rq-v8j9"]},{"id":"GO-2022-0969","modified":"2023-04-03T15:57:51Z","aliases":["CVE-2022-27664","GHSA-69cg-p879-7622"]}]vuln-1.0.1/cmd/govulncheck/testdata/vulndb-v1/index/vulns.json.gz000066400000000000000000000004031446745451500250030ustar00rootroot00000000000000AK3A 2$37KeiwpqBWiUi y,ߡ@ ;dbB"s=m# pjh=A~}]Ǜc'$ ,n<>E9$RMy# g5 izNxi5%aM6`5QCܥU CXŦ)aAv0`tz_K3#39SSg 'vuln-1.0.1/devtools/000077500000000000000000000000001446745451500143535ustar00rootroot00000000000000vuln-1.0.1/devtools/lib.sh000066400000000000000000000022671446745451500154640ustar00rootroot00000000000000#!/bin/bash # Copyright 2021 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. # Library of useful bash functions and variables. RED=; GREEN=; YELLOW=; NORMAL=; MAXWIDTH=0 if tput setaf 1 >& /dev/null; then RED=$(tput setaf 1) GREEN=$(tput setaf 2) YELLOW=$(tput setaf 3) NORMAL=$(tput sgr0) MAXWIDTH=$(( $(tput cols) - 2 )) fi EXIT_CODE=0 export EXIT_CODE info() { echo -e "${GREEN}$*${NORMAL}" 1>&2; } warn() { echo -e "${YELLOW}$*${NORMAL}" 1>&2; } err() { echo -e "${RED}$*${NORMAL}" 1>&2; EXIT_CODE=1; } die() { err "$@" exit 1 } dryrun=false # runcmd prints an info log describing the command that is about to be run, and # then runs it. It sets EXIT_CODE to non-zero if the command fails, but does not exit # the script. runcmd() { msg="$*" if $dryrun; then echo -e "${YELLOW}dryrun${GREEN}\$ $msg${NORMAL}" return 0 fi # Truncate command logging for narrow terminals. # Account for the 2 characters of '$ '. if [[ $MAXWIDTH -gt 0 && ${#msg} -gt $MAXWIDTH ]]; then msg="${msg::$(( MAXWIDTH - 3 ))}..." fi echo -e "$*\n" 1>&2; "$@" || err "command failed" } vuln-1.0.1/devtools/snapshot_vulndb.sh000077500000000000000000000027061446745451500201300ustar00rootroot00000000000000#!/usr/bin/env -S bash -e # Copyright 2023 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. source devtools/lib.sh || { echo "Are you at repo root?"; exit 1; } # Script for copying data from the v1 schema in vuln.go.dev for tests. origin="https://vuln.go.dev" go install golang.org/x/vulndb/cmd/indexdb@latest # Copy files for unit tests. copyFiles=( "ID/GO-2021-0159.json" "ID/GO-2022-0229.json" "ID/GO-2022-0463.json" "ID/GO-2022-0569.json" "ID/GO-2022-0572.json" "ID/GO-2021-0068.json" "ID/GO-2022-0475.json" "ID/GO-2022-0476.json" "ID/GO-2021-0240.json" "ID/GO-2021-0264.json" "ID/GO-2022-0273.json" ) UNIT_OUT_DIR=$(pwd)/internal/client/testdata/vulndb-v1 for f in "${copyFiles[@]}"; do mkdir -p "$UNIT_OUT_DIR/$(dirname "$f")" && curl -L $origin/"$f" --output "$UNIT_OUT_DIR"/"$f" done unit_vulns="$UNIT_OUT_DIR/ID" indexdb -out "$UNIT_OUT_DIR" -vulns "$unit_vulns" # Copy files for integration tests. copyFiles=( "ID/GO-2022-0969.json" "ID/GO-2020-0015.json" "ID/GO-2021-0113.json" "ID/GO-2021-0054.json" "ID/GO-2021-0059.json" "ID/GO-2021-0265.json" ) INTEG_OUT_DIR=$(pwd)/cmd/govulncheck/testdata/vulndb-v1 for f in "${copyFiles[@]}"; do mkdir -p "$INTEG_OUT_DIR"/"$(dirname "$f")" && curl -L "$origin"/"$f" --output "$INTEG_OUT_DIR"/"$f" done integ_vulns="$INTEG_OUT_DIR/ID" indexdb -out "$INTEG_OUT_DIR" -vulns "$integ_vulns" vuln-1.0.1/doc/000077500000000000000000000000001446745451500132615ustar00rootroot00000000000000vuln-1.0.1/doc/vulndb.md000066400000000000000000000031051446745451500150740ustar00rootroot00000000000000# Go Vulnerability Database ## Accessing the database The Go vulnerability database is rooted at `https://vuln.go.dev` and provides data as JSON. Do not rely on the contents of the x/vulndb repository. The YAML files in that repository are maintained using an internal format that is subject to change without warning. The endpoints the table below are supported. For each path: - $base is the path portion of a Go vulnerability database URL (`https://vuln.go.dev`). - $module is a module path - $vuln is a Go vulnerabilitiy ID (for example, `GO-2021-1234`) | Path | Description | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | $base/index.json | List of module paths in the database mapped to its last modified timestamp ([link](https://vuln.go.dev/index.json)). | | $base/$module.json | List of vulnerability entries for that module ([example](https://vuln.go.dev/golang.org/x/crypto.json)). | | $base/ID/index.json | List of all the vulnerability entries in the database | | $base/ID/$vuln.json | An individual Go vulnerability report | Note that these paths and format are provisional and likely to change until an approved proposal. vuln-1.0.1/go.mod000066400000000000000000000006301446745451500136210ustar00rootroot00000000000000module golang.org/x/vuln go 1.18 require ( github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 github.com/google/go-cmp v0.5.8 golang.org/x/mod v0.12.0 golang.org/x/sync v0.3.0 golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8 ) require ( github.com/google/renameio v0.1.0 // indirect golang.org/x/sys v0.11.0 // indirect ) vuln-1.0.1/go.sum000066400000000000000000000030071446745451500136470ustar00rootroot00000000000000github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU= github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8 h1:VuJo4Mt0EVPychre4fNlDWDuE5AjXtPJpRUWqZDQhaI= mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8/go.mod h1:Oh/d7dEtzsNHGOq1Cdv8aMm3KdKhVvPbRQcM8WFpBR8= vuln-1.0.1/internal/000077500000000000000000000000001446745451500143305ustar00rootroot00000000000000vuln-1.0.1/internal/client/000077500000000000000000000000001446745451500156065ustar00rootroot00000000000000vuln-1.0.1/internal/client/client.go000066400000000000000000000173501446745451500174210ustar00rootroot00000000000000// Copyright 2023 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 client provides an interface for accessing vulnerability // databases, via either HTTP or local filesystem access. // // The protocol is described at https://go.dev/security/vuln/database. package client import ( "bytes" "context" "encoding/json" "errors" "fmt" "net/http" "net/url" "os" "path/filepath" "sort" "strings" "time" "golang.org/x/sync/errgroup" "golang.org/x/vuln/internal/derrors" "golang.org/x/vuln/internal/osv" isem "golang.org/x/vuln/internal/semver" "golang.org/x/vuln/internal/web" ) // A Client for reading vulnerability databases. type Client struct { source } type Options struct { HTTPClient *http.Client } // NewClient returns a client that reads the vulnerability database // in source (an "http" or "file" prefixed URL). // // It supports databases following the API described // in https://go.dev/security/vuln/database#api. func NewClient(source string, opts *Options) (_ *Client, err error) { source = strings.TrimRight(source, "/") uri, err := url.Parse(source) if err != nil { return nil, err } switch uri.Scheme { case "http", "https": return newHTTPClient(uri, opts) case "file": return newLocalClient(uri) default: return nil, fmt.Errorf("source %q has unsupported scheme", uri) } } var errUnknownSchema = errors.New("unrecognized vulndb format; see https://go.dev/security/vuln/database#api for accepted schema") func newHTTPClient(uri *url.URL, opts *Options) (*Client, error) { source := uri.String() // v1 returns true if the source likely follows the V1 schema. v1 := func() bool { return source == "https://vuln.go.dev" || endpointExistsHTTP(source, "index/modules.json.gz") } if v1() { return &Client{source: newHTTPSource(uri.String(), opts)}, nil } return nil, errUnknownSchema } func endpointExistsHTTP(source, endpoint string) bool { r, err := http.Head(source + "/" + endpoint) return err == nil && r.StatusCode == http.StatusOK } func newLocalClient(uri *url.URL) (*Client, error) { dir, err := toDir(uri) if err != nil { return nil, err } // Check if the DB likely follows the v1 schema by // looking for the "index/modules.json" endpoint. if endpointExistsDir(dir, modulesEndpoint+".json") { return &Client{source: newLocalSource(dir)}, nil } // If the DB doesn't follow the v1 schema, // attempt to intepret it as a flat list of OSV files. // This is currently a "hidden" feature, so don't output the // specific error if this fails. src, err := newHybridSource(dir) if err != nil { return nil, errUnknownSchema } return &Client{source: src}, nil } func toDir(uri *url.URL) (string, error) { dir, err := web.URLToFilePath(uri) if err != nil { return "", err } fi, err := os.Stat(dir) if err != nil { return "", err } if !fi.IsDir() { return "", fmt.Errorf("%s is not a directory", dir) } return dir, nil } func endpointExistsDir(dir, endpoint string) bool { _, err := os.Stat(filepath.Join(dir, endpoint)) return err == nil } func NewInMemoryClient(entries []*osv.Entry) (*Client, error) { s, err := newInMemorySource(entries) if err != nil { return nil, err } return &Client{source: s}, nil } func (c *Client) LastModifiedTime(ctx context.Context) (_ time.Time, err error) { derrors.Wrap(&err, "LastModifiedTime()") b, err := c.source.get(ctx, dbEndpoint) if err != nil { return time.Time{}, err } var dbMeta dbMeta if err := json.Unmarshal(b, &dbMeta); err != nil { return time.Time{}, err } return dbMeta.Modified, nil } type ModuleRequest struct { // The module path to filter on. // This must be set (if empty, ByModule errors). Path string // (Optional) If set, only return vulnerabilities affected // at this version. Version string } type ModuleResponse struct { Path string Version string Entries []*osv.Entry } // ByModules returns a list of responses // containing the OSV entries corresponding to each request. // // The order of the requests is preserved, and each request has // a response even if there are no entries (in which case the Entries // field is nil). func (c *Client) ByModules(ctx context.Context, reqs []*ModuleRequest) (_ []*ModuleResponse, err error) { derrors.Wrap(&err, "ByModules(%v)", reqs) metas, err := c.moduleMetas(ctx, reqs) if err != nil { return nil, err } resps := make([]*ModuleResponse, len(reqs)) g, gctx := errgroup.WithContext(ctx) g.SetLimit(10) for i, req := range reqs { i, req := i, req g.Go(func() error { entries, err := c.byModule(gctx, req, metas[i]) if err != nil { return err } resps[i] = &ModuleResponse{ Path: req.Path, Version: req.Version, Entries: entries, } return nil }) } if err := g.Wait(); err != nil { return nil, err } return resps, nil } func (c *Client) moduleMetas(ctx context.Context, reqs []*ModuleRequest) (_ []*moduleMeta, err error) { b, err := c.source.get(ctx, modulesEndpoint) if err != nil { return nil, err } dec, err := newStreamDecoder(b) if err != nil { return nil, err } metas := make([]*moduleMeta, len(reqs)) for dec.More() { var m moduleMeta err := dec.Decode(&m) if err != nil { return nil, err } for i, req := range reqs { if m.Path == req.Path { metas[i] = &m } } } return metas, nil } // byModule returns the OSV entries matching the ModuleRequest, // or (nil, nil) if there are none. func (c *Client) byModule(ctx context.Context, req *ModuleRequest, m *moduleMeta) (_ []*osv.Entry, err error) { // This module isn't in the database. if m == nil { return nil, nil } if req.Path == "" { return nil, fmt.Errorf("module path must be set") } if req.Version != "" && !isem.Valid(req.Version) { return nil, fmt.Errorf("version %s is not valid semver", req.Version) } var ids []string for _, v := range m.Vulns { if v.Fixed == "" || isem.Less(req.Version, v.Fixed) { ids = append(ids, v.ID) } } if len(ids) == 0 { return nil, nil } entries, err := c.byIDs(ctx, ids) if err != nil { return nil, err } // Filter by version. if req.Version != "" { affected := func(e *osv.Entry) bool { for _, a := range e.Affected { if a.Module.Path == req.Path && isem.Affects(a.Ranges, req.Version) { return true } } return false } var filtered []*osv.Entry for _, entry := range entries { if affected(entry) { filtered = append(filtered, entry) } } if len(filtered) == 0 { return nil, nil } } sort.SliceStable(entries, func(i, j int) bool { return entries[i].ID < entries[j].ID }) return entries, nil } func (c *Client) byIDs(ctx context.Context, ids []string) (_ []*osv.Entry, err error) { entries := make([]*osv.Entry, len(ids)) g, gctx := errgroup.WithContext(ctx) g.SetLimit(10) for i, id := range ids { i, id := i, id g.Go(func() error { e, err := c.byID(gctx, id) if err != nil { return err } entries[i] = e return nil }) } if err := g.Wait(); err != nil { return nil, err } return entries, nil } // byID returns the OSV entry with the given ID, // or an error if it does not exist / cannot be unmarshaled. func (c *Client) byID(ctx context.Context, id string) (_ *osv.Entry, err error) { derrors.Wrap(&err, "byID(%s)", id) b, err := c.source.get(ctx, entryEndpoint(id)) if err != nil { return nil, err } var entry osv.Entry if err := json.Unmarshal(b, &entry); err != nil { return nil, err } return &entry, nil } // newStreamDecoder returns a decoder that can be used // to read an array of JSON objects. func newStreamDecoder(b []byte) (*json.Decoder, error) { dec := json.NewDecoder(bytes.NewBuffer(b)) // skip open bracket _, err := dec.Token() if err != nil { return nil, err } return dec, nil } vuln-1.0.1/internal/client/client_test.go000066400000000000000000000171101446745451500204520ustar00rootroot00000000000000// Copyright 2023 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 client import ( "context" "encoding/json" "errors" "fmt" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "time" "github.com/google/go-cmp/cmp" "golang.org/x/vuln/internal/osv" "golang.org/x/vuln/internal/web" ) var ( testLegacyVulndb = filepath.Join("testdata", "vulndb-legacy") testLegacyVulndbFileURL = localURL(testLegacyVulndb) testVulndb = filepath.Join("testdata", "vulndb-v1") testVulndbFileURL = localURL(testVulndb) testFlatVulndb = filepath.Join("testdata", "vulndb-v1", "ID") testFlatVulndbFileURL = localURL(testFlatVulndb) testIDs = []string{ "GO-2021-0159", "GO-2022-0229", "GO-2022-0463", "GO-2022-0569", "GO-2022-0572", "GO-2021-0068", "GO-2022-0475", "GO-2022-0476", "GO-2021-0240", "GO-2021-0264", "GO-2022-0273", } ) func newTestServer(dir string) *httptest.Server { mux := http.NewServeMux() mux.Handle("/", http.FileServer(http.Dir(dir))) return httptest.NewServer(mux) } func entries(ids []string) ([]*osv.Entry, error) { if len(ids) == 0 { return nil, nil } entries := make([]*osv.Entry, len(ids)) for i, id := range ids { b, err := os.ReadFile(filepath.Join(testVulndb, idDir, id+".json")) if err != nil { return nil, err } var entry osv.Entry if err := json.Unmarshal(b, &entry); err != nil { return nil, err } entries[i] = &entry } return entries, nil } func localURL(dir string) string { absDir, err := filepath.Abs(dir) if err != nil { panic(fmt.Sprintf("failed to read %s: %v", dir, err)) } u, err := web.URLFromFilePath(absDir) if err != nil { panic(fmt.Sprintf("failed to read %s: %v", dir, err)) } return u.String() } func TestNewClient(t *testing.T) { t.Run("vuln.go.dev", func(t *testing.T) { src := "https://vuln.go.dev" c, err := NewClient(src, nil) if err != nil { t.Fatal(err) } if c == nil { t.Errorf("NewClient(%s) = nil, want instantiated *Client", src) } }) t.Run("http/v1", func(t *testing.T) { srv := newTestServer(testVulndb) t.Cleanup(srv.Close) c, err := NewClient(srv.URL, &Options{HTTPClient: srv.Client()}) if err != nil { t.Fatal(err) } if c == nil { t.Errorf("NewClient(%s) = nil, want instantiated *Client", srv.URL) } }) t.Run("http/legacy", func(t *testing.T) { srv := newTestServer(testLegacyVulndb) t.Cleanup(srv.Close) _, err := NewClient(srv.URL, &Options{HTTPClient: srv.Client()}) if err == nil || !errors.Is(err, errUnknownSchema) { t.Errorf("NewClient() = %s, want error %s", err, errUnknownSchema) } }) t.Run("local/v1", func(t *testing.T) { src := testVulndbFileURL c, err := NewClient(src, nil) if err != nil { t.Fatal(err) } if c == nil { t.Errorf("NewClient(%s) = nil, want instantiated *Client", src) } }) t.Run("local/flat", func(t *testing.T) { src := testFlatVulndbFileURL c, err := NewClient(src, nil) if err != nil { t.Fatal(err) } if c == nil { t.Errorf("NewClient(%s) = nil, want instantiated *Client", src) } }) t.Run("local/legacy", func(t *testing.T) { src := testLegacyVulndbFileURL _, err := NewClient(src, nil) if err == nil || !errors.Is(err, errUnknownSchema) { t.Errorf("NewClient() = %s, want error %s", err, errUnknownSchema) } }) } func TestLastModifiedTime(t *testing.T) { test := func(t *testing.T, c *Client) { got, err := c.LastModifiedTime(context.Background()) if err != nil { t.Fatal(err) } want, err := time.Parse(time.RFC3339, "2023-04-03T15:57:51Z") if err != nil { t.Fatal(err) } if got != want { t.Errorf("LastModifiedTime = %s, want %s", got, want) } } testAllClientTypes(t, test) } func TestByModules(t *testing.T) { tcs := []struct { module *ModuleRequest wantIDs []string }{ { module: &ModuleRequest{ Path: "github.com/beego/beego", }, wantIDs: []string{"GO-2022-0463", "GO-2022-0569", "GO-2022-0572"}, }, { module: &ModuleRequest{ Path: "github.com/beego/beego", // "GO-2022-0463" not affected at this version. Version: "1.12.10", }, wantIDs: []string{"GO-2022-0569", "GO-2022-0572"}, }, { module: &ModuleRequest{ Path: "stdlib", }, wantIDs: []string{"GO-2021-0159", "GO-2021-0240", "GO-2021-0264", "GO-2022-0229", "GO-2022-0273"}, }, { module: &ModuleRequest{ Path: "stdlib", Version: "go1.17", }, wantIDs: []string{"GO-2021-0264", "GO-2022-0273"}, }, { module: &ModuleRequest{ Path: "toolchain", }, wantIDs: []string{"GO-2021-0068", "GO-2022-0475", "GO-2022-0476"}, }, { module: &ModuleRequest{ Path: "toolchain", // All vulns affected at this version. Version: "1.14.13", }, wantIDs: []string{"GO-2021-0068", "GO-2022-0475", "GO-2022-0476"}, }, { module: &ModuleRequest{ Path: "golang.org/x/crypto", }, wantIDs: []string{"GO-2022-0229"}, }, { module: &ModuleRequest{ Path: "golang.org/x/crypto", // Vuln was fixed at exactly this version. Version: "1.13.7", }, wantIDs: nil, }, { module: &ModuleRequest{ Path: "does.not/exist", }, wantIDs: nil, }, { module: &ModuleRequest{ Path: "does.not/exist", Version: "1.0.0", }, wantIDs: nil, }, } // Test each case as an individual call to ByModules. for _, tc := range tcs { t.Run(tc.module.Path+"@"+tc.module.Version, func(t *testing.T) { test := func(t *testing.T, c *Client) { got, err := c.ByModules(context.Background(), []*ModuleRequest{tc.module}) if err != nil { t.Fatal(err) } wantEntries, err := entries(tc.wantIDs) if err != nil { t.Fatal(err) } want := []*ModuleResponse{{ Path: tc.module.Path, Version: tc.module.Version, Entries: wantEntries, }} if diff := cmp.Diff(want, got); diff != "" { t.Errorf("ByModule() mismatch (-want +got):\n%s", diff) } } testAllClientTypes(t, test) }) } // Now create a single test that makes all the requests // in a single call to ByModules. reqs := make([]*ModuleRequest, len(tcs)) want := make([]*ModuleResponse, len(tcs)) for i, tc := range tcs { reqs[i] = tc.module wantEntries, err := entries(tc.wantIDs) if err != nil { t.Fatal(err) } want[i] = &ModuleResponse{ Path: tc.module.Path, Version: tc.module.Version, Entries: wantEntries, } } t.Run("all", func(t *testing.T) { test := func(t *testing.T, c *Client) { got, err := c.ByModules(context.Background(), reqs) if err != nil { t.Fatal(err) } if diff := cmp.Diff(want, got); diff != "" { t.Errorf("ByModules() mismatch (-want +got):\n%s", diff) } } testAllClientTypes(t, test) }) } // testAllClientTypes runs a given test for all client types. func testAllClientTypes(t *testing.T, test func(t *testing.T, c *Client)) { t.Run("http", func(t *testing.T) { srv := newTestServer(testVulndb) t.Cleanup(srv.Close) hc, err := NewClient(srv.URL, &Options{HTTPClient: srv.Client()}) if err != nil { t.Fatal(err) } test(t, hc) }) t.Run("local", func(t *testing.T) { fc, err := NewClient(testVulndbFileURL, nil) if err != nil { t.Fatal(err) } test(t, fc) }) t.Run("hybrid", func(t *testing.T) { fc, err := NewClient(testFlatVulndbFileURL, nil) if err != nil { t.Fatal(err) } test(t, fc) }) t.Run("in-memory", func(t *testing.T) { testEntries, err := entries(testIDs) if err != nil { t.Fatal(err) } mc, err := NewInMemoryClient(testEntries) if err != nil { t.Fatal(err) } test(t, mc) }) } vuln-1.0.1/internal/client/index.go000066400000000000000000000047231446745451500172520ustar00rootroot00000000000000// Copyright 2023 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 client import ( "encoding/json" "fmt" "io/fs" "os" "path/filepath" "golang.org/x/vuln/internal/osv" isem "golang.org/x/vuln/internal/semver" ) // indexFromDir returns a raw index created from a directory // containing OSV entries. // It skips any non-JSON files but errors if any of the JSON files // cannot be unmarshaled into OSV, or have a filename other than .json. func indexFromDir(dir string) (map[string][]byte, error) { idx := newIndex() f := os.DirFS(dir) if err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { fname := d.Name() ext := filepath.Ext(fname) switch { case err != nil: return err case d.IsDir(): return nil case ext != ".json": return nil } b, err := fs.ReadFile(f, d.Name()) if err != nil { return err } var entry osv.Entry if err := json.Unmarshal(b, &entry); err != nil { return err } if fname != entry.ID+".json" { return fmt.Errorf("OSV entries must have filename of the form .json, got %s", fname) } idx.add(&entry) return nil }); err != nil { return nil, err } return idx.raw() } func indexFromEntries(entries []*osv.Entry) (map[string][]byte, error) { idx := newIndex() for _, entry := range entries { idx.add(entry) } return idx.raw() } type index struct { db *dbMeta modules modulesIndex } func newIndex() *index { return &index{ db: &dbMeta{}, modules: make(map[string]*moduleMeta), } } func (i *index) add(entry *osv.Entry) { // Add to db index. if entry.Modified.After(i.db.Modified) { i.db.Modified = entry.Modified } // Add to modules index. for _, affected := range entry.Affected { modulePath := affected.Module.Path if _, ok := i.modules[modulePath]; !ok { i.modules[modulePath] = &moduleMeta{ Path: modulePath, Vulns: []moduleVuln{}, } } module := i.modules[modulePath] module.Vulns = append(module.Vulns, moduleVuln{ ID: entry.ID, Modified: entry.Modified, Fixed: isem.LatestFixedVersion(affected.Ranges), }) } } func (i *index) raw() (map[string][]byte, error) { data := make(map[string][]byte) b, err := json.Marshal(i.db) if err != nil { return nil, err } data[dbEndpoint] = b b, err = json.Marshal(i.modules) if err != nil { return nil, err } data[modulesEndpoint] = b return data, nil } vuln-1.0.1/internal/client/schema.go000066400000000000000000000041321446745451500173750ustar00rootroot00000000000000// Copyright 2023 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 client import ( "encoding/json" "path" "sort" "time" ) const ( idDir = "ID" indexDir = "index" ) var ( dbEndpoint = path.Join(indexDir, "db") modulesEndpoint = path.Join(indexDir, "modules") ) func entryEndpoint(id string) string { return path.Join(idDir, id) } // dbMeta contains metadata about the database itself. type dbMeta struct { // Modified is the time the database was last modified, calculated // as the most recent time any single OSV entry was modified. Modified time.Time `json:"modified"` } // moduleMeta contains metadata about a Go module that has one // or more vulnerabilities in the database. // // Found in the "index/modules" endpoint of the vulnerability database. type moduleMeta struct { // Path is the module path. Path string `json:"path"` // Vulns is a list of vulnerabilities that affect this module. Vulns []moduleVuln `json:"vulns"` } // moduleVuln contains metadata about a vulnerability that affects // a certain module. type moduleVuln struct { // ID is a unique identifier for the vulnerability. // The Go vulnerability database issues IDs of the form // GO--. ID string `json:"id"` // Modified is the time the vuln was last modified. Modified time.Time `json:"modified"` // Fixed is the latest version that introduces a fix for the // vulnerability, in SemVer 2.0.0 format, with no leading "v" prefix. Fixed string `json:"fixed,omitempty"` } // modulesIndex represents an in-memory modules index. type modulesIndex map[string]*moduleMeta func (m modulesIndex) MarshalJSON() ([]byte, error) { modules := make([]*moduleMeta, 0, len(m)) for _, module := range m { modules = append(modules, module) } sort.SliceStable(modules, func(i, j int) bool { return modules[i].Path < modules[j].Path }) for _, module := range modules { sort.SliceStable(module.Vulns, func(i, j int) bool { return module.Vulns[i].ID < module.Vulns[j].ID }) } return json.Marshal(modules) } vuln-1.0.1/internal/client/source.go000066400000000000000000000070431446745451500174410ustar00rootroot00000000000000// Copyright 2023 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 client import ( "compress/gzip" "context" "encoding/json" "fmt" "io" "io/fs" "net/http" "os" "path/filepath" "golang.org/x/vuln/internal/derrors" "golang.org/x/vuln/internal/osv" ) type source interface { // get returns the raw, uncompressed bytes at the // requested endpoint, which should be bare with no file extensions // (e.g., "index/modules" instead of "index/modules.json.gz"). // It errors if the endpoint cannot be reached or does not exist // in the expected form. get(ctx context.Context, endpoint string) ([]byte, error) } func newHTTPSource(url string, opts *Options) *httpSource { c := http.DefaultClient if opts != nil && opts.HTTPClient != nil { c = opts.HTTPClient } return &httpSource{url: url, c: c} } // httpSource reads a vulnerability database from an http(s) source. type httpSource struct { url string c *http.Client } func (hs *httpSource) get(ctx context.Context, endpoint string) (_ []byte, err error) { derrors.Wrap(&err, "get(%s)", endpoint) reqURL := fmt.Sprintf("%s/%s", hs.url, endpoint+".json.gz") req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil) if err != nil { return nil, err } resp, err := hs.c.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected HTTP status code: %d", resp.StatusCode) } // Uncompress the result. r, err := gzip.NewReader(resp.Body) if err != nil { return nil, err } defer r.Close() return io.ReadAll(r) } func newLocalSource(dir string) *localSource { return &localSource{fs: os.DirFS(dir)} } // localSource reads a vulnerability database from a local file system. type localSource struct { fs fs.FS } func (ls *localSource) get(ctx context.Context, endpoint string) (_ []byte, err error) { derrors.Wrap(&err, "get(%s)", endpoint) return fs.ReadFile(ls.fs, endpoint+".json") } func newHybridSource(dir string) (*hybridSource, error) { index, err := indexFromDir(dir) if err != nil { return nil, err } return &hybridSource{ index: &inMemorySource{data: index}, osv: &localSource{fs: os.DirFS(dir)}, }, nil } // hybridSource reads OSV entries from a local file system, but reads // indexes from an in-memory map. type hybridSource struct { index *inMemorySource osv *localSource } func (hs *hybridSource) get(ctx context.Context, endpoint string) (_ []byte, err error) { derrors.Wrap(&err, "get(%s)", endpoint) dir, file := filepath.Split(endpoint) if filepath.Dir(dir) == indexDir { return hs.index.get(ctx, endpoint) } return hs.osv.get(ctx, file) } // newInMemorySource creates a new in-memory source from OSV entries. // Adapted from x/vulndb/internal/database.go. func newInMemorySource(entries []*osv.Entry) (*inMemorySource, error) { data, err := indexFromEntries(entries) if err != nil { return nil, err } for _, entry := range entries { b, err := json.Marshal(entry) if err != nil { return nil, err } data[entryEndpoint(entry.ID)] = b } return &inMemorySource{data: data}, nil } // inMemorySource reads databases from an in-memory map. // Currently intended for use only in unit tests. type inMemorySource struct { data map[string][]byte } func (db *inMemorySource) get(ctx context.Context, endpoint string) ([]byte, error) { b, ok := db.data[endpoint] if !ok { return nil, fmt.Errorf("no data found at endpoint %q", endpoint) } return b, nil } vuln-1.0.1/internal/client/source_test.go000066400000000000000000000030261446745451500204750ustar00rootroot00000000000000// Copyright 2023 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 client import ( "context" "os" "testing" ) func TestGet(t *testing.T) { tcs := []struct { endpoint string }{ { endpoint: "index/db", }, { endpoint: "index/modules", }, { endpoint: "ID/GO-2021-0068", }, } for _, tc := range tcs { test := func(t *testing.T, s source) { got, err := s.get(context.Background(), tc.endpoint) if err != nil { t.Fatal(err) } want, err := os.ReadFile(testVulndb + "/" + tc.endpoint + ".json") if err != nil { t.Fatal(err) } if string(got) != string(want) { t.Errorf("get(%s) = %s, want %s", tc.endpoint, got, want) } } testAllSourceTypes(t, test) } } // testAllSourceTypes runs a given test for all source types. func testAllSourceTypes(t *testing.T, test func(t *testing.T, s source)) { t.Run("http", func(t *testing.T) { srv := newTestServer(testVulndb) hs := newHTTPSource(srv.URL, &Options{HTTPClient: srv.Client()}) test(t, hs) }) t.Run("local", func(t *testing.T) { test(t, newLocalSource(testVulndb)) }) t.Run("in-memory", func(t *testing.T) { testEntries, err := entries(testIDs) if err != nil { t.Fatal(err) } ms, err := newInMemorySource(testEntries) if err != nil { t.Fatal(err) } test(t, ms) }) t.Run("hybrid", func(t *testing.T) { hs, err := newHybridSource(testFlatVulndb) if err != nil { t.Fatal(err) } test(t, hs) }) } vuln-1.0.1/internal/client/testdata/000077500000000000000000000000001446745451500174175ustar00rootroot00000000000000vuln-1.0.1/internal/client/testdata/vulndb-legacy/000077500000000000000000000000001446745451500221535ustar00rootroot00000000000000vuln-1.0.1/internal/client/testdata/vulndb-legacy/ID/000077500000000000000000000000001446745451500224475ustar00rootroot00000000000000vuln-1.0.1/internal/client/testdata/vulndb-legacy/ID/GO-2021-0157.json000066400000000000000000000015521446745451500246260ustar00rootroot00000000000000{"id":"GO-2021-0157","published":"2022-01-05T20:00:00Z","modified":"2022-08-29T16:50:59Z","aliases":["CVE-2015-5739"],"details":"The MIME header parser treated spaces and hyphens\nas equivalent, which can permit HTTP request smuggling.\n","affected":[{"package":{"name":"stdlib","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.4.3"}]}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2021-0157"},"ecosystem_specific":{"imports":[{"path":"net/textproto","symbols":["CanonicalMIMEHeaderKey","canonicalMIMEHeaderKey"]}]}}],"references":[{"type":"FIX","url":"https://go.dev/cl/11772"},{"type":"FIX","url":"https://go.googlesource.com/go/+/117ddcb83d7f42d6aa72241240af99ded81118e9"},{"type":"REPORT","url":"https://go.dev/issue/53035"},{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/iSIyW4lM4hY/m/ADuQR4DiDwAJ"}]} vuln-1.0.1/internal/client/testdata/vulndb-legacy/ID/GO-2021-0159.json000066400000000000000000000031351446745451500246270ustar00rootroot00000000000000{"id":"GO-2021-0159","published":"2022-01-05T21:39:14Z","modified":"2022-08-29T16:50:59Z","aliases":["CVE-2015-5739","CVE-2015-5740","CVE-2015-5741"],"details":"HTTP headers were not properly parsed, which allows remote attackers to\nconduct HTTP request smuggling attacks via a request that contains\nContent-Length and Transfer-Encoding header fields.\n","affected":[{"package":{"name":"stdlib","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.4.3"}]}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2021-0159"},"ecosystem_specific":{"imports":[{"path":"net/http","symbols":["CanonicalMIMEHeaderKey","body.readLocked","canonicalMIMEHeaderKey","chunkWriter.writeHeader","fixLength","fixTransferEncoding","readTransfer","transferWriter.shouldSendContentLength","validHeaderFieldByte"]}]}}],"references":[{"type":"FIX","url":"https://go.dev/cl/13148"},{"type":"FIX","url":"https://go.googlesource.com/go/+/26049f6f9171d1190f3bbe05ec304845cfe6399f"},{"type":"FIX","url":"https://go.dev/cl/11772"},{"type":"FIX","url":"https://go.dev/cl/11810"},{"type":"FIX","url":"https://go.dev/cl/12865"},{"type":"FIX","url":"https://go.googlesource.com/go/+/117ddcb83d7f42d6aa72241240af99ded81118e9"},{"type":"FIX","url":"https://go.googlesource.com/go/+/300d9a21583e7cf0149a778a0611e76ff7c6680f"},{"type":"FIX","url":"https://go.googlesource.com/go/+/c2db5f4ccc61ba7df96a747e268a277b802cbb87"},{"type":"REPORT","url":"https://go.dev/issue/12027"},{"type":"REPORT","url":"https://go.dev/issue/11930"},{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/iSIyW4lM4hY/m/ADuQR4DiDwAJ"}]} vuln-1.0.1/internal/client/testdata/vulndb-legacy/ID/GO-2022-0463.json000066400000000000000000000215141446745451500246270ustar00rootroot00000000000000{ "id": "GO-2022-0463", "published": "2022-07-01T20:06:59Z", "modified": "2022-08-19T22:21:47Z", "aliases": [ "CVE-2022-31259", "GHSA-qx32-f6g6-fcfr" ], "details": "Routes in the beego HTTP router can match unintended patterns.\nThis overly-broad matching may permit an attacker to bypass access\ncontrols.\n\nFor example, the pattern \"/a/b/:name\" can match the URL \"/a.xml/b/\".\nThis may bypass access control applied to the prefix \"/a/\".\n", "affected": [ { "package": { "name": "github.com/beego/beego", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.12.9" } ] } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2022-0463" }, "ecosystem_specific": { "imports": [ { "path": "github.com/beego/beego", "symbols": [ "App.Run", "ControllerRegister.FindPolicy", "ControllerRegister.FindRouter", "ControllerRegister.ServeHTTP", "FilterRouter.ValidRouter", "InitBeegoBeforeTest", "Run", "RunWithMiddleWares", "TestBeegoInit", "Tree.Match", "Tree.match", "adminApp.Run" ] } ] } }, { "package": { "name": "github.com/beego/beego/v2", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "2.0.3" } ] } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2022-0463" }, "ecosystem_specific": { "imports": [ { "path": "github.com/beego/beego/v2/server/web", "symbols": [ "AddNamespace", "Any", "AutoPrefix", "AutoRouter", "Compare", "CompareNot", "Controller.Bind", "Controller.BindForm", "Controller.BindXML", "Controller.BindYAML", "Controller.GetSecureCookie", "Controller.ParseForm", "Controller.Render", "Controller.RenderBytes", "Controller.RenderString", "Controller.Resp", "Controller.SaveToFile", "Controller.ServeFormatted", "Controller.ServeXML", "Controller.ServeYAML", "Controller.SetSecureCookie", "Controller.Trace", "Controller.URLFor", "Controller.XMLResp", "Controller.XSRFFormHTML", "Controller.XSRFToken", "Controller.YamlResp", "ControllerRegister.Add", "ControllerRegister.AddAuto", "ControllerRegister.AddAutoPrefix", "ControllerRegister.AddMethod", "ControllerRegister.AddRouterMethod", "ControllerRegister.Any", "ControllerRegister.CtrlAny", "ControllerRegister.CtrlDelete", "ControllerRegister.CtrlGet", "ControllerRegister.CtrlHead", "ControllerRegister.CtrlOptions", "ControllerRegister.CtrlPatch", "ControllerRegister.CtrlPost", "ControllerRegister.CtrlPut", "ControllerRegister.Delete", "ControllerRegister.FindPolicy", "ControllerRegister.FindRouter", "ControllerRegister.Get", "ControllerRegister.Handler", "ControllerRegister.Head", "ControllerRegister.Include", "ControllerRegister.Init", "ControllerRegister.InsertFilter", "ControllerRegister.Options", "ControllerRegister.Patch", "ControllerRegister.Post", "ControllerRegister.Put", "ControllerRegister.ServeHTTP", "ControllerRegister.URLFor", "CtrlAny", "CtrlDelete", "CtrlGet", "CtrlHead", "CtrlOptions", "CtrlPatch", "CtrlPost", "CtrlPut", "Date", "DateParse", "Delete", "Exception", "ExecuteTemplate", "ExecuteViewPathTemplate", "FilterRouter.ValidRouter", "FlashData.Error", "FlashData.Notice", "FlashData.Set", "FlashData.Store", "FlashData.Success", "FlashData.Warning", "Get", "GetConfig", "HTML2str", "Handler", "Head", "Htmlquote", "Htmlunquote", "HttpServer.Any", "HttpServer.AutoPrefix", "HttpServer.AutoRouter", "HttpServer.CtrlAny", "HttpServer.CtrlDelete", "HttpServer.CtrlGet", "HttpServer.CtrlHead", "HttpServer.CtrlOptions", "HttpServer.CtrlPatch", "HttpServer.CtrlPost", "HttpServer.CtrlPut", "HttpServer.Delete", "HttpServer.Get", "HttpServer.Handler", "HttpServer.Head", "HttpServer.Include", "HttpServer.InsertFilter", "HttpServer.Options", "HttpServer.Patch", "HttpServer.Post", "HttpServer.PrintTree", "HttpServer.Put", "HttpServer.RESTRouter", "HttpServer.Router", "HttpServer.RouterWithOpts", "HttpServer.Run", "Include", "InitBeegoBeforeTest", "InsertFilter", "LoadAppConfig", "MapGet", "Namespace.Any", "Namespace.AutoPrefix", "Namespace.AutoRouter", "Namespace.Cond", "Namespace.CtrlAny", "Namespace.CtrlDelete", "Namespace.CtrlGet", "Namespace.CtrlHead", "Namespace.CtrlOptions", "Namespace.CtrlPatch", "Namespace.CtrlPost", "Namespace.CtrlPut", "Namespace.Delete", "Namespace.Filter", "Namespace.Get", "Namespace.Handler", "Namespace.Head", "Namespace.Include", "Namespace.Namespace", "Namespace.Options", "Namespace.Patch", "Namespace.Post", "Namespace.Put", "Namespace.Router", "NewControllerRegister", "NewControllerRegisterWithCfg", "NewHttpServerWithCfg", "NewHttpSever", "NewNamespace", "NotNil", "Options", "ParseForm", "Patch", "Policy", "Post", "PrintTree", "Put", "RESTRouter", "ReadFromRequest", "RenderForm", "Router", "RouterWithOpts", "Run", "RunWithMiddleWares", "TestBeegoInit", "Tree.AddRouter", "Tree.AddTree", "Tree.Match", "Tree.match", "URLFor", "URLMap.GetMap", "URLMap.GetMapData", "adminApp.Run", "adminController.AdminIndex", "adminController.Healthcheck", "adminController.ListConf", "adminController.ProfIndex", "adminController.PrometheusMetrics", "adminController.QpsIndex", "adminController.TaskStatus", "beegoAppConfig.Bool", "beegoAppConfig.DefaultBool" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/beego/beego/pull/4958" }, { "type": "FIX", "url": "https://github.com/beego/beego/commit/64cf44d725c8cc35d782327d333df9cbeb1bf2dd" }, { "type": "WEB", "url": "https://beego.vip" }, { "type": "WEB", "url": "https://github.com/beego/beego/issues/4946" }, { "type": "WEB", "url": "https://github.com/beego/beego/pull/4954" }, { "type": "WEB", "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-31259" }, { "type": "WEB", "url": "https://github.com/advisories/GHSA-qx32-f6g6-fcfr" } ] }vuln-1.0.1/internal/client/testdata/vulndb-legacy/ID/GO-2022-0569.json000066400000000000000000000040221446745451500246310ustar00rootroot00000000000000{ "id": "GO-2022-0569", "published": "2022-08-23T13:24:17Z", "modified": "2022-08-23T13:24:17Z", "aliases": [ "CVE-2022-31836", "GHSA-95f9-94vc-665h" ], "details": "The leafInfo.match() function uses path.join()\nto deal with wildcard values which can lead to cross directory risk.\n", "affected": [ { "package": { "name": "github.com/beego/beego", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.12.11" } ] } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2022-0569" }, "ecosystem_specific": { "imports": [ { "path": "github.com/beego/beego", "symbols": [ "Tree.Match" ] } ] } }, { "package": { "name": "github.com/beego/beego/v2", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "2.0.0" }, { "fixed": "2.0.4" } ] } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2022-0569" }, "ecosystem_specific": { "imports": [ { "path": "github.com/beego/beego/v2/server/web", "symbols": [ "Tree.Match" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/beego/beego/pull/5025" }, { "type": "FIX", "url": "https://github.com/beego/beego/pull/5025/commits/ea5ae58d40589d249cf577a053e490509de2bf57" }, { "type": "WEB", "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-31836" }, { "type": "WEB", "url": "https://github.com/advisories/GHSA-95f9-94vc-665h" } ] }vuln-1.0.1/internal/client/testdata/vulndb-legacy/ID/GO-2022-0572.json000066400000000000000000000036741446745451500246370ustar00rootroot00000000000000{ "id": "GO-2022-0572", "published": "2022-08-22T17:56:17Z", "modified": "2022-08-23T19:54:38Z", "aliases": [ "CVE-2021-30080", "GHSA-28r6-jm5h-mrgg" ], "details": "An issue was discovered in the route lookup process in\nbeego which attackers to bypass access control.\n", "affected": [ { "package": { "name": "github.com/beego/beego", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" } ] } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2022-0572" }, "ecosystem_specific": { "imports": [ { "path": "github.com/beego/beego", "symbols": [ "Tree.Match" ] } ] } }, { "package": { "name": "github.com/beego/beego/v2", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "2.0.0" }, { "fixed": "2.0.3" } ] } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2022-0572" }, "ecosystem_specific": { "imports": [ { "path": "github.com/beego/beego/v2/server/web", "symbols": [ "Tree.Match" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/beego/beego/pull/4459" }, { "type": "FIX", "url": "https://github.com/beego/beego/commit/d5df5e470d0a8ed291930ae802fd7e6b95226519" }, { "type": "WEB", "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-30080" }, { "type": "WEB", "url": "https://github.com/advisories/GHSA-28r6-jm5h-mrgg" } ] }vuln-1.0.1/internal/client/testdata/vulndb-legacy/ID/index.json000066400000000000000000000000711446745451500244470ustar00rootroot00000000000000[ "GO-2022-0463", "GO-2022-0569", "GO-2022-0572" ] vuln-1.0.1/internal/client/testdata/vulndb-legacy/aliases.json000066400000000000000000000453201446745451500244730ustar00rootroot00000000000000{ "CVE-2013-10005": [ "GO-2020-0024" ], "CVE-2014-125026": [ "GO-2020-0022" ], "CVE-2014-7189": [ "GO-2021-0154" ], "CVE-2014-8681": [ "GO-2020-0021" ], "CVE-2015-10004": [ "GO-2020-0023" ], "CVE-2015-1340": [ "GO-2021-0071" ], "CVE-2015-5305": [ "GO-2022-0701" ], "CVE-2015-5739": [ "GO-2021-0157", "GO-2021-0159" ], "CVE-2015-5740": [ "GO-2021-0159" ], "CVE-2015-5741": [ "GO-2021-0159" ], "CVE-2015-8618": [ "GO-2021-0160" ], "CVE-2016-15005": [ "GO-2020-0045" ], "CVE-2016-3697": [ "GO-2021-0070" ], "CVE-2016-3958": [ "GO-2021-0163" ], "CVE-2016-3959": [ "GO-2022-0166" ], "CVE-2016-5386": [ "GO-2022-0761" ], "CVE-2016-9121": [ "GO-2020-0010" ], "CVE-2016-9122": [ "GO-2020-0011", "GO-2022-0945" ], "CVE-2016-9123": [ "GO-2020-0009" ], "CVE-2017-1000097": [ "GO-2022-0171" ], "CVE-2017-1000098": [ "GO-2021-0172" ], "CVE-2017-11468": [ "GO-2021-0072" ], "CVE-2017-11480": [ "GO-2022-0643" ], "CVE-2017-15041": [ "GO-2022-0177" ], "CVE-2017-15042": [ "GO-2021-0178" ], "CVE-2017-15133": [ "GO-2020-0006" ], "CVE-2017-17831": [ "GO-2021-0073" ], "CVE-2017-18367": [ "GO-2020-0007" ], "CVE-2017-20146": [ "GO-2020-0020" ], "CVE-2017-3204": [ "GO-2020-0013" ], "CVE-2017-8932": [ "GO-2022-0187" ], "CVE-2018-1103": [ "GO-2020-0026" ], "CVE-2018-12018": [ "GO-2021-0075" ], "CVE-2018-14632": [ "GO-2021-0076" ], "CVE-2018-16873": [ "GO-2022-0189" ], "CVE-2018-16874": [ "GO-2022-0190" ], "CVE-2018-16875": [ "GO-2022-0191" ], "CVE-2018-16886": [ "GO-2021-0077" ], "CVE-2018-17075": [ "GO-2021-0078" ], "CVE-2018-17142": [ "GO-2022-0192" ], "CVE-2018-17143": [ "GO-2022-0193" ], "CVE-2018-17419": [ "GO-2020-0028" ], "CVE-2018-17846": [ "GO-2020-0014" ], "CVE-2018-17847": [ "GO-2022-0197" ], "CVE-2018-17848": [ "GO-2022-0197" ], "CVE-2018-18206": [ "GO-2021-0079" ], "CVE-2018-21246": [ "GO-2020-0043" ], "CVE-2018-25046": [ "GO-2020-0025" ], "CVE-2018-6558": [ "GO-2020-0027" ], "CVE-2018-6574": [ "GO-2022-0201" ], "CVE-2018-7187": [ "GO-2022-0203" ], "CVE-2019-0210": [ "GO-2021-0101" ], "CVE-2019-10214": [ "GO-2021-0081" ], "CVE-2019-10223": [ "GO-2022-0621" ], "CVE-2019-11250": [ "GO-2021-0065" ], "CVE-2019-11254": [ "GO-2020-0036" ], "CVE-2019-11289": [ "GO-2021-0102" ], "CVE-2019-11840": [ "GO-2022-0209" ], "CVE-2019-11939": [ "GO-2021-0082" ], "CVE-2019-12496": [ "GO-2021-0083" ], "CVE-2019-13209": [ "GO-2022-0755" ], "CVE-2019-14809": [ "GO-2022-0211" ], "CVE-2019-16276": [ "GO-2022-0212" ], "CVE-2019-16354": [ "GO-2021-0084" ], "CVE-2019-16884": [ "GO-2021-0085" ], "CVE-2019-17110": [ "GO-2022-0621" ], "CVE-2019-17596": [ "GO-2022-0213" ], "CVE-2019-19619": [ "GO-2021-0086" ], "CVE-2019-19794": [ "GO-2020-0008" ], "CVE-2019-19921": [ "GO-2021-0087" ], "CVE-2019-20786": [ "GO-2020-0038" ], "CVE-2019-25072": [ "GO-2020-0037" ], "CVE-2019-25073": [ "GO-2020-0032" ], "CVE-2019-3564": [ "GO-2021-0088" ], "CVE-2019-6486": [ "GO-2022-0217" ], "CVE-2019-9512": [ "GO-2022-0536" ], "CVE-2019-9514": [ "GO-2022-0536" ], "CVE-2019-9634": [ "GO-2022-0220" ], "CVE-2020-0601": [ "GO-2022-0535" ], "CVE-2020-10675": [ "GO-2021-0089" ], "CVE-2020-12666": [ "GO-2020-0039" ], "CVE-2020-14039": [ "GO-2021-0223" ], "CVE-2020-14040": [ "GO-2020-0015" ], "CVE-2020-15091": [ "GO-2021-0090" ], "CVE-2020-15106": [ "GO-2020-0005" ], "CVE-2020-15111": [ "GO-2021-0091", "GO-2021-0108" ], "CVE-2020-15112": [ "GO-2020-0005" ], "CVE-2020-15216": [ "GO-2020-0050" ], "CVE-2020-15222": [ "GO-2021-0092", "GO-2021-0110" ], "CVE-2020-15223": [ "GO-2021-0109" ], "CVE-2020-15586": [ "GO-2021-0224" ], "CVE-2020-16845": [ "GO-2021-0142" ], "CVE-2020-24553": [ "GO-2021-0226" ], "CVE-2020-25614": [ "GO-2020-0048" ], "CVE-2020-26160": [ "GO-2020-0017" ], "CVE-2020-26242": [ "GO-2021-0103" ], "CVE-2020-26264": [ "GO-2021-0063" ], "CVE-2020-26265": [ "GO-2021-0105" ], "CVE-2020-26290": [ "GO-2020-0050" ], "CVE-2020-26521": [ "GO-2022-0402" ], "CVE-2020-26892": [ "GO-2022-0380" ], "CVE-2020-27813": [ "GO-2020-0019" ], "CVE-2020-27846": [ "GO-2021-0058" ], "CVE-2020-27847": [ "GO-2020-0050" ], "CVE-2020-28362": [ "GO-2021-0069" ], "CVE-2020-28366": [ "GO-2022-0475" ], "CVE-2020-28367": [ "GO-2022-0476" ], "CVE-2020-28483": [ "GO-2020-0029", "GO-2021-0052" ], "CVE-2020-29242": [ "GO-2021-0097" ], "CVE-2020-29243": [ "GO-2021-0097" ], "CVE-2020-29244": [ "GO-2021-0097" ], "CVE-2020-29245": [ "GO-2021-0097" ], "CVE-2020-29509": [ "GO-2021-0060" ], "CVE-2020-29529": [ "GO-2021-0094" ], "CVE-2020-29652": [ "GO-2021-0227" ], "CVE-2020-35380": [ "GO-2021-0059" ], "CVE-2020-35381": [ "GO-2021-0057" ], "CVE-2020-36066": [ "GO-2022-0957" ], "CVE-2020-36067": [ "GO-2021-0054" ], "CVE-2020-36559": [ "GO-2020-0033" ], "CVE-2020-36560": [ "GO-2020-0034" ], "CVE-2020-36561": [ "GO-2020-0035" ], "CVE-2020-36562": [ "GO-2020-0040" ], "CVE-2020-36563": [ "GO-2020-0047" ], "CVE-2020-36564": [ "GO-2020-0049" ], "CVE-2020-36565": [ "GO-2021-0051" ], "CVE-2020-36566": [ "GO-2021-0106" ], "CVE-2020-36567": [ "GO-2020-0001" ], "CVE-2020-36568": [ "GO-2020-0003" ], "CVE-2020-36569": [ "GO-2020-0004" ], "CVE-2020-7664": [ "GO-2021-0228" ], "CVE-2020-7667": [ "GO-2020-0042" ], "CVE-2020-7668": [ "GO-2020-0041" ], "CVE-2020-7711": [ "GO-2020-0046" ], "CVE-2020-7919": [ "GO-2022-0229" ], "CVE-2020-8564": [ "GO-2021-0066" ], "CVE-2020-8565": [ "GO-2021-0064" ], "CVE-2020-8568": [ "GO-2022-0629" ], "CVE-2020-8911": [ "GO-2022-0646" ], "CVE-2020-8918": [ "GO-2021-0095" ], "CVE-2020-8945": [ "GO-2020-0002", "GO-2020-0031", "GO-2021-0096" ], "CVE-2020-9283": [ "GO-2020-0012" ], "CVE-2021-20206": [ "GO-2022-0230" ], "CVE-2021-20291": [ "GO-2021-0100" ], "CVE-2021-20329": [ "GO-2021-0111", "GO-2021-0112" ], "CVE-2021-21237": [ "GO-2021-0098" ], "CVE-2021-21272": [ "GO-2021-0099" ], "CVE-2021-22133": [ "GO-2022-0706" ], "CVE-2021-23409": [ "GO-2022-0233" ], "CVE-2021-23772": [ "GO-2022-0272" ], "CVE-2021-27918": [ "GO-2021-0234" ], "CVE-2021-27919": [ "GO-2021-0067" ], "CVE-2021-28681": [ "GO-2021-0104" ], "CVE-2021-29272": [ "GO-2022-0762" ], "CVE-2021-29482": [ "GO-2020-0016" ], "CVE-2021-30080": [ "GO-2022-0572" ], "CVE-2021-3114": [ "GO-2021-0235" ], "CVE-2021-3115": [ "GO-2021-0068" ], "CVE-2021-3121": [ "GO-2021-0053" ], "CVE-2021-3127": [ "GO-2022-0386" ], "CVE-2021-31525": [ "GO-2022-0236" ], "CVE-2021-32690": [ "GO-2022-0384" ], "CVE-2021-32721": [ "GO-2021-0237" ], "CVE-2021-33194": [ "GO-2021-0238" ], "CVE-2021-33195": [ "GO-2021-0239" ], "CVE-2021-33196": [ "GO-2021-0240" ], "CVE-2021-33197": [ "GO-2021-0241" ], "CVE-2021-33198": [ "GO-2021-0242" ], "CVE-2021-34558": [ "GO-2021-0243" ], "CVE-2021-3538": [ "GO-2020-0018", "GO-2022-0244" ], "CVE-2021-3602": [ "GO-2022-0345" ], "CVE-2021-36221": [ "GO-2021-0245" ], "CVE-2021-3761": [ "GO-2022-0246" ], "CVE-2021-3762": [ "GO-2022-0346" ], "CVE-2021-38297": [ "GO-2022-0247" ], "CVE-2021-38561": [ "GO-2021-0113" ], "CVE-2021-3907": [ "GO-2022-0248" ], "CVE-2021-3910": [ "GO-2022-0251" ], "CVE-2021-3911": [ "GO-2022-0252" ], "CVE-2021-3912": [ "GO-2022-0253" ], "CVE-2021-39137": [ "GO-2022-0254" ], "CVE-2021-39293": [ "GO-2022-0273" ], "CVE-2021-41173": [ "GO-2022-0256" ], "CVE-2021-41230": [ "GO-2021-0258" ], "CVE-2021-41771": [ "GO-2021-0263" ], "CVE-2021-41772": [ "GO-2021-0264" ], "CVE-2021-42248": [ "GO-2021-0265" ], "CVE-2021-4235": [ "GO-2021-0061" ], "CVE-2021-4236": [ "GO-2021-0107" ], "CVE-2021-4238": [ "GO-2022-0411" ], "CVE-2021-4239": [ "GO-2022-0425" ], "CVE-2021-42576": [ "GO-2022-0588" ], "CVE-2021-42836": [ "GO-2021-0265" ], "CVE-2021-43784": [ "GO-2022-0274" ], "CVE-2021-44716": [ "GO-2022-0288" ], "CVE-2021-44717": [ "GO-2022-0289" ], "CVE-2021-46398": [ "GO-2022-0563" ], "CVE-2022-0317": [ "GO-2022-0294" ], "CVE-2022-1227": [ "GO-2022-0558" ], "CVE-2022-1705": [ "GO-2022-0525" ], "CVE-2022-1962": [ "GO-2022-0515" ], "CVE-2022-1996": [ "GO-2022-0619" ], "CVE-2022-21221": [ "GO-2022-0355" ], "CVE-2022-21235": [ "GO-2022-0414" ], "CVE-2022-21698": [ "GO-2022-0322" ], "CVE-2022-21708": [ "GO-2022-0300" ], "CVE-2022-23628": [ "GO-2022-0316" ], "CVE-2022-23772": [ "GO-2021-0317" ], "CVE-2022-23773": [ "GO-2022-0318" ], "CVE-2022-23806": [ "GO-2021-0319" ], "CVE-2022-24675": [ "GO-2022-0433" ], "CVE-2022-24778": [ "GO-2021-0412" ], "CVE-2022-24912": [ "GO-2022-0534" ], "CVE-2022-24921": [ "GO-2021-0347" ], "CVE-2022-24968": [ "GO-2021-0321", "GO-2022-0370", "GO-2022-0947" ], "CVE-2022-2582": [ "GO-2022-0391" ], "CVE-2022-2583": [ "GO-2022-0400" ], "CVE-2022-2584": [ "GO-2022-0422" ], "CVE-2022-25856": [ "GO-2022-0492" ], "CVE-2022-25891": [ "GO-2022-0528" ], "CVE-2022-26945": [ "GO-2022-0586" ], "CVE-2022-27191": [ "GO-2021-0356" ], "CVE-2022-27536": [ "GO-2022-0434" ], "CVE-2022-27651": [ "GO-2022-0417" ], "CVE-2022-28131": [ "GO-2022-0521" ], "CVE-2022-28327": [ "GO-2022-0435" ], "CVE-2022-28946": [ "GO-2022-0587" ], "CVE-2022-28948": [ "GO-2022-0603" ], "CVE-2022-29173": [ "GO-2022-0444" ], "CVE-2022-29189": [ "GO-2022-0461" ], "CVE-2022-29190": [ "GO-2022-0460" ], "CVE-2022-29222": [ "GO-2022-0462" ], "CVE-2022-29526": [ "GO-2022-0493" ], "CVE-2022-29804": [ "GO-2022-0533" ], "CVE-2022-29810": [ "GO-2022-0438" ], "CVE-2022-30321": [ "GO-2022-0586" ], "CVE-2022-30322": [ "GO-2022-0586" ], "CVE-2022-30323": [ "GO-2022-0586" ], "CVE-2022-30580": [ "GO-2022-0532" ], "CVE-2022-30629": [ "GO-2022-0531" ], "CVE-2022-30630": [ "GO-2022-0527" ], "CVE-2022-30631": [ "GO-2022-0524" ], "CVE-2022-30632": [ "GO-2022-0522" ], "CVE-2022-30633": [ "GO-2022-0523" ], "CVE-2022-30634": [ "GO-2022-0477" ], "CVE-2022-30635": [ "GO-2022-0526" ], "CVE-2022-3064": [ "GO-2022-0956" ], "CVE-2022-31022": [ "GO-2022-0470" ], "CVE-2022-31053": [ "GO-2022-0564" ], "CVE-2022-31145": [ "GO-2022-0519" ], "CVE-2022-31259": [ "GO-2022-0463" ], "CVE-2022-31836": [ "GO-2022-0569" ], "CVE-2022-32148": [ "GO-2022-0520" ], "CVE-2022-32189": [ "GO-2022-0537" ], "CVE-2022-33082": [ "GO-2022-0574" ], "CVE-2022-36009": [ "GO-2022-0952" ], "CVE-2022-37315": [ "GO-2022-0942" ], "GHSA-25xm-hr59-7c27": [ "GO-2020-0016" ], "GHSA-27rq-4943-qcwp": [ "GO-2022-0438" ], "GHSA-28r2-q6m8-9hpx": [ "GO-2022-0586" ], "GHSA-28r6-jm5h-mrgg": [ "GO-2022-0572" ], "GHSA-2c64-vj8g-vwrq": [ "GO-2022-0380" ], "GHSA-2m4x-4q9j-w97g": [ "GO-2022-0574" ], "GHSA-2v6x-frw8-7r7f": [ "GO-2022-0621" ], "GHSA-2x32-jm95-2cpx": [ "GO-2020-0050" ], "GHSA-3fx4-7f69-5mmg": [ "GO-2020-0009" ], "GHSA-3x58-xr87-2fcj": [ "GO-2022-0762" ], "GHSA-3xh2-74w9-5vxm": [ "GO-2020-0019" ], "GHSA-44r7-7p62-q3fr": [ "GO-2020-0008" ], "GHSA-477v-w82m-634j": [ "GO-2022-0528" ], "GHSA-4hq8-gmxx-h6w9": [ "GO-2021-0058" ], "GHSA-4w5x-x539-ppf5": [ "GO-2022-0380" ], "GHSA-56hp-xqp3-w2jf": [ "GO-2022-0384" ], "GHSA-5796-p3m6-9qj4": [ "GO-2021-0102" ], "GHSA-58v3-j75h-xr49": [ "GO-2020-0007" ], "GHSA-59hh-656j-3p7v": [ "GO-2022-0256" ], "GHSA-5cgx-vhfp-6cf9": [ "GO-2022-0629" ], "GHSA-5gjg-jgh4-gppm": [ "GO-2021-0107" ], "GHSA-5mxh-2qfv-4g7j": [ "GO-2022-0251" ], "GHSA-5rcv-m4m3-hfh7": [ "GO-2020-0015" ], "GHSA-5x29-3hr9-6wpw": [ "GO-2021-0095" ], "GHSA-62mh-w5cv-p88c": [ "GO-2022-0386" ], "GHSA-6635-c626-vj4r": [ "GO-2022-0414" ], "GHSA-66vw-v2x9-hw75": [ "GO-2022-0558" ], "GHSA-66x3-6cw3-v5gj": [ "GO-2022-0444" ], "GHSA-6jqj-f58p-mrw3": [ "GO-2021-0090" ], "GHSA-72wf-hwcq-65h9": [ "GO-2022-0563" ], "GHSA-733f-44f3-3frw": [ "GO-2020-0039" ], "GHSA-74xm-qj29-cq8p": [ "GO-2021-0104" ], "GHSA-75rw-34q6-72cr": [ "GO-2022-0564" ], "GHSA-7638-r9r3-rmjj": [ "GO-2022-0345" ], "GHSA-76wf-9vgp-pj7w": [ "GO-2022-0391" ], "GHSA-77gc-fj98-665h": [ "GO-2020-0011", "GO-2022-0945" ], "GHSA-7gfg-6934-mqq2": [ "GO-2020-0038" ], "GHSA-7jr6-prv4-5wf5": [ "GO-2022-0384" ], "GHSA-7mqr-2v3q-v2wm": [ "GO-2021-0109" ], "GHSA-7qw8-847f-pggm": [ "GO-2021-0100" ], "GHSA-85p9-j7c9-v4gr": [ "GO-2021-0081" ], "GHSA-86r9-39j9-99wp": [ "GO-2020-0010" ], "GHSA-88jf-7rch-32qc": [ "GO-2020-0041" ], "GHSA-8c26-wmh5-6g9v": [ "GO-2021-0356" ], "GHSA-8v99-48m9-c8pm": [ "GO-2021-0412" ], "GHSA-8vrw-m3j9-j27c": [ "GO-2021-0057" ], "GHSA-9423-6c93-gpp8": [ "GO-2020-0042" ], "GHSA-95f9-94vc-665h": [ "GO-2022-0569" ], "GHSA-9856-9gg9-qcmq": [ "GO-2022-0254" ], "GHSA-99cg-575x-774p": [ "GO-2022-0294" ], "GHSA-9cx9-x2gp-9qvh": [ "GO-2021-0091", "GO-2021-0108" ], "GHSA-9jcx-pr2f-qvq5": [ "GO-2020-0028" ], "GHSA-9q3g-m353-cp4p": [ "GO-2022-0643" ], "GHSA-9r5x-fjv3-q6h4": [ "GO-2022-0386" ], "GHSA-9w9f-6mg8-jp7w": [ "GO-2022-0470" ], "GHSA-9x4h-8wgm-8xfg": [ "GO-2022-0503" ], "GHSA-c3g4-w6cv-6v7h": [ "GO-2022-0417" ], "GHSA-c3h9-896r-86jm": [ "GO-2021-0053" ], "GHSA-c8xp-8mf3-62h9": [ "GO-2022-0246" ], "GHSA-c9gm-7rfj-8w5h": [ "GO-2021-0265" ], "GHSA-cg3q-j54f-5p7p": [ "GO-2022-0322" ], "GHSA-cjjc-xp8v-855w": [ "GO-2022-0229" ], "GHSA-cjr4-fv6c-f3mv": [ "GO-2022-0586" ], "GHSA-cm8f-h6j3-p25c": [ "GO-2022-0460" ], "GHSA-cqh2-vc2f-q4fh": [ "GO-2022-0248" ], "GHSA-cx3w-xqmc-84g5": [ "GO-2021-0098" ], "GHSA-cx94-mrg9-rq4j": [ "GO-2022-0461" ], "GHSA-f5pg-7wfw-84q9": [ "GO-2022-0646" ], "GHSA-f6mq-5m25-4r72": [ "GO-2021-0111", "GO-2021-0112" ], "GHSA-f6px-w8rh-7r89": [ "GO-2021-0084" ], "GHSA-fcgg-rvwg-jv58": [ "GO-2022-0586" ], "GHSA-ffhg-7mh4-33c4": [ "GO-2020-0012" ], "GHSA-fgv8-vj5c-2ppq": [ "GO-2021-0085" ], "GHSA-fh74-hm69-rqjw": [ "GO-2021-0087" ], "GHSA-fx95-883v-4q4h": [ "GO-2022-0355" ], "GHSA-g3vv-g2j5-45f2": [ "GO-2022-0422" ], "GHSA-g5v4-5x39-vwhx": [ "GO-2021-0099" ], "GHSA-g9mp-8g3h-3c5c": [ "GO-2022-0425" ], "GHSA-g9wh-3vrx-r7hg": [ "GO-2022-0253" ], "GHSA-h289-x5wc-xcv8": [ "GO-2022-0370" ], "GHSA-h2fg-54x9-5qhq": [ "GO-2022-0402" ], "GHSA-h2x7-2ff6-v32p": [ "GO-2022-0400" ], "GHSA-h395-qcrw-5vmq": [ "GO-2020-0029", "GO-2021-0052" ], "GHSA-h3qm-jrrf-cgj3": [ "GO-2022-0942" ], "GHSA-h6xx-pmxh-3wgp": [ "GO-2021-0077" ], "GHSA-hcw3-j74m-qc58": [ "GO-2022-0316" ], "GHSA-hmm9-r2m2-qg9w": [ "GO-2022-0402" ], "GHSA-hp87-p4gw-j4gq": [ "GO-2022-0603" ], "GHSA-j6wp-3859-vxfg": [ "GO-2021-0258" ], "GHSA-j756-f273-xhp4": [ "GO-2022-0386" ], "GHSA-jcxc-rh6w-wf49": [ "GO-2022-0272" ], "GHSA-jm5c-rv3w-w83m": [ "GO-2021-0103" ], "GHSA-jp32-vmm6-3vf5": [ "GO-2022-0701" ], "GHSA-jq7p-26h5-w78r": [ "GO-2021-0101" ], "GHSA-jxqv-jcvh-7gr4": [ "GO-2022-0534" ], "GHSA-m658-p24x-p74r": [ "GO-2021-0321", "GO-2022-0370", "GO-2022-0947" ], "GHSA-m6wg-2mwg-4rfq": [ "GO-2020-0002", "GO-2020-0031", "GO-2021-0096" ], "GHSA-m9hp-7r99-94h5": [ "GO-2020-0050" ], "GHSA-mh3m-8c74-74xh": [ "GO-2022-0300" ], "GHSA-mj9r-wwm8-7q52": [ "GO-2021-0237" ], "GHSA-mq47-6wwv-v79w": [ "GO-2022-0346" ], "GHSA-mr6h-chqp-p9g2": [ "GO-2020-0021" ], "GHSA-p55x-7x9v-q8m4": [ "GO-2020-0006" ], "GHSA-ppj4-34rq-v8j9": [ "GO-2021-0265" ], "GHSA-q3j5-32m5-58c2": [ "GO-2021-0070" ], "GHSA-q547-gmf8-8jr7": [ "GO-2020-0050" ], "GHSA-q6gq-997w-f55g": [ "GO-2021-0142" ], "GHSA-qj26-7grj-whg3": [ "GO-2020-0027" ], "GHSA-qpgx-64h2-gc3c": [ "GO-2022-0492" ], "GHSA-qq97-vm5h-rrhg": [ "GO-2022-0379" ], "GHSA-qqc5-rgcc-cjqh": [ "GO-2022-0706" ], "GHSA-qwrj-9hmp-gpxh": [ "GO-2022-0519" ], "GHSA-qx32-f6g6-fcfr": [ "GO-2022-0463" ], "GHSA-r33q-22hv-j29q": [ "GO-2021-0063" ], "GHSA-r48q-9g5r-8q2h": [ "GO-2022-0619" ], "GHSA-rmh2-65xw-9m6q": [ "GO-2021-0089" ], "GHSA-v3q9-2p3m-7g43": [ "GO-2021-0092", "GO-2021-0110" ], "GHSA-v95c-p5hm-xq8f": [ "GO-2022-0274" ], "GHSA-vc3x-gx6c-g99f": [ "GO-2021-0079" ], "GHSA-vpx7-vm66-qx8r": [ "GO-2021-0228" ], "GHSA-w45j-f832-hxvh": [ "GO-2022-0462" ], "GHSA-w6ww-fmfx-2x22": [ "GO-2022-0252" ], "GHSA-w73w-5m7g-f7qc": [ "GO-2020-0017" ], "GHSA-w942-gw6m-p62c": [ "GO-2021-0059" ], "GHSA-wjm3-fq3r-5x46": [ "GO-2022-0957" ], "GHSA-wmwp-pggc-h4mj": [ "GO-2021-0086" ], "GHSA-wxc4-f4m6-wwqv": [ "GO-2020-0036" ], "GHSA-x24g-9w7v-vprh": [ "GO-2022-0586" ], "GHSA-x4rg-4545-4w7w": [ "GO-2021-0088" ], "GHSA-x7f3-62pm-9p38": [ "GO-2022-0587" ], "GHSA-x95h-979x-cf3j": [ "GO-2022-0588" ], "GHSA-xcf7-q56x-78gh": [ "GO-2022-0233" ], "GHSA-xg2h-wx96-xgxr": [ "GO-2022-0411" ], "GHSA-xhg2-rvm8-w2jh": [ "GO-2022-0755" ], "GHSA-xhqq-x44f-9fgg": [ "GO-2021-0060" ], "GHSA-xjqr-g762-pxwp": [ "GO-2022-0230" ], "GHSA-xw37-57qp-9mm4": [ "GO-2021-0105" ] } vuln-1.0.1/internal/client/testdata/vulndb-legacy/github.com/000077500000000000000000000000001446745451500242125ustar00rootroot00000000000000vuln-1.0.1/internal/client/testdata/vulndb-legacy/github.com/!bee!go/000077500000000000000000000000001446745451500253755ustar00rootroot00000000000000vuln-1.0.1/internal/client/testdata/vulndb-legacy/github.com/!bee!go/beego.json000066400000000000000000000333361446745451500273610ustar00rootroot00000000000000[ { "id": "GO-2022-0463", "published": "2022-07-01T20:06:59Z", "modified": "2022-08-19T22:21:47Z", "aliases": [ "CVE-2022-31259", "GHSA-qx32-f6g6-fcfr" ], "details": "Routes in the beego HTTP router can match unintended patterns.\nThis overly-broad matching may permit an attacker to bypass access\ncontrols.\n\nFor example, the pattern \"/a/b/:name\" can match the URL \"/a.xml/b/\".\nThis may bypass access control applied to the prefix \"/a/\".\n", "affected": [ { "package": { "name": "github.com/beego/beego", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.12.9" } ] } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2022-0463" }, "ecosystem_specific": { "imports": [ { "path": "github.com/beego/beego", "symbols": [ "App.Run", "ControllerRegister.FindPolicy", "ControllerRegister.FindRouter", "ControllerRegister.ServeHTTP", "FilterRouter.ValidRouter", "InitBeegoBeforeTest", "Run", "RunWithMiddleWares", "TestBeegoInit", "Tree.Match", "Tree.match", "adminApp.Run" ] } ] } }, { "package": { "name": "github.com/beego/beego/v2", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "2.0.3" } ] } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2022-0463" }, "ecosystem_specific": { "imports": [ { "path": "github.com/beego/beego/v2/server/web", "symbols": [ "AddNamespace", "Any", "AutoPrefix", "AutoRouter", "Compare", "CompareNot", "Controller.Bind", "Controller.BindForm", "Controller.BindXML", "Controller.BindYAML", "Controller.GetSecureCookie", "Controller.ParseForm", "Controller.Render", "Controller.RenderBytes", "Controller.RenderString", "Controller.Resp", "Controller.SaveToFile", "Controller.ServeFormatted", "Controller.ServeXML", "Controller.ServeYAML", "Controller.SetSecureCookie", "Controller.Trace", "Controller.URLFor", "Controller.XMLResp", "Controller.XSRFFormHTML", "Controller.XSRFToken", "Controller.YamlResp", "ControllerRegister.Add", "ControllerRegister.AddAuto", "ControllerRegister.AddAutoPrefix", "ControllerRegister.AddMethod", "ControllerRegister.AddRouterMethod", "ControllerRegister.Any", "ControllerRegister.CtrlAny", "ControllerRegister.CtrlDelete", "ControllerRegister.CtrlGet", "ControllerRegister.CtrlHead", "ControllerRegister.CtrlOptions", "ControllerRegister.CtrlPatch", "ControllerRegister.CtrlPost", "ControllerRegister.CtrlPut", "ControllerRegister.Delete", "ControllerRegister.FindPolicy", "ControllerRegister.FindRouter", "ControllerRegister.Get", "ControllerRegister.Handler", "ControllerRegister.Head", "ControllerRegister.Include", "ControllerRegister.Init", "ControllerRegister.InsertFilter", "ControllerRegister.Options", "ControllerRegister.Patch", "ControllerRegister.Post", "ControllerRegister.Put", "ControllerRegister.ServeHTTP", "ControllerRegister.URLFor", "CtrlAny", "CtrlDelete", "CtrlGet", "CtrlHead", "CtrlOptions", "CtrlPatch", "CtrlPost", "CtrlPut", "Date", "DateParse", "Delete", "Exception", "ExecuteTemplate", "ExecuteViewPathTemplate", "FilterRouter.ValidRouter", "FlashData.Error", "FlashData.Notice", "FlashData.Set", "FlashData.Store", "FlashData.Success", "FlashData.Warning", "Get", "GetConfig", "HTML2str", "Handler", "Head", "Htmlquote", "Htmlunquote", "HttpServer.Any", "HttpServer.AutoPrefix", "HttpServer.AutoRouter", "HttpServer.CtrlAny", "HttpServer.CtrlDelete", "HttpServer.CtrlGet", "HttpServer.CtrlHead", "HttpServer.CtrlOptions", "HttpServer.CtrlPatch", "HttpServer.CtrlPost", "HttpServer.CtrlPut", "HttpServer.Delete", "HttpServer.Get", "HttpServer.Handler", "HttpServer.Head", "HttpServer.Include", "HttpServer.InsertFilter", "HttpServer.Options", "HttpServer.Patch", "HttpServer.Post", "HttpServer.PrintTree", "HttpServer.Put", "HttpServer.RESTRouter", "HttpServer.Router", "HttpServer.RouterWithOpts", "HttpServer.Run", "Include", "InitBeegoBeforeTest", "InsertFilter", "LoadAppConfig", "MapGet", "Namespace.Any", "Namespace.AutoPrefix", "Namespace.AutoRouter", "Namespace.Cond", "Namespace.CtrlAny", "Namespace.CtrlDelete", "Namespace.CtrlGet", "Namespace.CtrlHead", "Namespace.CtrlOptions", "Namespace.CtrlPatch", "Namespace.CtrlPost", "Namespace.CtrlPut", "Namespace.Delete", "Namespace.Filter", "Namespace.Get", "Namespace.Handler", "Namespace.Head", "Namespace.Include", "Namespace.Namespace", "Namespace.Options", "Namespace.Patch", "Namespace.Post", "Namespace.Put", "Namespace.Router", "NewControllerRegister", "NewControllerRegisterWithCfg", "NewHttpServerWithCfg", "NewHttpSever", "NewNamespace", "NotNil", "Options", "ParseForm", "Patch", "Policy", "Post", "PrintTree", "Put", "RESTRouter", "ReadFromRequest", "RenderForm", "Router", "RouterWithOpts", "Run", "RunWithMiddleWares", "TestBeegoInit", "Tree.AddRouter", "Tree.AddTree", "Tree.Match", "Tree.match", "URLFor", "URLMap.GetMap", "URLMap.GetMapData", "adminApp.Run", "adminController.AdminIndex", "adminController.Healthcheck", "adminController.ListConf", "adminController.ProfIndex", "adminController.PrometheusMetrics", "adminController.QpsIndex", "adminController.TaskStatus", "beegoAppConfig.Bool", "beegoAppConfig.DefaultBool" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/beego/beego/pull/4958" }, { "type": "FIX", "url": "https://github.com/beego/beego/commit/64cf44d725c8cc35d782327d333df9cbeb1bf2dd" }, { "type": "WEB", "url": "https://beego.vip" }, { "type": "WEB", "url": "https://github.com/beego/beego/issues/4946" }, { "type": "WEB", "url": "https://github.com/beego/beego/pull/4954" }, { "type": "WEB", "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-31259" }, { "type": "WEB", "url": "https://github.com/advisories/GHSA-qx32-f6g6-fcfr" } ] }, { "id": "GO-2022-0569", "published": "2022-08-23T13:24:17Z", "modified": "2022-08-23T13:24:17Z", "aliases": [ "CVE-2022-31836", "GHSA-95f9-94vc-665h" ], "details": "The leafInfo.match() function uses path.join()\nto deal with wildcard values which can lead to cross directory risk.\n", "affected": [ { "package": { "name": "github.com/beego/beego", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.12.11" } ] } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2022-0569" }, "ecosystem_specific": { "imports": [ { "path": "github.com/beego/beego", "symbols": [ "Tree.Match" ] } ] } }, { "package": { "name": "github.com/beego/beego/v2", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "2.0.0" }, { "fixed": "2.0.4" } ] } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2022-0569" }, "ecosystem_specific": { "imports": [ { "path": "github.com/beego/beego/v2/server/web", "symbols": [ "Tree.Match" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/beego/beego/pull/5025" }, { "type": "FIX", "url": "https://github.com/beego/beego/pull/5025/commits/ea5ae58d40589d249cf577a053e490509de2bf57" }, { "type": "WEB", "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-31836" }, { "type": "WEB", "url": "https://github.com/advisories/GHSA-95f9-94vc-665h" } ] }, { "id": "GO-2022-0572", "published": "2022-08-22T17:56:17Z", "modified": "2022-08-23T19:54:38Z", "aliases": [ "CVE-2021-30080", "GHSA-28r6-jm5h-mrgg" ], "details": "An issue was discovered in the route lookup process in\nbeego which attackers to bypass access control.\n", "affected": [ { "package": { "name": "github.com/beego/beego", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" } ] } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2022-0572" }, "ecosystem_specific": { "imports": [ { "path": "github.com/beego/beego", "symbols": [ "Tree.Match" ] } ] } }, { "package": { "name": "github.com/beego/beego/v2", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "2.0.0" }, { "fixed": "2.0.3" } ] } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2022-0572" }, "ecosystem_specific": { "imports": [ { "path": "github.com/beego/beego/v2/server/web", "symbols": [ "Tree.Match" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/beego/beego/pull/4459" }, { "type": "FIX", "url": "https://github.com/beego/beego/commit/d5df5e470d0a8ed291930ae802fd7e6b95226519" }, { "type": "WEB", "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-30080" }, { "type": "WEB", "url": "https://github.com/advisories/GHSA-28r6-jm5h-mrgg" } ] } ]vuln-1.0.1/internal/client/testdata/vulndb-legacy/github.com/tidwall/000077500000000000000000000000001446745451500256525ustar00rootroot00000000000000vuln-1.0.1/internal/client/testdata/vulndb-legacy/github.com/tidwall/gjson.json000066400000000000000000000213651446745451500276740ustar00rootroot00000000000000[ {"id": "GO-2021-0054", "published": "2021-04-14T20:04:52Z", "modified": "2022-08-19T22:21:47Z", "aliases": [ "CVE-2020-36067" ], "details": "Due to improper bounds checking, maliciously crafted JSON objects can cause an out-of-bounds panic. If parsing user input, this may be used as a denial of service vector.", "affected": [ { "package": { "name": "github.com/tidwall/gjson", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.6.6" } ] } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0054" }, "ecosystem_specific": { "imports": [ { "path": "github.com/tidwall/gjson", "symbols": [ "Result.ForEach", "unwrap" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/tidwall/gjson/commit/bf4efcb3c18d1825b2988603dea5909140a5302b" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/196" }, { "type": "WEB", "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-36067" } ] }, { "id": "GO-2021-0059", "published": "2021-04-14T20:04:52Z", "modified": "2022-08-19T22:21:47Z", "aliases": [ "CVE-2020-35380", "GHSA-w942-gw6m-p62c" ], "details": "Due to improper bounds checking, maliciously crafted JSON objects can cause an out-of-bounds panic. If parsing user input, this may be used as a denial of service vector.", "affected": [ { "package": { "name": "github.com/tidwall/gjson", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.6.4" } ] } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0059" }, "ecosystem_specific": { "imports": [ { "path": "github.com/tidwall/gjson", "symbols": [ "sqaush" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/tidwall/gjson/commit/f0ee9ebde4b619767ae4ac03e8e42addb530f6bc" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/192" }, { "type": "WEB", "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-35380" }, { "type": "WEB", "url": "https://github.com/advisories/GHSA-w942-gw6m-p62c" } ] }, { "id": "GO-2021-0265", "published": "2022-01-14T17:30:24Z", "modified": "2022-08-19T22:21:47Z", "aliases": [ "CVE-2020-36066", "CVE-2021-42836", "GHSA-ppj4-34rq-v8j9", "GHSA-wjm3-fq3r-5x46" ], "details": "GJSON allowed a ReDoS (regular expression denial of service) attack.", "affected": [ { "package": { "name": "github.com/tidwall/gjson", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.9.3" } ] } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0265" }, "ecosystem_specific": { "imports": [ { "path": "github.com/tidwall/gjson", "symbols": [ "match.Match" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/tidwall/gjson/commit/590010fdac311cc8990ef5c97448d4fec8f29944" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/compare/v1.9.2...v1.9.3" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/236" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/237" }, { "type": "WEB", "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-36066" }, { "type": "WEB", "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-42836" }, { "type": "WEB", "url": "https://github.com/advisories/GHSA-ppj4-34rq-v8j9" }, { "type": "WEB", "url": "https://github.com/advisories/GHSA-wjm3-fq3r-5x46" } ] }, { "id": "GO-2022-0592", "published": "2022-08-15T18:06:07Z", "modified": "2022-08-19T22:21:47Z", "aliases": [ "CVE-2021-42248", "GHSA-c9gm-7rfj-8w5h" ], "details": "A maliciously crafted path can cause Get and other query functions to consume excessive amounts of CPU and time.", "affected": [ { "package": { "name": "github.com/tidwall/gjson", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.9.3" } ] } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2022-0592" }, "ecosystem_specific": { "imports": [ { "path": "github.com/tidwall/gjson", "symbols": [ "Get", "GetBytes", "GetMany", "GetManyBytes", "Result.Get", "queryMatches" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://github.com/tidwall/gjson/commit/77a57fda87dca6d0d7d4627d512a630f89a91c96" }, { "type": "WEB", "url": "https://github.com/tidwall/gjson/issues/237" }, { "type": "WEB", "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-42248" }, { "type": "WEB", "url": "https://github.com/advisories/GHSA-c9gm-7rfj-8w5h" } ] } ] vuln-1.0.1/internal/client/testdata/vulndb-legacy/index.json000066400000000000000000000001611446745451500241530ustar00rootroot00000000000000{ "github.com/BeeGo/beego": "2022-08-23T19:54:38Z", "github.com/tidwall/gjson": "2022-08-23T19:54:38Z" } vuln-1.0.1/internal/client/testdata/vulndb-v1/000077500000000000000000000000001446745451500212355ustar00rootroot00000000000000vuln-1.0.1/internal/client/testdata/vulndb-v1/ID/000077500000000000000000000000001446745451500215315ustar00rootroot00000000000000vuln-1.0.1/internal/client/testdata/vulndb-v1/ID/GO-2021-0068.json000066400000000000000000000021721446745451500237100ustar00rootroot00000000000000{"schema_version":"1.3.1","id":"GO-2021-0068","modified":"2023-04-03T15:57:51Z","published":"2021-04-14T20:04:52Z","aliases":["CVE-2021-3115"],"details":"The go command may execute arbitrary code at build time when using cgo on Windows. This can be triggered by running go get on a malicious module, or any other time the code is built.","affected":[{"package":{"name":"toolchain","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.14.14"},{"introduced":"1.15.0"},{"fixed":"1.15.7"}]}],"ecosystem_specific":{"imports":[{"path":"cmd/go","goos":["windows"]}]}}],"references":[{"type":"FIX","url":"https://go.dev/cl/284783"},{"type":"FIX","url":"https://go.googlesource.com/go/+/953d1feca9b21af075ad5fc8a3dad096d3ccc3a0"},{"type":"REPORT","url":"https://go.dev/issue/43783"},{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/mperVMGa98w/m/yo5W5wnvAAAJ"},{"type":"FIX","url":"https://go.dev/cl/284780"},{"type":"FIX","url":"https://go.googlesource.com/go/+/46e2e2e9d99925bbf724b12693c6d3e27a95d6a0"}],"credits":[{"name":"RyotaK"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2021-0068"}}vuln-1.0.1/internal/client/testdata/vulndb-v1/ID/GO-2021-0068.json.gz000066400000000000000000000012131446745451500243220ustar00rootroot00000000000000o6ƿul%=r\ن#4#Ʌ^줰RҞgf~̠ x yCjqs(çUf|-D029YJUyY@wI,>K봨lV?缄C 02ML0 2pg&I!5 f@vвȲWc|L dѡb͙9Aihl 51rL3С _B<׶(l B~B 0"@Iz)_A4ñhyv. o&:4o1Lnf!|$]J&bHhJ;k +HVBմҚm(RUY.i]QZ9gYU/RJV,UdyXFYFkVX@J9yb)Y˲⤠BREEԋ &\eB.xȊl+DVO/U{?aBaOuĻg8;4q7݂c'#0zΆ5$Φ/3}vp(i-a]g}t)rOnyh; &yUӺvuln-1.0.1/internal/client/testdata/vulndb-v1/ID/GO-2021-0240.json000066400000000000000000000020571446745451500237020ustar00rootroot00000000000000{"schema_version":"1.3.1","id":"GO-2021-0240","modified":"2023-04-03T15:57:51Z","published":"2022-02-17T17:33:25Z","aliases":["CVE-2021-33196"],"details":"NewReader and OpenReader can cause a panic or an unrecoverable fatal error when reading an archive that claims to contain a large number of files, regardless of its actual size.","affected":[{"package":{"name":"stdlib","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.15.13"},{"introduced":"1.16.0"},{"fixed":"1.16.5"}]}],"ecosystem_specific":{"imports":[{"path":"archive/zip","symbols":["Reader.init"]}]}}],"references":[{"type":"FIX","url":"https://go.dev/cl/318909"},{"type":"FIX","url":"https://go.googlesource.com/go/+/74242baa4136c7a9132a8ccd9881354442788c8c"},{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/RgCMkAEQjSI"},{"type":"REPORT","url":"https://go.dev/issue/46242"}],"credits":[{"name":"the OSS-Fuzz project for discovering this issue and\nEmmanuel Odeke for reporting it\n"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2021-0240"}}vuln-1.0.1/internal/client/testdata/vulndb-v1/ID/GO-2021-0240.json.gz000066400000000000000000000012161446745451500243150ustar00rootroot00000000000000|ݎ6_ꏒ6p\nhbcj$1H {AX4$?9sfx':뙬FC<)*uY,/37l$<ˋ8+xjo`J?$~,Y'zk$5GI9 R>}Tv8;;/{gkD#|"D#~{UXV`LR-bY/rݬ7ͺKdb}R-\=Tf]7*BP3z$?uT)N^3#6 |9G2 8F4,[=gҚlHB˚๷@ԓ= l ^APrGF =Ly4'ZP{*bDI%-4a>8bG2=4"["{g3@Oj}('f,{Aٟg:"GR7mMGPΦlf2@z;u}$y B5[t#:F>c(<[:%oxZ=PܶȄyhع,&n <IM"res@c(c:RnWXE&Ǽ=yRn'R%ifh = ~rǻh[!^Χ1EDY>([ }!ާL84]:c߼xs&8&ns)ًhk*"/翝ʼ?`7JO'Z>$,bF]㍈F=ƣȄ[ӿ9i,x:<"<͟"Ӣ}o]!u6Y;k;MNNR.PtVfuUokZU["TK+,i?n~MfM@cd$XoW)͇w6${?QQ_./"$)>}ܕk/2@۸F;=ܣ ^>dCp*7&EpHܢE_(t7iS*jqvuln-1.0.1/internal/client/testdata/vulndb-v1/ID/GO-2022-0229.json000066400000000000000000000030121446745451500237020ustar00rootroot00000000000000{"schema_version":"1.3.1","id":"GO-2022-0229","modified":"2023-04-03T15:57:51Z","published":"2022-07-06T18:23:48Z","aliases":["CVE-2020-7919","GHSA-cjjc-xp8v-855w"],"details":"On 32-bit architectures, a malformed input to crypto/x509 or the ASN.1 parsing functions of golang.org/x/crypto/cryptobyte can lead to a panic.\n\nThe malformed certificate can be delivered via a crypto/tls connection to a client, or to a server that accepts client certificates. net/http clients can be made to crash by an HTTPS server, while net/http servers that accept client certificates will recover the panic and are unaffected.","affected":[{"package":{"name":"stdlib","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.12.16"},{"introduced":"1.13.0"},{"fixed":"1.13.7"}]}],"ecosystem_specific":{"imports":[{"path":"crypto/x509"}]}},{"package":{"name":"golang.org/x/crypto","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.0.0-20200124225646-8b5121be2f68"}]}],"ecosystem_specific":{"imports":[{"path":"golang.org/x/crypto/cryptobyte"}]}}],"references":[{"type":"FIX","url":"https://go.dev/cl/216680"},{"type":"FIX","url":"https://go.googlesource.com/go/+/b13ce14c4a6aa59b7b041ad2b6eed2d23e15b574"},{"type":"FIX","url":"https://go.dev/cl/216677"},{"type":"REPORT","url":"https://go.dev/issue/36837"},{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/Hsw4mHYc470"}],"credits":[{"name":"Project Wycheproof"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2022-0229"}}vuln-1.0.1/internal/client/testdata/vulndb-v1/ID/GO-2022-0229.json.gz000066400000000000000000000014011446745451500243210ustar00rootroot00000000000000TMo6+\/R-Mmm,FHbV"rbd=d|3dK=~ّuh(Gi!UAoC&B\ATh:Hd&gE.2'0e\ a|7Hf`БzꟄ+>.OO2|6pe@EU砀[R3U-!뱫bJg0i7K\1co{-l@nX=jю5CD6K~ʽ'&Q#P+}֟3nI֫ZI\*JbujG*SUFkN,vZ;;g(% -s"1M>nScEIkYgv{s:)ȞU1KҜi u55IOU4OP~ņ8ƞNIS?%1r&'n2:)F9.cpZ+q|޻lx|< $'ߓb. 8 T:5|k&Q%JX <ܔ$Qjbv,iy_~FAS\Ǎ*ŲI1MGΌVR$M7&!.y* s\Ɋc%ʜHge^ 3A9~O%ʹ4ߤE|[a8C|lZQKe|WrN`T: YDҳ{`gdKttqMߍ/>_I:! vuln-1.0.1/internal/client/testdata/vulndb-v1/ID/GO-2022-0273.json000066400000000000000000000021331446745451500237040ustar00rootroot00000000000000{"schema_version":"1.3.1","id":"GO-2022-0273","modified":"2023-04-03T15:57:51Z","published":"2022-05-18T18:23:31Z","aliases":["CVE-2021-39293"],"details":"The NewReader and OpenReader functions in archive/zip can cause a panic or an unrecoverable fatal error when reading an archive that claims to contain a large number of files, regardless of its actual size. This is caused by an incomplete fix for CVE-2021-33196.","affected":[{"package":{"name":"stdlib","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.16.8"},{"introduced":"1.17.0"},{"fixed":"1.17.1"}]}],"ecosystem_specific":{"imports":[{"path":"archive/zip","symbols":["NewReader","OpenReader"]}]}}],"references":[{"type":"FIX","url":"https://go.dev/cl/343434"},{"type":"FIX","url":"https://go.googlesource.com/go/+/bacbc33439b124ffd7392c91a5f5d96eca8c0c0b"},{"type":"REPORT","url":"https://go.dev/issue/47801"},{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/dx9d7IOseHw"}],"credits":[{"name":"OSS-Fuzz Project and Emmanuel Odeke"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2022-0273"}}vuln-1.0.1/internal/client/testdata/vulndb-v1/ID/GO-2022-0273.json.gz000066400000000000000000000012331446745451500243230ustar00rootroot00000000000000|TM6+Z}^:pZFZ40'Ib"~nFB'3 zqO_켲+̓21B%ŸqE#TyȊ2VqVuTOpk|uo*ʪ\ yX}>OJ ot$ u،FeeԙW5 F@0QL8̎jP uԱ$igu7T!X@k6(>-9yR$Hz9 ˬ904[$eK漚Er#  1Bֿܛ)BG]`xf|fޖ Q,e8El2ʓ..S7<7wMtN| (1V`UtaK_[܌4vl>|# _ikT\b񭵭foG'8O[$jQrWŪibӺY ڊLd{<)GNWm'{/O8$Ku z[>9X'3G^[Oc\OrIoފi;4)[vuln-1.0.1/internal/client/testdata/vulndb-v1/ID/GO-2022-0463.json000066400000000000000000000145631446745451500237170ustar00rootroot00000000000000{"schema_version":"1.3.1","id":"GO-2022-0463","modified":"2023-04-03T15:57:51Z","published":"2022-07-01T20:06:59Z","aliases":["CVE-2022-31259","GHSA-qx32-f6g6-fcfr"],"details":"Routes in the beego HTTP router can match unintended patterns. This overly-broad matching may permit an attacker to bypass access controls.\n\nFor example, the pattern \"/a/b/:name\" can match the URL \"/a.xml/b/\". This may bypass access control applied to the prefix \"/a/\".","affected":[{"package":{"name":"github.com/astaxie/beego","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"}]}],"ecosystem_specific":{"imports":[{"path":"github.com/astaxie/beego","symbols":["App.Run","ControllerRegister.FindPolicy","ControllerRegister.FindRouter","ControllerRegister.ServeHTTP","FilterRouter.ValidRouter","InitBeegoBeforeTest","Run","RunWithMiddleWares","TestBeegoInit","Tree.Match","adminApp.Run"]}]}},{"package":{"name":"github.com/beego/beego","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.12.9"}]}],"ecosystem_specific":{"imports":[{"path":"github.com/beego/beego","symbols":["App.Run","ControllerRegister.FindPolicy","ControllerRegister.FindRouter","ControllerRegister.ServeHTTP","FilterRouter.ValidRouter","InitBeegoBeforeTest","Run","RunWithMiddleWares","TestBeegoInit","Tree.Match","Tree.match","adminApp.Run"]}]}},{"package":{"name":"github.com/beego/beego/v2","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"2.0.3"}]}],"ecosystem_specific":{"imports":[{"path":"github.com/beego/beego/v2/server/web","symbols":["AddNamespace","AddViewPath","Any","AutoPrefix","AutoRouter","BuildTemplate","Compare","CompareNot","Controller.Abort","Controller.Bind","Controller.BindForm","Controller.BindJSON","Controller.BindProtobuf","Controller.BindXML","Controller.BindYAML","Controller.CheckXSRFCookie","Controller.CustomAbort","Controller.Delete","Controller.DestroySession","Controller.Get","Controller.GetBool","Controller.GetFile","Controller.GetFloat","Controller.GetInt","Controller.GetInt16","Controller.GetInt32","Controller.GetInt64","Controller.GetInt8","Controller.GetSecureCookie","Controller.GetString","Controller.GetStrings","Controller.GetUint16","Controller.GetUint32","Controller.GetUint64","Controller.GetUint8","Controller.Head","Controller.Input","Controller.IsAjax","Controller.JSONResp","Controller.Options","Controller.ParseForm","Controller.Patch","Controller.Post","Controller.Put","Controller.Redirect","Controller.Render","Controller.RenderBytes","Controller.RenderString","Controller.Resp","Controller.SaveToFile","Controller.SaveToFileWithBuffer","Controller.ServeFormatted","Controller.ServeJSON","Controller.ServeJSONP","Controller.ServeXML","Controller.ServeYAML","Controller.SessionRegenerateID","Controller.SetData","Controller.SetSecureCookie","Controller.Trace","Controller.URLFor","Controller.XMLResp","Controller.XSRFFormHTML","Controller.XSRFToken","Controller.YamlResp","ControllerRegister.Add","ControllerRegister.AddAuto","ControllerRegister.AddAutoPrefix","ControllerRegister.AddMethod","ControllerRegister.AddRouterMethod","ControllerRegister.Any","ControllerRegister.CtrlAny","ControllerRegister.CtrlDelete","ControllerRegister.CtrlGet","ControllerRegister.CtrlHead","ControllerRegister.CtrlOptions","ControllerRegister.CtrlPatch","ControllerRegister.CtrlPost","ControllerRegister.CtrlPut","ControllerRegister.Delete","ControllerRegister.FindPolicy","ControllerRegister.FindRouter","ControllerRegister.Get","ControllerRegister.GetContext","ControllerRegister.Handler","ControllerRegister.Head","ControllerRegister.Include","ControllerRegister.Init","ControllerRegister.InsertFilter","ControllerRegister.Options","ControllerRegister.Patch","ControllerRegister.Post","ControllerRegister.Put","ControllerRegister.ServeHTTP","ControllerRegister.URLFor","CtrlAny","CtrlDelete","CtrlGet","CtrlHead","CtrlOptions","CtrlPatch","CtrlPost","CtrlPut","Date","DateFormat","DateParse","Delete","Exception","ExecuteTemplate","ExecuteViewPathTemplate","FileSystem.Open","FilterRouter.ValidRouter","FlashData.Error","FlashData.Notice","FlashData.Set","FlashData.Store","FlashData.Success","FlashData.Warning","Get","GetConfig","HTML2str","Handler","Head","Htmlquote","Htmlunquote","HttpServer.Any","HttpServer.AutoPrefix","HttpServer.AutoRouter","HttpServer.CtrlAny","HttpServer.CtrlDelete","HttpServer.CtrlGet","HttpServer.CtrlHead","HttpServer.CtrlOptions","HttpServer.CtrlPatch","HttpServer.CtrlPost","HttpServer.CtrlPut","HttpServer.Delete","HttpServer.Get","HttpServer.Handler","HttpServer.Head","HttpServer.Include","HttpServer.InsertFilter","HttpServer.LogAccess","HttpServer.Options","HttpServer.Patch","HttpServer.Post","HttpServer.PrintTree","HttpServer.Put","HttpServer.RESTRouter","HttpServer.Router","HttpServer.RouterWithOpts","HttpServer.Run","Include","InitBeegoBeforeTest","InsertFilter","LoadAppConfig","LogAccess","MapGet","Namespace.Any","Namespace.AutoPrefix","Namespace.AutoRouter","Namespace.Cond","Namespace.CtrlAny","Namespace.CtrlDelete","Namespace.CtrlGet","Namespace.CtrlHead","Namespace.CtrlOptions","Namespace.CtrlPatch","Namespace.CtrlPost","Namespace.CtrlPut","Namespace.Delete","Namespace.Filter","Namespace.Get","Namespace.Handler","Namespace.Head","Namespace.Include","Namespace.Namespace","Namespace.Options","Namespace.Patch","Namespace.Post","Namespace.Put","Namespace.Router","NewControllerRegister","NewControllerRegisterWithCfg","NewHttpServerWithCfg","NewHttpSever","NewNamespace","NotNil","Options","ParseForm","Patch","Policy","Post","PrintTree","Put","RESTRouter","ReadFromRequest","RenderForm","Router","RouterWithOpts","Run","RunWithMiddleWares","TestBeegoInit","Tree.AddRouter","Tree.AddTree","Tree.Match","Tree.match","URLFor","URLMap.GetMap","URLMap.GetMapData","Walk","adminApp.Run","adminController.AdminIndex","adminController.Healthcheck","adminController.ListConf","adminController.ProfIndex","adminController.PrometheusMetrics","adminController.QpsIndex","adminController.TaskStatus","beegoAppConfig.Bool","beegoAppConfig.DefaultBool"]}]}}],"references":[{"type":"FIX","url":"https://github.com/beego/beego/pull/4958"},{"type":"FIX","url":"https://github.com/beego/beego/commit/64cf44d725c8cc35d782327d333df9cbeb1bf2dd"},{"type":"WEB","url":"https://beego.vip"},{"type":"WEB","url":"https://github.com/beego/beego/issues/4946"},{"type":"WEB","url":"https://github.com/beego/beego/pull/4954"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2022-0463"}}vuln-1.0.1/internal/client/testdata/vulndb-v1/ID/GO-2022-0463.json.gz000066400000000000000000000033551446745451500243330ustar00rootroot00000000000000ܙ[s8KCoB'[!adv1hcKnIR[/Swt97Kb 18CglD4F_z^z(f (g㳋_FIxE?Xyq||v`Gorz|s 7 1K%PKnZluZ-1b,RB%+R±V[",zkpu&tc`%c"-L-,%߁[YCϨ,+}S-8KYS+:}[<>ܵrŔ Y8I"F!$lW(!R`o?Zцmv|XH'j!AHUt!Q× yTf"4 R_>>*!IH|a CfΏI8"]g/`CL ,"[Ӆ˗wrhJ" <|)GS"jW2+(jg"3%0j?\@3=y5"&. Ai`˜]$6A[-V_%W* Csy]`Ț[ؙ6f K1;,qvg-̰et* w+SQ%Yw06o8It T=),jr{->+';юOIaKeJX6pMڶ%k.6d]vOYwO@ RQw0 YxO( KHdD؜KwL:Q=5\j-/$d\=TI"Kn3żq۽zZTB̔㩫"Qx}Sʉ4b s9G&x%Zj+mɸ#W{Ɯf_yYDU=!e^枽q#e*;eK&:rSg /cm"ό0`rya)b,[LMVwV`Cr[`mV$l39F[ m1i*͋-|LIi(Jo&IRdm 3d/.DyNVՔB_3}"CH:6gAc ΀iT8DeVqU%miSӖ24*M&.Ԭ`򑨻]iDp488VTwQfGm,SH'}g-篾' ~I)>3lg/y`4}p$HПќk9g1-b_g"x_J,S5N?oQl!ԯ?!M_R1Jv<$i˳/Q鯏Y鞏p4 .3ς/лa^kX֡UoIIc:D;32Bʿx$gÜv.[LUsvuln-1.0.1/internal/client/testdata/vulndb-v1/ID/GO-2022-0475.json000066400000000000000000000021621446745451500237120ustar00rootroot00000000000000{"schema_version":"1.3.1","id":"GO-2022-0475","modified":"2023-04-03T15:57:51Z","published":"2022-07-28T17:24:30Z","aliases":["CVE-2020-28366"],"details":"The go command may execute arbitrary code at build time when cgo is in use. This may occur when running go get on a malicious package, or any other command that builds untrusted code.\n\nThis can be caused by malicious unquoted symbol name in a linked object file.","affected":[{"package":{"name":"toolchain","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.14.12"},{"introduced":"1.15.0"},{"fixed":"1.15.5"}]}],"ecosystem_specific":{"imports":[{"path":"cmd/go","symbols":["Builder.cgo"]},{"path":"cmd/cgo","symbols":["dynimport"]}]}}],"references":[{"type":"FIX","url":"https://go.dev/cl/269658"},{"type":"FIX","url":"https://go.googlesource.com/go/+/062e0e5ce6df339dc26732438ad771f73dbf2292"},{"type":"REPORT","url":"https://go.dev/issue/42559"},{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/NpBGTTmKzpM"}],"credits":[{"name":"Chris Brown and Tempus Ex"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2022-0475"}}vuln-1.0.1/internal/client/testdata/vulndb-v1/ID/GO-2022-0475.json.gz000066400000000000000000000012421446745451500243270ustar00rootroot00000000000000|ݎ6_e0CY.7pHSlhb9ؕH?κ޽vR9 xF'z#YL LPI[m FVQ?MQ݇sIˑ2䌭z]X[ɊoUZ|7,DBJDh5N^]'{Q2 &xcY.TtF+-'}i0GoK-Y&5OβΤ2yT}_ LRaƬ3wYaS%h#۲|m%[\nE-e2n w?r.PfUx[ kd3݊km~?cJ’TY\Mo'khq vK7DI.à'dPx(mrvuln-1.0.1/internal/client/testdata/vulndb-v1/ID/GO-2022-0476.json000066400000000000000000000020701446745451500237110ustar00rootroot00000000000000{"schema_version":"1.3.1","id":"GO-2022-0476","modified":"2023-04-03T15:57:51Z","published":"2022-07-28T17:24:43Z","aliases":["CVE-2020-28367"],"details":"The go command may execute arbitrary code at build time when cgo is in use. This may occur when running go get on a malicious package, or any other command that builds untrusted code.\n\nThis can be caused by malicious gcc flags specified via a cgo directive.","affected":[{"package":{"name":"toolchain","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.14.12"},{"introduced":"1.15.0"},{"fixed":"1.15.5"}]}],"ecosystem_specific":{"imports":[{"path":"cmd/go","symbols":["validCompilerFlags"]}]}}],"references":[{"type":"FIX","url":"https://go.dev/cl/267277"},{"type":"FIX","url":"https://go.googlesource.com/go/+/da7aa86917811a571e6634b45a457f918b8e6561"},{"type":"REPORT","url":"https://go.dev/issue/42556"},{"type":"WEB","url":"https://groups.google.com/g/golang-announce/c/NpBGTTmKzpM"}],"credits":[{"name":"Imre Rad"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2022-0476"}}vuln-1.0.1/internal/client/testdata/vulndb-v1/ID/GO-2022-0476.json.gz000066400000000000000000000012121446745451500243250ustar00rootroot00000000000000|ͮ6_meɔh "MqkE#cH q~BNa]Js8/@,tM!-wW,Wm >j+ܬjۮBSGrnWvۮj3Kp(@|׫riZ8d )t)DŽ3dόH^Ḡ0>Q L(t`ڲ(gA"gOjfE9ː.62Hrɿ/vhYOL` $Yk`U`a"$N.R{Q(:I9AAw(2 9D2\3hy>|=\6z'Xfkv|W9/2:bfr:aaddΦw'|̤Gop=\羞Ɋ[}d 1N+ rIBEٴe.WΩK^P.)+~*$ocrjMW5Vu{|ŷO_?>Iʺn{wi 7gBZ.YA(~^O`NIxmQO R@y4{J-a-8vuln-1.0.1/internal/client/testdata/vulndb-v1/ID/GO-2022-0569.json000066400000000000000000000140341446745451500237170ustar00rootroot00000000000000{"schema_version":"1.3.1","id":"GO-2022-0569","modified":"2023-04-03T15:57:51Z","published":"2022-08-23T13:24:17Z","aliases":["CVE-2022-31836","GHSA-95f9-94vc-665h"],"details":"The leafInfo.match() function uses path.join() to deal with wildcard values which can lead to cross directory risk.","affected":[{"package":{"name":"github.com/astaxie/beego","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"}]}],"ecosystem_specific":{"imports":[{"path":"github.com/astaxie/beego","symbols":["App.Run","ControllerRegister.FindPolicy","ControllerRegister.FindRouter","ControllerRegister.ServeHTTP","FilterRouter.ValidRouter","InitBeegoBeforeTest","Run","RunWithMiddleWares","TestBeegoInit","Tree.Match","adminApp.Run"]}]}},{"package":{"name":"github.com/beego/beego","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.12.11"}]}],"ecosystem_specific":{"imports":[{"path":"github.com/beego/beego","symbols":["App.Run","ControllerRegister.FindPolicy","ControllerRegister.FindRouter","ControllerRegister.ServeHTTP","FilterRouter.ValidRouter","InitBeegoBeforeTest","Run","RunWithMiddleWares","TestBeegoInit","Tree.Match","adminApp.Run"]}]}},{"package":{"name":"github.com/beego/beego/v2","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"2.0.0"},{"fixed":"2.0.4"}]}],"ecosystem_specific":{"imports":[{"path":"github.com/beego/beego/v2/server/web","symbols":["AddNamespace","AddViewPath","Any","AutoPrefix","AutoRouter","BuildTemplate","Compare","CompareNot","Controller.Abort","Controller.Bind","Controller.BindForm","Controller.BindJSON","Controller.BindProtobuf","Controller.BindXML","Controller.BindYAML","Controller.CheckXSRFCookie","Controller.CustomAbort","Controller.Delete","Controller.DestroySession","Controller.Get","Controller.GetBool","Controller.GetFile","Controller.GetFloat","Controller.GetInt","Controller.GetInt16","Controller.GetInt32","Controller.GetInt64","Controller.GetInt8","Controller.GetSecureCookie","Controller.GetString","Controller.GetStrings","Controller.GetUint16","Controller.GetUint32","Controller.GetUint64","Controller.GetUint8","Controller.Head","Controller.Input","Controller.IsAjax","Controller.JSONResp","Controller.Options","Controller.ParseForm","Controller.Patch","Controller.Post","Controller.Put","Controller.Redirect","Controller.Render","Controller.RenderBytes","Controller.RenderString","Controller.Resp","Controller.SaveToFile","Controller.SaveToFileWithBuffer","Controller.ServeFormatted","Controller.ServeJSON","Controller.ServeJSONP","Controller.ServeXML","Controller.ServeYAML","Controller.SessionRegenerateID","Controller.SetData","Controller.SetSecureCookie","Controller.Trace","Controller.URLFor","Controller.XMLResp","Controller.XSRFFormHTML","Controller.XSRFToken","Controller.YamlResp","ControllerRegister.Add","ControllerRegister.AddAuto","ControllerRegister.AddAutoPrefix","ControllerRegister.AddMethod","ControllerRegister.AddRouterMethod","ControllerRegister.Any","ControllerRegister.CtrlAny","ControllerRegister.CtrlDelete","ControllerRegister.CtrlGet","ControllerRegister.CtrlHead","ControllerRegister.CtrlOptions","ControllerRegister.CtrlPatch","ControllerRegister.CtrlPost","ControllerRegister.CtrlPut","ControllerRegister.Delete","ControllerRegister.FindPolicy","ControllerRegister.FindRouter","ControllerRegister.Get","ControllerRegister.GetContext","ControllerRegister.Handler","ControllerRegister.Head","ControllerRegister.Include","ControllerRegister.Init","ControllerRegister.InsertFilter","ControllerRegister.Options","ControllerRegister.Patch","ControllerRegister.Post","ControllerRegister.Put","ControllerRegister.ServeHTTP","ControllerRegister.URLFor","CtrlAny","CtrlDelete","CtrlGet","CtrlHead","CtrlOptions","CtrlPatch","CtrlPost","CtrlPut","Date","DateFormat","DateParse","Delete","Exception","ExecuteTemplate","ExecuteViewPathTemplate","FileSystem.Open","FilterRouter.ValidRouter","FlashData.Error","FlashData.Notice","FlashData.Set","FlashData.Store","FlashData.Success","FlashData.Warning","Get","GetConfig","HTML2str","Handler","Head","Htmlquote","Htmlunquote","HttpServer.Any","HttpServer.AutoPrefix","HttpServer.AutoRouter","HttpServer.CtrlAny","HttpServer.CtrlDelete","HttpServer.CtrlGet","HttpServer.CtrlHead","HttpServer.CtrlOptions","HttpServer.CtrlPatch","HttpServer.CtrlPost","HttpServer.CtrlPut","HttpServer.Delete","HttpServer.Get","HttpServer.Handler","HttpServer.Head","HttpServer.Include","HttpServer.InsertFilter","HttpServer.LogAccess","HttpServer.Options","HttpServer.Patch","HttpServer.Post","HttpServer.PrintTree","HttpServer.Put","HttpServer.RESTRouter","HttpServer.Router","HttpServer.RouterWithOpts","HttpServer.Run","Include","InitBeegoBeforeTest","InsertFilter","LoadAppConfig","LogAccess","MapGet","Namespace.Any","Namespace.AutoPrefix","Namespace.AutoRouter","Namespace.Cond","Namespace.CtrlAny","Namespace.CtrlDelete","Namespace.CtrlGet","Namespace.CtrlHead","Namespace.CtrlOptions","Namespace.CtrlPatch","Namespace.CtrlPost","Namespace.CtrlPut","Namespace.Delete","Namespace.Filter","Namespace.Get","Namespace.Handler","Namespace.Head","Namespace.Include","Namespace.Namespace","Namespace.Options","Namespace.Patch","Namespace.Post","Namespace.Put","Namespace.Router","NewControllerRegister","NewControllerRegisterWithCfg","NewHttpServerWithCfg","NewHttpSever","NewNamespace","NotNil","Options","ParseForm","Patch","Policy","Post","PrintTree","Put","RESTRouter","ReadFromRequest","RenderForm","Router","RouterWithOpts","Run","RunWithMiddleWares","TestBeegoInit","Tree.AddRouter","Tree.AddTree","Tree.Match","URLFor","URLMap.GetMap","URLMap.GetMapData","Walk","adminApp.Run","adminController.AdminIndex","adminController.Healthcheck","adminController.ListConf","adminController.ProfIndex","adminController.PrometheusMetrics","adminController.QpsIndex","adminController.TaskStatus","beegoAppConfig.Bool","beegoAppConfig.DefaultBool"]}]}}],"references":[{"type":"FIX","url":"https://github.com/beego/beego/pull/5025"},{"type":"FIX","url":"https://github.com/beego/beego/pull/5025/commits/ea5ae58d40589d249cf577a053e490509de2bf57"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2022-0569"}}vuln-1.0.1/internal/client/testdata/vulndb-v1/ID/GO-2022-0569.json.gz000066400000000000000000000032061446745451500243350ustar00rootroot00000000000000o])-\ LIN'mU_9 I$\]<wl| 7u. =@"/]8r,2;nnj2Y(Np3NJu`MD%З;v//Gz*mP<bȕEDXߌDޑ7"9` :.Ԍr&D#\GʾTFbE84A;"dk,a!;`ǐeCH3:ZhZGW"H$97ud_1'R10eyGe44U! 29J`GnH-%G| Y~f,4#Tz0%yDR|aB" eZ;x@R=jjs{A It2zvuln-1.0.1/internal/client/testdata/vulndb-v1/ID/GO-2022-0572.json000066400000000000000000000120721446745451500237110ustar00rootroot00000000000000{"schema_version":"1.3.1","id":"GO-2022-0572","modified":"2023-04-03T15:57:51Z","published":"2022-08-22T17:56:17Z","aliases":["CVE-2021-30080","GHSA-28r6-jm5h-mrgg"],"details":"An issue was discovered in the route lookup process in beego which attackers to bypass access control.","affected":[{"package":{"name":"github.com/astaxie/beego","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"}]}],"ecosystem_specific":{"imports":[{"path":"github.com/astaxie/beego","symbols":["App.Run","ControllerRegister.FindPolicy","ControllerRegister.FindRouter","ControllerRegister.ServeHTTP","FilterRouter.ValidRouter","InitBeegoBeforeTest","Run","RunWithMiddleWares","TestBeegoInit","Tree.Match","adminApp.Run"]}]}},{"package":{"name":"github.com/beego/beego","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"}]}],"ecosystem_specific":{"imports":[{"path":"github.com/beego/beego","symbols":["App.Run","ControllerRegister.FindPolicy","ControllerRegister.FindRouter","ControllerRegister.ServeHTTP","FilterRouter.ValidRouter","InitBeegoBeforeTest","Run","RunWithMiddleWares","TestBeegoInit","Tree.Match","adminApp.Run"]}]}},{"package":{"name":"github.com/beego/beego/v2","ecosystem":"Go"},"ranges":[{"type":"SEMVER","events":[{"introduced":"2.0.0"},{"fixed":"2.0.3"}]}],"ecosystem_specific":{"imports":[{"path":"github.com/beego/beego/v2/server/web","symbols":["AddNamespace","AddViewPath","Any","AutoPrefix","AutoRouter","BuildTemplate","Compare","CompareNot","Controller.Abort","Controller.CheckXSRFCookie","Controller.CustomAbort","Controller.Delete","Controller.DestroySession","Controller.Get","Controller.GetBool","Controller.GetFile","Controller.GetFloat","Controller.GetInt","Controller.GetInt16","Controller.GetInt32","Controller.GetInt64","Controller.GetInt8","Controller.GetSecureCookie","Controller.GetString","Controller.GetStrings","Controller.GetUint16","Controller.GetUint32","Controller.GetUint64","Controller.GetUint8","Controller.Head","Controller.Input","Controller.IsAjax","Controller.Options","Controller.ParseForm","Controller.Patch","Controller.Post","Controller.Put","Controller.Redirect","Controller.Render","Controller.RenderBytes","Controller.RenderString","Controller.SaveToFile","Controller.ServeFormatted","Controller.ServeJSON","Controller.ServeJSONP","Controller.ServeXML","Controller.ServeYAML","Controller.SessionRegenerateID","Controller.SetData","Controller.SetSecureCookie","Controller.Trace","Controller.URLFor","Controller.XSRFFormHTML","Controller.XSRFToken","ControllerRegister.Add","ControllerRegister.AddAuto","ControllerRegister.AddAutoPrefix","ControllerRegister.AddMethod","ControllerRegister.Any","ControllerRegister.Delete","ControllerRegister.FindPolicy","ControllerRegister.FindRouter","ControllerRegister.Get","ControllerRegister.GetContext","ControllerRegister.Handler","ControllerRegister.Head","ControllerRegister.Include","ControllerRegister.InsertFilter","ControllerRegister.InsertFilterChain","ControllerRegister.Options","ControllerRegister.Patch","ControllerRegister.Post","ControllerRegister.Put","ControllerRegister.ServeHTTP","ControllerRegister.URLFor","Date","DateFormat","DateParse","Delete","Exception","ExecuteTemplate","ExecuteViewPathTemplate","FileSystem.Open","FilterRouter.ValidRouter","FlashData.Error","FlashData.Notice","FlashData.Set","FlashData.Store","FlashData.Success","FlashData.Warning","Get","GetConfig","HTML2str","Handler","Head","Htmlquote","Htmlunquote","HttpServer.Any","HttpServer.AutoPrefix","HttpServer.AutoRouter","HttpServer.Delete","HttpServer.Get","HttpServer.Handler","HttpServer.Head","HttpServer.Include","HttpServer.InsertFilter","HttpServer.InsertFilterChain","HttpServer.LogAccess","HttpServer.Options","HttpServer.Patch","HttpServer.Post","HttpServer.PrintTree","HttpServer.Put","HttpServer.RESTRouter","HttpServer.Router","HttpServer.Run","Include","InitBeegoBeforeTest","InsertFilter","InsertFilterChain","LoadAppConfig","LogAccess","MapGet","Namespace.Any","Namespace.AutoPrefix","Namespace.AutoRouter","Namespace.Cond","Namespace.Delete","Namespace.Filter","Namespace.Get","Namespace.Handler","Namespace.Head","Namespace.Include","Namespace.Namespace","Namespace.Options","Namespace.Patch","Namespace.Post","Namespace.Put","Namespace.Router","NewControllerRegister","NewControllerRegisterWithCfg","NewHttpServerWithCfg","NewHttpSever","NewNamespace","NotNil","Options","ParseForm","Patch","Policy","Post","PrintTree","Put","RESTRouter","ReadFromRequest","RenderForm","Router","Run","RunWithMiddleWares","TestBeegoInit","Tree.AddRouter","Tree.AddTree","Tree.Match","URLFor","URLMap.GetMap","URLMap.GetMapData","Walk","adminApp.Run","adminController.AdminIndex","adminController.Healthcheck","adminController.ListConf","adminController.ProfIndex","adminController.PrometheusMetrics","adminController.QpsIndex","adminController.TaskStatus","beegoAppConfig.Bool","beegoAppConfig.DefaultBool"]}]}}],"references":[{"type":"FIX","url":"https://github.com/beego/beego/pull/4459"},{"type":"FIX","url":"https://github.com/beego/beego/commit/d5df5e470d0a8ed291930ae802fd7e6b95226519"}],"database_specific":{"url":"https://pkg.go.dev/vuln/GO-2022-0572"}}vuln-1.0.1/internal/client/testdata/vulndb-v1/ID/GO-2022-0572.json.gz000066400000000000000000000027231446745451500243320ustar00rootroot00000000000000Ksں 1 y#@tu*A[~ә.⮂~GSA1!)ghG ѧǮ~A1'tMH|?z]d AoD"*]t}?Άs3GKh]?ݚϻ>-F]Buǃ84hFCyŲC eBGpqI'<)lאa+-QzO3Δk\!PƎ(o ?1!P𸇥oz7 ]*8;H`I @=1ӧ۹25&:H}L k%hpqX`\!x:|$\36T*22 s`,_r9CHfO8 ʨvk.` R!eZ5{*RB"x$r.0+XSĉĔzٿ/╺:Ū9廞!5}ɟ boªRBp 2 (v1ˑV|&`M<WFd qaiT W`tB9RTT'gh&mә14 sȶK, 6-/SЭT V&__$` XPAǓ/AZDhB9ZDGQtpv>qLU zya59Kd&XPjq_Kw]NGWW~Oz67:vuln-1.0.1/internal/client/testdata/vulndb-v1/index/000077500000000000000000000000001446745451500223445ustar00rootroot00000000000000vuln-1.0.1/internal/client/testdata/vulndb-v1/index/db.json000066400000000000000000000000431446745451500236210ustar00rootroot00000000000000{"modified":"2023-04-03T15:57:51Z"}vuln-1.0.1/internal/client/testdata/vulndb-v1/index/db.json.gz000066400000000000000000000000731446745451500242430ustar00rootroot00000000000000VOLLMQR22025050142525RuB#vuln-1.0.1/internal/client/testdata/vulndb-v1/index/modules.json000066400000000000000000000027441446745451500247160ustar00rootroot00000000000000[{"path":"github.com/astaxie/beego","vulns":[{"id":"GO-2022-0463","modified":"2023-04-03T15:57:51Z"},{"id":"GO-2022-0569","modified":"2023-04-03T15:57:51Z"},{"id":"GO-2022-0572","modified":"2023-04-03T15:57:51Z"}]},{"path":"github.com/beego/beego","vulns":[{"id":"GO-2022-0463","modified":"2023-04-03T15:57:51Z","fixed":"1.12.9"},{"id":"GO-2022-0569","modified":"2023-04-03T15:57:51Z","fixed":"1.12.11"},{"id":"GO-2022-0572","modified":"2023-04-03T15:57:51Z"}]},{"path":"github.com/beego/beego/v2","vulns":[{"id":"GO-2022-0463","modified":"2023-04-03T15:57:51Z","fixed":"2.0.3"},{"id":"GO-2022-0569","modified":"2023-04-03T15:57:51Z","fixed":"2.0.4"},{"id":"GO-2022-0572","modified":"2023-04-03T15:57:51Z","fixed":"2.0.3"}]},{"path":"golang.org/x/crypto","vulns":[{"id":"GO-2022-0229","modified":"2023-04-03T15:57:51Z","fixed":"0.0.0-20200124225646-8b5121be2f68"}]},{"path":"stdlib","vulns":[{"id":"GO-2021-0159","modified":"2023-04-03T15:57:51Z","fixed":"1.4.3"},{"id":"GO-2021-0240","modified":"2023-04-03T15:57:51Z","fixed":"1.16.5"},{"id":"GO-2021-0264","modified":"2023-04-03T15:57:51Z","fixed":"1.17.3"},{"id":"GO-2022-0229","modified":"2023-04-03T15:57:51Z","fixed":"1.13.7"},{"id":"GO-2022-0273","modified":"2023-04-03T15:57:51Z","fixed":"1.17.1"}]},{"path":"toolchain","vulns":[{"id":"GO-2021-0068","modified":"2023-04-03T15:57:51Z","fixed":"1.15.7"},{"id":"GO-2022-0475","modified":"2023-04-03T15:57:51Z","fixed":"1.15.5"},{"id":"GO-2022-0476","modified":"2023-04-03T15:57:51Z","fixed":"1.15.5"}]}]vuln-1.0.1/internal/client/testdata/vulndb-v1/index/modules.json.gz000066400000000000000000000004621446745451500253300ustar00rootroot000000000000000eؙA^jE!X A $Q2nS,Ϋndp95`d va7^Y ¸'ܳלq9ǿ-da3S}<$ђ/ /<9KjGk-.I d=^̔kJ/z @#Dsxy{ahU߳MAʝ$Nz+2( umN٪Ŧ8YjWACR ?4`lU"F 0. Position *Position `json:"position,omitempty"` } // Position represents arbitrary source position. type Position struct { Filename string `json:"filename,omitempty"` // filename, if any Offset int `json:"offset"` // byte offset, starting at 0 Line int `json:"line"` // line number, starting at 1 Column int `json:"column"` // column number, starting at 1 (byte count) } // ScanLevel represents the detail level at which a scan occurred. // This can be necessary to correctly interpret the findings, for instance if // a scan is at symbol level and a finding does not have a symbol it means the // vulnerability was imported but not called. If the scan however was at // "package" level, that determination cannot be made. type ScanLevel string const ( scanLevelModule = "module" scanLevelPackage = "package" scanLevelSymbol = "symbol" ) // WantSymbols can be used to check whether the scan level is one that is able // to generate symbols called findings. func (l ScanLevel) WantSymbols() bool { return l == scanLevelSymbol } vuln-1.0.1/internal/govulncheck/govulncheck_test.go000066400000000000000000000006011446745451500225330ustar00rootroot00000000000000// Copyright 2022 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 govulncheck_test import ( "testing" "golang.org/x/vuln/internal/test" ) func TestImports(t *testing.T) { test.VerifyImports(t, "golang.org/x/vuln/internal/osv", // allowed to pull in the osv json entries ) } vuln-1.0.1/internal/govulncheck/handler.go000066400000000000000000000025551446745451500206130ustar00rootroot00000000000000// Copyright 2023 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 govulncheck import ( "encoding/json" "io" "golang.org/x/vuln/internal/osv" ) // Handler handles messages to be presented in a vulnerability scan output // stream. type Handler interface { // Config communicates introductory message to the user. Config(config *Config) error // Progress is called to display a progress message. Progress(progress *Progress) error // OSV is invoked for each osv Entry in the stream. OSV(entry *osv.Entry) error // Finding is called for each vulnerability finding in the stream. Finding(finding *Finding) error } // HandleJSON reads the json from the supplied stream and hands the decoded // output to the handler. func HandleJSON(from io.Reader, to Handler) error { dec := json.NewDecoder(from) for dec.More() { msg := Message{} // decode the next message in the stream if err := dec.Decode(&msg); err != nil { return err } // dispatch the message var err error if msg.Config != nil { err = to.Config(msg.Config) } if msg.Progress != nil { err = to.Progress(msg.Progress) } if msg.OSV != nil { err = to.OSV(msg.OSV) } if msg.Finding != nil { err = to.Finding(msg.Finding) } if err != nil { return err } } return nil } vuln-1.0.1/internal/govulncheck/jsonhandler.go000066400000000000000000000022351446745451500215000ustar00rootroot00000000000000// Copyright 2022 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 govulncheck import ( "encoding/json" "io" "golang.org/x/vuln/internal/osv" ) type jsonHandler struct { enc *json.Encoder } // NewJSONHandler returns a handler that writes govulncheck output as json. func NewJSONHandler(w io.Writer) Handler { enc := json.NewEncoder(w) enc.SetIndent("", " ") return &jsonHandler{enc: enc} } // Config writes config block in JSON to the underlying writer. func (h *jsonHandler) Config(config *Config) error { return h.enc.Encode(Message{Config: config}) } // Progress writes a progress message in JSON to the underlying writer. func (h *jsonHandler) Progress(progress *Progress) error { return h.enc.Encode(Message{Progress: progress}) } // OSV writes an osv entry in JSON to the underlying writer. func (h *jsonHandler) OSV(entry *osv.Entry) error { return h.enc.Encode(Message{OSV: entry}) } // Finding writes a finding in JSON to the underlying writer. func (h *jsonHandler) Finding(finding *Finding) error { return h.enc.Encode(Message{Finding: finding}) } vuln-1.0.1/internal/internal.go000066400000000000000000000017511446745451500164770ustar00rootroot00000000000000// Copyright 2021 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 internal contains functionality for x/vuln. package internal // IDDirectory is the name of the directory that contains entries // listed by their IDs. const IDDirectory = "ID" // Pseudo-module paths used for parts of the Go system. // These are technically not valid module paths, so we // mustn't pass them to module.EscapePath. // Keep in sync with vulndb/internal/database/generate.go. const ( // GoStdModulePath is the internal Go module path string used // when listing vulnerabilities in standard library. GoStdModulePath = "stdlib" // GoCmdModulePath is the internal Go module path string used // when listing vulnerabilities in the go command. GoCmdModulePath = "toolchain" // UnknownModulePath is a special module path for when we cannot work out // the module for a package. UnknownModulePath = "unknown" ) vuln-1.0.1/internal/osv/000077500000000000000000000000001446745451500151375ustar00rootroot00000000000000vuln-1.0.1/internal/osv/osv.go000066400000000000000000000226471446745451500163100ustar00rootroot00000000000000// Copyright 2023 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 osv implements the Go OSV vulnerability format // (https://go.dev/security/vuln/database#schema), which is a subset of // the OSV shared vulnerability format // (https://ossf.github.io/osv-schema), with database and // ecosystem-specific meanings and fields. // // As this package is intended for use with the Go vulnerability // database, only the subset of features which are used by that // database are implemented (for instance, only the SEMVER affected // range type is implemented). package osv import "time" // RangeType specifies the type of version range being recorded and // defines the interpretation of the RangeEvent object's Introduced // and Fixed fields. // // In this implementation, only the "SEMVER" type is supported. // // See https://ossf.github.io/osv-schema/#affectedrangestype-field. type RangeType string // RangeTypeSemver indicates a semantic version as defined by // SemVer 2.0.0, with no leading "v" prefix. const RangeTypeSemver RangeType = "SEMVER" // Ecosystem identifies the overall library ecosystem. // In this implementation, only the "Go" ecosystem is supported. type Ecosystem string // GoEcosystem indicates the Go ecosystem. const GoEcosystem Ecosystem = "Go" // Pseudo-module paths used to describe vulnerabilities // in the Go standard library and toolchain. const ( // GoStdModulePath is the pseudo-module path string used // to describe vulnerabilities in the Go standard library. GoStdModulePath = "stdlib" // GoCmdModulePath is the pseudo-module path string used // to describe vulnerabilities in the go command. GoCmdModulePath = "toolchain" ) // Module identifies the Go module containing the vulnerability. // Note that this field is called "package" in the OSV specification. // // See https://ossf.github.io/osv-schema/#affectedpackage-field. type Module struct { // The Go module path. Required. // For the Go standard library, this is "stdlib". // For the Go toolchain, this is "toolchain." Path string `json:"name"` // The ecosystem containing the module. Required. // This should always be "Go". Ecosystem Ecosystem `json:"ecosystem"` } // RangeEvent describes a single module version that either // introduces or fixes a vulnerability. // // Exactly one of Introduced and Fixed must be present. Other range // event types (e.g, "last_affected" and "limit") are not supported in // this implementation. // // See https://ossf.github.io/osv-schema/#affectedrangesevents-fields. type RangeEvent struct { // Introduced is a version that introduces the vulnerability. // A special value, "0", represents a version that sorts before // any other version, and should be used to indicate that the // vulnerability exists from the "beginning of time". Introduced string `json:"introduced,omitempty"` // Fixed is a version that fixes the vulnerability. Fixed string `json:"fixed,omitempty"` } // Range describes the affected versions of the vulnerable module. // // See https://ossf.github.io/osv-schema/#affectedranges-field. type Range struct { // Type is the version type that should be used to interpret the // versions in Events. Required. // In this implementation, only the "SEMVER" type is supported. Type RangeType `json:"type"` // Events is a list of versions representing the ranges in which // the module is vulnerable. Required. // The events should be sorted, and MUST represent non-overlapping // ranges. // There must be at least one RangeEvent containing a value for // Introduced. // See https://ossf.github.io/osv-schema/#examples for examples. Events []RangeEvent `json:"events"` } // Reference type is a reference (link) type. type ReferenceType string const ( // ReferenceTypeAdvisory is a published security advisory for // the vulnerability. ReferenceTypeAdvisory = ReferenceType("ADVISORY") // ReferenceTypeArticle is an article or blog post describing the vulnerability. ReferenceTypeArticle = ReferenceType("ARTICLE") // ReferenceTypeReport is a report, typically on a bug or issue tracker, of // the vulnerability. ReferenceTypeReport = ReferenceType("REPORT") // ReferenceTypeFix is a source code browser link to the fix (e.g., a GitHub commit). ReferenceTypeFix = ReferenceType("FIX") // ReferenceTypePackage is a home web page for the package. ReferenceTypePackage = ReferenceType("PACKAGE") // ReferenceTypeEvidence is a demonstration of the validity of a vulnerability claim. ReferenceTypeEvidence = ReferenceType("EVIDENCE") // ReferenceTypeWeb is a web page of some unspecified kind. ReferenceTypeWeb = ReferenceType("WEB") ) // Reference is a reference URL containing additional information, // advisories, issue tracker entries, etc., about the vulnerability. // // See https://ossf.github.io/osv-schema/#references-field. type Reference struct { // The type of reference. Required. Type ReferenceType `json:"type"` // The fully-qualified URL of the reference. Required. URL string `json:"url"` } // Affected gives details about a module affected by the vulnerability. // // See https://ossf.github.io/osv-schema/#affected-fields. type Affected struct { // The affected Go module. Required. // Note that this field is called "package" in the OSV specification. Module Module `json:"package"` // The module version ranges affected by the vulnerability. Ranges []Range `json:"ranges,omitempty"` // Details on the affected packages and symbols within the module. EcosystemSpecific EcosystemSpecific `json:"ecosystem_specific"` } // Package contains additional information about an affected package. // This is an ecosystem-specific field for the Go ecosystem. type Package struct { // Path is the package import path. Required. Path string `json:"path,omitempty"` // GOOS is the execution operating system where the symbols appear, if // known. GOOS []string `json:"goos,omitempty"` // GOARCH specifies the execution architecture where the symbols appear, if // known. GOARCH []string `json:"goarch,omitempty"` // Symbols is a list of function and method names affected by // this vulnerability. Methods are listed as .. // // If included, only programs which use these symbols will be marked as // vulnerable by `govulncheck`. If omitted, any program which imports this // package will be marked vulnerable. Symbols []string `json:"symbols,omitempty"` } // EcosystemSpecific contains additional information about the vulnerable // module for the Go ecosystem. // // See https://go.dev/security/vuln/database#schema. type EcosystemSpecific struct { // Packages is the list of affected packages within the module. Packages []Package `json:"imports,omitempty"` } // Entry represents a vulnerability in the Go OSV format, documented // in https://go.dev/security/vuln/database#schema. // It is a subset of the OSV schema (https://ossf.github.io/osv-schema). // Only fields that are published in the Go Vulnerability Database // are supported. type Entry struct { // SchemaVersion is the OSV schema version used to encode this // vulnerability. SchemaVersion string `json:"schema_version,omitempty"` // ID is a unique identifier for the vulnerability. Required. // The Go vulnerability database issues IDs of the form // GO--. ID string `json:"id"` // Modified is the time the entry was last modified. Required. Modified time.Time `json:"modified,omitempty"` // Published is the time the entry should be considered to have // been published. Published time.Time `json:"published,omitempty"` // Withdrawn is the time the entry should be considered to have // been withdrawn. If the field is missing, then the entry has // not been withdrawn. Withdrawn *time.Time `json:"withdrawn,omitempty"` // Aliases is a list of IDs for the same vulnerability in other // databases. Aliases []string `json:"aliases,omitempty"` // Summary gives a one-line, English textual summary of the vulnerability. // It is recommended that this field be kept short, on the order of no more // than 120 characters. Summary string `json:"summary,omitempty"` // Details contains additional English textual details about the vulnerability. Details string `json:"details"` // Affected contains information on the modules and versions // affected by the vulnerability. Affected []Affected `json:"affected"` // References contains links to more information about the // vulnerability. References []Reference `json:"references,omitempty"` // Credits contains credits to entities that helped find or fix the // vulnerability. Credits []Credit `json:"credits,omitempty"` // DatabaseSpecific contains additional information about the // vulnerability, specific to the Go vulnerability database. DatabaseSpecific *DatabaseSpecific `json:"database_specific,omitempty"` } // Credit represents a credit for the discovery, confirmation, patch, or // other event in the life cycle of a vulnerability. // // See https://ossf.github.io/osv-schema/#credits-fields. type Credit struct { // Name is the name, label, or other identifier of the individual or // entity being credited. Required. Name string `json:"name"` } // DatabaseSpecific contains additional information about the // vulnerability, specific to the Go vulnerability database. // // See https://go.dev/security/vuln/database#schema. type DatabaseSpecific struct { // The URL of the Go advisory for this vulnerability, of the form // "https://pkg.go.dev/GO-YYYY-XXXX". URL string `json:"url,omitempty"` } vuln-1.0.1/internal/osv/osv_test.go000066400000000000000000000005101446745451500173300ustar00rootroot00000000000000// Copyright 2022 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 osv_test import ( "testing" "golang.org/x/vuln/internal/test" ) func TestImports(t *testing.T) { test.VerifyImports(t) // no non stdlib imports allowed } vuln-1.0.1/internal/scan/000077500000000000000000000000001446745451500152545ustar00rootroot00000000000000vuln-1.0.1/internal/scan/binary.go000066400000000000000000000053221446745451500170710ustar00rootroot00000000000000// Copyright 2022 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. //go:build go1.18 // +build go1.18 package scan import ( "context" "fmt" "os" "strings" "unicode" "golang.org/x/vuln/internal/client" "golang.org/x/vuln/internal/govulncheck" "golang.org/x/vuln/internal/vulncheck" ) // runBinary detects presence of vulnerable symbols in an executable. func runBinary(ctx context.Context, handler govulncheck.Handler, cfg *config, client *client.Client) error { var exe *os.File exe, err := os.Open(cfg.patterns[0]) if err != nil { return err } defer exe.Close() p := &govulncheck.Progress{Message: binaryProgressMessage} if err := handler.Progress(p); err != nil { return err } vr, err := vulncheck.Binary(ctx, exe, &cfg.Config, client) if err != nil { return fmt.Errorf("govulncheck: %v", err) } callstacks := binaryCallstacks(vr) return emitResult(handler, vr, callstacks) } func binaryCallstacks(vr *vulncheck.Result) map[*vulncheck.Vuln]vulncheck.CallStack { callstacks := map[*vulncheck.Vuln]vulncheck.CallStack{} for _, vv := range uniqueVulns(vr.Vulns) { f := &vulncheck.FuncNode{Package: vv.ImportSink, Name: vv.Symbol} parts := strings.Split(vv.Symbol, ".") if len(parts) != 1 { f.RecvType = parts[0] f.Name = parts[1] } callstacks[vv] = vulncheck.CallStack{vulncheck.StackEntry{Function: f}} } return callstacks } // uniqueVulns does for binary mode what uniqueCallStack does for source mode. // It tries not to report redundant symbols. Since there are no call stacks in // binary mode, the following approximate approach is used. Do not report unexported // symbols for a triple if there are some exported symbols. // Otherwise, report all unexported symbols to avoid not reporting anything. func uniqueVulns(vulns []*vulncheck.Vuln) []*vulncheck.Vuln { type key struct { id string pkg string mod string } hasExported := make(map[key]bool) for _, v := range vulns { if isExported(v.Symbol) { k := key{id: v.OSV.ID, pkg: v.ImportSink.PkgPath, mod: v.ImportSink.Module.Path} hasExported[k] = true } } var uniques []*vulncheck.Vuln for _, v := range vulns { k := key{id: v.OSV.ID, pkg: v.ImportSink.PkgPath, mod: v.ImportSink.Module.Path} if isExported(v.Symbol) || !hasExported[k] { uniques = append(uniques, v) } } return uniques } // isExported checks if the symbol is exported. Assumes that the // symbol is of the form "identifier" or "identifier1.identifier2". func isExported(symbol string) bool { parts := strings.Split(symbol, ".") if len(parts) == 1 { return unicode.IsUpper(rune(symbol[0])) } return unicode.IsUpper(rune(parts[1][0])) } vuln-1.0.1/internal/scan/binary_test.go000066400000000000000000000011051446745451500201230ustar00rootroot00000000000000// Copyright 2023 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 scan import ( "testing" ) func TestIsExported(t *testing.T) { for _, tc := range []struct { symbol string want bool }{ {"foo", false}, {"Foo", true}, {"x.foo", false}, {"X.foo", false}, {"x.Foo", true}, {"X.Foo", true}, } { tc := tc t.Run(tc.symbol, func(t *testing.T) { if got := isExported(tc.symbol); tc.want != got { t.Errorf("want %t; got %t", tc.want, got) } }) } } vuln-1.0.1/internal/scan/color.go000066400000000000000000000046701446745451500167300ustar00rootroot00000000000000// Copyright 2022 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 scan const ( // These are all the constants for the terminal escape strings colorEscape = "\033[" colorEnd = "m" colorReset = colorEscape + "0" + colorEnd colorBold = colorEscape + "1" + colorEnd colorFaint = colorEscape + "2" + colorEnd colorUnderline = colorEscape + "4" + colorEnd colorBlink = colorEscape + "5" + colorEnd fgBlack = colorEscape + "30" + colorEnd fgRed = colorEscape + "31" + colorEnd fgGreen = colorEscape + "32" + colorEnd fgYellow = colorEscape + "33" + colorEnd fgBlue = colorEscape + "34" + colorEnd fgMagenta = colorEscape + "35" + colorEnd fgCyan = colorEscape + "36" + colorEnd fgWhite = colorEscape + "37" + colorEnd bgBlack = colorEscape + "40" + colorEnd bgRed = colorEscape + "41" + colorEnd bgGreen = colorEscape + "42" + colorEnd bgYellow = colorEscape + "43" + colorEnd bgBlue = colorEscape + "44" + colorEnd bgMagenta = colorEscape + "45" + colorEnd bgCyan = colorEscape + "46" + colorEnd bgWhite = colorEscape + "47" + colorEnd fgBlackHi = colorEscape + "90" + colorEnd fgRedHi = colorEscape + "91" + colorEnd fgGreenHi = colorEscape + "92" + colorEnd fgYellowHi = colorEscape + "93" + colorEnd fgBlueHi = colorEscape + "94" + colorEnd fgMagentaHi = colorEscape + "95" + colorEnd fgCyanHi = colorEscape + "96" + colorEnd fgWhiteHi = colorEscape + "97" + colorEnd bgBlackHi = colorEscape + "100" + colorEnd bgRedHi = colorEscape + "101" + colorEnd bgGreenHi = colorEscape + "102" + colorEnd bgYellowHi = colorEscape + "103" + colorEnd bgBlueHi = colorEscape + "104" + colorEnd bgMagentaHi = colorEscape + "105" + colorEnd bgCyanHi = colorEscape + "106" + colorEnd bgWhiteHi = colorEscape + "107" + colorEnd ) const ( _ = colorReset _ = colorBold _ = colorFaint _ = colorUnderline _ = colorBlink _ = fgBlack _ = fgRed _ = fgGreen _ = fgYellow _ = fgBlue _ = fgMagenta _ = fgCyan _ = fgWhite _ = fgBlackHi _ = fgRedHi _ = fgGreenHi _ = fgYellowHi _ = fgBlueHi _ = fgMagentaHi _ = fgCyanHi _ = fgWhiteHi _ = bgBlack _ = bgRed _ = bgGreen _ = bgYellow _ = bgBlue _ = bgMagenta _ = bgCyan _ = bgWhite _ = bgBlackHi _ = bgRedHi _ = bgGreenHi _ = bgYellowHi _ = bgBlueHi _ = bgMagentaHi _ = bgCyanHi _ = bgWhiteHi ) vuln-1.0.1/internal/scan/errors.go000066400000000000000000000047211446745451500171230ustar00rootroot00000000000000// Copyright 2022 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 scan import ( "errors" "strings" ) //lint:file-ignore ST1005 Ignore staticcheck message about error formatting var ( // ErrVulnerabilitiesFound indicates that vulnerabilities were detected // when running govulncheck. This returns exit status 3 when running // without the -json flag. errVulnerabilitiesFound = &exitCodeError{message: "vulnerabilities found", code: 3} // errHelp indicates that usage help was requested. errHelp = &exitCodeError{message: "help requested", code: 0} // errUsage indicates that there was a usage error on the command line. // // In this case, we assume that the user does not know how to run // govulncheck, and print the usage message with exit status 2. errUsage = &exitCodeError{message: "invalid usage", code: 2} // errGoVersionMismatch is used to indicate that there is a mismatch between // the Go version used to build govulncheck and the one currently on PATH. errGoVersionMismatch = errors.New(`Loading packages failed, possibly due to a mismatch between the Go version used to build govulncheck and the Go version on PATH. Consider rebuilding govulncheck with the current Go version.`) // errNoGoMod indicates that a go.mod file was not found in this module. errNoGoMod = errors.New(`no go.mod file govulncheck only works with Go modules. Try navigating to your module directory. Otherwise, run go mod init to make your project a module. See https://go.dev/doc/modules/managing-dependencies for more information.`) // errNoBinaryFlag indicates that govulncheck was run on a file, without // the -mode=binary flag. errNoBinaryFlag = errors.New(`By default, govulncheck runs source analysis on Go modules. Did you mean to run govulncheck with -mode=binary? For details, run govulncheck -h.`) ) type exitCodeError struct { message string code int } func (e *exitCodeError) Error() string { return e.message } func (e *exitCodeError) ExitCode() int { return e.code } // isGoVersionMismatchError checks if err is due to mismatch between // the Go version used to build govulncheck and the one currently // on PATH. func isGoVersionMismatchError(err error) bool { msg := err.Error() // See golang.org/x/tools/go/packages/packages.go. return strings.Contains(msg, "This application uses version go") && strings.Contains(msg, "It may fail to process source files") } vuln-1.0.1/internal/scan/filepath.go000066400000000000000000000014101446745451500173730ustar00rootroot00000000000000// Copyright 2022 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 scan import ( "path/filepath" "strings" ) // AbsRelShorter takes path and returns its path relative // to the current directory, if shorter. Returns path // when path is an empty string or upon any error. func AbsRelShorter(path string) string { if path == "" { return "" } c, err := filepath.Abs(".") if err != nil { return path } r, err := filepath.Rel(c, path) if err != nil { return path } rSegments := strings.Split(r, string(filepath.Separator)) pathSegments := strings.Split(path, string(filepath.Separator)) if len(rSegments) < len(pathSegments) { return r } return path } vuln-1.0.1/internal/scan/filepath_test.go000066400000000000000000000010671446745451500204420ustar00rootroot00000000000000// Copyright 2022 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 scan import ( "path/filepath" "testing" ) func TestAbsRelShorter(t *testing.T) { thisFileAbs, _ := filepath.Abs("filepath_test.go") for _, test := range []struct { l string want string }{ {"filepath_test.go", "filepath_test.go"}, {thisFileAbs, "filepath_test.go"}, } { if got := AbsRelShorter(test.l); got != test.want { t.Errorf("want %s; got %s", test.want, got) } } } vuln-1.0.1/internal/scan/flags.go000066400000000000000000000116471446745451500167100ustar00rootroot00000000000000// Copyright 2022 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 scan import ( "errors" "flag" "fmt" "io" "os" "strings" "golang.org/x/tools/go/buildutil" "golang.org/x/vuln/internal/govulncheck" ) type config struct { govulncheck.Config patterns []string mode string db string json bool dir string tags []string test bool show []string env []string } const ( modeBinary = "binary" modeSource = "source" modeConvert = "convert" // only intended for use by gopls modeQuery = "query" // only intended for use by gopls ) func parseFlags(cfg *config, stderr io.Writer, args []string) error { var tagsFlag buildutil.TagsFlag var showFlag showFlag var version bool flags := flag.NewFlagSet("", flag.ContinueOnError) flags.SetOutput(stderr) flags.BoolVar(&cfg.json, "json", false, "output JSON") flags.BoolVar(&cfg.test, "test", false, "analyze test files (only valid for source mode)") flags.StringVar(&cfg.dir, "C", "", "change to `dir` before running govulncheck") flags.StringVar(&cfg.db, "db", "https://vuln.go.dev", "vulnerability database `url`") flags.StringVar(&cfg.mode, "mode", modeSource, "supports source or binary") flags.Var(&tagsFlag, "tags", "comma-separated `list` of build tags") flags.Var(&showFlag, "show", "enable display of additional information specified by the comma separated `list`\nThe only supported value is 'traces'") flags.BoolVar(&version, "version", false, "print the version information") scanLevel := flags.String("scan", "symbol", "set the scanning level desired, one of module, package or symbol") flags.Usage = func() { fmt.Fprint(flags.Output(), `Govulncheck reports known vulnerabilities in dependencies. Usage: govulncheck [flags] [patterns] govulncheck -mode=binary [flags] [binary] `) flags.PrintDefaults() fmt.Fprintf(flags.Output(), "\n%s\n", detailsMessage) } if err := flags.Parse(args); err != nil { if err == flag.ErrHelp { return errHelp } return err } cfg.patterns = flags.Args() cfg.tags = tagsFlag cfg.show = showFlag if version { cfg.show = append(cfg.show, "version") } cfg.ScanLevel = govulncheck.ScanLevel(*scanLevel) if err := validateConfig(cfg); err != nil { fmt.Fprintln(flags.Output(), err) return errUsage } return nil } var supportedModes = map[string]bool{ modeSource: true, modeBinary: true, modeConvert: true, modeQuery: true, } func validateConfig(cfg *config) error { if _, ok := supportedModes[cfg.mode]; !ok { return fmt.Errorf("%q is not a valid mode", cfg.mode) } switch cfg.mode { case modeSource: if len(cfg.patterns) == 1 && isFile(cfg.patterns[0]) { return fmt.Errorf("%q is a file.\n\n%v", cfg.patterns[0], errNoBinaryFlag) } case modeBinary: if cfg.test { return fmt.Errorf("the -test flag is not supported in binary mode") } if len(cfg.tags) > 0 { return fmt.Errorf("the -tags flag is not supported in binary mode") } if len(cfg.patterns) != 1 { return fmt.Errorf("only 1 binary can be analyzed at a time") } if !isFile(cfg.patterns[0]) { return fmt.Errorf("%q is not a file", cfg.patterns[0]) } case modeConvert: if len(cfg.patterns) != 0 { return fmt.Errorf("patterns are not accepted in convert mode") } if cfg.dir != "" { return fmt.Errorf("the -C flag is not supported in convert mode") } if cfg.test { return fmt.Errorf("the -test flag is not supported in convert mode") } if len(cfg.tags) > 0 { return fmt.Errorf("the -tags flag is not supported in convert mode") } case modeQuery: if cfg.test { return fmt.Errorf("the -test flag is not supported in query mode") } if len(cfg.tags) > 0 { return fmt.Errorf("the -tags flag is not supported in query mode") } if !cfg.json { return fmt.Errorf("the -json flag must be set in query mode") } for _, pattern := range cfg.patterns { // Parse the input here so that we can catch errors before // outputting the Config. if _, _, err := parseModuleQuery(pattern); err != nil { return err } } } if cfg.json && len(cfg.show) > 0 { return fmt.Errorf("the -show flag is not supported for JSON output") } return nil } func isFile(path string) bool { s, err := os.Stat(path) if err != nil { return false } return !s.IsDir() } // fileExists checks if file path exists. Returns true // if the file exists or it cannot prove that it does // not exist. Otherwise, returns false. func fileExists(path string) bool { if _, err := os.Stat(path); err == nil { return true } else if errors.Is(err, os.ErrNotExist) { return false } // Conservatively return true if os.Stat fails // for some other reason. return true } type showFlag []string func (v *showFlag) Set(s string) error { *v = append(*v, strings.Split(s, ",")...) return nil } func (f *showFlag) Get() interface{} { return *f } func (f *showFlag) String() string { return "" } vuln-1.0.1/internal/scan/print_test.go000066400000000000000000000043011446745451500177740ustar00rootroot00000000000000// Copyright 2022 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 scan_test import ( "bytes" "flag" "io/fs" "os" "path/filepath" "strings" "testing" "github.com/google/go-cmp/cmp" "golang.org/x/vuln/internal/govulncheck" "golang.org/x/vuln/internal/scan" ) var update = flag.Bool("update", false, "update test files with results") func TestPrinting(t *testing.T) { testdata := os.DirFS("testdata") inputs, err := fs.Glob(testdata, "*.json") if err != nil { t.Fatal(err) } for _, input := range inputs { name := strings.TrimSuffix(input, ".json") rawJSON, _ := fs.ReadFile(testdata, input) textfiles, err := fs.Glob(testdata, name+"*.txt") if err != nil { t.Fatal(err) } for _, textfile := range textfiles { textname := strings.TrimSuffix(textfile, ".txt") t.Run(textname, func(t *testing.T) { wantText, _ := fs.ReadFile(testdata, textfile) got := &bytes.Buffer{} handler := scan.NewTextHandler(got) handler.Show(strings.Split(textname, "_")[1:]) testRunHandler(t, rawJSON, handler) if diff := cmp.Diff(string(wantText), got.String()); diff != "" { if *update { // write the output back to the file os.WriteFile(filepath.Join("testdata", textfile), got.Bytes(), 0644) return } t.Errorf("Readable mismatch (-want, +got):\n%s", diff) } }) } t.Run(name+"_json", func(t *testing.T) { // this effectively tests that we can round trip the json got := &strings.Builder{} testRunHandler(t, rawJSON, govulncheck.NewJSONHandler(got)) if diff := cmp.Diff(strings.TrimSpace(string(rawJSON)), strings.TrimSpace(got.String())); diff != "" { t.Errorf("JSON mismatch (-want, +got):\n%s", diff) } }) } } func testRunHandler(t *testing.T, rawJSON []byte, handler govulncheck.Handler) { if err := govulncheck.HandleJSON(bytes.NewReader(rawJSON), handler); err != nil { t.Fatal(err) } err := scan.Flush(handler) switch e := err.(type) { case nil: case interface{ ExitCode() int }: if e.ExitCode() != 0 && e.ExitCode() != 3 { // not success or vulnerabilities found t.Fatal(err) } default: t.Fatal(err) } } vuln-1.0.1/internal/scan/query.go000066400000000000000000000035451446745451500167570ustar00rootroot00000000000000// Copyright 2023 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 scan import ( "context" "fmt" "regexp" "golang.org/x/vuln/internal/client" "golang.org/x/vuln/internal/govulncheck" isem "golang.org/x/vuln/internal/semver" ) // runQuery reports vulnerabilities that apply to the queries in the config. func runQuery(ctx context.Context, handler govulncheck.Handler, cfg *config, c *client.Client) error { reqs := make([]*client.ModuleRequest, len(cfg.patterns)) for i, query := range cfg.patterns { mod, ver, err := parseModuleQuery(query) if err != nil { return err } if err := handler.Progress(queryProgressMessage(mod, ver)); err != nil { return err } reqs[i] = &client.ModuleRequest{ Path: mod, Version: ver, } } resps, err := c.ByModules(ctx, reqs) if err != nil { return err } ids := make(map[string]bool) for _, resp := range resps { for _, entry := range resp.Entries { if _, ok := ids[entry.ID]; !ok { err := handler.OSV(entry) if err != nil { return err } ids[entry.ID] = true } } } return nil } func queryProgressMessage(module, version string) *govulncheck.Progress { return &govulncheck.Progress{ Message: fmt.Sprintf("Looking up vulnerabilities in %s at %s...", module, version), } } var modQueryRegex = regexp.MustCompile(`(.+)@(.+)`) func parseModuleQuery(pattern string) (_ string, _ string, err error) { matches := modQueryRegex.FindStringSubmatch(pattern) // matches should be [module@version, module, version] if len(matches) != 3 { return "", "", fmt.Errorf("invalid query %s: must be of the form module@version", pattern) } mod, ver := matches[1], matches[2] if !isem.Valid(ver) { return "", "", fmt.Errorf("version %s is not valid semver", ver) } return mod, ver, nil } vuln-1.0.1/internal/scan/query_test.go000066400000000000000000000100031446745451500200010ustar00rootroot00000000000000// Copyright 2023 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 scan import ( "context" "strings" "testing" "github.com/google/go-cmp/cmp" "golang.org/x/vuln/internal/client" "golang.org/x/vuln/internal/osv" "golang.org/x/vuln/internal/test" ) func TestRunQuery(t *testing.T) { e := &osv.Entry{ ID: "GO-1999-0001", Affected: []osv.Affected{{ Module: osv.Module{Path: "bad.com"}, Ranges: []osv.Range{{ Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0"}, {Fixed: "1.2.3"}}, }}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ Path: "bad.com", }, { Path: "bad.com/bad", }}, }, }, { Module: osv.Module{Path: "unfixable.com"}, Ranges: []osv.Range{{ Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0"}}, // no fix }}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ Path: "unfixable.com", }}, }, }}, } e2 := &osv.Entry{ ID: "GO-1999-0002", Affected: []osv.Affected{{ Module: osv.Module{Path: "bad.com"}, Ranges: []osv.Range{{ Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0"}, {Fixed: "1.2.0"}}, }}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ Path: "bad.com/pkg", }, }, }, }}, } stdlib := &osv.Entry{ ID: "GO-2000-0003", Affected: []osv.Affected{{ Module: osv.Module{Path: "stdlib"}, Ranges: []osv.Range{{ Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0"}, {Fixed: "1.19.4"}}, }}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ Path: "net/http", }}, }, }}, } c, err := client.NewInMemoryClient([]*osv.Entry{e, e2, stdlib}) if err != nil { t.Fatal(err) } ctx := context.Background() for _, tc := range []struct { query []string want []*osv.Entry }{ { query: []string{"stdlib@go1.18"}, want: []*osv.Entry{stdlib}, }, { query: []string{"stdlib@1.18"}, want: []*osv.Entry{stdlib}, }, { query: []string{"stdlib@v1.18.0"}, want: []*osv.Entry{stdlib}, }, { query: []string{"bad.com@1.2.3"}, want: nil, }, { query: []string{"bad.com@v1.1.0"}, want: []*osv.Entry{e, e2}, }, { query: []string{"unfixable.com@2.0.0"}, want: []*osv.Entry{e}, }, { // each entry should only show up once query: []string{"bad.com@1.1.0", "unfixable.com@2.0.0"}, want: []*osv.Entry{e, e2}, }, { query: []string{"stdlib@1.18", "unfixable.com@2.0.0"}, want: []*osv.Entry{stdlib, e}, }, } { t.Run(strings.Join(tc.query, ","), func(t *testing.T) { h := test.NewMockHandler() err := runQuery(ctx, h, &config{patterns: tc.query}, c) if err != nil { t.Fatal(err) } if diff := cmp.Diff(h.OSVMessages, tc.want); diff != "" { t.Errorf("runQuery: unexpected diff:\n%s", diff) } }) } } func TestParseModuleQuery(t *testing.T) { for _, tc := range []struct { pattern, wantMod, wantVer string wantErr string }{ { pattern: "stdlib@go1.18", wantMod: "stdlib", wantVer: "go1.18", }, { pattern: "golang.org/x/tools@v0.0.0-20140414041502-123456789012", wantMod: "golang.org/x/tools", wantVer: "v0.0.0-20140414041502-123456789012", }, { pattern: "golang.org/x/tools", wantErr: "invalid query", }, { pattern: "golang.org/x/tools@1.0.0.2", wantErr: "not valid semver", }, } { t.Run(tc.pattern, func(t *testing.T) { gotMod, gotVer, err := parseModuleQuery(tc.pattern) if tc.wantErr == "" { if err != nil { t.Fatal(err) } if gotMod != tc.wantMod || gotVer != tc.wantVer { t.Errorf("parseModuleQuery = (%s, %s), want (%s, %s)", gotMod, gotVer, tc.wantMod, tc.wantVer) } } else { if err == nil || !strings.Contains(err.Error(), tc.wantErr) { t.Errorf("parseModuleQuery = %v, want err containing %s", err, tc.wantErr) } } }) } } vuln-1.0.1/internal/scan/result_test.go000066400000000000000000000037151446745451500201660ustar00rootroot00000000000000// Copyright 2022 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 scan import ( "strings" "testing" "golang.org/x/vuln/internal/govulncheck" ) func TestFrame(t *testing.T) { for _, test := range []struct { name string frame *govulncheck.Frame short bool wantFunc string wantPos string }{ { name: "position and function", frame: &govulncheck.Frame{ Package: "golang.org/x/vuln/internal/vulncheck", Function: "Foo", Position: &govulncheck.Position{Filename: "some/path/file.go", Line: 12}, }, wantFunc: "golang.org/x/vuln/internal/vulncheck.Foo", wantPos: "some/path/file.go:12", }, { name: "receiver", frame: &govulncheck.Frame{ Package: "golang.org/x/vuln/internal/vulncheck", Receiver: "Bar", Function: "Foo", }, wantFunc: "golang.org/x/vuln/internal/vulncheck.Bar.Foo", }, { name: "function and receiver", frame: &govulncheck.Frame{Receiver: "*ServeMux", Function: "Handle"}, wantFunc: "ServeMux.Handle", }, { name: "package and function", frame: &govulncheck.Frame{Package: "net/http", Function: "Get"}, wantFunc: "net/http.Get", }, { name: "package, function and receiver", frame: &govulncheck.Frame{Package: "net/http", Receiver: "*ServeMux", Function: "Handle"}, wantFunc: "net/http.ServeMux.Handle", }, { name: "short", frame: &govulncheck.Frame{Package: "net/http", Function: "Get"}, short: true, wantFunc: "http.Get", }, } { t.Run(test.name, func(t *testing.T) { buf := &strings.Builder{} addSymbolName(buf, test.frame, test.short) got := buf.String() if got != test.wantFunc { t.Errorf("want %v func name; got %v", test.wantFunc, got) } if got := posToString(test.frame.Position); got != test.wantPos { t.Errorf("want %v call position; got %v", test.wantPos, got) } }) } } vuln-1.0.1/internal/scan/run.go000066400000000000000000000063641446745451500164200ustar00rootroot00000000000000// Copyright 2022 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 scan import ( "context" "fmt" "io" "os/exec" "path" "path/filepath" "runtime/debug" "strings" "time" "golang.org/x/vuln/internal/client" "golang.org/x/vuln/internal/govulncheck" ) // RunGovulncheck performs main govulncheck functionality and exits the // program upon success with an appropriate exit status. Otherwise, // returns an error. func RunGovulncheck(ctx context.Context, env []string, r io.Reader, stdout io.Writer, stderr io.Writer, args []string) error { cfg := &config{env: env} if err := parseFlags(cfg, stderr, args); err != nil { return err } client, err := client.NewClient(cfg.db, nil) if err != nil { return fmt.Errorf("creating client: %w", err) } prepareConfig(ctx, cfg, client) var handler govulncheck.Handler switch { case cfg.json: handler = govulncheck.NewJSONHandler(stdout) default: th := NewTextHandler(stdout) th.Show(cfg.show) handler = th } // Write the introductory message to the user. if err := handler.Config(&cfg.Config); err != nil { return err } switch cfg.mode { case modeSource: dir := filepath.FromSlash(cfg.dir) err = runSource(ctx, handler, cfg, client, dir) case modeBinary: err = runBinary(ctx, handler, cfg, client) case modeQuery: err = runQuery(ctx, handler, cfg, client) case modeConvert: err = govulncheck.HandleJSON(r, handler) } if err != nil { return err } if err := Flush(handler); err != nil { return err } return nil } func prepareConfig(ctx context.Context, cfg *config, client *client.Client) { cfg.ProtocolVersion = govulncheck.ProtocolVersion cfg.DB = cfg.db if cfg.mode == modeSource && cfg.GoVersion == "" { const goverPrefix = "GOVERSION=" for _, env := range cfg.env { if val := strings.TrimPrefix(env, goverPrefix); val != env { cfg.GoVersion = val } } if cfg.GoVersion == "" { if out, err := exec.Command("go", "env", "GOVERSION").Output(); err == nil { cfg.GoVersion = strings.TrimSpace(string(out)) } } } if bi, ok := debug.ReadBuildInfo(); ok { scannerVersion(cfg, bi) } if mod, err := client.LastModifiedTime(ctx); err == nil { cfg.DBLastModified = &mod } } // scannerVersion reconstructs the current version of // this binary used from the build info. func scannerVersion(cfg *config, bi *debug.BuildInfo) { if bi.Path != "" { cfg.ScannerName = path.Base(bi.Path) } if bi.Main.Version != "" && bi.Main.Version != "(devel)" { cfg.ScannerVersion = bi.Main.Version return } // TODO(https://go.dev/issue/29228): we need to manually construct the // version string when it is "(devel)" until #29228 is resolved. var revision, at string for _, s := range bi.Settings { if s.Key == "vcs.revision" { revision = s.Value } if s.Key == "vcs.time" { at = s.Value } } buf := strings.Builder{} buf.WriteString("v0.0.0") if revision != "" { buf.WriteString("-") buf.WriteString(revision[:12]) } if at != "" { // commit time is of the form 2023-01-25T19:57:54Z p, err := time.Parse(time.RFC3339, at) if err == nil { buf.WriteString("-") buf.WriteString(p.Format("20060102150405")) } } cfg.ScannerVersion = buf.String() } vuln-1.0.1/internal/scan/run_test.go000066400000000000000000000011471446745451500174510ustar00rootroot00000000000000// Copyright 2022 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 scan import ( "runtime/debug" "testing" ) func TestGovulncheckVersion(t *testing.T) { bi := &debug.BuildInfo{ Settings: []debug.BuildSetting{ {Key: "vcs.revision", Value: "1234567890001234"}, {Key: "vcs.time", Value: "2023-01-25T19:57:54Z"}, }, } want := "v0.0.0-123456789000-20230125195754" got := &config{} scannerVersion(got, bi) if got.ScannerVersion != want { t.Errorf("got %s; want %s", got.ScannerVersion, want) } } vuln-1.0.1/internal/scan/source.go000066400000000000000000000136061446745451500171110ustar00rootroot00000000000000// Copyright 2022 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 scan import ( "context" "fmt" "path/filepath" "golang.org/x/tools/go/packages" "golang.org/x/vuln/internal" "golang.org/x/vuln/internal/client" "golang.org/x/vuln/internal/govulncheck" "golang.org/x/vuln/internal/osv" "golang.org/x/vuln/internal/vulncheck" ) // runSource reports vulnerabilities that affect the analyzed packages. // // Vulnerabilities can be called (affecting the package, because a vulnerable // symbol is actually exercised) or just imported by the package // (likely having a non-affecting outcome). func runSource(ctx context.Context, handler govulncheck.Handler, cfg *config, client *client.Client, dir string) error { if len(cfg.patterns) == 0 { return nil } var pkgs []*packages.Package graph := vulncheck.NewPackageGraph(cfg.GoVersion) pkgConfig := &packages.Config{ Dir: dir, Tests: cfg.test, Env: cfg.env, } pkgs, err := graph.LoadPackages(pkgConfig, cfg.tags, cfg.patterns) if err != nil { // Try to provide a meaningful and actionable error message. if !fileExists(filepath.Join(dir, "go.mod")) { return fmt.Errorf("govulncheck: %v", errNoGoMod) } if isGoVersionMismatchError(err) { return fmt.Errorf("govulncheck: %v\n\n%v", errGoVersionMismatch, err) } return fmt.Errorf("govulncheck: loading packages: %w", err) } if err := handler.Progress(sourceProgressMessage(pkgs)); err != nil { return err } vr, err := vulncheck.Source(ctx, pkgs, &cfg.Config, client, graph) if err != nil { return err } callStacks := vulncheck.CallStacks(vr) return emitResult(handler, vr, callStacks) } func emitResult(handler govulncheck.Handler, vr *vulncheck.Result, callstacks map[*vulncheck.Vuln]vulncheck.CallStack) error { osvs := map[string]*osv.Entry{} // first deal with all the affected vulnerabilities emitted := map[string]bool{} seen := map[string]bool{} for _, vv := range vr.Vulns { osvs[vv.OSV.ID] = vv.OSV fixed := fixedVersion(vv.ImportSink.Module.Path, vv.OSV.Affected) stack := callstacks[vv] if stack == nil { continue } emitted[vv.OSV.ID] = true emitFinding(handler, osvs, seen, &govulncheck.Finding{ OSV: vv.OSV.ID, FixedVersion: fixed, Trace: tracefromEntries(stack), }) } for _, vv := range vr.Vulns { if emitted[vv.OSV.ID] { continue } stacks := callstacks[vv] if len(stacks) != 0 { continue } emitted[vv.OSV.ID] = true emitFinding(handler, osvs, seen, &govulncheck.Finding{ OSV: vv.OSV.ID, FixedVersion: fixedVersion(vv.ImportSink.Module.Path, vv.OSV.Affected), Trace: []*govulncheck.Frame{frameFromPackage(vv.ImportSink)}, }) } return nil } func emitFinding(handler govulncheck.Handler, osvs map[string]*osv.Entry, seen map[string]bool, finding *govulncheck.Finding) error { if !seen[finding.OSV] { seen[finding.OSV] = true if err := handler.OSV(osvs[finding.OSV]); err != nil { return err } } return handler.Finding(finding) } // tracefromEntries creates a sequence of // frames from vcs. Position of a Frame is the // call position of the corresponding stack entry. func tracefromEntries(vcs vulncheck.CallStack) []*govulncheck.Frame { var frames []*govulncheck.Frame for i := len(vcs) - 1; i >= 0; i-- { e := vcs[i] fr := frameFromPackage(e.Function.Package) fr.Function = e.Function.Name fr.Receiver = e.Function.Receiver() if e.Call == nil || e.Call.Pos == nil { fr.Position = nil } else { fr.Position = &govulncheck.Position{ Filename: e.Call.Pos.Filename, Offset: e.Call.Pos.Offset, Line: e.Call.Pos.Line, Column: e.Call.Pos.Column, } } frames = append(frames, fr) } return frames } func frameFromPackage(pkg *packages.Package) *govulncheck.Frame { fr := &govulncheck.Frame{} if pkg != nil { fr.Module = pkg.Module.Path fr.Version = pkg.Module.Version fr.Package = pkg.PkgPath } if pkg.Module.Replace != nil { fr.Module = pkg.Module.Replace.Path fr.Version = pkg.Module.Replace.Version } return fr } // sourceProgressMessage returns a string of the form // // "Scanning your code and P packages across M dependent modules for known vulnerabilities..." // // P is the number of strictly dependent packages of // topPkgs and Y is the number of their modules. func sourceProgressMessage(topPkgs []*packages.Package) *govulncheck.Progress { pkgs, mods := depPkgsAndMods(topPkgs) pkgsPhrase := fmt.Sprintf("%d package", pkgs) if pkgs != 1 { pkgsPhrase += "s" } modsPhrase := fmt.Sprintf("%d dependent module", mods) if mods != 1 { modsPhrase += "s" } msg := fmt.Sprintf("Scanning your code and %s across %s for known vulnerabilities...", pkgsPhrase, modsPhrase) return &govulncheck.Progress{Message: msg} } // depPkgsAndMods returns the number of packages that // topPkgs depend on and the number of their modules. func depPkgsAndMods(topPkgs []*packages.Package) (int, int) { tops := make(map[string]bool) depPkgs := make(map[string]bool) depMods := make(map[string]bool) for _, t := range topPkgs { tops[t.PkgPath] = true } var visit func(*packages.Package, bool) visit = func(p *packages.Package, top bool) { path := p.PkgPath if depPkgs[path] { return } if tops[path] && !top { // A top package that is a dependency // will not be in depPkgs, so we skip // reiterating on it here. return } // We don't count a top-level package as // a dependency even when they are used // as a dependent package. if !tops[path] { depPkgs[path] = true if p.Module != nil && p.Module.Path != internal.GoStdModulePath && // no module for stdlib p.Module.Path != internal.UnknownModulePath { // no module for unknown depMods[p.Module.Path] = true } } for _, d := range p.Imports { visit(d, false) } } for _, t := range topPkgs { visit(t, true) } return len(depPkgs), len(depMods) } vuln-1.0.1/internal/scan/source_test.go000066400000000000000000000032741446745451500201500ustar00rootroot00000000000000// Copyright 2022 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 scan import ( "strings" "testing" "golang.org/x/vuln/internal/govulncheck" ) func TestSummarizeCallStack(t *testing.T) { for _, test := range []struct { in, want string }{ {"ma.a.F", "a.F"}, {"m1.p1.F", "p1.F"}, {"mv.v.V", "v.V"}, { "m1.p1.F mv.v.V", "p1.F calls v.V", }, { "m1.p1.F m1.p2.G mv.v.V1 mv.v.v2", "p2.G calls v.V1, which calls v.v2", }, { "m1.p1.F m1.p2.G mv.v.V$1 mv.v.V1", "p2.G calls v.V, which calls v.V1", }, { "m1.p1.F m1.p2.G$1 mv.v.V1", "p2.G calls v.V1", }, { "m1.p1.F m1.p2.G$1 mv.v.V$1 mv.v.V1", "p2.G calls v.V, which calls v.V1", }, { "m1.p1.F w.x.Y m1.p2.G ma.a.H mb.b.I mc.c.J mv.v.V", "p2.G calls a.H, which eventually calls v.V", }, { "m1.p1.F w.x.Y m1.p2.G ma.a.H mb.b.I mc.c.J mv.v.V$1 mv.v.V1", "p2.G calls a.H, which eventually calls v.V1", }, { "m1.p1.F m1.p1.F$1 ma.a.H mb.b.I mv.v.V1", "p1.F calls a.H, which eventually calls v.V1", }, } { in := stringToFinding(test.in) got := compactTrace(in) if got != test.want { t.Errorf("%s:\ngot %s\nwant %s", test.in, got, test.want) } } } func stringToFinding(s string) *govulncheck.Finding { f := &govulncheck.Finding{} entries := strings.Fields(s) for i := len(entries) - 1; i >= 0; i-- { e := entries[i] firstDot := strings.Index(e, ".") lastDot := strings.LastIndex(e, ".") f.Trace = append(f.Trace, &govulncheck.Frame{ Module: e[:firstDot], Package: e[:firstDot] + "/" + e[firstDot+1:lastDot], Function: e[lastDot+1:], }) } return f } vuln-1.0.1/internal/scan/stdlib.go000066400000000000000000000041511446745451500170650ustar00rootroot00000000000000// Copyright 2022 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 scan import ( "fmt" "strings" "golang.org/x/mod/semver" ) // Support functions for standard library packages. // These are copied from the internal/stdlib package in the pkgsite repo. // semverToGoTag returns the Go standard library repository tag corresponding // to semver, a version string without the initial "v". // Go tags differ from standard semantic versions in a few ways, // such as beginning with "go" instead of "v". func semverToGoTag(v string) string { if strings.HasPrefix(v, "v0.0.0") { return "master" } // Special case: v1.0.0 => go1. if v == "v1.0.0" { return "go1" } if !semver.IsValid(v) { return fmt.Sprintf("", v) } goVersion := semver.Canonical(v) prerelease := semver.Prerelease(goVersion) versionWithoutPrerelease := strings.TrimSuffix(goVersion, prerelease) patch := strings.TrimPrefix(versionWithoutPrerelease, semver.MajorMinor(goVersion)+".") if patch == "0" { versionWithoutPrerelease = strings.TrimSuffix(versionWithoutPrerelease, ".0") } goVersion = fmt.Sprintf("go%s", strings.TrimPrefix(versionWithoutPrerelease, "v")) if prerelease != "" { // Go prereleases look like "beta1" instead of "beta.1". // "beta1" is bad for sorting (since beta10 comes before beta9), so // require the dot form. i := finalDigitsIndex(prerelease) if i >= 1 { if prerelease[i-1] != '.' { return fmt.Sprintf("", v) } // Remove the dot. prerelease = prerelease[:i-1] + prerelease[i:] } goVersion += strings.TrimPrefix(prerelease, "-") } return goVersion } // finalDigitsIndex returns the index of the first digit in the sequence of digits ending s. // If s doesn't end in digits, it returns -1. func finalDigitsIndex(s string) int { // Assume ASCII (since the semver package does anyway). var i int for i = len(s) - 1; i >= 0; i-- { if s[i] < '0' || s[i] > '9' { break } } if i == len(s)-1 { return -1 } return i + 1 } vuln-1.0.1/internal/scan/template.go000066400000000000000000000155601446745451500174250ustar00rootroot00000000000000// Copyright 2022 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 scan import ( "go/token" "io" "path" "sort" "strconv" "strings" "unicode" "unicode/utf8" "golang.org/x/vuln/internal" "golang.org/x/vuln/internal/govulncheck" "golang.org/x/vuln/internal/osv" ) type findingSummary struct { *govulncheck.Finding Compact string OSV *osv.Entry } type summaryCounters struct { VulnerabilitiesCalled int ModulesCalled int StdlibCalled bool } func fixupFindings(osvs []*osv.Entry, findings []*findingSummary) { for _, f := range findings { f.OSV = getOSV(osvs, f.Finding.OSV) } } func groupByVuln(findings []*findingSummary) [][]*findingSummary { return groupBy(findings, func(left, right *findingSummary) int { return -strings.Compare(left.OSV.ID, right.OSV.ID) }) } func groupByModule(findings []*findingSummary) [][]*findingSummary { return groupBy(findings, func(left, right *findingSummary) int { return strings.Compare(left.Trace[0].Module, right.Trace[0].Module) }) } func groupBy(findings []*findingSummary, compare func(left, right *findingSummary) int) [][]*findingSummary { switch len(findings) { case 0: return nil case 1: return [][]*findingSummary{findings} } sort.SliceStable(findings, func(i, j int) bool { return compare(findings[i], findings[j]) < 0 }) result := [][]*findingSummary{} first := 0 for i, next := range findings { if i == first { continue } if compare(findings[first], next) != 0 { result = append(result, findings[first:i]) first = i } } result = append(result, findings[first:]) return result } func counters(findings []*findingSummary) summaryCounters { vulns := map[string]struct{}{} modules := map[string]struct{}{} for _, f := range findings { if f.Trace[0].Function == "" { continue } id := f.OSV.ID vulns[id] = struct{}{} mod := f.Trace[0].Module modules[mod] = struct{}{} } result := summaryCounters{ VulnerabilitiesCalled: len(vulns), ModulesCalled: len(modules), } if _, found := modules[internal.GoStdModulePath]; found { result.StdlibCalled = true result.ModulesCalled-- } return result } func isCalled(findings []*findingSummary) bool { for _, f := range findings { if f.Trace[0].Function != "" { return true } } return false } func getOSV(osvs []*osv.Entry, id string) *osv.Entry { for _, entry := range osvs { if entry.ID == id { return entry } } return &osv.Entry{ ID: id, DatabaseSpecific: &osv.DatabaseSpecific{}, } } func newFindingSummary(f *govulncheck.Finding) *findingSummary { return &findingSummary{ Finding: f, Compact: compactTrace(f), } } // platforms returns a string describing the GOOS, GOARCH, // or GOOS/GOARCH pairs that the vuln affects for a particular // module mod. If it affects all of them, it returns the empty // string. // // When mod is an empty string, returns platform information for // all modules of e. func platforms(mod string, e *osv.Entry) []string { if e == nil { return nil } platforms := map[string]bool{} for _, a := range e.Affected { if mod != "" && a.Module.Path != mod { continue } for _, p := range a.EcosystemSpecific.Packages { for _, os := range p.GOOS { // In case there are no specific architectures, // just list the os entries. if len(p.GOARCH) == 0 { platforms[os] = true continue } // Otherwise, list all the os+arch combinations. for _, arch := range p.GOARCH { platforms[os+"/"+arch] = true } } // Cover the case where there are no specific // operating systems listed. if len(p.GOOS) == 0 { for _, arch := range p.GOARCH { platforms[arch] = true } } } } var keys []string for k := range platforms { keys = append(keys, k) } sort.Strings(keys) return keys } func posToString(p *govulncheck.Position) string { if p == nil || p.Line <= 0 { return "" } return token.Position{ Filename: AbsRelShorter(p.Filename), Offset: p.Offset, Line: p.Line, Column: p.Column, }.String() } func symbol(frame *govulncheck.Frame, short bool) string { buf := &strings.Builder{} addSymbolName(buf, frame, short) return buf.String() } // compactTrace returns a short description of the call stack. // It prefers to show you the edge from the top module to other code, along with // the vulnerable symbol. // Where the vulnerable symbol directly called by the users code, it will only // show those two points. // If the vulnerable symbol is in the users code, it will show the entry point // and the vulnerable symbol. func compactTrace(finding *govulncheck.Finding) string { if len(finding.Trace) < 1 { return "" } iTop := len(finding.Trace) - 1 topModule := finding.Trace[iTop].Module // search for the exit point of the top module for i, frame := range finding.Trace { if frame.Module == topModule { iTop = i break } } if iTop == 0 { // all in one module, reset to the end iTop = len(finding.Trace) - 1 } buf := &strings.Builder{} topPos := posToString(finding.Trace[iTop].Position) if topPos != "" { buf.WriteString(topPos) buf.WriteString(": ") } if iTop > 0 { addSymbolName(buf, finding.Trace[iTop], true) buf.WriteString(" calls ") } if iTop > 1 { addSymbolName(buf, finding.Trace[iTop-1], true) buf.WriteString(", which") if iTop > 2 { buf.WriteString(" eventually") } buf.WriteString(" calls ") } addSymbolName(buf, finding.Trace[0], true) return buf.String() } // notIdentifier reports whether ch is an invalid identifier character. func notIdentifier(ch rune) bool { return !('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || '0' <= ch && ch <= '9' || ch == '_' || ch >= utf8.RuneSelf && (unicode.IsLetter(ch) || unicode.IsDigit(ch))) } // importPathToAssumedName is taken from goimports, it works out the natural imported name // for a package. // This is used to get a shorter identifier in the compact stack trace func importPathToAssumedName(importPath string) string { base := path.Base(importPath) if strings.HasPrefix(base, "v") { if _, err := strconv.Atoi(base[1:]); err == nil { dir := path.Dir(importPath) if dir != "." { base = path.Base(dir) } } } base = strings.TrimPrefix(base, "go-") if i := strings.IndexFunc(base, notIdentifier); i >= 0 { base = base[:i] } return base } func addSymbolName(w io.Writer, frame *govulncheck.Frame, short bool) { if frame.Function == "" { return } if frame.Package != "" { pkg := frame.Package if short { pkg = importPathToAssumedName(frame.Package) } io.WriteString(w, pkg) io.WriteString(w, ".") } if frame.Receiver != "" { if frame.Receiver[0] == '*' { io.WriteString(w, frame.Receiver[1:]) } else { io.WriteString(w, frame.Receiver) } io.WriteString(w, ".") } funcname := strings.Split(frame.Function, "$")[0] io.WriteString(w, funcname) } vuln-1.0.1/internal/scan/testdata/000077500000000000000000000000001446745451500170655ustar00rootroot00000000000000vuln-1.0.1/internal/scan/testdata/binary.json000066400000000000000000000027451446745451500212540ustar00rootroot00000000000000{ "config": { "protocol_version": "v0.1.0", "scanner_name": "govulncheck" } } { "osv": { "id": "GO-0000-0001", "modified": "0001-01-01T00:00:00Z", "published": "0001-01-01T00:00:00Z", "details": "Third-party vulnerability", "affected": [ { "package": { "name": "golang.org/vmod", "ecosystem": "" }, "ecosystem_specific": { "imports": [ { "goos": [ "amd" ] } ] } } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-0000-0001" } } } { "finding": { "osv": "GO-0000-0001", "fixed_version": "v0.1.3", "trace": [ { "module": "golang.org/vmod", "version": "v0.0.1", "package": "golang.org/vmod", "function": "Vuln" } ] } } { "osv": { "id": "GO-0000-0002", "modified": "0001-01-01T00:00:00Z", "published": "0001-01-01T00:00:00Z", "details": "Stdlib vulnerability", "affected": [ { "package": { "name": "stdlib", "ecosystem": "" }, "ecosystem_specific": {} } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-0000-0002" } } } { "finding": { "osv": "GO-0000-0002", "trace": [ { "module": "stdlib", "version": "v0.0.1", "package": "net/http", "function": "Vuln2" } ] } }vuln-1.0.1/internal/scan/testdata/binary.txt000066400000000000000000000012031446745451500211060ustar00rootroot00000000000000Vulnerability #1: GO-0000-0002 Stdlib vulnerability More info: https://pkg.go.dev/vuln/GO-0000-0002 Standard library Found in: net/http@go0.0.1 Fixed in: N/A Example traces found: #1: http.Vuln2 Vulnerability #2: GO-0000-0001 Third-party vulnerability More info: https://pkg.go.dev/vuln/GO-0000-0001 Module: golang.org/vmod Found in: golang.org/vmod@v0.0.1 Fixed in: golang.org/vmod@v0.1.3 Platforms: amd Example traces found: #1: vmod.Vuln Your code is affected by 2 vulnerabilities from 1 module and the Go standard library. Share feedback at https://go.dev/s/govulncheck-feedback. vuln-1.0.1/internal/scan/testdata/multi-stacks.json000066400000000000000000000041571446745451500224070ustar00rootroot00000000000000{ "config": { "protocol_version": "v0.1.0", "scanner_name": "govulncheck" } } { "osv": { "id": "GO-0000-0001", "modified": "0001-01-01T00:00:00Z", "published": "0001-01-01T00:00:00Z", "details": "Third-party vulnerability", "affected": [ { "package": { "name": "golang.org/vmod", "ecosystem": "" }, "ecosystem_specific": { "imports": [ { "goos": [ "amd" ] } ] } } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-0000-0001" } } } { "finding": { "osv": "GO-0000-0001", "fixed_version": "v0.1.3", "trace": [ { "module": "golang.org/vmod", "version": "v0.0.1", "package": "vmod", "function": "Vuln" }, { "module": "golang.org/main", "version": "v0.0.1", "package": "main", "function": "main" } ] } } { "finding": { "osv": "GO-0000-0001", "fixed_version": "v0.1.3", "trace": [ { "module": "golang.org/vmod", "version": "v0.0.1", "package": "vmod", "function": "VulnFoo" }, { "module": "golang.org/main", "version": "v0.0.1", "package": "main", "function": "main" } ] } } { "finding": { "osv": "GO-0000-0001", "fixed_version": "v0.0.4", "trace": [ { "module": "golang.org/vmod1", "version": "v0.0.3", "package": "vmod1", "function": "Vuln" }, { "module": "golang.org/other", "version": "v2.0.3", "package": "other", "function": "Foo" } ] } } { "finding": { "osv": "GO-0000-0001", "fixed_version": "v0.0.4", "trace": [ { "module": "golang.org/vmod1", "version": "v0.0.3", "package": "vmod1", "function": "VulnFoo" }, { "module": "golang.org/other", "version": "v2.0.3", "package": "other", "function": "Bar" } ] } }vuln-1.0.1/internal/scan/testdata/multi-stacks.txt000066400000000000000000000012161446745451500222460ustar00rootroot00000000000000Vulnerability #1: GO-0000-0001 Third-party vulnerability More info: https://pkg.go.dev/vuln/GO-0000-0001 Module: golang.org/vmod Found in: golang.org/vmod@v0.0.1 Fixed in: golang.org/vmod@v0.1.3 Platforms: amd Example traces found: #1: main.main calls vmod.Vuln #2: main.main calls vmod.VulnFoo Module: golang.org/vmod1 Found in: golang.org/vmod1@v0.0.3 Fixed in: golang.org/vmod1@v0.0.4 Example traces found: #1: other.Foo calls vmod1.Vuln #2: other.Bar calls vmod1.VulnFoo Your code is affected by 1 vulnerability from 2 modules. Share feedback at https://go.dev/s/govulncheck-feedback. vuln-1.0.1/internal/scan/testdata/no-vulns.json000066400000000000000000000014761446745451500215510ustar00rootroot00000000000000{ "config": { "protocol_version": "v0.1.0", "scanner_name": "govulncheck" } } { "osv": { "id": "GO-0000-0001", "modified": "0001-01-01T00:00:00Z", "published": "0001-01-01T00:00:00Z", "details": "Third-party vulnerability", "affected": [ { "package": { "name": "golang.org/vmod", "ecosystem": "" }, "ecosystem_specific": { "imports": [ { "goos": [ "amd" ] } ] } } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-0000-0001" } } } { "finding": { "osv": "GO-0000-0001", "fixed_version": "v0.1.3", "trace": [ { "module": "golang.org/vmod", "version": "v0.0.1" } ] } }vuln-1.0.1/internal/scan/testdata/no-vulns.txt000066400000000000000000000010701446745451500214050ustar00rootroot00000000000000=== Informational === Found 1 vulnerability in packages that you import, but there are no call stacks leading to the use of this vulnerability. You may not need to take any action. See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck for details. Vulnerability #1: GO-0000-0001 Third-party vulnerability More info: https://pkg.go.dev/vuln/GO-0000-0001 Module: golang.org/vmod Found in: golang.org/vmod@v0.0.1 Fixed in: golang.org/vmod@v0.1.3 Platforms: amd No vulnerabilities found. Share feedback at https://go.dev/s/govulncheck-feedback. vuln-1.0.1/internal/scan/testdata/platform-all.json000066400000000000000000000007611446745451500223560ustar00rootroot00000000000000{ "config": { "protocol_version": "v0.1.0", "scanner_name": "govulncheck" } } { "osv": { "id": "All", "modified": "0001-01-01T00:00:00Z", "published": "0001-01-01T00:00:00Z", "details": "", "affected": null, "database_specific": { "url": "https://pkg.go.dev/vuln/All" } } } { "finding": { "osv": "All", "fixed_version": "v0.1.3", "trace": [ { "module": "golang.org/vmod", "version": "v0.0.1" } ] } }vuln-1.0.1/internal/scan/testdata/platform-all.txt000066400000000000000000000007661446745451500222310ustar00rootroot00000000000000=== Informational === Found 1 vulnerability in packages that you import, but there are no call stacks leading to the use of this vulnerability. You may not need to take any action. See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck for details. Vulnerability #1: All More info: https://pkg.go.dev/vuln/All Module: golang.org/vmod Found in: golang.org/vmod@v0.0.1 Fixed in: golang.org/vmod@v0.1.3 No vulnerabilities found. Share feedback at https://go.dev/s/govulncheck-feedback. vuln-1.0.1/internal/scan/testdata/platform-one-arch-only.json000066400000000000000000000017531446745451500242630ustar00rootroot00000000000000{ "config": { "protocol_version": "v0.1.0", "scanner_name": "govulncheck" } } { "osv": { "id": "one-arch-only", "modified": "0001-01-01T00:00:00Z", "published": "0001-01-01T00:00:00Z", "details": "", "affected": [ { "package": { "name": "golang.org/vmod", "ecosystem": "" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "1.2.0" } ] } ], "ecosystem_specific": { "imports": [ { "goos": [ "amd64" ] } ] } } ], "database_specific": { "url": "https://pkg.go.dev/vuln/one-arch-only" } } } { "finding": { "osv": "one-arch-only", "fixed_version": "v0.1.3", "trace": [ { "module": "golang.org/vmod", "version": "v0.0.1" } ] } }vuln-1.0.1/internal/scan/testdata/platform-one-arch-only.txt000066400000000000000000000010371446745451500241240ustar00rootroot00000000000000=== Informational === Found 1 vulnerability in packages that you import, but there are no call stacks leading to the use of this vulnerability. You may not need to take any action. See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck for details. Vulnerability #1: one-arch-only More info: https://pkg.go.dev/vuln/one-arch-only Module: golang.org/vmod Found in: golang.org/vmod@v0.0.1 Fixed in: golang.org/vmod@v0.1.3 Platforms: amd64 No vulnerabilities found. Share feedback at https://go.dev/s/govulncheck-feedback. vuln-1.0.1/internal/scan/testdata/platform-one-import.json000066400000000000000000000021301446745451500236670ustar00rootroot00000000000000{ "config": { "protocol_version": "v0.1.0", "scanner_name": "govulncheck" } } { "osv": { "id": "one-import", "modified": "0001-01-01T00:00:00Z", "published": "0001-01-01T00:00:00Z", "details": "", "affected": [ { "package": { "name": "golang.org/vmod", "ecosystem": "" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "1.2.0" } ] } ], "ecosystem_specific": { "imports": [ { "goos": [ "windows", "linux" ], "goarch": [ "amd64", "wasm" ] } ] } } ], "database_specific": { "url": "https://pkg.go.dev/vuln/one-import" } } } { "finding": { "osv": "one-import", "fixed_version": "v0.1.3", "trace": [ { "module": "golang.org/vmod", "version": "v0.0.1" } ] } }vuln-1.0.1/internal/scan/testdata/platform-one-import.txt000066400000000000000000000011101446745451500235320ustar00rootroot00000000000000=== Informational === Found 1 vulnerability in packages that you import, but there are no call stacks leading to the use of this vulnerability. You may not need to take any action. See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck for details. Vulnerability #1: one-import More info: https://pkg.go.dev/vuln/one-import Module: golang.org/vmod Found in: golang.org/vmod@v0.0.1 Fixed in: golang.org/vmod@v0.1.3 Platforms: linux/amd64, linux/wasm, windows/amd64, windows/wasm No vulnerabilities found. Share feedback at https://go.dev/s/govulncheck-feedback. vuln-1.0.1/internal/scan/testdata/platform-two-imports.json000066400000000000000000000023121446745451500241040ustar00rootroot00000000000000{ "config": { "protocol_version": "v0.1.0", "scanner_name": "govulncheck" } } { "osv": { "id": "two-imports", "modified": "0001-01-01T00:00:00Z", "published": "0001-01-01T00:00:00Z", "details": "", "affected": [ { "package": { "name": "golang.org/vmod", "ecosystem": "" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "1.2.0" } ] } ], "ecosystem_specific": { "imports": [ { "goos": [ "windows" ], "goarch": [ "amd64" ] }, { "goos": [ "linux" ], "goarch": [ "amd64" ] } ] } } ], "database_specific": { "url": "https://pkg.go.dev/vuln/two-imports" } } } { "finding": { "osv": "two-imports", "fixed_version": "v0.1.3", "trace": [ { "module": "golang.org/vmod", "version": "v0.0.1" } ] } }vuln-1.0.1/internal/scan/testdata/platform-two-imports.txt000066400000000000000000000010601446745451500237510ustar00rootroot00000000000000=== Informational === Found 1 vulnerability in packages that you import, but there are no call stacks leading to the use of this vulnerability. You may not need to take any action. See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck for details. Vulnerability #1: two-imports More info: https://pkg.go.dev/vuln/two-imports Module: golang.org/vmod Found in: golang.org/vmod@v0.0.1 Fixed in: golang.org/vmod@v0.1.3 Platforms: linux/amd64, windows/amd64 No vulnerabilities found. Share feedback at https://go.dev/s/govulncheck-feedback. vuln-1.0.1/internal/scan/testdata/platform-two-os-only.json000066400000000000000000000017561446745451500240220ustar00rootroot00000000000000{ "config": { "protocol_version": "v0.1.0", "scanner_name": "govulncheck" } } { "osv": { "id": "two-os-only", "modified": "0001-01-01T00:00:00Z", "published": "0001-01-01T00:00:00Z", "details": "", "affected": [ { "package": { "name": "golang.org/vmod", "ecosystem": "" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "1.2.0" } ] } ], "ecosystem_specific": { "imports": [ { "goos": [ "windows, linux" ] } ] } } ], "database_specific": { "url": "https://pkg.go.dev/vuln/two-os-only" } } } { "finding": { "osv": "two-os-only", "fixed_version": "v0.1.3", "trace": [ { "module": "golang.org/vmod", "version": "v0.0.1" } ] } }vuln-1.0.1/internal/scan/testdata/platform-two-os-only.txt000066400000000000000000000010441446745451500236560ustar00rootroot00000000000000=== Informational === Found 1 vulnerability in packages that you import, but there are no call stacks leading to the use of this vulnerability. You may not need to take any action. See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck for details. Vulnerability #1: two-os-only More info: https://pkg.go.dev/vuln/two-os-only Module: golang.org/vmod Found in: golang.org/vmod@v0.0.1 Fixed in: golang.org/vmod@v0.1.3 Platforms: windows, linux No vulnerabilities found. Share feedback at https://go.dev/s/govulncheck-feedback. vuln-1.0.1/internal/scan/testdata/source.json000066400000000000000000000031051446745451500212570ustar00rootroot00000000000000{ "config": { "protocol_version": "v0.1.0", "scanner_name": "govulncheck" } } { "osv": { "id": "GO-0000-0001", "modified": "0001-01-01T00:00:00Z", "published": "0001-01-01T00:00:00Z", "details": "Third-party vulnerability", "affected": [ { "package": { "name": "golang.org/vmod", "ecosystem": "" }, "ecosystem_specific": { "imports": [ { "goos": [ "amd" ] } ] } } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-0000-0001" } } } { "finding": { "osv": "GO-0000-0001", "fixed_version": "v0.1.3", "trace": [ { "module": "golang.org/vmod", "version": "v0.0.1", "package": "vmod", "function": "Vuln" }, { "module": "golang.org/app", "version": "v0.0.1", "package": "main", "function": "main" } ] } } { "osv": { "id": "GO-0000-0002", "modified": "0001-01-01T00:00:00Z", "published": "0001-01-01T00:00:00Z", "details": "Stdlib vulnerability", "affected": [ { "package": { "name": "stdlib", "ecosystem": "" }, "ecosystem_specific": {} } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-0000-0002" } } } { "finding": { "osv": "GO-0000-0002", "trace": [ { "module": "stdlib", "version": "v0.0.1", "package": "net/http" } ] } }vuln-1.0.1/internal/scan/testdata/source.txt000066400000000000000000000015031446745451500211250ustar00rootroot00000000000000Vulnerability #1: GO-0000-0001 Third-party vulnerability More info: https://pkg.go.dev/vuln/GO-0000-0001 Module: golang.org/vmod Found in: golang.org/vmod@v0.0.1 Fixed in: golang.org/vmod@v0.1.3 Platforms: amd Example traces found: #1: main.main calls vmod.Vuln === Informational === Found 1 vulnerability in packages that you import, but there are no call stacks leading to the use of this vulnerability. You may not need to take any action. See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck for details. Vulnerability #1: GO-0000-0002 Stdlib vulnerability More info: https://pkg.go.dev/vuln/GO-0000-0002 Standard library Found in: net/http@go0.0.1 Fixed in: N/A Your code is affected by 1 vulnerability from 1 module. Share feedback at https://go.dev/s/govulncheck-feedback. vuln-1.0.1/internal/scan/testdata/source_traces.txt000066400000000000000000000015441446745451500224730ustar00rootroot00000000000000Vulnerability #1: GO-0000-0001 Third-party vulnerability More info: https://pkg.go.dev/vuln/GO-0000-0001 Module: golang.org/vmod Found in: golang.org/vmod@v0.0.1 Fixed in: golang.org/vmod@v0.1.3 Platforms: amd Example traces found: #1: for function vmod.Vuln main.main vmod.Vuln === Informational === Found 1 vulnerability in packages that you import, but there are no call stacks leading to the use of this vulnerability. You may not need to take any action. See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck for details. Vulnerability #1: GO-0000-0002 Stdlib vulnerability More info: https://pkg.go.dev/vuln/GO-0000-0002 Standard library Found in: net/http@go0.0.1 Fixed in: N/A Your code is affected by 1 vulnerability from 1 module. Share feedback at https://go.dev/s/govulncheck-feedback. vuln-1.0.1/internal/scan/text.go000066400000000000000000000201551446745451500165720ustar00rootroot00000000000000// Copyright 2022 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 scan import ( "fmt" "io" "strings" "golang.org/x/vuln/internal" "golang.org/x/vuln/internal/govulncheck" "golang.org/x/vuln/internal/osv" ) type style int const ( defaultStyle = style(iota) osvCalledStyle osvImportedStyle detailsStyle sectionStyle keyStyle valueStyle ) // NewtextHandler returns a handler that writes govulncheck output as text. func NewTextHandler(w io.Writer) *TextHandler { return &TextHandler{w: w} } type TextHandler struct { w io.Writer osvs []*osv.Entry findings []*findingSummary err error showColor bool showTraces bool showVersion bool } const ( detailsMessage = `For details, see https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck.` binaryProgressMessage = `Scanning your binary for known vulnerabilities...` ) func (h *TextHandler) Show(show []string) { for _, show := range show { switch show { case "traces": h.showTraces = true case "color": h.showColor = true case "version": h.showVersion = true } } } func Flush(h govulncheck.Handler) error { if th, ok := h.(interface{ Flush() error }); ok { return th.Flush() } return nil } func (h *TextHandler) Flush() error { fixupFindings(h.osvs, h.findings) h.byVulnerability(h.findings) h.summary(h.findings) h.print("\nShare feedback at https://go.dev/s/govulncheck-feedback.\n") if h.err != nil { return h.err } if isCalled(h.findings) { return errVulnerabilitiesFound } return nil } // Config writes version information only if --version was set. func (h *TextHandler) Config(config *govulncheck.Config) error { if !h.showVersion { return nil } if config.GoVersion != "" { h.style(keyStyle, "Go: ") h.print(config.GoVersion, "\n") } if config.ScannerName != "" { h.style(keyStyle, "Scanner: ") h.print(config.ScannerName) if config.ScannerVersion != "" { h.print(`@`, config.ScannerVersion) } h.print("\n") } if config.DB != "" { h.style(keyStyle, "DB: ") h.print(config.DB, "\n") if config.DBLastModified != nil { h.style(keyStyle, "DB updated: ") h.print(*config.DBLastModified, "\n") } } h.print("\n") return h.err } // Progress writes progress updates during govulncheck execution.. func (h *TextHandler) Progress(progress *govulncheck.Progress) error { h.print(progress.Message, "\n\n") return h.err } // OSV gathers osv entries to be written. func (h *TextHandler) OSV(entry *osv.Entry) error { h.osvs = append(h.osvs, entry) return nil } // Finding gathers vulnerability findings to be written. func (h *TextHandler) Finding(finding *govulncheck.Finding) error { if err := validateFindings(finding); err != nil { return err } h.findings = append(h.findings, newFindingSummary(finding)) return nil } func (h *TextHandler) byVulnerability(findings []*findingSummary) { byVuln := groupByVuln(findings) called := 0 for _, findings := range byVuln { if isCalled(findings) { h.vulnerability(called, findings) called++ } } unCalled := len(byVuln) - called if unCalled == 0 { return } h.style(sectionStyle, "=== Informational ===\n") h.print("\nFound ", unCalled) h.print(choose(unCalled == 1, ` vulnerability`, ` vulnerabilities`)) h.print(" in packages that you import, but there are no call\nstacks leading to the use of ") h.print(choose(unCalled == 1, `this vulnerability`, `these vulnerabilities`)) h.print(". You may not need to\ntake any action. See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck\nfor details.\n\n") index := 0 for _, findings := range byVuln { if !isCalled(findings) { h.vulnerability(index, findings) index++ } } } func (h *TextHandler) vulnerability(index int, findings []*findingSummary) { h.style(keyStyle, "Vulnerability") h.print(" #", index+1, ": ") if isCalled(findings) { h.style(osvCalledStyle, findings[0].OSV.ID) } else { h.style(osvImportedStyle, findings[0].OSV.ID) } h.print("\n") h.style(detailsStyle) description := findings[0].OSV.Summary if description == "" { description = findings[0].OSV.Details } h.wrap(" ", description, 80) h.style(defaultStyle) h.print("\n") h.style(keyStyle, " More info:") h.print(" ", findings[0].OSV.DatabaseSpecific.URL, "\n") byModule := groupByModule(findings) first := true for _, module := range byModule { //TODO: this assumes all traces on a module are found and fixed at the same versions lastFrame := module[0].Trace[0] mod := lastFrame.Module path := lastFrame.Module if path == internal.GoStdModulePath { path = lastFrame.Package } foundVersion := moduleVersionString(lastFrame.Module, lastFrame.Version) fixedVersion := moduleVersionString(lastFrame.Module, module[0].FixedVersion) if !first { h.print("\n") } first = false h.print(" ") if mod == internal.GoStdModulePath { h.print("Standard library") } else { h.style(keyStyle, "Module: ") h.print(mod) } h.print("\n ") h.style(keyStyle, "Found in: ") h.print(path, "@", foundVersion, "\n ") h.style(keyStyle, "Fixed in: ") if fixedVersion != "" { h.print(path, "@", fixedVersion) } else { h.print("N/A") } h.print("\n") platforms := platforms(mod, module[0].OSV) if len(platforms) > 0 { h.style(keyStyle, " Platforms: ") for ip, p := range platforms { if ip > 0 { h.print(", ") } h.print(p) } h.print("\n") } h.traces(module) } h.print("\n") } func (h *TextHandler) traces(traces []*findingSummary) { first := true for i, entry := range traces { if entry.Compact == "" { continue } if first { h.style(keyStyle, " Example traces found:\n") } first = false h.print(" #", i+1, ": ") if !h.showTraces { h.print(entry.Compact, "\n") } else { h.print("for function ", symbol(entry.Trace[0], false), "\n") for i := len(entry.Trace) - 1; i >= 0; i-- { t := entry.Trace[i] h.print(" ") if t.Position != nil { h.print(posToString(t.Position), ": ") } h.print(symbol(t, false), "\n") } } } } func (h *TextHandler) summary(findings []*findingSummary) { counters := counters(findings) if counters.VulnerabilitiesCalled == 0 { h.print("No vulnerabilities found.\n") return } h.print(`Your code is affected by `) h.style(valueStyle, counters.VulnerabilitiesCalled) h.print(choose(counters.VulnerabilitiesCalled == 1, ` vulnerability`, ` vulnerabilities`)) h.print(` from`) if counters.ModulesCalled > 0 { h.print(` `) h.style(valueStyle, counters.ModulesCalled) h.print(choose(counters.ModulesCalled == 1, ` module`, ` modules`)) } if counters.StdlibCalled { if counters.ModulesCalled != 0 { h.print(` and`) } h.print(` the Go standard library`) } h.print(".\n") } func (h *TextHandler) style(style style, values ...any) { if h.showColor { switch style { default: h.print(colorReset) case osvCalledStyle: h.print(colorBold, fgRed) case osvImportedStyle: h.print(colorBold, fgGreen) case detailsStyle: h.print(colorFaint) case sectionStyle: h.print(fgBlue) case keyStyle: h.print(colorFaint, fgYellow) case valueStyle: h.print(colorBold, fgCyan) } } h.print(values...) if h.showColor && len(values) > 0 { h.print(colorReset) } } func (h *TextHandler) print(values ...any) int { total, w := 0, 0 for _, v := range values { if h.err != nil { return total } // do we need to specialize for some types, like time? w, h.err = fmt.Fprint(h.w, v) total += w } return total } // wrap wraps s to fit in maxWidth by breaking it into lines at whitespace. If a // single word is longer than maxWidth, it is retained as its own line. func (h *TextHandler) wrap(indent string, s string, maxWidth int) { w := 0 for _, f := range strings.Fields(s) { if w > 0 && w+len(f)+1 > maxWidth { // line would be too long with this word h.print("\n") w = 0 } if w == 0 { // first field on line, indent w = h.print(indent) } else { // not first word, space separate w += h.print(" ") } // now write the word w += h.print(f) } } func choose(b bool, yes, no any) any { if b { return yes } return no } vuln-1.0.1/internal/scan/util.go000066400000000000000000000042311446745451500165600ustar00rootroot00000000000000// Copyright 2022 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 scan import ( "fmt" "golang.org/x/vuln/internal" "golang.org/x/vuln/internal/govulncheck" "golang.org/x/vuln/internal/osv" isem "golang.org/x/vuln/internal/semver" ) // validateFindings checks that the supplied findings all obey the protocol // rules. func validateFindings(findings ...*govulncheck.Finding) error { for _, f := range findings { if f.OSV == "" { return fmt.Errorf("invalid finding: all findings must have an associated OSV") } if len(f.Trace) < 1 { return fmt.Errorf("invalid finding: all callstacks must have at least one frame") } for _, frame := range f.Trace { if frame.Version != "" && frame.Module == "" { return fmt.Errorf("invalid finding: if Frame.Version is set, Frame.Module must also be") } if frame.Package != "" && frame.Module == "" { return fmt.Errorf("invalid finding: if Frame.Package is set, Frame.Module must also be") } if frame.Function != "" && frame.Package == "" { return fmt.Errorf("invalid finding: if Frame.Function is set, Frame.Package must also be") } } } return nil } // latestFixed returns the latest fixed version in the list of affected ranges, // or the empty string if there are no fixed versions. func latestFixed(modulePath string, as []osv.Affected) string { v := "" for _, a := range as { if modulePath != a.Module.Path { continue } fixed := isem.LatestFixedVersion(a.Ranges) // Special case: if there is any affected block for this module // with no fix, the module is considered unfixed. if fixed == "" { return "" } if isem.Less(v, fixed) { v = fixed } } return v } func fixedVersion(modulePath string, affected []osv.Affected) string { fixed := latestFixed(modulePath, affected) if fixed != "" { fixed = "v" + fixed } return fixed } func moduleVersionString(modulePath, version string) string { if version == "" { return "" } if modulePath == internal.GoStdModulePath || modulePath == internal.GoCmdModulePath { version = semverToGoTag(version) } return version } vuln-1.0.1/internal/scan/util_test.go000066400000000000000000000071051446745451500176220ustar00rootroot00000000000000// Copyright 2022 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 scan import ( "testing" "golang.org/x/vuln/internal/osv" ) func TestLatestFixed(t *testing.T) { for _, test := range []struct { name string module string in []osv.Affected want string }{ { name: "empty", want: "", }, { name: "no semver", module: "example.com/module", in: []osv.Affected{ { Module: osv.Module{ Path: "example.com/module", }, Ranges: []osv.Range{ { Type: osv.RangeType("unspecified"), Events: []osv.RangeEvent{ {Introduced: "v1.0.0", Fixed: "v1.2.3"}, }, }}, }, }, want: "", }, { name: "one", module: "example.com/module", in: []osv.Affected{ { Module: osv.Module{ Path: "example.com/module", }, Ranges: []osv.Range{ { Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{ {Introduced: "v1.0.0", Fixed: "v1.2.3"}, }, }}, }, }, want: "v1.2.3", }, { name: "several", module: "example.com/module", in: []osv.Affected{ { Module: osv.Module{ Path: "example.com/module", }, Ranges: []osv.Range{ { Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{ {Introduced: "v1.0.0", Fixed: "v1.2.3"}, {Introduced: "v1.5.0", Fixed: "v1.5.6"}, }, }}, }, { Module: osv.Module{ Path: "example.com/module", }, Ranges: []osv.Range{ { Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{ {Introduced: "v1.3.0", Fixed: "v1.4.1"}, }, }}, }, { // This should be ignored. Module: osv.Module{ Path: "example.com/anothermodule", }, Ranges: []osv.Range{ { Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{ {Introduced: "0", Fixed: "v1.6.0"}, }, }}, }, }, want: "v1.5.6", }, { name: "no v prefix", module: "example.com/module", in: []osv.Affected{ { Module: osv.Module{ Path: "example.com/module", }, Ranges: []osv.Range{ { Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{ {Fixed: "1.17.2"}, }, }}, }, { Module: osv.Module{ Path: "example.com/module", }, Ranges: []osv.Range{ { Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{ {Introduced: "1.18.0", Fixed: "1.18.4"}, }, }}, }, }, want: "1.18.4", }, { name: "unfixed", module: "example.com/module", in: []osv.Affected{ { Module: osv.Module{ Path: "example.com/module", }, Ranges: []osv.Range{ { Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{ {Introduced: "v1.0.0", Fixed: "v1.2.3"}, // Reintroduced and never fixed. {Introduced: "v1.5.0"}, }, }}, }, { Module: osv.Module{ Path: "example.com/module", }, Ranges: []osv.Range{ { Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{ // Even though this block has a fix, // it will be overridden by the block // with no fix. {Introduced: "v1.3.0", Fixed: "v1.4.1"}, }, }}, }, }, want: "", }, } { t.Run(test.name, func(t *testing.T) { got := latestFixed(test.module, test.in) if got != test.want { t.Errorf("got %q, want %q", got, test.want) } }) } } vuln-1.0.1/internal/semver/000077500000000000000000000000001446745451500156315ustar00rootroot00000000000000vuln-1.0.1/internal/semver/affects.go000066400000000000000000000042101446745451500175700ustar00rootroot00000000000000// Copyright 2023 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 semver import ( "sort" "golang.org/x/vuln/internal/osv" ) func Affects(a []osv.Range, v string) bool { if len(a) == 0 { // No ranges implies all versions are affected return true } var semverRangePresent bool for _, r := range a { if r.Type != osv.RangeTypeSemver { continue } semverRangePresent = true if containsSemver(r, v) { return true } } // If there were no semver ranges present we // assume that all semvers are affected, similarly // to how to we assume all semvers are affected // if there are no ranges at all. return !semverRangePresent } // containsSemver checks if semver version v is in the // range encoded by ar. If ar is not a semver range, // returns false. // // Assumes that // - exactly one of Introduced or Fixed fields is set // - ranges in ar are not overlapping // - beginning of time is encoded with .Introduced="0" // - no-fix is not an event, as opposed to being an // event where Introduced="" and Fixed="" func containsSemver(ar osv.Range, v string) bool { if ar.Type != osv.RangeTypeSemver { return false } if len(ar.Events) == 0 { return true } // Strip and then add the semver prefix so we can support bare versions, // versions prefixed with 'v', and versions prefixed with 'go'. v = canonicalizeSemverPrefix(v) // Sort events by semver versions. Event for beginning // of time, if present, always comes first. sort.SliceStable(ar.Events, func(i, j int) bool { e1 := ar.Events[i] v1 := e1.Introduced if v1 == "0" { // -inf case. return true } if e1.Fixed != "" { v1 = e1.Fixed } e2 := ar.Events[j] v2 := e2.Introduced if v2 == "0" { // -inf case. return false } if e2.Fixed != "" { v2 = e2.Fixed } return Less(v1, v2) }) var affected bool for _, e := range ar.Events { if !affected && e.Introduced != "" { affected = e.Introduced == "0" || !Less(v, e.Introduced) } else if affected && e.Fixed != "" { affected = Less(v, e.Fixed) } } return affected } vuln-1.0.1/internal/semver/affects_test.go000066400000000000000000000077561446745451500206510ustar00rootroot00000000000000// Copyright 2021 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 semver import ( "testing" "golang.org/x/vuln/internal/osv" ) func TestAffectsSemver(t *testing.T) { cases := []struct { affects []osv.Range version string want bool }{ { // empty []Range indicates everything is affected affects: []osv.Range{}, version: "v0.0.0", want: true, }, { // []Range containing an empty SEMVER range also indicates // everything is affected affects: []osv.Range{{Type: osv.RangeTypeSemver}}, version: "v0.0.0", want: true, }, { // []Range containing a SEMVER range with only an "introduced":"0" // also indicates everything is affected affects: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0"}}}}, version: "v0.0.0", want: true, }, { // v1.0.0 < v2.0.0 affects: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0"}, {Fixed: "2.0.0"}}}}, version: "v1.0.0", want: true, }, { // v0.0.1 <= v1.0.0 affects: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0.0.1"}}}}, version: "v1.0.0", want: true, }, { // v1.0.0 <= v1.0.0 affects: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}}}}, version: "v1.0.0", want: true, }, { // v1.0.0 <= v1.0.0 < v2.0.0 affects: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "2.0.0"}}}}, version: "v1.0.0", want: true, }, { // v0.0.1 <= v1.0.0 < v2.0.0 affects: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0.0.1"}, {Fixed: "2.0.0"}}}}, version: "v1.0.0", want: true, }, { // v2.0.0 < v3.0.0 affects: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "2.0.0"}}}}, version: "v3.0.0", want: false, }, { // Multiple ranges affects: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "2.0.0"}, {Introduced: "3.0.0"}}}}, version: "v3.0.0", want: true, }, { affects: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0"}, {Fixed: "1.18.6"}, {Introduced: "1.19.0"}, {Fixed: "1.19.1"}}}}, version: "v1.18.6", want: false, }, { affects: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0"}, {Introduced: "1.19.0"}, {Fixed: "1.19.1"}}}}, version: "v1.18.6", want: true, }, { // Multiple non-sorted ranges. affects: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "1.19.0"}, {Fixed: "1.19.1"}, {Introduced: "0"}, {Fixed: "1.18.6"}}}}, version: "v1.18.1", want: true, }, { // Wrong type range affects: []osv.Range{{Type: osv.RangeType("unspecified"), Events: []osv.RangeEvent{{Introduced: "3.0.0"}}}}, version: "v3.0.0", want: true, }, { // Semver ranges don't match affects: []osv.Range{ {Type: osv.RangeType("unspecified"), Events: []osv.RangeEvent{{Introduced: "3.0.0"}}}, {Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "4.0.0"}}}, }, version: "v3.0.0", want: false, }, { // Semver ranges do match affects: []osv.Range{ {Type: osv.RangeType("unspecified"), Events: []osv.RangeEvent{{Introduced: "3.0.0"}}}, {Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "3.0.0"}}}, }, version: "v3.0.0", want: true, }, { // Semver ranges match (go prefix) affects: []osv.Range{ {Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "3.0.0"}}}, }, version: "go3.0.1", want: true, }, } for _, c := range cases { got := Affects(c.affects, c.version) if c.want != got { t.Errorf("%#v.AffectsSemver(%s): want %t, got %t", c.affects, c.version, c.want, got) } } } vuln-1.0.1/internal/semver/fixed.go000066400000000000000000000014671446745451500172670ustar00rootroot00000000000000// Copyright 2023 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 semver import "golang.org/x/vuln/internal/osv" func LatestFixedVersion(ranges []osv.Range) string { var latestFixed string for _, r := range ranges { if r.Type == "SEMVER" { for _, e := range r.Events { fixed := e.Fixed if fixed != "" && Less(latestFixed, fixed) { latestFixed = fixed } } // If the vulnerability was re-introduced after the latest fix // we found, there is no latest fix for this range. for _, e := range r.Events { introduced := e.Introduced if introduced != "" && introduced != "0" && Less(latestFixed, introduced) { latestFixed = "" break } } } } return latestFixed } vuln-1.0.1/internal/semver/fixed_test.go000066400000000000000000000045651446745451500203300ustar00rootroot00000000000000// Copyright 2023 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 semver import ( "testing" "golang.org/x/vuln/internal/osv" ) func TestLatestFixedVersion(t *testing.T) { tests := []struct { name string ranges []osv.Range want string }{ { name: "empty", ranges: []osv.Range{}, want: "", }, { name: "no fix", ranges: []osv.Range{{ Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{ { Introduced: "0", }, }, }}, want: "", }, { name: "no latest fix", ranges: []osv.Range{{ Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{ {Introduced: "0"}, {Fixed: "1.0.4"}, {Introduced: "1.1.2"}, }, }}, want: "", }, { name: "unsorted no latest fix", ranges: []osv.Range{{ Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{ {Fixed: "1.0.4"}, {Introduced: "0"}, {Introduced: "1.1.2"}, {Introduced: "1.5.0"}, {Fixed: "1.1.4"}, }, }}, want: "", }, { name: "unsorted with fix", ranges: []osv.Range{{ Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{ { Fixed: "1.0.0", }, { Introduced: "0", }, { Fixed: "0.1.0", }, { Introduced: "0.5.0", }, }, }}, want: "1.0.0", }, { name: "multiple ranges", ranges: []osv.Range{{ Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{ { Introduced: "0", }, { Fixed: "0.1.0", }, }, }, { Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{ { Introduced: "0", }, { Fixed: "0.2.0", }, }, }}, want: "0.2.0", }, { name: "pseudoversion", ranges: []osv.Range{{ Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{ { Introduced: "0", }, { Fixed: "0.0.0-20220824120805-abc", }, { Introduced: "0.0.0-20230824120805-efg", }, { Fixed: "0.0.0-20240824120805-hij", }, }, }}, want: "0.0.0-20240824120805-hij", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { got := LatestFixedVersion(test.ranges) if got != test.want { t.Errorf("LatestFixedVersion = %q, want %q", got, test.want) } }) } } vuln-1.0.1/internal/semver/semver.go000066400000000000000000000047511446745451500174700ustar00rootroot00000000000000// Copyright 2022 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 semver provides shared utilities for manipulating // Go semantic versions. package semver import ( "regexp" "strings" "golang.org/x/mod/semver" ) // addSemverPrefix adds a 'v' prefix to s if it isn't already prefixed // with 'v' or 'go'. This allows us to easily test go-style SEMVER // strings against normal SEMVER strings. func addSemverPrefix(s string) string { if !strings.HasPrefix(s, "v") && !strings.HasPrefix(s, "go") { return "v" + s } return s } // removeSemverPrefix removes the 'v' or 'go' prefixes from go-style // SEMVER strings, for usage in the public vulnerability format. func removeSemverPrefix(s string) string { s = strings.TrimPrefix(s, "v") s = strings.TrimPrefix(s, "go") return s } // canonicalizeSemverPrefix turns a SEMVER string into the canonical // representation using the 'v' prefix, as used by the OSV format. // Input may be a bare SEMVER ("1.2.3"), Go prefixed SEMVER ("go1.2.3"), // or already canonical SEMVER ("v1.2.3"). func canonicalizeSemverPrefix(s string) string { return addSemverPrefix(removeSemverPrefix(s)) } // Less returns whether v1 < v2, where v1 and v2 are // semver versions with either a "v", "go" or no prefix. func Less(v1, v2 string) bool { return semver.Compare(canonicalizeSemverPrefix(v1), canonicalizeSemverPrefix(v2)) < 0 } // Valid returns whether v is valid semver, allowing // either a "v", "go" or no prefix. func Valid(v string) bool { return semver.IsValid(canonicalizeSemverPrefix(v)) } var ( // Regexp for matching go tags. The groups are: // 1 the major.minor version // 2 the patch version, or empty if none // 3 the entire prerelease, if present // 4 the prerelease type ("beta" or "rc") // 5 the prerelease number tagRegexp = regexp.MustCompile(`^go(\d+\.\d+)(\.\d+|)((beta|rc|-pre)(\d+))?$`) ) // This is a modified copy of pkgsite/internal/stdlib:VersionForTag. func GoTagToSemver(tag string) string { if tag == "" { return "" } tag = strings.Fields(tag)[0] // Special cases for go1. if tag == "go1" { return "v1.0.0" } if tag == "go1.0" { return "" } m := tagRegexp.FindStringSubmatch(tag) if m == nil { return "" } version := "v" + m[1] if m[2] != "" { version += m[2] } else { version += ".0" } if m[3] != "" { if !strings.HasPrefix(m[4], "-") { version += "-" } version += m[4] + "." + m[5] } return version } vuln-1.0.1/internal/semver/semver_test.go000066400000000000000000000014171446745451500205230ustar00rootroot00000000000000// Copyright 2022 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 semver import ( "testing" ) func TestCanonicalize(t *testing.T) { for _, test := range []struct { v string want string }{ {"v1.2.3", "v1.2.3"}, {"1.2.3", "v1.2.3"}, {"go1.2.3", "v1.2.3"}, } { got := canonicalizeSemverPrefix(test.v) if got != test.want { t.Errorf("want %s; got %s", test.want, got) } } } func TestGoTagToSemver(t *testing.T) { for _, test := range []struct { v string want string }{ {"go1.19", "v1.19.0"}, {"go1.20-pre4", "v1.20.0-pre.4"}, } { got := GoTagToSemver(test.v) if got != test.want { t.Errorf("want %s; got %s", test.want, got) } } } vuln-1.0.1/internal/test/000077500000000000000000000000001446745451500153075ustar00rootroot00000000000000vuln-1.0.1/internal/test/buildtest.go000066400000000000000000000050371446745451500176420ustar00rootroot00000000000000// Copyright 2022 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 test import ( "fmt" "os" "os/exec" "path/filepath" "runtime" "strings" "testing" "golang.org/x/vuln/internal/testenv" ) var unsupportedGoosGoarch = map[string]bool{ "darwin/386": true, "darwin/arm": true, } // GoBuild runs "go build" on dir using the additional environment variables in // envVarVals, which should be an alternating list of variables and values. // It returns the path to the resulting binary, and a function // to call when finished with the binary. func GoBuild(t *testing.T, dir, tags string, strip bool, envVarVals ...string) (binaryPath string, cleanup func()) { testenv.NeedsGoBuild(t) if len(envVarVals)%2 != 0 { t.Fatal("last args should be alternating variables and values") } var env []string if len(envVarVals) > 0 { env = os.Environ() for i := 0; i < len(envVarVals); i += 2 { env = append(env, fmt.Sprintf("%s=%s", envVarVals[i], envVarVals[i+1])) } } gg := lookupEnv("GOOS", env, runtime.GOOS) + "/" + lookupEnv("GOARCH", env, runtime.GOARCH) if unsupportedGoosGoarch[gg] { t.Skipf("skipping unsupported GOOS/GOARCH pair %s", gg) } tmpDir, err := os.MkdirTemp("", "buildtest") if err != nil { t.Fatal(err) } abs, err := filepath.Abs(dir) if err != nil { t.Fatal(err) } binaryPath = filepath.Join(tmpDir, filepath.Base(abs)) var exeSuffix string if runtime.GOOS == "windows" { exeSuffix = ".exe" } // Make sure we use the same version of go that is running this test. goCommandPath := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix) if _, err := os.Stat(goCommandPath); err != nil { t.Fatal(err) } args := []string{"build", "-o", binaryPath + exeSuffix} if tags != "" { args = append(args, "-tags", tags) } if strip { args = append(args, "-ldflags", "-s -w") } cmd := exec.Command(goCommandPath, args...) cmd.Dir = dir cmd.Env = env cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { t.Fatal(err) } return binaryPath + exeSuffix, func() { os.RemoveAll(tmpDir) } } // lookEnv looks for name in env, a list of "VAR=VALUE" strings. It returns // the value if name is found, and defaultValue if it is not. func lookupEnv(name string, env []string, defaultValue string) string { for _, vv := range env { i := strings.IndexByte(vv, '=') if i < 0 { // malformed env entry; just ignore it continue } if name == vv[:i] { return vv[i+1:] } } return defaultValue } vuln-1.0.1/internal/test/handler.go000066400000000000000000000050471446745451500172610ustar00rootroot00000000000000// Copyright 2022 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 test import ( "sort" "golang.org/x/vuln/internal/govulncheck" "golang.org/x/vuln/internal/osv" ) // MockHandler implements govulncheck.Handler but (currently) // does nothing. // // For use in tests. type MockHandler struct { ConfigMessages []*govulncheck.Config ProgressMessages []*govulncheck.Progress OSVMessages []*osv.Entry FindingMessages []*govulncheck.Finding } func NewMockHandler() *MockHandler { return &MockHandler{} } func (h *MockHandler) Config(config *govulncheck.Config) error { h.ConfigMessages = append(h.ConfigMessages, config) return nil } func (h *MockHandler) Progress(progress *govulncheck.Progress) error { h.ProgressMessages = append(h.ProgressMessages, progress) return nil } func (h *MockHandler) OSV(entry *osv.Entry) error { h.OSVMessages = append(h.OSVMessages, entry) return nil } func (h *MockHandler) Finding(finding *govulncheck.Finding) error { h.FindingMessages = append(h.FindingMessages, finding) return nil } func (h *MockHandler) Sort() { sort.Slice(h.FindingMessages, func(i, j int) bool { if h.FindingMessages[i].OSV > h.FindingMessages[j].OSV { return true } if h.FindingMessages[i].OSV < h.FindingMessages[j].OSV { return false } iframe := h.FindingMessages[i].Trace[0] jframe := h.FindingMessages[j].Trace[0] if iframe.Module < jframe.Module { return true } if iframe.Module > jframe.Module { return false } if iframe.Package < jframe.Package { return true } if iframe.Package > jframe.Package { return false } return iframe.Function < jframe.Function }) } func (h *MockHandler) Write(to govulncheck.Handler) error { h.Sort() for _, config := range h.ConfigMessages { if err := to.Config(config); err != nil { return err } } for _, progress := range h.ProgressMessages { if err := to.Progress(progress); err != nil { return err } } seen := map[string]bool{} for _, finding := range h.FindingMessages { if !seen[finding.OSV] { seen[finding.OSV] = true // first time seeing this osv, so find and write the osv message for _, osv := range h.OSVMessages { if osv.ID == finding.OSV { if err := to.OSV(osv); err != nil { return err } } } } if err := to.Finding(finding); err != nil { return err } } for _, osv := range h.OSVMessages { if !seen[osv.ID] { if err := to.OSV(osv); err != nil { return err } } } return nil } vuln-1.0.1/internal/test/packages.go000066400000000000000000000016421446745451500174170ustar00rootroot00000000000000// Copyright 2022 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 test import ( "os/exec" "strings" "testing" "golang.org/x/tools/go/packages" ) func VerifyImports(t *testing.T, allowed ...string) { if _, err := exec.LookPath("go"); err != nil { t.Skipf("skipping: %v", err) } cfg := &packages.Config{Mode: packages.NeedImports | packages.NeedDeps} pkgs, err := packages.Load(cfg, ".") if err != nil { t.Fatal(err) } check := map[string]struct{}{} for _, imp := range allowed { check[imp] = struct{}{} } for _, p := range pkgs { for _, imp := range p.Imports { // this is an approximate stdlib check that is good enough for these tests if !strings.ContainsRune(imp.ID, '.') { continue } if _, ok := check[imp.ID]; !ok { t.Errorf("include of %s is not allowed", imp.ID) } } } } vuln-1.0.1/internal/test/testenv.go000066400000000000000000000007121446745451500173260ustar00rootroot00000000000000// Copyright 2023 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 test import ( "os/exec" "testing" ) // NeedsGoEnv skips t if the current system can't get the environment with // “go env” in a subprocess. func NeedsGoEnv(t testing.TB) { t.Helper() if _, err := exec.LookPath("go"); err != nil { t.Skip("skipping test: can't run go env") } } vuln-1.0.1/internal/testenv/000077500000000000000000000000001446745451500160205ustar00rootroot00000000000000vuln-1.0.1/internal/testenv/testenv.go000066400000000000000000000063671446745451500200530ustar00rootroot00000000000000// Copyright 2015 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 testenv import ( "errors" "flag" "fmt" "os" "os/exec" "path/filepath" "runtime" "sync" "testing" ) var origEnv = os.Environ() // NeedsExec checks that the current system can start new processes // using os.StartProcess or (more commonly) exec.Command. // If not, NeedsExec calls t.Skip with an explanation. // // On some platforms NeedsExec checks for exec support by re-executing the // current executable, which must be a binary built by 'go test'. // We intentionally do not provide a HasExec function because of the risk of // inappropriate recursion in TestMain functions. func NeedsExec(t testing.TB) { tryExecOnce.Do(func() { tryExecErr = tryExec() }) if tryExecErr != nil { t.Helper() t.Skipf("skipping test: cannot exec subprocess on %s/%s: %v", runtime.GOOS, runtime.GOARCH, tryExecErr) } } var ( tryExecOnce sync.Once tryExecErr error ) func tryExec() error { switch runtime.GOOS { case "aix", "android", "darwin", "dragonfly", "freebsd", "illumos", "linux", "netbsd", "openbsd", "plan9", "solaris", "windows": // Known OS that isn't ios or wasm; assume that exec works. return nil default: } // ios has an exec syscall but on real iOS devices it might return a // permission error. In an emulated environment (such as a Corellium host) // it might succeed, so if we need to exec we'll just have to try it and // find out. // // As of 2023-04-19 wasip1 and js don't have exec syscalls at all, but we // may as well use the same path so that this branch can be tested without // an ios environment. if flag.Lookup("test.list") == nil { // This isn't a standard 'go test' binary, so we don't know how to // self-exec in a way that should succeed without side effects. // Just forget it. return errors.New("can't probe for exec support with a non-test executable") } // We know that this is a test executable. We should be able to run it with a // no-op flag to check for overall exec support. exe, err := os.Executable() if err != nil { return fmt.Errorf("can't probe for exec support: %w", err) } cmd := exec.Command(exe, "-test.list=^$") cmd.Env = origEnv return cmd.Run() } func NeedsGoBuild(t testing.TB) { goBuildOnce.Do(func() { dir, err := os.MkdirTemp("", "testenv-*") if err != nil { goBuildErr = err return } defer os.RemoveAll(dir) mainGo := filepath.Join(dir, "main.go") if err := os.WriteFile(mainGo, []byte("package main\nfunc main() {}\n"), 0644); err != nil { t.Fatal(err) } cmd := exec.Command("go", "build", "-o", os.DevNull, mainGo) cmd.Dir = dir if err := cmd.Run(); err != nil { goBuildErr = fmt.Errorf("%v: %v", cmd, err) } }) if goBuildErr != nil { t.Helper() t.Skipf("skipping test: 'go build' not supported on %s/%s", runtime.GOOS, runtime.GOARCH) } } var ( goBuildOnce sync.Once goBuildErr error ) // NeedsLocalhostNet skips t if networking does not work for ports opened // with "localhost". func NeedsLocalhostNet(t testing.TB) { switch runtime.GOOS { case "js", "wasip1": t.Skipf(`Listening on "localhost" fails on %s; see https://go.dev/issue/59718`, runtime.GOOS) } } vuln-1.0.1/internal/vulncheck/000077500000000000000000000000001446745451500163125ustar00rootroot00000000000000vuln-1.0.1/internal/vulncheck/binary.go000066400000000000000000000113761446745451500201350ustar00rootroot00000000000000// Copyright 2021 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. //go:build go1.18 // +build go1.18 package vulncheck import ( "context" "fmt" "io" "runtime/debug" "golang.org/x/vuln/internal" "golang.org/x/vuln/internal/client" "golang.org/x/vuln/internal/govulncheck" "golang.org/x/vuln/internal/osv" "golang.org/x/vuln/internal/vulncheck/internal/buildinfo" ) // Binary detects presence of vulnerable symbols in exe. // The Calls, Imports, and Requires fields on Result will be empty. func Binary(ctx context.Context, exe io.ReaderAt, cfg *govulncheck.Config, client *client.Client) (_ *Result, err error) { mods, packageSymbols, bi, err := buildinfo.ExtractPackagesAndSymbols(exe) if err != nil { return nil, fmt.Errorf("could not parse provided binary: %v", err) } graph := NewPackageGraph(bi.GoVersion) graph.AddModules(mods...) mods = append(mods, graph.GetModule(internal.GoStdModulePath)) mv, err := FetchVulnerabilities(ctx, client, mods) if err != nil { return nil, err } modVulns := moduleVulnerabilities(mv) goos := findSetting("GOOS", bi) goarch := findSetting("GOARCH", bi) if goos == "" || goarch == "" { fmt.Printf("warning: failed to extract build system specification GOOS: %s GOARCH: %s\n", goos, goarch) } modVulns = modVulns.filter(goos, goarch) result := &Result{} if packageSymbols == nil { // The binary exe is stripped. We currently cannot detect inlined // symbols for stripped binaries (see #57764), so we report // vulnerabilities at the go.mod-level precision. addRequiresOnlyVulns(result, graph, modVulns) } else { for pkg, symbols := range packageSymbols { if !cfg.ScanLevel.WantSymbols() { addImportsOnlyVulns(result, graph, pkg, symbols, modVulns) } else { addSymbolVulns(result, graph, pkg, symbols, modVulns) } } } return result, nil } // addImportsOnlyVulns adds Vuln entries to result in imports only mode, i.e., for each vulnerable symbol // of pkg. func addImportsOnlyVulns(result *Result, graph *PackageGraph, pkg string, symbols []string, modVulns moduleVulnerabilities) { for _, osv := range modVulns.vulnsForPackage(pkg) { for _, affected := range osv.Affected { for _, p := range affected.EcosystemSpecific.Packages { if p.Path != pkg { continue } syms := p.Symbols if len(syms) == 0 { // If every symbol of pkg is vulnerable, we would ideally // compute every symbol mentioned in the pkg and then add // Vuln entry for it, just as we do in Source. However, // we don't have code of pkg here so we have to do best // we can, which is the symbols of pkg actually appearing // in the binary. syms = symbols } for _, symbol := range syms { addVuln(result, graph, osv, symbol, pkg) } } } } } // addSymbolVulns adds Vuln entries to result for every symbol of pkg in the binary that is vulnerable. func addSymbolVulns(result *Result, graph *PackageGraph, pkg string, symbols []string, modVulns moduleVulnerabilities) { for _, symbol := range symbols { for _, osv := range modVulns.vulnsForSymbol(pkg, symbol) { addVuln(result, graph, osv, symbol, pkg) } } } // findSetting returns value of setting from bi if present. // Otherwise, returns "". func findSetting(setting string, bi *debug.BuildInfo) string { for _, s := range bi.Settings { if s.Key == setting { return s.Value } } return "" } // addRequiresOnlyVulns adds to result all vulnerabilities in modVulns. // Used when the binary under analysis is stripped. func addRequiresOnlyVulns(result *Result, graph *PackageGraph, modVulns moduleVulnerabilities) { for _, mv := range modVulns { for _, osv := range mv.Vulns { for _, affected := range osv.Affected { for _, p := range affected.EcosystemSpecific.Packages { syms := p.Symbols if len(syms) == 0 { // If every symbol of pkg is vulnerable, we would ideally // compute every symbol mentioned in the pkg and then add // Vuln entry for it, just as we do in Source. However, // we don't have code of pkg here and we don't even have // pkg symbols used in stripped binary, so we add a placeholder // symbol. // // Note: this should not affect output of govulncheck since // in binary mode no symbol/call stack information is // communicated back to the user. syms = []string{fmt.Sprintf("%s/*", p.Path)} } for _, symbol := range syms { addVuln(result, graph, osv, symbol, p.Path) } } } } } } func addVuln(result *Result, graph *PackageGraph, osv *osv.Entry, symbol string, pkgPath string) { result.Vulns = append(result.Vulns, &Vuln{ OSV: osv, Symbol: symbol, ImportSink: graph.GetPackage(pkgPath), }) } vuln-1.0.1/internal/vulncheck/binary_test.go000066400000000000000000000146431446745451500211740ustar00rootroot00000000000000// Copyright 2021 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. //go:build go1.18 // +build go1.18 package vulncheck import ( "context" "fmt" "io" "os" "os/exec" "path/filepath" "sort" "testing" "golang.org/x/tools/go/packages/packagestest" "golang.org/x/vuln/internal/govulncheck" "golang.org/x/vuln/internal/semver" "golang.org/x/vuln/internal/testenv" "golang.org/x/vuln/internal/vulncheck/internal/buildinfo" ) // TODO: we build binary programatically, so what if the underlying tool chain changes? func TestBinary(t *testing.T) { testenv.NeedsGoBuild(t) e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{ { Name: "golang.org/entry", Files: map[string]interface{}{ "main.go": ` package main import ( "archive/zip" "golang.org/cmod/c" "golang.org/bmod/bvuln" ) func main() { c.C() bvuln.NoVuln() // no vuln use _, err := zip.OpenReader("file.zip") print(err) } `, }}, { Name: "golang.org/cmod@v1.1.3", Files: map[string]interface{}{"c/c.go": ` package c import ( "golang.org/amod/avuln" ) func C() { v := avuln.VulnData{} v.Vuln1() // vuln use } `}, }, { Name: "golang.org/amod@v1.1.3", Files: map[string]interface{}{"avuln/avuln.go": ` package avuln type VulnData struct {} func (v VulnData) Vuln1() { print("vuln1") } func (v VulnData) Vuln2() { print("vuln2") } `}, }, { Name: "golang.org/bmod@v0.5.0", Files: map[string]interface{}{"bvuln/bvuln.go": ` package bvuln func Vuln() { print("vuln") } func NoVuln() { print("novuln") } `}, }, }) defer e.Cleanup() cmd := exec.Command("go", "build", "-o", "entry") cmd.Dir = e.Config.Dir cmd.Env = e.Config.Env out, err := cmd.CombinedOutput() if err != nil || len(out) > 0 { t.Fatalf("failed to build the binary %v %v", err, string(out)) } bin, err := os.Open(filepath.Join(e.Config.Dir, "entry")) if err != nil { t.Fatalf("failed to access the binary %v", err) } defer bin.Close() c, err := newTestClient() if err != nil { t.Fatal(err) } // Test imports only mode cfg := &govulncheck.Config{ScanLevel: "package"} res, err := Binary(context.Background(), bin, cfg, c) if err != nil { t.Fatal(err) } goversion := getGoVersion(bin) // In importsOnly mode, vulnerable symbols // {avuln.VulnData.Vuln1, avuln.VulnData.Vuln2, bvuln.Vuln} // should be detected. wantVulns := []*testVuln{ {Symbol: "Vuln", PkgPath: "golang.org/bmod/bvuln", ModPath: "golang.org/bmod"}, {Symbol: "VulnData.Vuln1", PkgPath: "golang.org/amod/avuln", ModPath: "golang.org/amod"}, {Symbol: "VulnData.Vuln2", PkgPath: "golang.org/amod/avuln", ModPath: "golang.org/amod"}, } if goversion != "" { // If binary has recognizable Go version available, // then archive/zip.OpenReader should be detected too. wantVulns = append(wantVulns, &testVuln{Symbol: "OpenReader", PkgPath: "archive/zip", ModPath: "stdlib"}) } compareVulns(t, wantVulns, res) // Test the symbols (non-import mode) cfg.ScanLevel = "symbol" res, err = Binary(context.Background(), bin, cfg, c) if err != nil { t.Fatal(err) } wantVulns = []*testVuln{ {Symbol: "VulnData.Vuln1", PkgPath: "golang.org/amod/avuln", ModPath: "golang.org/amod"}, } if goversion != "" { // If binary has recognizable Go version available, // then archive/zip.OpenReader should be detected too. wantVulns = append(wantVulns, &testVuln{Symbol: "OpenReader", PkgPath: "archive/zip", ModPath: "stdlib"}) } compareVulns(t, wantVulns, res) } func getGoVersion(exe io.ReaderAt) string { _, _, bi, _ := buildinfo.ExtractPackagesAndSymbols(exe) return semver.GoTagToSemver(bi.GoVersion) } // Test58509 is supposed to test issue #58509 where a whole // vulnerable function is deleted from the binary so we // cannot detect its presence. // // Note: the issue is still not addressed and the test // expectations are set to fail once it gets addressed. func Test58509(t *testing.T) { testenv.NeedsGoBuild(t) vulnLib := `package bvuln %s debug = true func Vuln() { if debug { return } print("vuln") }` for _, tc := range []struct { gl string want []*testVuln }{ {"const", nil}, // TODO(https://go.dev/issue/58509): change expectations once issue is addressed {"var", []*testVuln{{Symbol: "Vuln", PkgPath: "golang.org/bmod/bvuln", ModPath: "golang.org/bmod"}}}, } { tc := tc t.Run(tc.gl, func(t *testing.T) { e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{ { Name: "golang.org/entry", Files: map[string]interface{}{ "main.go": ` package main import ( "golang.org/bmod/bvuln" ) func main() { bvuln.Vuln() } `, }}, { Name: "golang.org/bmod@v0.5.0", Files: map[string]interface{}{"bvuln/bvuln.go": fmt.Sprintf(vulnLib, tc.gl)}, }, }) defer e.Cleanup() cmd := exec.Command("go", "build", "-o", "entry") cmd.Dir = e.Config.Dir cmd.Env = e.Config.Env out, err := cmd.CombinedOutput() if err != nil || len(out) > 0 { t.Fatalf("failed to build the binary %v %v", err, string(out)) } bin, err := os.Open(filepath.Join(e.Config.Dir, "entry")) if err != nil { t.Fatalf("failed to access the binary %v", err) } defer bin.Close() c, err := newTestClient() if err != nil { t.Fatal(err) } cfg := &govulncheck.Config{ScanLevel: "symbol"} res, err := Binary(context.Background(), bin, cfg, c) if err != nil { t.Fatal(err) } compareVulns(t, tc.want, res) }) } } type testVuln struct { Symbol string PkgPath string ModPath string } func compareVulns(t *testing.T, want []*testVuln, res *Result) { if len(want) != len(res.Vulns) { t.Error("want", len(want), "vulnerabilities, got", len(res.Vulns)) return } sort.Slice(want, func(i, j int) bool { return want[i].Symbol < want[j].Symbol }) sort.Slice(res.Vulns, func(i, j int) bool { return res.Vulns[i].Symbol < res.Vulns[j].Symbol }) for i, want := range want { got := res.Vulns[i] if want.Symbol != got.Symbol { t.Error("[", i, "] want", want.Symbol, ", got", got.Symbol) } if want.PkgPath != got.ImportSink.PkgPath { t.Error("[", i, "] want", want.ModPath, ", got", got.ImportSink.Module.Path) } if want.ModPath != got.ImportSink.Module.Path { t.Error("[", i, "] want", want.ModPath, ", got", got.ImportSink.Module.Path) } } } vuln-1.0.1/internal/vulncheck/doc.go000066400000000000000000000041361446745451500174120ustar00rootroot00000000000000// Copyright 2022 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 vulncheck detects uses of known vulnerabilities in Go programs. Vulncheck identifies vulnerability uses in Go programs at the level of call graph, package import graph, and module requires graph. For instance, vulncheck identifies which vulnerable functions and methods are transitively called from the program entry points. vulncheck also detects transitively imported packages and required modules that contain known vulnerable functions and methods. We recommend using the command line tool [govulncheck] to detect vulnerabilities in your code. # Usage The two main APIs of vulncheck, [Source] and [Binary], allow vulnerability detection in Go source code and binaries, respectively. [Source] accepts a list of [Package] objects, which are a trimmed version of [golang.org/x/tools/go/packages.Package] objects to reduce memory consumption. [Binary] accepts a path to a Go binary file that must have been compiled with Go 1.18 or greater. Both [Source] and [Binary] require information about known vulnerabilities in the form of a vulnerability database, specifically a [golang.org/x/vuln/internal/client.Client]. The vulnerabilities are modeled using the [golang.org/x/vuln/internal/osv] format. # Results The results of vulncheck are slices of the call graph, package imports graph, and module requires graph leading to the use of an identified vulnerability. The parts of these graphs not related to any vulnerabilities are omitted. The [CallStacks] and [ImportChains] functions search the returned slices for user-friendly representative call stacks and import chains. These call stacks and import chains are provided as examples of vulnerability uses in the client code. # Limitations There are some limitations with vulncheck. Please see the [documented limitations] for more information. [govulncheck]: https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck [documented limitations]: https://go.dev/security/vulncheck#limitations. */ package vulncheck vuln-1.0.1/internal/vulncheck/entries.go000066400000000000000000000027721446745451500203220ustar00rootroot00000000000000// Copyright 2021 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 vulncheck import ( "strings" "golang.org/x/tools/go/ssa" ) func entryPoints(topPackages []*ssa.Package) []*ssa.Function { var entries []*ssa.Function for _, pkg := range topPackages { if pkg.Pkg.Name() == "main" { // for "main" packages the only valid entry points are the "main" // function and any "init#" functions, even if there are other // exported functions or types. similarly to isEntry it should be // safe to ignore the validity of the main or init# signatures, // since the compiler will reject malformed definitions, // and the init function is synthetic entries = append(entries, memberFuncs(pkg.Members["main"], pkg.Prog)...) for name, member := range pkg.Members { if strings.HasPrefix(name, "init#") || name == "init" { entries = append(entries, memberFuncs(member, pkg.Prog)...) } } continue } for _, member := range pkg.Members { for _, f := range memberFuncs(member, pkg.Prog) { if isEntry(f) { entries = append(entries, f) } } } } return entries } func isEntry(f *ssa.Function) bool { // it should be safe to ignore checking that the signature of the "init" function // is valid, since it is synthetic if f.Name() == "init" && f.Synthetic == "package initializer" { return true } return f.Synthetic == "" && f.Object() != nil && f.Object().Exported() } vuln-1.0.1/internal/vulncheck/fetch.go000066400000000000000000000017201446745451500177320ustar00rootroot00000000000000// Copyright 2021 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 vulncheck import ( "context" "golang.org/x/tools/go/packages" "golang.org/x/vuln/internal/client" ) // FetchVulnerabilities fetches vulnerabilities that affect the supplied modules. func FetchVulnerabilities(ctx context.Context, c *client.Client, modules []*packages.Module) ([]*ModVulns, error) { mreqs := make([]*client.ModuleRequest, len(modules)) for i, mod := range modules { modPath := mod.Path if mod.Replace != nil { modPath = mod.Replace.Path } mreqs[i] = &client.ModuleRequest{ Path: modPath, } } resps, err := c.ByModules(ctx, mreqs) if err != nil { return nil, err } var mv []*ModVulns for i, resp := range resps { if len(resp.Entries) == 0 { continue } mv = append(mv, &ModVulns{ Module: modules[i], Vulns: resp.Entries, }) } return mv, nil } vuln-1.0.1/internal/vulncheck/fetch_test.go000066400000000000000000000044321446745451500207740ustar00rootroot00000000000000// Copyright 2021 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 vulncheck_test import ( "context" "testing" "github.com/google/go-cmp/cmp" "golang.org/x/tools/go/packages" "golang.org/x/vuln/internal/client" "golang.org/x/vuln/internal/osv" "golang.org/x/vuln/internal/vulncheck" ) func TestFetchVulnerabilities(t *testing.T) { a := &osv.Entry{ID: "a", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/a"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Fixed: "2.0.0"}}}}}}} b := &osv.Entry{ID: "b", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/b"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Fixed: "1.1.1"}}}}}}} c := &osv.Entry{ID: "c", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/d"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Fixed: "2.0.0"}}}}}}} d := &osv.Entry{ID: "e", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/e"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Fixed: "2.2.0"}}}}}}} mc, err := client.NewInMemoryClient([]*osv.Entry{a, b, c, d}) if err != nil { t.Fatal(err) } got, err := vulncheck.FetchVulnerabilities(context.Background(), mc, []*packages.Module{ {Path: "example.mod/a", Version: "v1.0.0"}, {Path: "example.mod/b", Version: "v1.0.4"}, {Path: "example.mod/c", Replace: &packages.Module{Path: "example.mod/d", Version: "v1.0.0"}, Version: "v2.0.0"}, {Path: "example.mod/e", Replace: &packages.Module{Path: "../local/example.mod/d", Version: "v1.0.1"}, Version: "v2.1.0"}, }) if err != nil { t.Fatalf("FetchVulnerabilities failed: %s", err) } want := []*vulncheck.ModVulns{ { Module: &packages.Module{Path: "example.mod/a", Version: "v1.0.0"}, Vulns: []*osv.Entry{a}, }, { Module: &packages.Module{Path: "example.mod/b", Version: "v1.0.4"}, Vulns: []*osv.Entry{b}, }, { Module: &packages.Module{Path: "example.mod/c", Replace: &packages.Module{Path: "example.mod/d", Version: "v1.0.0"}, Version: "v2.0.0"}, Vulns: []*osv.Entry{c}, }, } if diff := cmp.Diff(got, want); diff != "" { t.Fatalf("mismatch (-want, +got):\n%s", diff) } } vuln-1.0.1/internal/vulncheck/helpers_test.go000066400000000000000000000106421446745451500213450ustar00rootroot00000000000000// Copyright 2021 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 vulncheck import ( "fmt" "runtime" "sort" "golang.org/x/tools/go/packages" "golang.org/x/tools/go/packages/packagestest" "golang.org/x/vuln/internal/client" "golang.org/x/vuln/internal/osv" "golang.org/x/vuln/internal/semver" ) // newTestClient returns a client that reads // a database with the following vulnerable symbols: // // golang.org/amod/avuln.{VulnData.Vuln1, vulnData.Vuln2} // golang.org/bmod/bvuln.Vuln // archive/zip.OpenReader func newTestClient() (*client.Client, error) { return client.NewInMemoryClient( []*osv.Entry{ { ID: "VA", Affected: []osv.Affected{{ Module: osv.Module{Path: "golang.org/amod"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "1.0.4"}, {Introduced: "1.1.2"}}}}, EcosystemSpecific: osv.EcosystemSpecific{Packages: []osv.Package{{ Path: "golang.org/amod/avuln", Symbols: []string{"VulnData.Vuln1", "VulnData.Vuln2"}}, }}, }}, }, { ID: "VB", Affected: []osv.Affected{{ Module: osv.Module{Path: "golang.org/bmod"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver}}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ Path: "golang.org/bmod/bvuln", Symbols: []string{"Vuln"}, }}, }, }}, }, { ID: "STD", Affected: []osv.Affected{{ Module: osv.Module{Path: osv.GoStdModulePath}, // Range is populated also using runtime info for testing binaries since // setting fixed Go version for binaries is very difficult. Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "1.18"}, {Introduced: semver.GoTagToSemver(runtime.Version())}}}}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ Path: "archive/zip", Symbols: []string{"OpenReader"}, }}, }, }}, }}) } func vulnsToString(vulns []*osv.Entry) string { var s string for _, v := range vulns { s += fmt.Sprintf("\t%v\n", v) } return s } type edge struct { // src and dest are ids of source and // destination nodes in a callgraph edge. src, dst string } func callGraphToStrMap(r *Result) map[string][]string { // seen edges, to avoid repetitions seen := make(map[edge]bool) m := make(map[string][]string) for _, v := range r.Vulns { updateCallGraph(m, v.CallSink, seen) } sortStrMap(m) return m } func updateCallGraph(callGraph map[string][]string, f *FuncNode, seen map[edge]bool) { fName := f.String() for _, callsite := range f.CallSites { e := edge{src: callsite.Parent.Name, dst: f.Name} if seen[e] { continue } seen[e] = true callerName := callsite.Parent.String() callGraph[callerName] = append(callGraph[callerName], fName) updateCallGraph(callGraph, callsite.Parent, seen) } } func pkgPathToImports(pkgs []*packages.Package) map[string][]string { m := make(map[string][]string) seen := make(map[*packages.Package]bool) var visit func(*packages.Package) visit = func(p *packages.Package) { if seen[p] { return } seen[p] = true var imports []string for _, i := range p.Imports { imports = append(imports, i.PkgPath) visit(i) } m[p.PkgPath] = imports } for _, p := range pkgs { visit(p) } sortStrMap(m) return m } func modulePathToVersion(pkgs []*packages.Package) map[string]string { m := make(map[string]string) seen := make(map[*packages.Package]bool) var visit func(*packages.Package) visit = func(p *packages.Package) { if seen[p] || p.Module == nil { return } seen[p] = true for _, i := range p.Imports { visit(i) } m[p.Module.Path] = p.Module.Version } for _, p := range pkgs { visit(p) } return m } // sortStrMap sorts the map string slice values to make them deterministic. func sortStrMap(m map[string][]string) { for _, strs := range m { sort.Strings(strs) } } func loadTestPackages(e *packagestest.Exported, patterns ...string) ([]*packages.Package, error) { e.Config.Mode |= packages.NeedModule | packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedTypes | packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedDeps graph := NewPackageGraph("go1.18") return graph.LoadPackages(e.Config, nil, patterns) } vuln-1.0.1/internal/vulncheck/internal/000077500000000000000000000000001446745451500201265ustar00rootroot00000000000000vuln-1.0.1/internal/vulncheck/internal/buildinfo/000077500000000000000000000000001446745451500221015ustar00rootroot00000000000000vuln-1.0.1/internal/vulncheck/internal/buildinfo/README.md000066400000000000000000000006051446745451500233610ustar00rootroot00000000000000This code is a copied and slightly modified subset of go/src/debug/buildinfo. It contains logic for parsing Go binary files for the purpose of extracting module dependency and symbol table information. Logic added by vulncheck is located in files with "additions_" prefix. Within the originally named files, changed or added logic is annotated with a comment starting with "Addition:". vuln-1.0.1/internal/vulncheck/internal/buildinfo/additions_buildinfo.go000066400000000000000000000146051446745451500264470ustar00rootroot00000000000000// Copyright 2022 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. //go:build go1.18 // +build go1.18 package buildinfo // This file adds to buildinfo the functionality for extracting the PCLN table. import ( "debug/elf" "debug/macho" "debug/pe" "encoding/binary" "errors" "fmt" "io" ) // ErrNoSymbols represents non-existence of symbol // table in binaries supported by buildinfo. var ErrNoSymbols = errors.New("no symbol section") // SymbolInfo is derived from cmd/internal/objfile/elf.go:symbols, symbolData. func (x *elfExe) SymbolInfo(name string) (uint64, uint64, io.ReaderAt, error) { sym, err := x.lookupSymbol(name) if err != nil { if errors.Is(err, elf.ErrNoSymbols) { return 0, 0, nil, ErrNoSymbols } return 0, 0, nil, fmt.Errorf("no symbol %q", name) } prog := x.progContaining(sym.Value) if prog == nil { return 0, 0, nil, fmt.Errorf("no Prog containing value %d for %q", sym.Value, name) } return sym.Value, prog.Vaddr, prog.ReaderAt, nil } func (x *elfExe) lookupSymbol(name string) (*elf.Symbol, error) { x.symbolsOnce.Do(func() { syms, err := x.f.Symbols() if err != nil { x.symbolsErr = err return } x.symbols = make(map[string]*elf.Symbol, len(syms)) for _, s := range syms { s := s // make a copy to prevent aliasing x.symbols[s.Name] = &s } }) if x.symbolsErr != nil { return nil, x.symbolsErr } return x.symbols[name], nil } func (x *elfExe) progContaining(addr uint64) *elf.Prog { for _, p := range x.f.Progs { if addr >= p.Vaddr && addr < p.Vaddr+p.Filesz { return p } } return nil } const go12magic = 0xfffffffb const go116magic = 0xfffffffa // PCLNTab is derived from cmd/internal/objfile/elf.go:pcln. func (x *elfExe) PCLNTab() ([]byte, uint64) { var offset uint64 text := x.f.Section(".text") if text != nil { offset = text.Offset } pclntab := x.f.Section(".gopclntab") if pclntab == nil { // Addition: this code is added to support some form of stripping. pclntab = x.f.Section(".data.rel.ro.gopclntab") if pclntab == nil { pclntab = x.f.Section(".data.rel.ro") if pclntab == nil { return nil, 0 } // Possibly the PCLN table has been stuck in the .data.rel.ro section, but without // its own section header. We can search for for the start by looking for the four // byte magic and the go magic. b, err := pclntab.Data() if err != nil { return nil, 0 } // TODO(rolandshoemaker): I'm not sure if the 16 byte increment during the search is // actually correct. During testing it worked, but that may be because I got lucky // with the binary I was using, and we need to do four byte jumps to exhaustively // search the section? for i := 0; i < len(b); i += 16 { if len(b)-i > 16 && b[i+4] == 0 && b[i+5] == 0 && (b[i+6] == 1 || b[i+6] == 2 || b[i+6] == 4) && (b[i+7] == 4 || b[i+7] == 8) { // Also check for the go magic leMagic := binary.LittleEndian.Uint32(b[i:]) beMagic := binary.BigEndian.Uint32(b[i:]) switch { case leMagic == go12magic: fallthrough case beMagic == go12magic: fallthrough case leMagic == go116magic: fallthrough case beMagic == go116magic: return b[i:], offset } } } } } b, err := pclntab.Data() if err != nil { return nil, 0 } return b, offset } // SymbolInfo is derived from cmd/internal/objfile/pe.go:findPESymbol, loadPETable. func (x *peExe) SymbolInfo(name string) (uint64, uint64, io.ReaderAt, error) { sym, err := x.lookupSymbol(name) if err != nil { return 0, 0, nil, err } if sym == nil { return 0, 0, nil, fmt.Errorf("no symbol %q", name) } sect := x.f.Sections[sym.SectionNumber-1] // In PE, the symbol's value is the offset from the section start. return uint64(sym.Value), 0, sect.ReaderAt, nil } func (x *peExe) lookupSymbol(name string) (*pe.Symbol, error) { x.symbolsOnce.Do(func() { x.symbols = make(map[string]*pe.Symbol, len(x.f.Symbols)) if len(x.f.Symbols) == 0 { x.symbolsErr = ErrNoSymbols return } for _, s := range x.f.Symbols { x.symbols[s.Name] = s } }) if x.symbolsErr != nil { return nil, x.symbolsErr } return x.symbols[name], nil } // PCLNTab is derived from cmd/internal/objfile/pe.go:pcln. // Assumes that the underlying symbol table exists, otherwise // it might panic. func (x *peExe) PCLNTab() ([]byte, uint64) { var textOffset uint64 for _, section := range x.f.Sections { if section.Name == ".text" { textOffset = uint64(section.Offset) break } } var start, end int64 var section int if s, _ := x.lookupSymbol("runtime.pclntab"); s != nil { start = int64(s.Value) section = int(s.SectionNumber - 1) } if s, _ := x.lookupSymbol("runtime.epclntab"); s != nil { end = int64(s.Value) } if start == 0 || end == 0 { return nil, 0 } offset := int64(x.f.Sections[section].Offset) + start size := end - start pclntab := make([]byte, size) if _, err := x.r.ReadAt(pclntab, offset); err != nil { return nil, 0 } return pclntab, textOffset } // SymbolInfo is derived from cmd/internal/objfile/macho.go:symbols. func (x *machoExe) SymbolInfo(name string) (uint64, uint64, io.ReaderAt, error) { sym := x.lookupSymbol(name) if sym == nil { return 0, 0, nil, fmt.Errorf("no symbol %q", name) } seg := x.segmentContaining(sym.Value) if seg == nil { return 0, 0, nil, fmt.Errorf("no Segment containing value %d for %q", sym.Value, name) } return sym.Value, seg.Addr, seg.ReaderAt, nil } func (x *machoExe) lookupSymbol(name string) *macho.Symbol { x.symbolsOnce.Do(func() { x.symbols = make(map[string]*macho.Symbol, len(x.f.Symtab.Syms)) for _, s := range x.f.Symtab.Syms { s := s // make a copy to prevent aliasing x.symbols[s.Name] = &s } }) return x.symbols[name] } func (x *machoExe) segmentContaining(addr uint64) *macho.Segment { for _, load := range x.f.Loads { seg, ok := load.(*macho.Segment) if ok && seg.Addr <= addr && addr <= seg.Addr+seg.Filesz-1 && seg.Name != "__PAGEZERO" { return seg } } return nil } // SymbolInfo is derived from cmd/internal/objfile/macho.go:pcln. func (x *machoExe) PCLNTab() ([]byte, uint64) { var textOffset uint64 text := x.f.Section("__text") if text != nil { textOffset = uint64(text.Offset) } pclntab := x.f.Section("__gopclntab") if pclntab == nil { return nil, 0 } b, err := pclntab.Data() if err != nil { return nil, 0 } return b, textOffset } vuln-1.0.1/internal/vulncheck/internal/buildinfo/additions_scan.go000066400000000000000000000075501446745451500254210ustar00rootroot00000000000000// Copyright 2021 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. //go:build go1.18 // +build go1.18 package buildinfo // Code in this package is dervied from src/cmd/go/internal/version/version.go // and cmd/go/internal/version/exe.go. import ( "debug/buildinfo" "errors" "fmt" "io" "net/url" "runtime/debug" "sort" "strings" "golang.org/x/tools/go/packages" "golang.org/x/vuln/internal/vulncheck/internal/gosym" ) func debugModulesToPackagesModules(debugModules []*debug.Module) []*packages.Module { packagesModules := make([]*packages.Module, len(debugModules)) for i, mod := range debugModules { packagesModules[i] = &packages.Module{ Path: mod.Path, Version: mod.Version, } if mod.Replace != nil { packagesModules[i].Replace = &packages.Module{ Path: mod.Replace.Path, Version: mod.Replace.Version, } } } return packagesModules } // ExtractPackagesAndSymbols extracts symbols, packages, modules from // bin as well as bin's metadata. // // If the symbol table is not available, such as in the case of stripped // binaries, returns module and binary info but without the symbol info. func ExtractPackagesAndSymbols(bin io.ReaderAt) ([]*packages.Module, map[string][]string, *debug.BuildInfo, error) { bi, err := buildinfo.Read(bin) if err != nil { return nil, nil, nil, err } funcSymName := gosym.FuncSymName(bi.GoVersion) if funcSymName == "" { return nil, nil, nil, fmt.Errorf("binary built using unsupported Go version: %q", bi.GoVersion) } x, err := openExe(bin) if err != nil { return nil, nil, nil, err } value, base, r, err := x.SymbolInfo(funcSymName) if err != nil { if errors.Is(err, ErrNoSymbols) { // bin is stripped, so return just module info and metadata. return debugModulesToPackagesModules(bi.Deps), nil, bi, nil } return nil, nil, nil, fmt.Errorf("reading %v: %v", funcSymName, err) } pclntab, textOffset := x.PCLNTab() if pclntab == nil { // TODO(https://go.dev/issue/59731): if we have build information, but // not PCLN table, we should be able to fall back to much higher // granularity vulnerability checking. return nil, nil, nil, errors.New("unable to load the PCLN table") } lineTab := gosym.NewLineTable(pclntab, textOffset) if lineTab == nil { return nil, nil, nil, errors.New("invalid line table") } tab, err := gosym.NewTable(nil, lineTab) if err != nil { return nil, nil, nil, err } type pkgSymbol struct { pkg string sym string } pkgSyms := make(map[pkgSymbol]bool) for _, f := range tab.Funcs { if f.Func == nil { continue } pkgName, symName, err := parseName(f.Func.Sym) if err != nil { return nil, nil, nil, err } pkgSyms[pkgSymbol{pkgName, symName}] = true // Collect symbols that were inlined in f. it, err := lineTab.InlineTree(&f, value, base, r) if err != nil { return nil, nil, nil, fmt.Errorf("InlineTree: %v", err) } for _, ic := range it { pkgName, symName, err := parseName(&gosym.Sym{Name: ic.Name}) if err != nil { return nil, nil, nil, err } pkgSyms[pkgSymbol{pkgName, symName}] = true } } packageSymbols := make(map[string][]string) for p := range pkgSyms { packageSymbols[p.pkg] = append(packageSymbols[p.pkg], p.sym) } // Sort symbols per pkg for deterministic results. for _, syms := range packageSymbols { sort.Strings(syms) } return debugModulesToPackagesModules(bi.Deps), packageSymbols, bi, nil } func parseName(s *gosym.Sym) (pkg, sym string, err error) { symName := s.BaseName() if r := s.ReceiverName(); r != "" { if strings.HasPrefix(r, "(*") { r = strings.Trim(r, "(*)") } symName = fmt.Sprintf("%s.%s", r, symName) } pkgName := s.PackageName() if pkgName != "" { pkgName, err = url.PathUnescape(pkgName) if err != nil { return "", "", err } } return pkgName, symName, nil } vuln-1.0.1/internal/vulncheck/internal/buildinfo/additions_scan_test.go000066400000000000000000000061131446745451500264520ustar00rootroot00000000000000// Copyright 2021 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. //go:build go1.18 // +build go1.18 package buildinfo import ( "os" "testing" "github.com/google/go-cmp/cmp" "golang.org/x/vuln/internal/test" ) // testAll executes testing function ft on all valid combinations // of gooss and goarchs. func testAll(t *testing.T, gooss, goarchs []string, ft func(*testing.T, string, string)) { // unsupported platforms for building Go binaries. var unsupported = map[string]bool{ "darwin/386": true, "darwin/arm": true, } for _, g := range gooss { for _, a := range goarchs { goos := g goarch := a ga := goos + "/" + goarch if unsupported[ga] { continue } t.Run(ga, func(t *testing.T) { ft(t, goos, goarch) }) } } } func TestExtractPackagesAndSymbols(t *testing.T) { testAll(t, []string{"linux", "darwin", "windows", "freebsd"}, []string{"amd64", "386", "arm", "arm64"}, func(t *testing.T, goos, goarch string) { binary, done := test.GoBuild(t, "testdata", "", false, "GOOS", goos, "GOARCH", goarch) defer done() f, err := os.Open(binary) if err != nil { t.Fatal(err) } defer f.Close() _, syms, _, err := ExtractPackagesAndSymbols(f) if err != nil { t.Fatal(err) } got := syms["main"] want := []string{"f", "g", "main"} if !cmp.Equal(got, want) { t.Errorf("\ngot %q\nwant %q", got, want) } }) } // TestStrippedBinary checks that there is no symbol table for // stripped binaries. This does not include darwin binaries. // For more info, see #61051. func TestStrippedBinary(t *testing.T) { // We exclude darwin as its stripped binaries seem to // preserve the symbol table. See TestStrippedDarwin. testAll(t, []string{"linux", "windows", "freebsd"}, []string{"amd64", "386", "arm", "arm64"}, func(t *testing.T, goos, goarch string) { binary, done := test.GoBuild(t, "testdata", "", true, "GOOS", goos, "GOARCH", goarch) defer done() f, err := os.Open(binary) if err != nil { t.Fatal(err) } defer f.Close() _, syms, _, err := ExtractPackagesAndSymbols(f) if err != nil { t.Fatal(err) } if syms != nil { t.Errorf("want empty symbol table; got %v symbols", len(syms)) } }) } // TestStrippedDarwin checks that the symbol table exists and // is complete on darwin even in the presence of stripping. // This test will become obsolete once #61051 is addressed. func TestStrippedDarwin(t *testing.T) { t.Skip("to temporarily resolve #61511") testAll(t, []string{"darwin"}, []string{"amd64", "386", "arm", "arm64"}, func(t *testing.T, goos, goarch string) { binary, done := test.GoBuild(t, "testdata", "", true, "GOOS", goos, "GOARCH", goarch) defer done() f, err := os.Open(binary) if err != nil { t.Fatal(err) } defer f.Close() _, syms, _, err := ExtractPackagesAndSymbols(f) if err != nil { t.Fatal(err) } got := syms["main"] want := []string{"f", "g", "main"} if !cmp.Equal(got, want) { t.Errorf("\ngot %q\nwant %q", got, want) } }) } vuln-1.0.1/internal/vulncheck/internal/buildinfo/buildinfo.go000066400000000000000000000135431446745451500244110ustar00rootroot00000000000000// Copyright 2021 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. //go:build go1.18 // +build go1.18 package buildinfo // Addition: this file is a trimmed and slightly modified version of debug/buildinfo/buildinfo.go import ( "bytes" "debug/elf" "debug/macho" "debug/pe" "fmt" "sync" // "internal/xcoff" "io" ) // Addition: modification of rawBuildInfo in the original file. // openExe returns reader r as an exe. func openExe(r io.ReaderAt) (exe, error) { data := make([]byte, 16) if _, err := r.ReadAt(data, 0); err != nil { return nil, err } if bytes.HasPrefix(data, []byte("\x7FELF")) { e, err := elf.NewFile(r) if err != nil { return nil, err } return &elfExe{f: e}, nil } if bytes.HasPrefix(data, []byte("MZ")) { e, err := pe.NewFile(r) if err != nil { return nil, err } return &peExe{r: r, f: e}, nil } if bytes.HasPrefix(data, []byte("\xFE\xED\xFA")) || bytes.HasPrefix(data[1:], []byte("\xFA\xED\xFE")) { e, err := macho.NewFile(r) if err != nil { return nil, err } return &machoExe{f: e}, nil } return nil, fmt.Errorf("unrecognized executable format") } type exe interface { // ReadData reads and returns up to size byte starting at virtual address addr. ReadData(addr, size uint64) ([]byte, error) // DataStart returns the virtual address of the segment or section that // should contain build information. This is either a specially named section // or the first writable non-zero data segment. DataStart() uint64 PCLNTab() ([]byte, uint64) // Addition: for constructing symbol table SymbolInfo(name string) (uint64, uint64, io.ReaderAt, error) // Addition: for inlining purposes } // elfExe is the ELF implementation of the exe interface. type elfExe struct { f *elf.File symbols map[string]*elf.Symbol // Addition: symbols in the binary symbolsOnce sync.Once // Addition: for computing symbols symbolsErr error // Addition: error for computing symbols } func (x *elfExe) ReadData(addr, size uint64) ([]byte, error) { for _, prog := range x.f.Progs { if prog.Vaddr <= addr && addr <= prog.Vaddr+prog.Filesz-1 { n := prog.Vaddr + prog.Filesz - addr if n > size { n = size } data := make([]byte, n) _, err := prog.ReadAt(data, int64(addr-prog.Vaddr)) if err != nil { return nil, err } return data, nil } } return nil, fmt.Errorf("address not mapped") // Addition: custom error } func (x *elfExe) DataStart() uint64 { for _, s := range x.f.Sections { if s.Name == ".go.buildinfo" { return s.Addr } } for _, p := range x.f.Progs { if p.Type == elf.PT_LOAD && p.Flags&(elf.PF_X|elf.PF_W) == elf.PF_W { return p.Vaddr } } return 0 } // peExe is the PE (Windows Portable Executable) implementation of the exe interface. type peExe struct { r io.ReaderAt f *pe.File symbols map[string]*pe.Symbol // Addition: symbols in the binary symbolsOnce sync.Once // Addition: for computing symbols symbolsErr error // Addition: error for computing symbols } func (x *peExe) imageBase() uint64 { switch oh := x.f.OptionalHeader.(type) { case *pe.OptionalHeader32: return uint64(oh.ImageBase) case *pe.OptionalHeader64: return oh.ImageBase } return 0 } func (x *peExe) ReadData(addr, size uint64) ([]byte, error) { addr -= x.imageBase() for _, sect := range x.f.Sections { if uint64(sect.VirtualAddress) <= addr && addr <= uint64(sect.VirtualAddress+sect.Size-1) { n := uint64(sect.VirtualAddress+sect.Size) - addr if n > size { n = size } data := make([]byte, n) _, err := sect.ReadAt(data, int64(addr-uint64(sect.VirtualAddress))) if err != nil { return nil, err } return data, nil } } return nil, fmt.Errorf("address not mapped") // Addition: custom error } func (x *peExe) DataStart() uint64 { // Assume data is first writable section. const ( IMAGE_SCN_CNT_CODE = 0x00000020 IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040 IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080 IMAGE_SCN_MEM_EXECUTE = 0x20000000 IMAGE_SCN_MEM_READ = 0x40000000 IMAGE_SCN_MEM_WRITE = 0x80000000 IMAGE_SCN_MEM_DISCARDABLE = 0x2000000 IMAGE_SCN_LNK_NRELOC_OVFL = 0x1000000 IMAGE_SCN_ALIGN_32BYTES = 0x600000 ) for _, sect := range x.f.Sections { if sect.VirtualAddress != 0 && sect.Size != 0 && sect.Characteristics&^IMAGE_SCN_ALIGN_32BYTES == IMAGE_SCN_CNT_INITIALIZED_DATA|IMAGE_SCN_MEM_READ|IMAGE_SCN_MEM_WRITE { return uint64(sect.VirtualAddress) + x.imageBase() } } return 0 } // machoExe is the Mach-O (Apple macOS/iOS) implementation of the exe interface. type machoExe struct { f *macho.File symbols map[string]*macho.Symbol // Addition: symbols in the binary symbolsOnce sync.Once // Addition: for computing symbols } func (x *machoExe) ReadData(addr, size uint64) ([]byte, error) { for _, load := range x.f.Loads { seg, ok := load.(*macho.Segment) if !ok { continue } if seg.Addr <= addr && addr <= seg.Addr+seg.Filesz-1 { if seg.Name == "__PAGEZERO" { continue } n := seg.Addr + seg.Filesz - addr if n > size { n = size } data := make([]byte, n) _, err := seg.ReadAt(data, int64(addr-seg.Addr)) if err != nil { return nil, err } return data, nil } } return nil, fmt.Errorf("address not mapped") // Addition: custom error } func (x *machoExe) DataStart() uint64 { // Look for section named "__go_buildinfo". for _, sec := range x.f.Sections { if sec.Name == "__go_buildinfo" { return sec.Addr } } // Try the first non-empty writable segment. const RW = 3 for _, load := range x.f.Loads { seg, ok := load.(*macho.Segment) if ok && seg.Addr != 0 && seg.Filesz != 0 && seg.Prot == RW && seg.Maxprot == RW { return seg.Addr } } return 0 } vuln-1.0.1/internal/vulncheck/internal/buildinfo/testdata/000077500000000000000000000000001446745451500237125ustar00rootroot00000000000000vuln-1.0.1/internal/vulncheck/internal/buildinfo/testdata/main.go000066400000000000000000000001251446745451500251630ustar00rootroot00000000000000package main func main() { f() } func f() { g() g() } func g() { println(1) } vuln-1.0.1/internal/vulncheck/internal/gosym/000077500000000000000000000000001446745451500212645ustar00rootroot00000000000000vuln-1.0.1/internal/vulncheck/internal/gosym/README.md000066400000000000000000000010771446745451500225500ustar00rootroot00000000000000This code is a copied and slightly modified version of go/src/debug/gosym. The original code contains logic for accessing symbol tables and line numbers in Go binaries. The only reason why this is copied is to support inlining. Code added by vulncheck is located in files with "additions_" prefix and it contains logic for accessing inlining information. Within the originally named files, deleted or added logic is annotated with a comment starting with "Addition:". The modified logic allows the inlining code in "additions_*" files to access the necessary information. vuln-1.0.1/internal/vulncheck/internal/gosym/additions.go000066400000000000000000000134521446745451500235760ustar00rootroot00000000000000// Copyright 2022 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 gosym import ( "encoding/binary" "io" "strings" sv "golang.org/x/mod/semver" "golang.org/x/vuln/internal/semver" ) const ( funcSymNameGo119Lower string = "go.func.*" funcSymNameGo120 string = "go:func.*" ) // FuncSymName returns symbol name for Go functions used in binaries // based on Go version. Supported Go versions are 1.18 and greater. // If the go version is unreadable it assumes that it is a newer version // and returns the symbol name for go version 1.20 or greater. func FuncSymName(goVersion string) string { // Support devel goX.Y... v := strings.TrimPrefix(goVersion, "devel ") v = semver.GoTagToSemver(v) mm := sv.MajorMinor(v) if sv.Compare(mm, "v1.20") >= 0 || mm == "" { return funcSymNameGo120 } else if sv.Compare(mm, "v1.18") >= 0 { return funcSymNameGo119Lower } return "" } // Additions to the original package from cmd/internal/objabi/funcdata.go const ( pcdata_InlTreeIndex = 2 funcdata_InlTree = 3 ) // InlineTree returns the inline tree for Func f as a sequence of InlinedCalls. // goFuncValue is the value of the gosym.FuncSymName symbol. // baseAddr is the address of the memory region (ELF Prog) containing goFuncValue. // progReader is a ReaderAt positioned at the start of that region. func (t *LineTable) InlineTree(f *Func, goFuncValue, baseAddr uint64, progReader io.ReaderAt) ([]InlinedCall, error) { if f.inlineTreeCount == 0 { return nil, nil } if f.inlineTreeOffset == ^uint32(0) { return nil, nil } var offset int64 if t.version >= ver118 { offset = int64(goFuncValue - baseAddr + uint64(f.inlineTreeOffset)) } else { offset = int64(uint64(f.inlineTreeOffset) - baseAddr) } r := io.NewSectionReader(progReader, offset, 1<<32) // pick a size larger than we need var ics []InlinedCall for i := 0; i < f.inlineTreeCount; i++ { if t.version >= ver120 { var ric rawInlinedCall120 if err := binary.Read(r, t.binary, &ric); err != nil { return nil, err } ics = append(ics, InlinedCall{ FuncID: ric.FuncID, Name: t.funcName(uint32(ric.NameOff)), ParentPC: ric.ParentPC, }) } else { var ric rawInlinedCall112 if err := binary.Read(r, t.binary, &ric); err != nil { return nil, err } ics = append(ics, InlinedCall{ FuncID: ric.FuncID, Name: t.funcName(uint32(ric.Func_)), ParentPC: ric.ParentPC, }) } } return ics, nil } // InlinedCall describes a call to an inlined function. type InlinedCall struct { FuncID uint8 // type of the called function Name string // name of called function ParentPC int32 // position of an instruction whose source position is the call site (offset from entry) } // rawInlinedCall112 is the encoding of entries in the FUNCDATA_InlTree table // from Go 1.12 through 1.19. It is equivalent to runtime.inlinedCall. type rawInlinedCall112 struct { Parent int16 // index of parent in the inltree, or < 0 FuncID uint8 // type of the called function _ byte File int32 // perCU file index for inlined call. See cmd/link:pcln.go Line int32 // line number of the call site Func_ int32 // offset into pclntab for name of called function ParentPC int32 // position of an instruction whose source position is the call site (offset from entry) } // rawInlinedCall120 is the encoding of entries in the FUNCDATA_InlTree table // from Go 1.20. It is equivalent to runtime.inlinedCall. type rawInlinedCall120 struct { FuncID uint8 // type of the called function _ [3]byte NameOff int32 // offset into pclntab for name of called function ParentPC int32 // position of an instruction whose source position is the call site (offset from entry) StartLine int32 // line number of start of function (func keyword/TEXT directive) } func (f funcData) npcdata() uint32 { return f.field(7) } func (f funcData) nfuncdata(numFuncFields uint32) uint32 { return uint32(f.data[f.fieldOffset(numFuncFields-1)+3]) } func (f funcData) funcdataOffset(i uint8, numFuncFields uint32) uint32 { if uint32(i) >= f.nfuncdata(numFuncFields) { return ^uint32(0) } var off uint32 if f.t.version >= ver118 { off = f.fieldOffset(numFuncFields) + // skip fixed part of _func f.npcdata()*4 + // skip pcdata uint32(i)*4 // index of i'th FUNCDATA } else { off = f.fieldOffset(numFuncFields) + // skip fixed part of _func f.npcdata()*4 off += uint32(i) * f.t.ptrsize } return f.t.binary.Uint32(f.data[off:]) } func (f funcData) fieldOffset(n uint32) uint32 { // In Go 1.18, the first field of _func changed // from a uintptr entry PC to a uint32 entry offset. sz0 := f.t.ptrsize if f.t.version >= ver118 { sz0 = 4 } return sz0 + (n-1)*4 // subsequent fields are 4 bytes each } func (f funcData) pcdataOffset(i uint8, numFuncFields uint32) uint32 { if uint32(i) >= f.npcdata() { return ^uint32(0) } off := f.fieldOffset(numFuncFields) + // skip fixed part of _func uint32(i)*4 // index of i'th PCDATA return f.t.binary.Uint32(f.data[off:]) } // maxInlineTreeIndexValue returns the maximum value of the inline tree index // pc-value table in info. This is the only way to determine how many // IndexedCalls are in an inline tree, since the data of the tree itself is not // delimited in any way. func (t *LineTable) maxInlineTreeIndexValue(info funcData, numFuncFields uint32) int { if info.npcdata() <= pcdata_InlTreeIndex { return -1 } off := info.pcdataOffset(pcdata_InlTreeIndex, numFuncFields) p := t.pctab[off:] val := int32(-1) max := int32(-1) var pc uint64 for t.step(&p, &pc, &val, pc == 0) { if val > max { max = val } } return int(max) } type inlTree struct { inlineTreeOffset uint32 // offset from go.func.* symbol inlineTreeCount int // number of entries in inline tree } vuln-1.0.1/internal/vulncheck/internal/gosym/additions_test.go000066400000000000000000000047241446745451500246370ustar00rootroot00000000000000// Copyright 2022 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 gosym import ( "debug/elf" "runtime" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" ) func TestFuncSymName(t *testing.T) { for _, test := range []struct { v string want string }{ {"go1.15", ""}, {"go1.18", funcSymNameGo119Lower}, {"go1.19", funcSymNameGo119Lower}, {"devel go1.19", funcSymNameGo119Lower}, {"go1.19-pre4", funcSymNameGo119Lower}, {"go1.20", funcSymNameGo120}, {"devel bd56cb90a72e6725e", funcSymNameGo120}, {"go1.21", funcSymNameGo120}, {"unknown version", funcSymNameGo120}, } { if got := FuncSymName(test.v); got != test.want { t.Errorf("got %s; want %s", got, test.want) } } } func TestInlineTree(t *testing.T) { t.Skip("to temporarily resolve #61511") pclinetestBinary, cleanup := dotest(t) defer cleanup() f, err := elf.Open(pclinetestBinary) if err != nil { t.Fatal(err) } defer f.Close() pclndat, err := f.Section(".gopclntab").Data() if err != nil { t.Fatalf("reading %s gopclntab: %v", pclinetestBinary, err) } // The test binaries will be compiled with the same Go version // used to run the tests. goFunc := lookupSymbol(f, FuncSymName(runtime.Version())) if goFunc == nil { t.Fatal("couldn't find go.func.*") } prog := progContaining(f, goFunc.Value) if prog == nil { t.Fatal("couldn't find go.func.* Prog") } pcln := NewLineTable(pclndat, f.Section(".text").Addr) s := f.Section(".gosymtab") if s == nil { t.Fatal("no .gosymtab section") } d, err := s.Data() if err != nil { t.Fatal(err) } tab, err := NewTable(d, pcln) if err != nil { t.Fatal(err) } fun := tab.LookupFunc("main.main") got, err := pcln.InlineTree(fun, goFunc.Value, prog.Vaddr, prog.ReaderAt) if err != nil { t.Fatal(err) } want := []InlinedCall{ {FuncID: 0, Name: "main.inline1"}, {FuncID: 0, Name: "main.inline2"}, } if !cmp.Equal(got, want, cmpopts.IgnoreFields(InlinedCall{}, "ParentPC")) { t.Errorf("got\n%+v\nwant\n%+v", got, want) } } func progContaining(f *elf.File, addr uint64) *elf.Prog { for _, p := range f.Progs { if addr >= p.Vaddr && addr < p.Vaddr+p.Filesz { return p } } return nil } func lookupSymbol(f *elf.File, name string) *elf.Symbol { syms, err := f.Symbols() if err != nil { return nil } for _, s := range syms { if s.Name == name { return &s } } return nil } vuln-1.0.1/internal/vulncheck/internal/gosym/pclntab.go000066400000000000000000000460011446745451500232370ustar00rootroot00000000000000// Copyright 2009 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. /* * Line tables */ package gosym import ( "bytes" "encoding/binary" "sort" "sync" ) // version of the pclntab type version int const ( verUnknown version = iota ver11 ver12 ver116 ver118 ver120 ) // A LineTable is a data structure mapping program counters to line numbers. // // In Go 1.1 and earlier, each function (represented by a Func) had its own LineTable, // and the line number corresponded to a numbering of all source lines in the // program, across all files. That absolute line number would then have to be // converted separately to a file name and line number within the file. // // In Go 1.2, the format of the data changed so that there is a single LineTable // for the entire program, shared by all Funcs, and there are no absolute line // numbers, just line numbers within specific files. // // For the most part, LineTable's methods should be treated as an internal // detail of the package; callers should use the methods on Table instead. type LineTable struct { Data []byte PC uint64 Line int // This mutex is used to keep parsing of pclntab synchronous. mu sync.Mutex // Contains the version of the pclntab section. version version // Go 1.2/1.16/1.18 state binary binary.ByteOrder quantum uint32 ptrsize uint32 textStart uint64 // address of runtime.text symbol (1.18+) funcnametab []byte cutab []byte funcdata []byte functab []byte nfunctab uint32 filetab []byte pctab []byte // points to the pctables. nfiletab uint32 funcNames map[uint32]string // cache the function names strings map[uint32]string // interned substrings of Data, keyed by offset // fileMap varies depending on the version of the object file. // For ver12, it maps the name to the index in the file table. // For ver116, it maps the name to the offset in filetab. fileMap map[string]uint32 } // NOTE(rsc): This is wrong for GOARCH=arm, which uses a quantum of 4, // but we have no idea whether we're using arm or not. This only // matters in the old (pre-Go 1.2) symbol table format, so it's not worth // fixing. const oldQuantum = 1 func (t *LineTable) parse(targetPC uint64, targetLine int) (b []byte, pc uint64, line int) { // The PC/line table can be thought of as a sequence of // * // batches. Each update batch results in a (pc, line) pair, // where line applies to every PC from pc up to but not // including the pc of the next pair. // // Here we process each update individually, which simplifies // the code, but makes the corner cases more confusing. b, pc, line = t.Data, t.PC, t.Line for pc <= targetPC && line != targetLine && len(b) > 0 { code := b[0] b = b[1:] switch { case code == 0: if len(b) < 4 { b = b[0:0] break } val := binary.BigEndian.Uint32(b) b = b[4:] line += int(val) case code <= 64: line += int(code) case code <= 128: line -= int(code - 64) default: pc += oldQuantum * uint64(code-128) continue } pc += oldQuantum } return b, pc, line } func (t *LineTable) slice(pc uint64) *LineTable { data, pc, line := t.parse(pc, -1) return &LineTable{Data: data, PC: pc, Line: line} } // PCToLine returns the line number for the given program counter. // // Deprecated: Use Table's PCToLine method instead. func (t *LineTable) PCToLine(pc uint64) int { if t.isGo12() { return t.go12PCToLine(pc) } _, _, line := t.parse(pc, -1) return line } // LineToPC returns the program counter for the given line number, // considering only program counters before maxpc. // // Deprecated: Use Table's LineToPC method instead. func (t *LineTable) LineToPC(line int, maxpc uint64) uint64 { if t.isGo12() { return 0 } _, pc, line1 := t.parse(maxpc, line) if line1 != line { return 0 } // Subtract quantum from PC to account for post-line increment return pc - oldQuantum } // NewLineTable returns a new PC/line table // corresponding to the encoded data. // Text must be the start address of the // corresponding text segment. func NewLineTable(data []byte, text uint64) *LineTable { return &LineTable{Data: data, PC: text, Line: 0, funcNames: make(map[uint32]string), strings: make(map[uint32]string)} } // Go 1.2 symbol table format. // See golang.org/s/go12symtab. // // A general note about the methods here: rather than try to avoid // index out of bounds errors, we trust Go to detect them, and then // we recover from the panics and treat them as indicative of a malformed // or incomplete table. // // The methods called by symtab.go, which begin with "go12" prefixes, // are expected to have that recovery logic. // isGo12 reports whether this is a Go 1.2 (or later) symbol table. func (t *LineTable) isGo12() bool { t.parsePclnTab() return t.version >= ver12 } const ( go12magic = 0xfffffffb go116magic = 0xfffffffa go118magic = 0xfffffff0 go120magic = 0xfffffff1 ) // uintptr returns the pointer-sized value encoded at b. // The pointer size is dictated by the table being read. func (t *LineTable) uintptr(b []byte) uint64 { if t.ptrsize == 4 { return uint64(t.binary.Uint32(b)) } return t.binary.Uint64(b) } // parsePclnTab parses the pclntab, setting the version. func (t *LineTable) parsePclnTab() { t.mu.Lock() defer t.mu.Unlock() if t.version != verUnknown { return } // Note that during this function, setting the version is the last thing we do. // If we set the version too early, and parsing failed (likely as a panic on // slice lookups), we'd have a mistaken version. // // Error paths through this code will default the version to 1.1. t.version = ver11 if !disableRecover { defer func() { // If we panic parsing, assume it's a Go 1.1 pclntab. _ = recover() }() } // Check header: 4-byte magic, two zeros, pc quantum, pointer size. if len(t.Data) < 16 || t.Data[4] != 0 || t.Data[5] != 0 || (t.Data[6] != 1 && t.Data[6] != 2 && t.Data[6] != 4) || // pc quantum (t.Data[7] != 4 && t.Data[7] != 8) { // pointer size return } var possibleVersion version leMagic := binary.LittleEndian.Uint32(t.Data) beMagic := binary.BigEndian.Uint32(t.Data) switch { case leMagic == go12magic: t.binary, possibleVersion = binary.LittleEndian, ver12 case beMagic == go12magic: t.binary, possibleVersion = binary.BigEndian, ver12 case leMagic == go116magic: t.binary, possibleVersion = binary.LittleEndian, ver116 case beMagic == go116magic: t.binary, possibleVersion = binary.BigEndian, ver116 case leMagic == go118magic: t.binary, possibleVersion = binary.LittleEndian, ver118 case beMagic == go118magic: t.binary, possibleVersion = binary.BigEndian, ver118 case leMagic == go120magic: t.binary, possibleVersion = binary.LittleEndian, ver120 case beMagic == go120magic: t.binary, possibleVersion = binary.BigEndian, ver120 default: return } t.version = possibleVersion // quantum and ptrSize are the same between 1.2, 1.16, and 1.18 t.quantum = uint32(t.Data[6]) t.ptrsize = uint32(t.Data[7]) offset := func(word uint32) uint64 { return t.uintptr(t.Data[8+word*t.ptrsize:]) } data := func(word uint32) []byte { return t.Data[offset(word):] } switch possibleVersion { case ver118, ver120: t.nfunctab = uint32(offset(0)) t.nfiletab = uint32(offset(1)) t.textStart = t.PC // use the start PC instead of reading from the table, which may be unrelocated t.funcnametab = data(3) t.cutab = data(4) t.filetab = data(5) t.pctab = data(6) t.funcdata = data(7) t.functab = data(7) functabsize := (int(t.nfunctab)*2 + 1) * t.functabFieldSize() t.functab = t.functab[:functabsize] case ver116: t.nfunctab = uint32(offset(0)) t.nfiletab = uint32(offset(1)) t.funcnametab = data(2) t.cutab = data(3) t.filetab = data(4) t.pctab = data(5) t.funcdata = data(6) t.functab = data(6) functabsize := (int(t.nfunctab)*2 + 1) * t.functabFieldSize() t.functab = t.functab[:functabsize] case ver12: t.nfunctab = uint32(t.uintptr(t.Data[8:])) t.funcdata = t.Data t.funcnametab = t.Data t.functab = t.Data[8+t.ptrsize:] t.pctab = t.Data functabsize := (int(t.nfunctab)*2 + 1) * t.functabFieldSize() fileoff := t.binary.Uint32(t.functab[functabsize:]) t.functab = t.functab[:functabsize] t.filetab = t.Data[fileoff:] t.nfiletab = t.binary.Uint32(t.filetab) t.filetab = t.filetab[:t.nfiletab*4] default: panic("unreachable") } } // go12Funcs returns a slice of Funcs derived from the Go 1.2+ pcln table. func (t *LineTable) go12Funcs() []Func { // Assume it is malformed and return nil on error. if !disableRecover { defer func() { _ = recover() }() } ft := t.funcTab() funcs := make([]Func, ft.Count()) syms := make([]Sym, len(funcs)) for i := range funcs { f := &funcs[i] f.Entry = ft.pc(i) f.End = ft.pc(i + 1) info := t.funcData(uint32(i)) f.LineTable = t f.FrameSize = int(info.deferreturn()) // Additions: // numFuncField is the number of (32 bit) fields in _func (src/runtime/runtime2.go) // Note that the last 4 fields are 32 bits combined. This number is 11 for go1.20, // 10 for earlier versions down to go1.16, and 9 before that. var numFuncFields uint32 = 11 if t.version < ver116 { numFuncFields = 9 } else if t.version < ver120 { numFuncFields = 10 } f.inlineTreeOffset = info.funcdataOffset(funcdata_InlTree, numFuncFields) f.inlineTreeCount = 1 + t.maxInlineTreeIndexValue(info, numFuncFields) syms[i] = Sym{ Value: f.Entry, Type: 'T', Name: t.funcName(info.nameOff()), GoType: 0, Func: f, goVersion: t.version, } f.Sym = &syms[i] } return funcs } // findFunc returns the funcData corresponding to the given program counter. func (t *LineTable) findFunc(pc uint64) funcData { ft := t.funcTab() if pc < ft.pc(0) || pc >= ft.pc(ft.Count()) { return funcData{} } idx := sort.Search(int(t.nfunctab), func(i int) bool { return ft.pc(i) > pc }) idx-- return t.funcData(uint32(idx)) } // readvarint reads, removes, and returns a varint from *pp. func (t *LineTable) readvarint(pp *[]byte) uint32 { var v, shift uint32 p := *pp for shift = 0; ; shift += 7 { b := p[0] p = p[1:] v |= (uint32(b) & 0x7F) << shift if b&0x80 == 0 { break } } *pp = p return v } // funcName returns the name of the function found at off. func (t *LineTable) funcName(off uint32) string { if s, ok := t.funcNames[off]; ok { return s } i := bytes.IndexByte(t.funcnametab[off:], 0) s := string(t.funcnametab[off : off+uint32(i)]) t.funcNames[off] = s return s } // stringFrom returns a Go string found at off from a position. func (t *LineTable) stringFrom(arr []byte, off uint32) string { if s, ok := t.strings[off]; ok { return s } i := bytes.IndexByte(arr[off:], 0) s := string(arr[off : off+uint32(i)]) t.strings[off] = s return s } // string returns a Go string found at off. func (t *LineTable) string(off uint32) string { return t.stringFrom(t.funcdata, off) } // functabFieldSize returns the size in bytes of a single functab field. func (t *LineTable) functabFieldSize() int { if t.version >= ver118 { return 4 } return int(t.ptrsize) } // funcTab returns t's funcTab. func (t *LineTable) funcTab() funcTab { return funcTab{LineTable: t, sz: t.functabFieldSize()} } // funcTab is memory corresponding to a slice of functab structs, followed by an invalid PC. // A functab struct is a PC and a func offset. type funcTab struct { *LineTable sz int // cached result of t.functabFieldSize } // Count returns the number of func entries in f. func (f funcTab) Count() int { return int(f.nfunctab) } // pc returns the PC of the i'th func in f. func (f funcTab) pc(i int) uint64 { u := f.uint(f.functab[2*i*f.sz:]) if f.version >= ver118 { u += f.textStart } return u } // funcOff returns the funcdata offset of the i'th func in f. func (f funcTab) funcOff(i int) uint64 { return f.uint(f.functab[(2*i+1)*f.sz:]) } // uint returns the uint stored at b. func (f funcTab) uint(b []byte) uint64 { if f.sz == 4 { return uint64(f.binary.Uint32(b)) } return f.binary.Uint64(b) } // funcData is memory corresponding to an _func struct. type funcData struct { t *LineTable // LineTable this data is a part of data []byte // raw memory for the function } // funcData returns the ith funcData in t.functab. func (t *LineTable) funcData(i uint32) funcData { data := t.funcdata[t.funcTab().funcOff(int(i)):] return funcData{t: t, data: data} } // IsZero reports whether f is the zero value. func (f funcData) IsZero() bool { return f.t == nil && f.data == nil } // entryPC returns the func's entry PC. func (f *funcData) entryPC() uint64 { // In Go 1.18, the first field of _func changed // from a uintptr entry PC to a uint32 entry offset. if f.t.version >= ver118 { // TODO: support multiple text sections. // See runtime/symtab.go:(*moduledata).textAddr. return uint64(f.t.binary.Uint32(f.data)) + f.t.textStart } return f.t.uintptr(f.data) } func (f funcData) nameOff() uint32 { return f.field(1) } func (f funcData) deferreturn() uint32 { return f.field(3) } func (f funcData) pcfile() uint32 { return f.field(5) } func (f funcData) pcln() uint32 { return f.field(6) } func (f funcData) cuOffset() uint32 { return f.field(8) } // field returns the nth field of the _func struct. // It panics if n == 0 or n > 9; for n == 0, call f.entryPC. // Most callers should use a named field accessor (just above). func (f funcData) field(n uint32) uint32 { if n == 0 || n > 9 { panic("bad funcdata field") } // Addition: some code deleted here to support inlining. off := f.fieldOffset(n) data := f.data[off:] return f.t.binary.Uint32(data) } // step advances to the next pc, value pair in the encoded table. func (t *LineTable) step(p *[]byte, pc *uint64, val *int32, first bool) bool { uvdelta := t.readvarint(p) if uvdelta == 0 && !first { return false } if uvdelta&1 != 0 { uvdelta = ^(uvdelta >> 1) } else { uvdelta >>= 1 } vdelta := int32(uvdelta) pcdelta := t.readvarint(p) * t.quantum *pc += uint64(pcdelta) *val += vdelta return true } // pcvalue reports the value associated with the target pc. // off is the offset to the beginning of the pc-value table, // and entry is the start PC for the corresponding function. func (t *LineTable) pcvalue(off uint32, entry, targetpc uint64) int32 { p := t.pctab[off:] val := int32(-1) pc := entry for t.step(&p, &pc, &val, pc == entry) { if targetpc < pc { return val } } return -1 } // findFileLine scans one function in the binary looking for a // program counter in the given file on the given line. // It does so by running the pc-value tables mapping program counter // to file number. Since most functions come from a single file, these // are usually short and quick to scan. If a file match is found, then the // code goes to the expense of looking for a simultaneous line number match. func (t *LineTable) findFileLine(entry uint64, filetab, linetab uint32, filenum, line int32, cutab []byte) uint64 { if filetab == 0 || linetab == 0 { return 0 } fp := t.pctab[filetab:] fl := t.pctab[linetab:] fileVal := int32(-1) filePC := entry lineVal := int32(-1) linePC := entry fileStartPC := filePC for t.step(&fp, &filePC, &fileVal, filePC == entry) { fileIndex := fileVal if t.version == ver116 || t.version == ver118 || t.version == ver120 { fileIndex = int32(t.binary.Uint32(cutab[fileVal*4:])) } if fileIndex == filenum && fileStartPC < filePC { // fileIndex is in effect starting at fileStartPC up to // but not including filePC, and it's the file we want. // Run the PC table looking for a matching line number // or until we reach filePC. lineStartPC := linePC for linePC < filePC && t.step(&fl, &linePC, &lineVal, linePC == entry) { // lineVal is in effect until linePC, and lineStartPC < filePC. if lineVal == line { if fileStartPC <= lineStartPC { return lineStartPC } if fileStartPC < linePC { return fileStartPC } } lineStartPC = linePC } } fileStartPC = filePC } return 0 } // go12PCToLine maps program counter to line number for the Go 1.2+ pcln table. func (t *LineTable) go12PCToLine(pc uint64) (line int) { defer func() { if !disableRecover && recover() != nil { line = -1 } }() f := t.findFunc(pc) if f.IsZero() { return -1 } entry := f.entryPC() linetab := f.pcln() return int(t.pcvalue(linetab, entry, pc)) } // go12PCToFile maps program counter to file name for the Go 1.2+ pcln table. func (t *LineTable) go12PCToFile(pc uint64) (file string) { defer func() { if !disableRecover && recover() != nil { file = "" } }() f := t.findFunc(pc) if f.IsZero() { return "" } entry := f.entryPC() filetab := f.pcfile() fno := t.pcvalue(filetab, entry, pc) if t.version == ver12 { if fno <= 0 { return "" } return t.string(t.binary.Uint32(t.filetab[4*fno:])) } // Go ≥ 1.16 if fno < 0 { // 0 is valid for ≥ 1.16 return "" } cuoff := f.cuOffset() if fnoff := t.binary.Uint32(t.cutab[(cuoff+uint32(fno))*4:]); fnoff != ^uint32(0) { return t.stringFrom(t.filetab, fnoff) } return "" } // go12LineToPC maps a (file, line) pair to a program counter for the Go 1.2+ pcln table. func (t *LineTable) go12LineToPC(file string, line int) (pc uint64) { defer func() { if !disableRecover && recover() != nil { pc = 0 } }() t.initFileMap() filenum, ok := t.fileMap[file] if !ok { return 0 } // Scan all functions. // If this turns out to be a bottleneck, we could build a map[int32][]int32 // mapping file number to a list of functions with code from that file. var cutab []byte for i := uint32(0); i < t.nfunctab; i++ { f := t.funcData(i) entry := f.entryPC() filetab := f.pcfile() linetab := f.pcln() if t.version == ver116 || t.version == ver118 || t.version == ver120 { if f.cuOffset() == ^uint32(0) { // skip functions without compilation unit (not real function, or linker generated) continue } cutab = t.cutab[f.cuOffset()*4:] } pc := t.findFileLine(entry, filetab, linetab, int32(filenum), int32(line), cutab) if pc != 0 { return pc } } return 0 } // initFileMap initializes the map from file name to file number. func (t *LineTable) initFileMap() { t.mu.Lock() defer t.mu.Unlock() if t.fileMap != nil { return } m := make(map[string]uint32) if t.version == ver12 { for i := uint32(1); i < t.nfiletab; i++ { s := t.string(t.binary.Uint32(t.filetab[4*i:])) m[s] = i } } else { var pos uint32 for i := uint32(0); i < t.nfiletab; i++ { s := t.stringFrom(t.filetab, pos) m[s] = pos pos += uint32(len(s) + 1) } } t.fileMap = m } // go12MapFiles adds to m a key for every file in the Go 1.2 LineTable. // Every key maps to obj. That's not a very interesting map, but it provides // a way for callers to obtain the list of files in the program. func (t *LineTable) go12MapFiles(m map[string]*Obj, obj *Obj) { if !disableRecover { defer func() { _ = recover() }() } t.initFileMap() for file := range t.fileMap { m[file] = obj } } // disableRecover causes this package not to swallow panics. // This is useful when making changes. const disableRecover = true vuln-1.0.1/internal/vulncheck/internal/gosym/pclntab_test.go000066400000000000000000000222601446745451500242770ustar00rootroot00000000000000// Copyright 2009 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 gosym import ( "bytes" "compress/gzip" "debug/elf" "io" "os" "runtime" "strings" "testing" "golang.org/x/vuln/internal/test" "golang.org/x/vuln/internal/testenv" ) func dotest(t *testing.T) (binaryName string, cleanup func()) { testenv.NeedsGoBuild(t) // For now, only works on amd64 platforms. if runtime.GOARCH != "amd64" { t.Skipf("skipping on non-AMD64 system %s", runtime.GOARCH) } // This test builds a Linux/AMD64 binary. Skipping in short mode if cross compiling. if runtime.GOOS != "linux" && testing.Short() { t.Skipf("skipping in short mode on non-Linux system %s", runtime.GOARCH) } return test.GoBuild(t, "testdata", "", false, "GOOS", "linux") } // skipIfNotELF skips the test if we are not running on an ELF system. // These tests open and examine the test binary, and use elf.Open to do so. func skipIfNotELF(t *testing.T) { switch runtime.GOOS { case "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris", "illumos": // OK. default: t.Skipf("skipping on non-ELF system %s", runtime.GOOS) } } func getTable(t *testing.T) *Table { f, tab := crack(os.Args[0], t) f.Close() return tab } func crack(file string, t *testing.T) (*elf.File, *Table) { // Open self f, err := elf.Open(file) if err != nil { t.Fatal(err) } return parse(file, f, t) } func parse(file string, f *elf.File, t *testing.T) (*elf.File, *Table) { s := f.Section(".gosymtab") if s == nil { t.Skip("no .gosymtab section") } symdat, err := s.Data() if err != nil { f.Close() t.Fatalf("reading %s gosymtab: %v", file, err) } pclndat, err := f.Section(".gopclntab").Data() if err != nil { f.Close() t.Fatalf("reading %s gopclntab: %v", file, err) } pcln := NewLineTable(pclndat, f.Section(".text").Addr) tab, err := NewTable(symdat, pcln) if err != nil { f.Close() t.Fatalf("parsing %s gosymtab: %v", file, err) } return f, tab } func TestLineFromAline(t *testing.T) { skipIfNotELF(t) tab := getTable(t) if tab.go12line != nil { // aline's don't exist in the Go 1.2 table. t.Skip("not relevant to Go 1.2 symbol table") } // Find the sym package pkg := tab.LookupFunc("debug/gosym.TestLineFromAline").Obj if pkg == nil { t.Fatalf("nil pkg") } // Walk every absolute line and ensure that we hit every // source line monotonically lastline := make(map[string]int) final := -1 for i := 0; i < 10000; i++ { path, line := pkg.lineFromAline(i) // Check for end of object if path == "" { if final == -1 { final = i - 1 } continue } else if final != -1 { t.Fatalf("reached end of package at absolute line %d, but absolute line %d mapped to %s:%d", final, i, path, line) } // It's okay to see files multiple times (e.g., sys.a) if line == 1 { lastline[path] = 1 continue } // Check that the is the next line in path ll, ok := lastline[path] if !ok { t.Errorf("file %s starts on line %d", path, line) } else if line != ll+1 { t.Fatalf("expected next line of file %s to be %d, got %d", path, ll+1, line) } lastline[path] = line } if final == -1 { t.Errorf("never reached end of object") } } func TestLineAline(t *testing.T) { skipIfNotELF(t) tab := getTable(t) if tab.go12line != nil { // aline's don't exist in the Go 1.2 table. t.Skip("not relevant to Go 1.2 symbol table") } for _, o := range tab.Files { // A source file can appear multiple times in a // object. alineFromLine will always return alines in // the first file, so track which lines we've seen. found := make(map[string]int) for i := 0; i < 1000; i++ { path, line := o.lineFromAline(i) if path == "" { break } // cgo files are full of 'Z' symbols, which we don't handle if len(path) > 4 && path[len(path)-4:] == ".cgo" { continue } if minline, ok := found[path]; path != "" && ok { if minline >= line { // We've already covered this file continue } } found[path] = line a, err := o.alineFromLine(path, line) if err != nil { t.Errorf("absolute line %d in object %s maps to %s:%d, but mapping that back gives error %s", i, o.Paths[0].Name, path, line, err) } else if a != i { t.Errorf("absolute line %d in object %s maps to %s:%d, which maps back to absolute line %d\n", i, o.Paths[0].Name, path, line, a) } } } } func TestPCLine(t *testing.T) { pclinetestBinary, cleanup := dotest(t) defer cleanup() f, tab := crack(pclinetestBinary, t) defer f.Close() text := f.Section(".text") textdat, err := text.Data() if err != nil { t.Fatalf("reading .text: %v", err) } // Test PCToLine sym := tab.LookupFunc("main.linefrompc") wantLine := 0 for pc := sym.Entry; pc < sym.End; pc++ { off := pc - text.Addr // TODO(rsc): should not need off; bug in 8g if textdat[off] == 255 { break } wantLine += int(textdat[off]) t.Logf("off is %d %#x (max %d)", off, textdat[off], sym.End-pc) file, line, fn := tab.PCToLine(pc) if fn == nil { t.Errorf("failed to get line of PC %#x", pc) } else if !strings.HasSuffix(file, "pclinetest.s") || line != wantLine || fn != sym { t.Errorf("PCToLine(%#x) = %s:%d (%s), want %s:%d (%s)", pc, file, line, fn.Name, "pclinetest.s", wantLine, sym.Name) } } // Test LineToPC sym = tab.LookupFunc("main.pcfromline") lookupline := -1 wantLine = 0 off := uint64(0) // TODO(rsc): should not need off; bug in 8g for pc := sym.Value; pc < sym.End; pc += 2 + uint64(textdat[off]) { file, line, fn := tab.PCToLine(pc) off = pc - text.Addr if textdat[off] == 255 { break } wantLine += int(textdat[off]) if line != wantLine { t.Errorf("expected line %d at PC %#x in pcfromline, got %d", wantLine, pc, line) off = pc + 1 - text.Addr continue } if lookupline == -1 { lookupline = line } for ; lookupline <= line; lookupline++ { pc2, fn2, err := tab.LineToPC(file, lookupline) if lookupline != line { // Should be nothing on this line if err == nil { t.Errorf("expected no PC at line %d, got %#x (%s)", lookupline, pc2, fn2.Name) } } else if err != nil { t.Errorf("failed to get PC of line %d: %s", lookupline, err) } else if pc != pc2 { t.Errorf("expected PC %#x (%s) at line %d, got PC %#x (%s)", pc, fn.Name, line, pc2, fn2.Name) } } off = pc + 1 - text.Addr } } func TestSymVersion(t *testing.T) { skipIfNotELF(t) table := getTable(t) if table.go12line == nil { t.Skip("not relevant to Go 1.2+ symbol table") } for _, fn := range table.Funcs { if fn.goVersion == verUnknown { t.Fatalf("unexpected symbol version: %v", fn) } } } // read115Executable returns a hello world executable compiled by Go 1.15. // // The file was compiled in /tmp/hello.go: // // package main // // func main() { // println("hello") // } func read115Executable(tb testing.TB) []byte { zippedDat, err := os.ReadFile("testdata/pcln115.gz") if err != nil { tb.Fatal(err) } var gzReader *gzip.Reader gzReader, err = gzip.NewReader(bytes.NewBuffer(zippedDat)) if err != nil { tb.Fatal(err) } var dat []byte dat, err = io.ReadAll(gzReader) if err != nil { tb.Fatal(err) } return dat } // Test that we can parse a pclntab from 1.15. func Test115PclnParsing(t *testing.T) { dat := read115Executable(t) const textStart = 0x1001000 pcln := NewLineTable(dat, textStart) tab, err := NewTable(nil, pcln) if err != nil { t.Fatal(err) } var f *Func var pc uint64 pc, f, err = tab.LineToPC("/tmp/hello.go", 3) if err != nil { t.Fatal(err) } if pcln.version != ver12 { t.Fatal("Expected pcln to parse as an older version") } if pc != 0x105c280 { t.Fatalf("expect pc = 0x105c280, got 0x%x", pc) } if f.Name != "main.main" { t.Fatalf("expected to parse name as main.main, got %v", f.Name) } } var ( sinkLineTable *LineTable sinkTable *Table ) func Benchmark115(b *testing.B) { dat := read115Executable(b) const textStart = 0x1001000 b.Run("NewLineTable", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { sinkLineTable = NewLineTable(dat, textStart) } }) pcln := NewLineTable(dat, textStart) b.Run("NewTable", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { var err error sinkTable, err = NewTable(nil, pcln) if err != nil { b.Fatal(err) } } }) tab, err := NewTable(nil, pcln) if err != nil { b.Fatal(err) } b.Run("LineToPC", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { var f *Func var pc uint64 pc, f, err = tab.LineToPC("/tmp/hello.go", 3) if err != nil { b.Fatal(err) } if pcln.version != ver12 { b.Fatalf("want version=%d, got %d", ver12, pcln.version) } if pc != 0x105c280 { b.Fatalf("want pc=0x105c280, got 0x%x", pc) } if f.Name != "main.main" { b.Fatalf("want name=main.main, got %q", f.Name) } } }) b.Run("PCToLine", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { file, line, fn := tab.PCToLine(0x105c280) if file != "/tmp/hello.go" { b.Fatalf("want name=/tmp/hello.go, got %q", file) } if line != 3 { b.Fatalf("want line=3, got %d", line) } if fn.Name != "main.main" { b.Fatalf("want name=main.main, got %q", fn.Name) } } }) } vuln-1.0.1/internal/vulncheck/internal/gosym/symtab.go000066400000000000000000000436751446745451500231310ustar00rootroot00000000000000// Copyright 2009 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 gosym implements access to the Go symbol // and line number tables embedded in Go binaries generated // by the gc compilers. package gosym import ( "bytes" "encoding/binary" "fmt" "strconv" "strings" ) /* * Symbols */ // A Sym represents a single symbol table entry. type Sym struct { Value uint64 Type byte Name string GoType uint64 // If this symbol is a function symbol, the corresponding Func Func *Func goVersion version } // Static reports whether this symbol is static (not visible outside its file). func (s *Sym) Static() bool { return s.Type >= 'a' } // nameWithoutInst returns s.Name if s.Name has no brackets (does not reference an // instantiated type, function, or method). If s.Name contains brackets, then it // returns s.Name with all the contents between (and including) the outermost left // and right bracket removed. This is useful to ignore any extra slashes or dots // inside the brackets from the string searches below, where needed. func (s *Sym) nameWithoutInst() string { start := strings.Index(s.Name, "[") if start < 0 { return s.Name } end := strings.LastIndex(s.Name, "]") if end < 0 { // Malformed name, should contain closing bracket too. return s.Name } return s.Name[0:start] + s.Name[end+1:] } // PackageName returns the package part of the symbol name, // or the empty string if there is none. func (s *Sym) PackageName() string { name := s.nameWithoutInst() // Since go1.20, a prefix of "type:" and "go:" is a compiler-generated symbol, // they do not belong to any package. // // See cmd/compile/internal/base/link.go:ReservedImports variable. if s.goVersion >= ver120 && (strings.HasPrefix(name, "go:") || strings.HasPrefix(name, "type:")) { return "" } // For go1.18 and below, the prefix are "type." and "go." instead. if s.goVersion <= ver118 && (strings.HasPrefix(name, "go.") || strings.HasPrefix(name, "type.")) { return "" } pathend := strings.LastIndex(name, "/") if pathend < 0 { pathend = 0 } if i := strings.Index(name[pathend:], "."); i != -1 { return name[:pathend+i] } return "" } // ReceiverName returns the receiver type name of this symbol, // or the empty string if there is none. A receiver name is only detected in // the case that s.Name is fully-specified with a package name. func (s *Sym) ReceiverName() string { name := s.nameWithoutInst() // If we find a slash in name, it should precede any bracketed expression // that was removed, so pathend will apply correctly to name and s.Name. pathend := strings.LastIndex(name, "/") if pathend < 0 { pathend = 0 } // Find the first dot after pathend (or from the beginning, if there was // no slash in name). l := strings.Index(name[pathend:], ".") // Find the last dot after pathend (or the beginning). r := strings.LastIndex(name[pathend:], ".") if l == -1 || r == -1 || l == r { // There is no receiver if we didn't find two distinct dots after pathend. return "" } // Given there is a trailing '.' that is in name, find it now in s.Name. // pathend+l should apply to s.Name, because it should be the dot in the // package name. r = strings.LastIndex(s.Name[pathend:], ".") return s.Name[pathend+l+1 : pathend+r] } // BaseName returns the symbol name without the package or receiver name. func (s *Sym) BaseName() string { name := s.nameWithoutInst() if i := strings.LastIndex(name, "."); i != -1 { if s.Name != name { brack := strings.Index(s.Name, "[") if i > brack { // BaseName is a method name after the brackets, so // recalculate for s.Name. Otherwise, i applies // correctly to s.Name, since it is before the // brackets. i = strings.LastIndex(s.Name, ".") } } return s.Name[i+1:] } return s.Name } // A Func collects information about a single function. type Func struct { Entry uint64 *Sym End uint64 Params []*Sym // nil for Go 1.3 and later binaries Locals []*Sym // nil for Go 1.3 and later binaries FrameSize int LineTable *LineTable Obj *Obj // Addition: extra data to support inlining. inlTree } // An Obj represents a collection of functions in a symbol table. // // The exact method of division of a binary into separate Objs is an internal detail // of the symbol table format. // // In early versions of Go each source file became a different Obj. // // In Go 1 and Go 1.1, each package produced one Obj for all Go sources // and one Obj per C source file. // // In Go 1.2, there is a single Obj for the entire program. type Obj struct { // Funcs is a list of functions in the Obj. Funcs []Func // In Go 1.1 and earlier, Paths is a list of symbols corresponding // to the source file names that produced the Obj. // In Go 1.2, Paths is nil. // Use the keys of Table.Files to obtain a list of source files. Paths []Sym // meta } /* * Symbol tables */ // Table represents a Go symbol table. It stores all of the // symbols decoded from the program and provides methods to translate // between symbols, names, and addresses. type Table struct { Syms []Sym // nil for Go 1.3 and later binaries Funcs []Func Files map[string]*Obj // for Go 1.2 and later all files map to one Obj Objs []Obj // for Go 1.2 and later only one Obj in slice go12line *LineTable // Go 1.2 line number table } type sym struct { value uint64 gotype uint64 typ byte name []byte } var ( littleEndianSymtab = []byte{0xFD, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00} bigEndianSymtab = []byte{0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00} oldLittleEndianSymtab = []byte{0xFE, 0xFF, 0xFF, 0xFF, 0x00, 0x00} ) func walksymtab(data []byte, fn func(sym) error) error { if len(data) == 0 { // missing symtab is okay return nil } var order binary.ByteOrder = binary.BigEndian newTable := false switch { case bytes.HasPrefix(data, oldLittleEndianSymtab): // Same as Go 1.0, but little endian. // Format was used during interim development between Go 1.0 and Go 1.1. // Should not be widespread, but easy to support. data = data[6:] order = binary.LittleEndian case bytes.HasPrefix(data, bigEndianSymtab): newTable = true case bytes.HasPrefix(data, littleEndianSymtab): newTable = true order = binary.LittleEndian } var ptrsz int if newTable { if len(data) < 8 { return &DecodingError{len(data), "unexpected EOF", nil} } ptrsz = int(data[7]) if ptrsz != 4 && ptrsz != 8 { return &DecodingError{7, "invalid pointer size", ptrsz} } data = data[8:] } var s sym p := data for len(p) >= 4 { var typ byte if newTable { // Symbol type, value, Go type. typ = p[0] & 0x3F wideValue := p[0]&0x40 != 0 goType := p[0]&0x80 != 0 if typ < 26 { typ += 'A' } else { typ += 'a' - 26 } s.typ = typ p = p[1:] if wideValue { if len(p) < ptrsz { return &DecodingError{len(data), "unexpected EOF", nil} } // fixed-width value if ptrsz == 8 { s.value = order.Uint64(p[0:8]) p = p[8:] } else { s.value = uint64(order.Uint32(p[0:4])) p = p[4:] } } else { // varint value s.value = 0 shift := uint(0) for len(p) > 0 && p[0]&0x80 != 0 { s.value |= uint64(p[0]&0x7F) << shift shift += 7 p = p[1:] } if len(p) == 0 { return &DecodingError{len(data), "unexpected EOF", nil} } s.value |= uint64(p[0]) << shift p = p[1:] } if goType { if len(p) < ptrsz { return &DecodingError{len(data), "unexpected EOF", nil} } // fixed-width go type if ptrsz == 8 { s.gotype = order.Uint64(p[0:8]) p = p[8:] } else { s.gotype = uint64(order.Uint32(p[0:4])) p = p[4:] } } } else { // Value, symbol type. s.value = uint64(order.Uint32(p[0:4])) if len(p) < 5 { return &DecodingError{len(data), "unexpected EOF", nil} } typ = p[4] if typ&0x80 == 0 { return &DecodingError{len(data) - len(p) + 4, "bad symbol type", typ} } typ &^= 0x80 s.typ = typ p = p[5:] } // Name. var i int var nnul int for i = 0; i < len(p); i++ { if p[i] == 0 { nnul = 1 break } } switch typ { case 'z', 'Z': p = p[i+nnul:] for i = 0; i+2 <= len(p); i += 2 { if p[i] == 0 && p[i+1] == 0 { nnul = 2 break } } } if len(p) < i+nnul { return &DecodingError{len(data), "unexpected EOF", nil} } s.name = p[0:i] i += nnul p = p[i:] if !newTable { if len(p) < 4 { return &DecodingError{len(data), "unexpected EOF", nil} } // Go type. s.gotype = uint64(order.Uint32(p[:4])) p = p[4:] } _ = fn(s) } return nil } // NewTable decodes the Go symbol table (the ".gosymtab" section in ELF), // returning an in-memory representation. // Starting with Go 1.3, the Go symbol table no longer includes symbol data. func NewTable(symtab []byte, pcln *LineTable) (*Table, error) { var n int err := walksymtab(symtab, func(s sym) error { n++ return nil }) if err != nil { return nil, err } var t Table if pcln.isGo12() { t.go12line = pcln } fname := make(map[uint16]string) t.Syms = make([]Sym, 0, n) nf := 0 nz := 0 lasttyp := uint8(0) err = walksymtab(symtab, func(s sym) error { n := len(t.Syms) t.Syms = t.Syms[0 : n+1] ts := &t.Syms[n] ts.Type = s.typ ts.Value = s.value ts.GoType = s.gotype ts.goVersion = pcln.version switch s.typ { default: // rewrite name to use . instead of · (c2 b7) w := 0 b := s.name for i := 0; i < len(b); i++ { if b[i] == 0xc2 && i+1 < len(b) && b[i+1] == 0xb7 { i++ b[i] = '.' } b[w] = b[i] w++ } ts.Name = string(s.name[0:w]) case 'z', 'Z': if lasttyp != 'z' && lasttyp != 'Z' { nz++ } for i := 0; i < len(s.name); i += 2 { eltIdx := binary.BigEndian.Uint16(s.name[i : i+2]) elt, ok := fname[eltIdx] if !ok { return &DecodingError{-1, "bad filename code", eltIdx} } if n := len(ts.Name); n > 0 && ts.Name[n-1] != '/' { ts.Name += "/" } ts.Name += elt } } switch s.typ { case 'T', 't', 'L', 'l': nf++ case 'f': fname[uint16(s.value)] = ts.Name } lasttyp = s.typ return nil }) if err != nil { return nil, err } t.Funcs = make([]Func, 0, nf) t.Files = make(map[string]*Obj) var obj *Obj if t.go12line != nil { // Put all functions into one Obj. t.Objs = make([]Obj, 1) obj = &t.Objs[0] t.go12line.go12MapFiles(t.Files, obj) } else { t.Objs = make([]Obj, 0, nz) } // Count text symbols and attach frame sizes, parameters, and // locals to them. Also, find object file boundaries. lastf := 0 for i := 0; i < len(t.Syms); i++ { sym := &t.Syms[i] switch sym.Type { case 'Z', 'z': // path symbol if t.go12line != nil { // Go 1.2 binaries have the file information elsewhere. Ignore. break } // Finish the current object if obj != nil { obj.Funcs = t.Funcs[lastf:] } lastf = len(t.Funcs) // Start new object n := len(t.Objs) t.Objs = t.Objs[0 : n+1] obj = &t.Objs[n] // Count & copy path symbols var end int for end = i + 1; end < len(t.Syms); end++ { if c := t.Syms[end].Type; c != 'Z' && c != 'z' { break } } obj.Paths = t.Syms[i:end] i = end - 1 // loop will i++ // Record file names depth := 0 for j := range obj.Paths { s := &obj.Paths[j] if s.Name == "" { depth-- } else { if depth == 0 { t.Files[s.Name] = obj } depth++ } } case 'T', 't', 'L', 'l': // text symbol if n := len(t.Funcs); n > 0 { t.Funcs[n-1].End = sym.Value } if sym.Name == "runtime.etext" || sym.Name == "etext" { continue } // Count parameter and local (auto) syms var np, na int var end int countloop: for end = i + 1; end < len(t.Syms); end++ { switch t.Syms[end].Type { case 'T', 't', 'L', 'l', 'Z', 'z': break countloop case 'p': np++ case 'a': na++ } } // Fill in the function symbol n := len(t.Funcs) t.Funcs = t.Funcs[0 : n+1] fn := &t.Funcs[n] sym.Func = fn fn.Params = make([]*Sym, 0, np) fn.Locals = make([]*Sym, 0, na) fn.Sym = sym fn.Entry = sym.Value fn.Obj = obj if t.go12line != nil { // All functions share the same line table. // It knows how to narrow down to a specific // function quickly. fn.LineTable = t.go12line } else if pcln != nil { fn.LineTable = pcln.slice(fn.Entry) pcln = fn.LineTable } for j := i; j < end; j++ { s := &t.Syms[j] switch s.Type { case 'm': fn.FrameSize = int(s.Value) case 'p': n := len(fn.Params) fn.Params = fn.Params[0 : n+1] fn.Params[n] = s case 'a': n := len(fn.Locals) fn.Locals = fn.Locals[0 : n+1] fn.Locals[n] = s } } i = end - 1 // loop will i++ } } if t.go12line != nil && nf == 0 { t.Funcs = t.go12line.go12Funcs() } if obj != nil { obj.Funcs = t.Funcs[lastf:] } return &t, nil } // PCToFunc returns the function containing the program counter pc, // or nil if there is no such function. func (t *Table) PCToFunc(pc uint64) *Func { funcs := t.Funcs for len(funcs) > 0 { m := len(funcs) / 2 fn := &funcs[m] switch { case pc < fn.Entry: funcs = funcs[0:m] case fn.Entry <= pc && pc < fn.End: return fn default: funcs = funcs[m+1:] } } return nil } // PCToLine looks up line number information for a program counter. // If there is no information, it returns fn == nil. func (t *Table) PCToLine(pc uint64) (file string, line int, fn *Func) { if fn = t.PCToFunc(pc); fn == nil { return } if t.go12line != nil { file = t.go12line.go12PCToFile(pc) line = t.go12line.go12PCToLine(pc) } else { file, line = fn.Obj.lineFromAline(fn.LineTable.PCToLine(pc)) } return } // LineToPC looks up the first program counter on the given line in // the named file. It returns UnknownPathError or UnknownLineError if // there is an error looking up this line. func (t *Table) LineToPC(file string, line int) (pc uint64, fn *Func, err error) { obj, ok := t.Files[file] if !ok { return 0, nil, UnknownFileError(file) } if t.go12line != nil { pc := t.go12line.go12LineToPC(file, line) if pc == 0 { return 0, nil, &UnknownLineError{file, line} } return pc, t.PCToFunc(pc), nil } abs, err := obj.alineFromLine(file, line) if err != nil { return } for i := range obj.Funcs { f := &obj.Funcs[i] pc := f.LineTable.LineToPC(abs, f.End) if pc != 0 { return pc, f, nil } } return 0, nil, &UnknownLineError{file, line} } // LookupSym returns the text, data, or bss symbol with the given name, // or nil if no such symbol is found. func (t *Table) LookupSym(name string) *Sym { // TODO(austin) Maybe make a map for i := range t.Syms { s := &t.Syms[i] switch s.Type { case 'T', 't', 'L', 'l', 'D', 'd', 'B', 'b': if s.Name == name { return s } } } return nil } // LookupFunc returns the text, data, or bss symbol with the given name, // or nil if no such symbol is found. func (t *Table) LookupFunc(name string) *Func { for i := range t.Funcs { f := &t.Funcs[i] if f.Sym.Name == name { return f } } return nil } // SymByAddr returns the text, data, or bss symbol starting at the given address. func (t *Table) SymByAddr(addr uint64) *Sym { for i := range t.Syms { s := &t.Syms[i] switch s.Type { case 'T', 't', 'L', 'l', 'D', 'd', 'B', 'b': if s.Value == addr { return s } } } return nil } /* * Object files */ // This is legacy code for Go 1.1 and earlier, which used the // Plan 9 format for pc-line tables. This code was never quite // correct. It's probably very close, and it's usually correct, but // we never quite found all the corner cases. // // Go 1.2 and later use a simpler format, documented at golang.org/s/go12symtab. func (o *Obj) lineFromAline(aline int) (string, int) { type stackEnt struct { path string start int offset int prev *stackEnt } noPath := &stackEnt{"", 0, 0, nil} tos := noPath pathloop: for _, s := range o.Paths { val := int(s.Value) switch { case val > aline: break pathloop case val == 1: // Start a new stack tos = &stackEnt{s.Name, val, 0, noPath} case s.Name == "": // Pop if tos == noPath { return "", 0 } tos.prev.offset += val - tos.start tos = tos.prev default: // Push tos = &stackEnt{s.Name, val, 0, tos} } } if tos == noPath { return "", 0 } return tos.path, aline - tos.start - tos.offset + 1 } func (o *Obj) alineFromLine(path string, line int) (int, error) { if line < 1 { return 0, &UnknownLineError{path, line} } for i, s := range o.Paths { // Find this path if s.Name != path { continue } // Find this line at this stack level depth := 0 var incstart int line += int(s.Value) pathloop: for _, s := range o.Paths[i:] { val := int(s.Value) switch { case depth == 1 && val >= line: return line - 1, nil case s.Name == "": depth-- if depth == 0 { break pathloop } else if depth == 1 { line += val - incstart } default: if depth == 1 { incstart = val } depth++ } } return 0, &UnknownLineError{path, line} } return 0, UnknownFileError(path) } /* * Errors */ // UnknownFileError represents a failure to find the specific file in // the symbol table. type UnknownFileError string func (e UnknownFileError) Error() string { return "unknown file: " + string(e) } // UnknownLineError represents a failure to map a line to a program // counter, either because the line is beyond the bounds of the file // or because there is no code on the given line. type UnknownLineError struct { File string Line int } func (e *UnknownLineError) Error() string { return "no code at " + e.File + ":" + strconv.Itoa(e.Line) } // DecodingError represents an error during the decoding of // the symbol table. type DecodingError struct { off int msg string val any } func (e *DecodingError) Error() string { msg := e.msg if e.val != nil { msg += fmt.Sprintf(" '%v'", e.val) } msg += fmt.Sprintf(" at byte %#x", e.off) return msg } vuln-1.0.1/internal/vulncheck/internal/gosym/symtab_test.go000066400000000000000000000104361446745451500241550ustar00rootroot00000000000000// Copyright 2016 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 gosym import ( "fmt" "testing" ) func assertString(t *testing.T, dsc, out, tgt string) { if out != tgt { t.Fatalf("Expected: %q Actual: %q for %s", tgt, out, dsc) } } func TestStandardLibPackage(t *testing.T) { s1 := Sym{Name: "io.(*LimitedReader).Read"} s2 := Sym{Name: "io.NewSectionReader"} assertString(t, fmt.Sprintf("package of %q", s1.Name), s1.PackageName(), "io") assertString(t, fmt.Sprintf("package of %q", s2.Name), s2.PackageName(), "io") assertString(t, fmt.Sprintf("receiver of %q", s1.Name), s1.ReceiverName(), "(*LimitedReader)") assertString(t, fmt.Sprintf("receiver of %q", s2.Name), s2.ReceiverName(), "") } func TestStandardLibPathPackage(t *testing.T) { s1 := Sym{Name: "debug/gosym.(*LineTable).PCToLine"} s2 := Sym{Name: "debug/gosym.NewTable"} assertString(t, fmt.Sprintf("package of %q", s1.Name), s1.PackageName(), "debug/gosym") assertString(t, fmt.Sprintf("package of %q", s2.Name), s2.PackageName(), "debug/gosym") assertString(t, fmt.Sprintf("receiver of %q", s1.Name), s1.ReceiverName(), "(*LineTable)") assertString(t, fmt.Sprintf("receiver of %q", s2.Name), s2.ReceiverName(), "") } func TestGenericNames(t *testing.T) { s1 := Sym{Name: "main.set[int]"} s2 := Sym{Name: "main.(*value[int]).get"} s3 := Sym{Name: "a/b.absDifference[c/d.orderedAbs[float64]]"} s4 := Sym{Name: "main.testfunction[.shape.int]"} assertString(t, fmt.Sprintf("package of %q", s1.Name), s1.PackageName(), "main") assertString(t, fmt.Sprintf("package of %q", s2.Name), s2.PackageName(), "main") assertString(t, fmt.Sprintf("package of %q", s3.Name), s3.PackageName(), "a/b") assertString(t, fmt.Sprintf("package of %q", s4.Name), s4.PackageName(), "main") assertString(t, fmt.Sprintf("receiver of %q", s1.Name), s1.ReceiverName(), "") assertString(t, fmt.Sprintf("receiver of %q", s2.Name), s2.ReceiverName(), "(*value[int])") assertString(t, fmt.Sprintf("receiver of %q", s3.Name), s3.ReceiverName(), "") assertString(t, fmt.Sprintf("receiver of %q", s4.Name), s4.ReceiverName(), "") assertString(t, fmt.Sprintf("base of %q", s1.Name), s1.BaseName(), "set[int]") assertString(t, fmt.Sprintf("base of %q", s2.Name), s2.BaseName(), "get") assertString(t, fmt.Sprintf("base of %q", s3.Name), s3.BaseName(), "absDifference[c/d.orderedAbs[float64]]") assertString(t, fmt.Sprintf("base of %q", s4.Name), s4.BaseName(), "testfunction[.shape.int]") } func TestRemotePackage(t *testing.T) { s1 := Sym{Name: "github.com/docker/doc.ker/pkg/mflag.(*FlagSet).PrintDefaults"} s2 := Sym{Name: "github.com/docker/doc.ker/pkg/mflag.PrintDefaults"} assertString(t, fmt.Sprintf("package of %q", s1.Name), s1.PackageName(), "github.com/docker/doc.ker/pkg/mflag") assertString(t, fmt.Sprintf("package of %q", s2.Name), s2.PackageName(), "github.com/docker/doc.ker/pkg/mflag") assertString(t, fmt.Sprintf("receiver of %q", s1.Name), s1.ReceiverName(), "(*FlagSet)") assertString(t, fmt.Sprintf("receiver of %q", s2.Name), s2.ReceiverName(), "") } func TestIssue29551(t *testing.T) { tests := []struct { sym Sym pkgName string }{ {Sym{goVersion: ver120, Name: "type:.eq.[9]debug/elf.intName"}, ""}, {Sym{goVersion: ver120, Name: "type:.hash.debug/elf.ProgHeader"}, ""}, {Sym{goVersion: ver120, Name: "type:.eq.runtime._panic"}, ""}, {Sym{goVersion: ver120, Name: "type:.hash.struct { runtime.gList; runtime.n int32 }"}, ""}, {Sym{goVersion: ver120, Name: "go:(*struct { sync.Mutex; math/big.table [64]math/big"}, ""}, {Sym{goVersion: ver120, Name: "go.uber.org/zap/buffer.(*Buffer).AppendString"}, "go.uber.org/zap/buffer"}, {Sym{goVersion: ver118, Name: "type..eq.[9]debug/elf.intName"}, ""}, {Sym{goVersion: ver118, Name: "type..hash.debug/elf.ProgHeader"}, ""}, {Sym{goVersion: ver118, Name: "type..eq.runtime._panic"}, ""}, {Sym{goVersion: ver118, Name: "type..hash.struct { runtime.gList; runtime.n int32 }"}, ""}, {Sym{goVersion: ver118, Name: "go.(*struct { sync.Mutex; math/big.table [64]math/big"}, ""}, // unfortunate {Sym{goVersion: ver118, Name: "go.uber.org/zap/buffer.(*Buffer).AppendString"}, ""}, } for _, tc := range tests { assertString(t, fmt.Sprintf("package of %q", tc.sym.Name), tc.sym.PackageName(), tc.pkgName) } } vuln-1.0.1/internal/vulncheck/internal/gosym/testdata/000077500000000000000000000000001446745451500230755ustar00rootroot00000000000000vuln-1.0.1/internal/vulncheck/internal/gosym/testdata/main.go000066400000000000000000000003141446745451500243460ustar00rootroot00000000000000package main func linefrompc() func pcfromline() func main() { // Prevent GC of our test symbols linefrompc() pcfromline() inline1() } func inline1() { inline2() } func inline2() { println(1) } vuln-1.0.1/internal/vulncheck/internal/gosym/testdata/pclinetest.h000066400000000000000000000001121446745451500254120ustar00rootroot00000000000000// +build ignore // Empty include file to generate z symbols // EOF vuln-1.0.1/internal/vulncheck/internal/gosym/testdata/pclinetest.s000066400000000000000000000237151446745451500254430ustar00rootroot00000000000000TEXT ·linefrompc(SB),4,$0 // Each byte stores its line delta BYTE $2; BYTE $1; BYTE $1; BYTE $0; BYTE $1; BYTE $0; BYTE $0; BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $1; BYTE $1; BYTE $1; BYTE $0; BYTE $1; BYTE $0; BYTE $0; BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $1; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; BYTE $0; #include "pclinetest.h" BYTE $2; #include "pclinetest.h" BYTE $2; BYTE $255; TEXT ·pcfromline(SB),4,$0 // Each record stores its line delta, then n, then n more bytes BYTE $32; BYTE $0; BYTE $1; BYTE $1; BYTE $0; BYTE $1; BYTE $0; BYTE $2; BYTE $4; BYTE $0; BYTE $0; BYTE $0; BYTE $0; #include "pclinetest.h" BYTE $4; BYTE $0; BYTE $3; BYTE $3; BYTE $0; BYTE $0; BYTE $0; #include "pclinetest.h" BYTE $4; BYTE $3; BYTE $0; BYTE $0; BYTE $0; BYTE $255; vuln-1.0.1/internal/vulncheck/internal/gosym/testdata/pcln115.gz000066400000000000000000003637131446745451500246370ustar00rootroot00000000000000}|UEr=yI( %% UA"݂< HPD +ʳcǎb,誋=t-c? V773g9s;w0I **-w` 6#H? 遣lG; c8cp(C|8_ӡpn~]8 a-EgB^mJ7\<~2n6*;n>/>gAbw pQpO?N^NJ`}`ؘ*en9x;C!7\^yBG? GW#^ۀ_ O*P^-_uznXyg^س xp3 \ ܲY/C`?+_ptMxf8|قWß.}?k!V`enw^Wvw;A.\< څ oߍH8+'!&~0p6wEh_AׁGCmQ}ҷ>˓p}#=NVYO#稿V+󳨏QAo?p+p"y#y?o{{s MȗཇPߩҧ9x((p[:ҿ  >n|>` '}_ۀCup7;;Lj/=>)pa6A(0q9+s/~Ϝ(+^5yA!B| _B;`.(Nou CyE~GpO?=%~~ ?.C?B?{^@} F?Cm/pO|=~#_wpC9{16XuWC t>埣{nLGCW xpYo 'B~{!f, ҟ?c_炿tl{C~rpE%:U'Ue5I@~?G[GiH_x҅(/!>#>J.9xg= 9m\/As{;Rm+ߩ#aհ(k~tpAInY޷M`;t k3Lr-lWs!/p+𶭐</|x畈?_}*a߫t;ʟ`cG"<Ȼ!~>jwH Qq\n<7;86@(/A~Y7Ae+QrppBsFw>֠ /ppK {`(pb/A~VC"~Q&Co#'p #[!ϣHA~(px?y@}\V~~{[v~OP?O6~߅F} |#wn7<~GQY{8t3y7Go?<~m? W߅" sʻ/G?|DW08?|O %>?|a78<{+"(#y u׻_{ y~ k .!⁏|Bp` ~!}þ"1^| }>C~Oa)B %`P@[k7=B? +GaoaP=s{2/K7l98  y~A{ŸQIW"}[[S@sp8 g9(B|m^LJu_{v_W wNpRp>am=<>t|g>aLė \<alg ౰w!>~] ab? O>=T g~J`C-3Q~ğ +s"=~|o+Ч !o5yg 8 pxE| 3 !pp)o=`rkF"~4.a1p WE~8_nß8t>\yNGZ{o ʯ@y.G}~[Q߆Sv7Q?]hg|8|#w'yݰlu><¿xE\ 0?* B? yA~`QG<tZ8E^Vz 5~G.A? 7!xp 8~W3N_q'2 xr3@UM^FEЯ |/A稿K@ }6@ ?p8|g3r_̹qjǁ#^Hot6Ezql8Nv3wo9x]I\#FOAy7>37zn3LHg:8x;gC;s|5Dy]` }> _zEz(p7@?}\꧟7aG? Q} π {>\{ < {>w~; n4CVB>\>YNh?#({^~8Z |_<({, yQ?h!0p+?3ϗ`Vב~F2_ 8.G{@_- +>\6ʿ.z. ۀK߃<._8 \>A|> jCG~#z o~0#U/j߁% ף}?GonMof-_ oGy#C=H݉wK\@}{!}a/ϐ0p ?`E9~?[(pK?D">/m[=U?yCGA>6{4P pCg"?F$ O~#} O/!_ C 2 d(_ ?@_6y珰/p'7!)陳fO>T ] b:xE7[>ܒ7{zCė`}+J}CЧ?|8_o7 |8Z}848x'ppثg(>à G~= 81# r}FBmPPs?A<)? g/wpx7'@QEg 4pxD;ca3aq5' }+,/pH+QӠpp:+\|8BmSg!tf>F~;3_BYO[CQQ9(Qy84砼? {nFC\X*V8FE?.n+!_y-GZo +~"xR~Q"[n.Z}#?p9po^jByQz ~ ȿp9xr> ^W_ۀg5AUOh! ,?/-ρkޖCߋp`e_^q%_{\V 'phn!{W#O^6Y!迀Cˁ@?mm[on>`'!VࢋQ `Jp8jȷCraK!/ (p+p+W+;ۀˁ.C+נ~7 P.o=m}+{[W?ppvp* VY;t;n> 8 [[E |oB(o/8 |8 <?xVЭσXyoECaoVCGPw 8?~Pw"p-蟁A'p{)?~}4G[C_gОBP>p _UϿ%sV99{m_+_$zW_)!ъZqHc]em%jk*kkVETڿ3<<[(>ƹ6gQfґ)[x ⑀PxQ;gYecmB%P)YjmD/t2mԩkLt*˶d#V϶Q`_M831ͱ?w@^vbk1 \vFX- ϏΟD/:~iB|4$+{ (~.U! sSxqAeӂ# %l:&,{̈ͱ_mx8|3&SjʻBk ο:ҁ?D@"cߋ R+EIMQ>) Kq窘<F}*[>W,BeNo: "݁-¿*ΟQR([ Qsw_ dpˊ_OGÔaUr†pMjnQOUgi6k(>2N׾,o? XHru4dv/9Vzej acD_F (h[U( ٯ:o LĦK'y# h$Z9^)sSqKʹ(#%v9w/(HCQx7яNy:*k;~ʔ}?h/5HM嚡J2[9 )|Q ^v]3N|߻ۏwBӸX/皯fYdR}H1T͏&Ȧ~d[œX~7O _x3Eyz -q0<\ [i9}*yAAMSG*3];Px0"ϥpLw^[[{zާ-T-XUXCSͦ:M4tI)?y4OhaeYj񑬀MJ䶟gw=@w>pR?DW;)xq2h@d:dzr4d<>pd=Wu,_75W(gpw7N_O,ښi +]&-gtI)(*A-CKT󞴲6e=LdR}JAwOT2/*9vžse9~)ҸLTclԱ_XZ?M[U[_ȝ5TM7ʎT7F*c˺D*F47oY]x%s_"+;ڳ/~a} b_>>gRp:K!dȠ0dK6X`LUh|ڕV~~KTLGye"'vXԷÆxq{ KWL~L&-L~CI:d0{%d]3|\|Y0 i% _,S=^񤌭U*׊^T}JO6dT<(?IP$Fkم[+S=#]JL&LkSh}'r%4~)Kz_21ؒf/㉬4ӲohkLzT)jYuGeekpB'Q\d" fO2!X&KrSa_Ƞ};Kx'S'8Fvz, I%%iVn`h%ߟ5 8y0[Qa^Iψ;xÍ'Ö'v#iX{}X\dvYJ>NOl/Rn ;Gf,=JL*Ӆ奼 zM]c*>}Y0Y(a*"XL>8~>vz En=YNrQjAe]4Ŀ1iAJ=lcdՀ3 ^b0-] q3}|ZRRBoȍl3C4ۭUL^ll[AFU!#?'|wޱkq-r׏MXJ#u/d@q!?:ǝ'\*ar*Jp>λS=48U^XJ88f̚4s\ZwL5KK"uKFF)~yTGo;v"#lL,J .{%SLOh5cxͼ)doj+<t V=~|%j\4anb?h/N%Bp%W)%̖* "M41<uI[[٬zB=Ü[X;,geoj{gR{DVdzd&Y_j{ GѬZ/,*h$8{9 Y9s,9%)9GnN:$;$IݓE^g S'53tflf{9X_Q k\=D았׭:uԱw`sтp6?h_tZM`|ff'9©XxIL'%^ q JBWo8%/mץEUcg,Pz􈵾TeC^4lz J[Re2پ%O /r^Ga{m}$ןggӂ ,87@5 S.ŶFm#據gyKk,`7D=QD@}"-,Hf><Ւ4KAqcWC9Mh#7jE;GW$.xԊ_E1++;F*NwVn΢E&7WCՌ>nfG&҈5 y4r&ON)hT'8lKe7-#Go36<ng Qd:LCPֶz_=Qޏx!̪;>O#s?6vʾ0>fr2# <-hYy1˽ٵi+{=;*op뱎]֣-{}6\G֩;C.>fVe$XGiYԭLAUcy%آ1~dYb}j{AUd1F+̸xOYADzΦ%K~[[_*e*P"G5sw~p5ƈ-;) nW^A}C~Y7ZJ7MT:X=o{O0 QB]|ʳA{lKlpPQmCۿKg^htO D}p[:w~Hsünc=%iQ&=cԿr%Vb4t4V#X^Jez'Smc4qɳ\f Mn{^Gᛉnor''+Xwn_ѩ/6lj4۪46S}) *,vKZ 햖RO&GK]e46mXi_CCCzB]3{JUrsy/.t/L}-y|F:.䬽Ql*xĦ1UDt2WΣeI[!x^_}ZViNإ&)}G&'4L">|&A2AA3fӾ5%)T.z% ZM#Ox=G9mdrwp#Ѳ宾_R(Qْc;y\sesE.}G",'fa 9ì|^pjo^2"+OY%<4(aT=-/y4[Uczz]emH298[[%<Z%)O40ӈD Ao't/ҿ][),ϓPF;3*]$WyJy*Y&=7~Mh} P60y?D똼C<﫜>U`rSŲarKRCJZT54u2`&ʄ{OKȔ2K-$d׺^<\tO!DOD6JIO( 5TIjojw֏V{]9ͅNC~*zɹ(a4ߖò [H3{|  Gjxyo;̕J6J)4 L kةɌ#EnO{+52G~w9/=iii츍t=8U.Lg&mel;=}[i2>׳`fFOfQVУ5~\ b֛l<ʂVj e)Ԡs6?XvQZ /]6͓ev HC?uC1|$(]3ui0>ش'=(S_ϼAjf}$rq^BG-'8)n?yj٧UέL{^MMvx\ZWJel|2lf+tA]G&_QXxx<Ӂemq?R -|{;kuGn6:/*玴w {sT2 WcV^z{s;O6R(rk[Η^v!{WuǿI{[/=/=szmJOR^^湔kԺl^H0Wea ,:k,1|\DD/\| x?󘏤o<ͯHc67bn5أ cV476GHѼ6~gm|b/ zInG˖bVF 0!{3ZSiewOv5G~G&}I?q4aYA+ 9{job>-AUdO,n&/@6BE=6vJDW# z)3F gM.4sxݼF3\X4otuBNڝw } 򬾞n JkDVOZ3ZeIVӌ}ˋ(<In'ge_hY>3hY1NՑB׹u8e}0ֹ~p ڿNs{֋m[ nKӂ̟P7 v_TU?/Мդ*PugE>l^]OgS3|ΘNZ"r݈ a>ғ߫M?YTQ{,Ona w:+XcM%VL's()54(u;U͸^D:s?u==j#͑91#Oa(cmFfm US_ꩰ/THHh}]~v1xKSޓw7w/k55ӼZqmߦ5qi9,c)y[OL)˧uI{Xqn4:[vΗb{ڏ's()˱sk( n݀{N|߳_t`oRfwAՠǴSOMB,@FoSѐ|)MZrپd dQŞO}bU|^q#jؕ/]¯m<^nTڣϱfܥl#/4rRxќMP 7x%;noz{]Ax L#$z=Ro OMYzfRfHMpL-If/Х/E-x/_xvXMPUMD8Y5-CKjpY_l;Ry% TW;b}gD5k9\^#rBQM|aq犠OSlNH{'=Fo1D&)e~w\Y/T5ْfF..ZZ7I ^žYB$FtjLOP д +Reߴ9*edl(·xiٽH.=DD^ٟ)ϚQITYvnc=ޣmj&f:gc6QՕc8O"|fj]=kȬ'Ah̏ (qAMeZ;qoA(֓a 1 t&5\eaηLyfזZz`ӑ0vXMQNapK{?h/Կ~:2)[;|%uWvS+O~zqe- O87.]i썼z"̴F5|FaX5< ;4~/EdИ8 26ggYf#3vN3,GiمJw{ $ZxOl:֧(] d:ϫۏ4liIu;͘W"fy 9%B|ҼV4gc(d1=z]bVУh,zt-NQ@G(RǭB9l1,ij O?Z3EIS\BRF:rP4CQ5ƐgTc*x h\c4뙓Jd3"?7yW吥4zL5#ꖦНPM jq7j8tHl)o#O\u@=R.1v^b E 5f?.0c?Ƭ팴4UTeSLjRFZfiȧ Үkė۠^Ό~lzXebu?b| + P1<]]3-A-7Ҳ̮DO?ѷ[fnp` ~)Ծng;]>7XQc?̫߯w\Km8 ͼJE7)EOIb^oW}#IzϞh<:qytJUIZ sG?FszL%K4-DqwkE^AtK'ca}x`qܱv ɗ~N#WSRyٗ K&_2fyjȲʚM!{؝qr$ˑMܝ׸Khصn=NNQaNB9w %Kk#;1T6u!n5̟i,ysuͲ)Ԇʺj+:+G3"-~OD%ZO*#5uB^ASj꼊jNF>ԝ5_+W Jkd`DO_B}Fᓾ?yPi'MaItuo]O,͐A.SMyn0Ԑ0xgx>2s>.$Jg߾4c p475~,n2C3r8Mmz䟸xzSgXȿr-hii 9"=LLXT#}hi qSS6L$I&F8UV+f {>b-<%0ĴOB0[=U{xC}yOxBF}|^/̻cgš<(v?'{enTVU;V%ʸ^?D7TG"Ƿߓ:WO[Uypa jf,Sv\| ':82>To߸6Z-,qǎӪ*Zv|#_ά֛p_~<7FB0ٛONDZfvb}F ,V חsn߹έu{$@:]w;z@#N<0RXY[{tHC{KRgaO4>/3+=MfEW4{ |-Eӥd?WEVA҄ -k{ϼ˻>CuN ؗų9c*x}<ŌVGSPWcזUhxq+'J>uպ hg(S?'>2BEGwEa]n(|ꮓr++WWs+iXB;)GG3taZ{.*/8KGq}{+A C.׿ln@_t`u}Ux=L\PP7F &ۖOKXRvK;Z3ceW>^ )cy[OP7zdܨ(yD]omBMt<#++;>zMCm~w8;idi;JE#z,C\< ~$׫iB=#6$x3 Vxt=Ezd"B}tv=U#$V_x4|O?Z/%8[{f>9`7Ǐ uGuil!5;UlMZۘn>WK6>lUZwvBDw5*ВXQj^Y٩oo%WW]}EW-gLQgZ{)W,Ģ7fL蕡TSKS+V!Tx@Sɱp|˯OK{g7i|i &Bq}ɂ%ς GR4ԱW)O@@V)F@Ly/ݝ|"[IV7Uɬ0%E}5]~oB4u'&6Oއ\N25 uU<>olIl 4^C #Dq<Y^I 4ە>9)JXG_ u6o*kxsuC*O)n~oxW?PXYQ7c+m;RSl,ďh4)D a圠RQLSHVs-2Q RL[ I&h{8AI'rT)ؘ%oW/ɽ5q#əb _$n Фjh %Q.Gnp?Ϳ -XlƺeF+|-S54y&0cgڗ,s&Dsez (sioq}~[:nuQͭiI_N,=ǥwNi>Að1Rn{mr;'?!rh/˟jjE˄gfh~vnYAUƂA͟4l*|odḰ&zZO"X}z:VޭB~$=k%/Bo=Au3iJ+Ӧ[8 u{t-Y=ՇY4[^3`f~., _{ f58Ȍ>x!{[)2_oA[IG8|deb}eʪz@KE.l|x2_l UC ϕ6o?Lm.sIk s5xk’j [ٝvĒȮ\$&={L w.~c?38j`~ &#~9\߸.FnpbBt~/ߨqFƦZZ/mh%-Naa9ޟG Gwc*- Άfb|>͍ğqF;T+d={!88+8==ow.9Kz`ԕ$,rzA1'T̢`@Q3f<3&'z_ٝeftWWWWuWW13qL垙af2)3a>gt!É xZ#ϗXMZ{c1kW0tu&ĕ5`/e \-=LǜEv J)9zu {$T l2K{f-ca'^[6>qˢZEۡ =Czu5iJfӳomsNhN S&@FRM4餦m"mQzMagN?߻=eF 6kNvFsx Яю\j=uXŀ]@Rҕ4yeX_F#ApwUqmlaZ]ҁ9i]0jbB+({&n/i\"#d9Չtq KH^ 1; g*C#w(;"~9a9|aÓw؉_̙>mf Ri#j*bM3OiɊBbήy)?N`lF`so\mn:}S1j씣IFiT 2ǫUp,cn§JM)M:Ǟ~Nz{+S!gr{8y^g[wq]rn9l?K4{P~;X/3l_TKO"F vpM,\zH,[YXXEūވ@c o 2f$g 2Q 8b `cޱcYaY5K]n$'5Rg~@9C;Fs' 3F]F Đ[PAOjmN|fS@!r$GɉHTj̓+%#BaA0lsܔ_&8Oƌɥ&a$R"R 4QսV@ uѭOw; @5!]U۳p/]q>pWԏgV/wgͳwjgL.D'j z{4 jkGށbQʹߑj%{~hִ?*PoG!QƔqek^ wT{ ߫i_Z!.-(O[]SΘryS`\Yz-RGރ@Ybл?))Sg܁@ =AphC9iõQn8@ʱ)O8\y_c{9+1՘nyk_$6o+,y1MPvK8MH 8KӖE?e%IR2L%eqD@fw Sl*+g#v wbqf_3݅IF+9V6$?{ Y/eW|/&/E2xvϩY(-&KjJW@Ne[=ϒ'1hO# שe5<+[b$ǐy~ _e(Hd츸 NƕR OŃ bcfƌ1x* $yo# A兮p5_tfŽYRY̔a t(c%S4gd:IRjF('0f e6> JQVw*Qk^Qk+%Tu @ |@Cz^/c3A"ۡwG~GUپ qe;?fNO[No9p*}Z^nD.dOBެ1ewag_Ç3yF# l?C԰/A: olJ o`TzeQjgx1h0>]QU:{)1új Mߌ 3|-5KPd|IB`> 7QѴGοFlacj$#0=@\ms&Vkfr)M>fv8aw>ƿwzx@ӣίY֣'[ٸ|5.zUXy"Nmb( H`46D\C-@\!ԂCjY 0۪k09j:co2c&_d*}Z"g&1hAQl4ޗ8E]+H^ ֡ ӹb!ޔ/s֩6nVΖrPQA✰l %݁h%bgʩcr cǴ*YscF?a 1)_0{)ޖv67mQv <יҬFrV4+j2gCYfm(#QYVJK e"RfUR̫kAtґֳδJ(o[$ԏTq휗uF85H4(M9W%hqUU|A ,jiXuxMy>sWg:I`MrEvhnUH[C\˰{OJ`0Pֱχ75>р pQy"wv%dJb@t6 9Yذ82pb5qU_'^x4Q~V?z<)Ş}ݖv4C6 hUUEa՞7ni1t?>cF d}n*zʚQUV@JI ;dQ]SiRc$ȹ:,ի?+oe_{{(^|Ia<$t)oBh^GEU~tvR$yVƩ ̪{ 0_@(k}\aU6Vh~>gu#oVGxxr}n옦sS_脹0|r),-ZP@YjO,:zW 3d|Ccĉ M\VZ(MF~) hPpFsCP%_H<=)M pS-o'Sgh(:OH fhq5H2V(EÂ3u:@ZĝB&#I/t`ՕKHa7x)L$~"5-ML<t@:P+B(IMli^_Gg}*SQOm7cGU&%5Qew E8coǐ]D^}rPѷQoOG:L8{J- cvU{w'HٮiFp&Yҙ.%A3*%LMvDQbdXF* =RC. @܏]t~,vaqA8%3@JiE_r*z1DXlo5 *`9x&-K×Jp>|d|LatxՉ= _( <&+^{.σ 00>Ffv~C '5̜t \W!cV7 eqʐ#78V[+U8O$BI}J;[xv pJZ~EVCs ;,N?naBw:1y;|# ?RSA&΢T : M]3rCvg,Q^[c=ͻִGt_o;:loti<~@_@@6 V |/ ӑ78[#Xm%4CWkJBYBco'p'ܙŬ@ ˨)t&3 Ӥ,04v|NVt&PkJJU(NB׉j\V{aǵwܲ6񂦹/#Qx@{}E6#8nZa@Е@lj`;(xIg/jjIIJ+C_!>3pTYV 32Ii1!98k^d _l5]'}#!7 H-SI | @K;W:{ݐwl 1,:W L w=2D6,3uRO:1mRuϹ6T.*K1)5C'oYWQ"&U[JVJ5)%QDjD ~i^b^f?/wqu<+tv73o`e){Z=cyb.R"ydQ>^)hނU3픇Q]ICZݪ!F)` =i\n"bBՖ5o-W@ = *J)oRds{ĩlMlfpLFY, >uA]|n^D@|@NƏM }]4.ԴZ/f:n, Ė4נ򯫻kk3à|j"#82h[T\<s3O^]ߌ;7w±ߟ8[N:fIs{tErN ni h i2aH4 Lw&QXU7E%,ĊWEr~ YQB!Ki6$+o& 8s[ YLiғOT _zڻ+C)\I=!gt%Agi2҇$f/R:%Ê# 5K~r3°ޑԻ8>f4 c3-^zϷo?ȍM3&_k`ئ4FJuFA쯭.+%pkFuvZbl/ g0Ų$PULIo)nZv@6t~N9]=+RA%JԺd-Wqpgy|vOp"*$/ xځTEN^ץt>>/!GB;:zQE.j)~|СBzΥu@u~dh!g 8}w8/D b5jv7M]QPESaL,bfl:k`9Q鍙bmPNQRUx,Ã2PhQ%;BKqFT&[/U0Zs{Qy.۔Q{oO5kYMj=/x}jSC㨸seH- R$Һ~ }~iCa0KlerwOKpbv.WUmxC䚶 f8pQ#nNoD{68!a{7RghQAW vcv‰'u":#%6Y%:V?;9'j5QG9#uʔ";dil$A=18}xCn%FN&13MMH R}-( i]>Pq*L֟Plx,QӪm3? vg'͌8 zbz}Z] 4xpsC8;cf^wznb8ޛ Qv F2 95-ٲ`)=VŷPV .1GE:,I|ͼG`\/v̟ߟӊfaT2L{/F=ZԾa0Eأذz1Ab+^D #>7Zhkt2ڤA**CxHn NPU!m%H;7'LdpBPj{<g/߯O0?mԴ;Ahc):hSQU6[ +|o/6ތfjא 19*6ʝћDhU>kp?n~}}?|Nh*[ 5Nmq#t!9dt);Hũ_3Id.BK3 u JϨc4""Jc9 wbk>Iqf7P,""]?&g㓈.7Ov+w?ݎ.*&b':DA:l04ux*ps_vΎR!F6nk8TP\*VV OYT* zO#S xOF|7uK%ͬQ,BlД1iPU`]Ćꄔ uJ}8S T0VV~0$p\G*bO#~WߵLvdZKg(mW,=9@H2ީ$pc9X?joXY'*,ҰSu6M6[~IЏlXrp2>D[H 70A*W=VU`@|c>uGep}>|yi0d-KQi+HV, 렧AAppceG|>m^_h/~O6iD3 3fLw3L ^F VKOec\P:/  %:;)At"C^mN;h:Z%7=uk-AU<ޏa]BET/x=[syUX;-I`:FSֻqF[}ѩ&d(#uz!Ż{X Jl|>0=ϔq>SN4w cP{ϔg*cy_++X?lJ˷Q+8` A74p7J)/E4be8-a^׿P}3k& o?gW<4jJzboTӪMl "67s3gt1}\hKt537. Xc^zٽyI/m5 )O@s^+pA'*(%XU;,d~"(uaH&u_aժWDU[i \ P9OH%-_ݧHGHz/~:Ah1߄=f~'fov6ImTk>#mߦ@S њ=|>l T`𥉉A;dT*m_j)3ېڔyW%Es\Sn7T$zcil#MjtI:+Y7sm6DF*ĩ$e qe#aꁂVѯޛM[M+M=8=?nܝ 7Z>'CErTqT9J2jYH[c8dRj*A&p#p% I(H1Fp;G۳z-_~|V+eŐE' F6,GɅL-r:V2g \P? {0fcT]O U!.#DenKOf'`lٴM VͪX76Y|b6I[V$f dxg&,}t]?J@7#as6Gټg^jO߃mq;Y𳘪$HI\עP'uLh_ W v0:Qjk,ts U%8'D6OY/dbˊAB"Eg _"h'j d>C H~~&`~gяSS#R"u aD6x s%sm:m4yΤcVRSGLxn\T~`V+@jPwNK,;܅@H'\" K8: Vw-`TB *߭ZjA&ЍqELZŧgu,t_vKdayNb$/[g/pŨG}脜:Z&+Kp =}N={j<}:GXO9I0s9no͡[ϯK_al=_-f~?z:ʲdӇoɿ`Ŧ86a8wq( 411#t&h6MM1b:Nc*H]ga/@8("h&%b(hsCJw8 sE.5zXe&r컐g@ԃc(ץ$J.|zeeie2"t,^1WW(WIYg2f0Y0 Yjy1^(e$yO '[~%_;ʧ<(vd}^> %sSt#-VQ].qp 7ù^[*pd$^=^H%ʿK-ំpɲIG to2@'iţcQ}oI+5?#܉Eth<*!_U* X.` ̨&w ~X>׷w)̏mo`S[a8pk8?Ov3O)O>3cph'U?;RN,{GE^磻zk@C_Ԗ'qsʜC@Iwx).j~qaF9cRm,ϷU䀦z\ːne*;#U#2sˑC=P`;7NZVE>w/*J2}1_^ɨ|ΡꠟO ퟟ"^CPi{wǞ^nG`,svHPu2;w*-:>/B\se]`ur6@PL* w3(~. BM _ByWˏ\ÿKz3:l C

G>_wmsADzk!Ps`kXG̡)n0n%;a(c+GY#ٹI{ڛ@PUrYA1^0Ġ+~0_09 `{("WYaOB Tl+P"B$Aڒcp@<, iV`OYAx!A`vr@6#Z=ow x=#mϏ*mPD@^ʎ:FElpiT~*͕ƭ)Mb tIAkY%peK+*[s$4-SesrrO: —k~IƷ,o{}X%no!Ͽѐv1 'pܬ*6WR\*NOqc0<.*PCo@m25(sE*tQ_T7cJ`RtEZJ:D09O"*YO&tdA]i˲C@[UIvu07mn9}Z~Z-xC>@[U*ʫ]x ܹ62½03ԢxJ\eqpʵ F\SO;n9c'sNQ\l퐳}O`7kPIE V]FD%S}aO+@ = =Įƃ綟N8Eއҵy,=|||p~JWIlfZ1}!$)1"2umu36+])Qc,mD-ذcA? 88~οg팋Ug[8Vt`x>j=Ҟ!ACHv`B| Oi& rV~eŒ=KiE{qнL $- )za1wЕ4Gm%y=Yh$llL³4 R@@"#1l=jO7O[C;1i>/GqN5`lm^xq@/Zն-IG )M/Zh :%*Z}뇚ϣbĶ`'%o>ٶ+'Q[Oi>gn}Y'9V1^ߴ&)x|^a_>Q Q#H;i}(4:$-P{G6/MR.ޙőmke%44q8 qPz H9.]l\Kl-FGjC2-aځڐHY(#qVh0RDu? [|w3o3lծ鯜  Λ2~ m@6cL]m<vT5kJ9'03yO,cTPu\:("Yj42Ter;uD_C5z#;A U4՜e Eg0b M']<4Z1H@qsnV)[olM X~˫cXڦNR,*]FOˠH԰;P$ kԶkG8Erpl"CĊdwnhV_O=@am'=}r6ij[o |]9iH*ư & Whok]mJ{)'c 0V 8ܭ mly":JΙrcu)]Ԉ PR> Po'Dmf*RՀA%Ѓ&_kRvd+ /v _^c`}ӘrVZ eΏUOq?OtyVo8–q0gK,p*$/@W J@NGr.\K3vKU(+eɾnNJI=cDGtrp<7 H\'໷/l̵đ InQ[rvQC)ΌlQjnЅwSg70/N9iߙS,| F p Ak ;dbv@ɏԆVj$ԀOυ%dL$Tv'L)ܮ,-I N#, g,kPѱ,  #ۥSy]K)TRZ?hSQwz(>>Jn@_%`l=ɻSxN]#g7M O=en]9La:o љ kLt[Tk_xj:! wiFz-BD9;i1$g"Iv'9}q =[w"cR*>d<`-J2MďEo!Y3qYA*2q*Yڸfklf v5bX7gfK1G]_wauvDAd Ke=Y?v7b#VFd7̾b1gP@xsKJ\TX]a? $؞OLǓ$"(%N\T1<"\9S-&U!]TL*?v(^3}]8\f 6vW9h~W^{чT* O$ Ys(1LoaIV*qCCD>@6eY $#}ccl&0ξvNm\AoGqR.Kt4AXn=yͫ3]ȊY'S,/%JhA(X@]sb.@Mcqd>n׸F$;j~uZ"/y-d 7bqʤ_ oͽ1>_QֳKgcbe 3qK&֭T\@dE޵Z.*_۪A/T{,uW:,t;G^@⟈]/ =}?zK]s-~A-uL{$7!V/D^wқ3fX-ٮé2avLMQ;j؇qP-b%n\~9}KvxIMJ`U;͈s̞פ-` &ޙ^R@ Lh`tm@qqǏMc-rl@73vuxc`JfLrtKVއMYDWdq|xH1-UNw`vjD 3$39AXƫSĩԧ3P aCK/wUpId' Dnלc&EYѬ@ w6JLOE{_JQk<2 XV˵y @X/^ݺ)3 j#ǴQ`m`Dm`n~å6f5M+ZW!Ӎ7 GԀ"rt B>ܱt0NAqxl+t/ccӘhH4 "1ȜNc-K9Z6>q&]>G7Ǎ3&zׯ;@JfZJq26]b]늧 j3 'nb.#\[vWB_`yaBߊs^Ǹq_JNYػWΕvvriIs.26%T^uD^qxw)\+ $/;]چMA*|ntRKc,E2O%<3qJ0t2hb WM۠mS'jZS,)cEJ Y0!TK 򨟌@ڟNq]ĻilI' #h B(8uEIhi fDU wB*18# )qO?'UX:W:V}NGj?s%B7a$E_2Y'M! $B?R]#tQ*7O!wJw~/CG^iI=$9~:48؂΋T :lo>6ݼ89퉬b a$^D?fAt55V:y:;YYğvgm(O;/kIW&ٽN%\j.\iPGrS\ =q]NA ?2"˾O82oNt}ug̔ B=V!O i_g 3!3QBsĮ:̖(J"+eP[ڿmW^nڳnvcьSO*Z-`'0lH6e*c1m`qAJ\P}/Tw|ORGPp'EIvD=qiUHyQҹ ƳSD͆GC0Q/}'6aQPK(_;pK})'۞95;G_=2ゆIxCMe\[# R``/kQ_nGe4^E@ `E.FL>o.13zw ] g>J0q0 lY'Ӽѝx@0{ȇs /ax<8Wzq.:rScDCo-x^P0cD259y andƽ b^Ì|SpY^ӰX }㬠e?КW@4)C7 VykQ ?`rEsQŸ$xb5 g[<'6sL-\:t`ȣ8,rX ʢ1@K ?`z-whO.8FTWwmrpxIr7K&lPΊBrk/mձ8e᛼CӔ,0J Ϛxgp#'! {@̂>q˶Ҁ'ia]X]v ٳѝs뚚&jǥƬ9g{O_(ޝuQac[CP7yA=}a?P* /l^ׯsrt{gTAoz߳iqM^Q/GX'H@=HQ YVޝWifERכ|6 F=@L# 47N32`)ڬ1}tCYkǭ~"F938~4!߮$F;wNb{((-7X꽖X>g˨C5$ri*B*%#SR"j"kZTQ*Pu*xft"򤧈Fbe*+YaXvw<B~'|&Itq~1_ꎨ]PۣIM!#ƟA` a؆+#H}`,n+++:hp< u@xP1 HwԹq}|_ȌB{|ګP_;@jgͩ?غ MxQ.w;AJ- xcAoq)lyoI;@wlh gt.UP} OZL0 Kނ,KKWW&5\p a=mK*(,D[* (Ê" ;#4˲6+0ӱA졭@±7 ˷zu b>"I@5.ti7FҌ Ѡp?6nÞd ;.:Mj)KnE:1TMM-m" yp63b؂a̐HL9i< |v25Oݧ~[͟ efgA;gg;tX6{-h[|L GﺆWҌp3=LQ7&R".!NDqêl1OS+Ul|2/o,/gdSo?g^r|QWuPTn9_Qu<"\b=LRKhӳJ67+Bo y` taH(_>`V܄IsϘS_;ĺ3O%%{o}^(Kv3Q$ĴW:HUavV/ݵBxS5{&9$OaѪ0VVn1R2}1 S^`{?7Ox&{uȺ^G;[M/ll-ݜPKJI7V5\VSdAAlh4] ڿa_Ipm_w6;GWeLK>Ms k?4.,ql7O I06LH)'tuiHarlYM4d'e[ fݱ/[4)Y*) Qzv0yk; CrO%||z >8szQzNI/N'ltLXW:ϐHL\ 5`4jsA? !sA ZD«g ^P]x,N@:Yx>pE>( "9=d˷{Uƿ35=]1Fk"y;}kT+U>e ą ­zDYL% B9Hpҧ.ڻdZi(R o(*c+op*y .joaQR0FGB}c_Jz!tpȷ'CHQJ:Ν+-h7n<$k9Đ'+_&Nx `4خcG0xT7<ΖBc@E(<V0LM[Dyzp6+"dEAT&"EYl+- n_ kg>eS>;oh_Հ x( zuM M AJJl5# lנUE|h}L]8GFGȚQc%\0:2f1j+//_ª t]?R>p̫'mC7h׋yEGMtS]52sQ@v=vR#s-#<ƒ# ".+b%EqRw8Hrd(l%:6#0+F| P[+X"-BG]diq tt%Gq!dVw] M27ߵzA}Z2XB 򳟩bv PtRݭ0u,VAq(KbqeN1Ԓ8ֲiN.aћ.RQ V\uJ+J!ӋkM84~g¼'w'8RGcȄVoN@ϏEk߈xZprz>-0oƜ&/KN pƷ{wH1j#$?5xd&y0KUzb?6H Mk# ey=JݹJOF4͔N4~A > @'lZ"T VaTiJXCZ493`s2TJ#@bo:trLխ@}z#A1Gvٱ쬺Y g.4 R3evF]ltrX`ʴIc܌1T5MMb& nh#f:FIRr] ЏFw }9aƌ=ӳDi,Q>v\mJ<5x2*w!6*`‡+߾׍H\4uLDe7 K}h Kk1JaM+}:hϺQ!7~}ڳF>ȾZF]uPj5|@zn4L-gPI'(_'UkU('yuXTT ywk<;ԫL4HQcʛ,Ksϵi]QW(@&7Rr)q-,#TUs|, Dn #v ?ƄЗcBn\(s?uRtqaRgE_rZ~TGj5͘Ht64հcO?"ݪ'93w֜Y%tc P9tda0a:0mԹh% Ku l+>o \46lC?53yF\O08 +(/Q3eU5q7oDf4ZU9{ӯ=!A ܫI8v!|vWe 挅Yk̓s$P3՚R?I٢Q 3%" #Y^s>gϟ#AH_|(^n}fW;?9g~Q:E_(#qԲad,i/]Ž+&].SD9䋄؏ÁR$1`!wuRmP߭fU?!np|yҔdH>+hU[*6}2Cr^B;piMks))u\Ʃ2RŢЌHTq/ Q$td#~_Iju~U+B-jU^K%ϚOoܞϏ|Y)QyC~l[~gZK"7q*H$574XL`[Π]W˽?Fc|ۼV_;o8~_~P'TT$z"J}n ׆5FCX=Fꗚi[X]Ռ ҹ%uN_-y7~~'h5Aѵuk'6̫;ڣ/lWs $^kzb$BJa l==Kݚvn%Q<8[,+4.>+=;_$ҽ O]0h%1Dtԝ4/}oB+~~}vrvQu3jK\.:AB)+ZPhv/2KǼRy1bW6R]2 d8Ͱt&#;WFkp`6'Myk'D'v^>@m7˦A ^L"\AkN/{idwPϐAU u T. i؝36?@3 N Y=ooǾ>-Ըْ&/&b. )?&Ƽ: i-%-AHZ6tC9Ss(Wⷿ[JKߤ6O;EMz˘~ R>Cy-ƒ֌a<~ ;O 3˧Lz-]uώ7g=Tל[yF fhQDuaӘ2-as]L4H`qmH5un}$]NB;P97ā)Ljh V+R:\noh]~:S+ Ǎz\7#/?q>-\z?i!} 2<;n쓾#螬?2Ҩm;]ώX瘿D7"\~a1"OwK5o ϙLDeGT.pM3c`o%':8rzɾbK`\Xɓ x)6dJm0p&Ee8vh?}s&(1pKZZ߁\6bm t|c^0{l;)rqg̰o'^'f+>/f=:l kJ0L\`Ѵվϣ!` ¥:0Lw0L {w}4_%>=gl"| CBzFց2`vۃuu|G'jwYv?SU\n|Ub!G߲^ s|`Ӳ޵Yj[vzyOI[s WGnHLp tS5@+hh a;?b!}ҙLe|S/BR8gaI߀2iYkS6}u%qJ@gX&ōl.?Ş} ?Łd;5xӏ<_'_/al&Y˔_dcUx xFk3Y?`w/˓5`~nuM[[ E078wZΑŤ.1}& mޜN,,55qv%oގ U=)'\H"N '{Cw=y>X&E)I?Pz3U&#sU!MӤ[ ~Ϳ-hwp@ N 瀒7^Ӂm];kVM-tRy13s|+g`'U:Bl*CL3g%d\PX0%4 SgAk0BX-boFc4ǩp,ޔ%&"᫔|{U &8=[=8 ą,V`['RDdyB2h \jc$ay_ Rsq hN#av[4o=gn݌$'spTXn$c BmSָ![pNsUVKtŌ{-U/xy߿'_@ߺ?"x!}AJlz=G e ğڢU@6Cٙˡap[ʢf<ABOR7;Zy,LQysGzY1$EJt杉BR\-M|zp|J5콹mvH y;@|ͯ m8o~-+e| 9J2ρ{^E9SӜ.ќ|x]Qu=&( -49\R$YzSq)8c$D1ARfscCi=X (9$nfUF.aһ=MGQΗuJas: b7I(ZzO-m0 \i"3]4 aD js \LLf|ضl0?~#lOVY_5{3jC)|Z\]fV9^i}8\&| pMWw2~Y hް:sq*\%N(tj8)ʺSlIyx8_eaMk@V7dej=_q5@= HpXCH_W5m ϧknؓ#jOnk?XBN ۣG}n֒玮nu%ŞXD\Pgdv19?^Xo+jׄFqTNx?}7PYF=/MpS>9EK#aRq>p4% x ˨e"&yF-rh4;*"6$O.)~n"8b~׹M\6dUu^]R4ayj/(X]}½?o;jZtq)7>Nn! Op7} EBٻAKE2R;8)4{4# a+MhW8CT%]hvs}[s78wa>=@G[#)ׇDaRP [? [3omqmo]hV_bsηX -0A?έ`ji>YkK)@ T_vfu6I"[uME5*\5."-J& Q?Bb0}WJ/Гz,KJtg$4ŮX'yw qM7-ݨL"sy}fۛWr\x yzSa) #H_B5B? 103_Wu::obv} m׻$E^.sh'c>D8_uoa)]|Ypt34GJDnai/`"+'5/Џ4-FWIYp(('D3LzE*!6a 0cXԖF#Ndp2bH^5;yeB(@Z~O)o [8qPqS~|ߞË@y` o|fq~Teqe;nn-oMjn/Y2g{_SRvv\_r ݕ =BM[7R#^fo揤֙OF&uF֭UROńtYQffč(U_cS=uЯgf/P}y.60-fg {'g1Oz/D  q9dL0HTEU{-v;Z2]JW9Rg)Xd;(1`°ccDWW05Z_ᇀ.PȺy ͧyun~. wȸO!(>W*5=C/эFz^$^x3hx שUNFwF #.MU>ס Qێ =I݄Z5X(HV=R ʌr/!?kj9rf[ 6P-Ed|XZ\\%Ͷz=Aq*)zmyX 8IOhcq-y-Z1iJVLHHLDn|~LOME]g=]8KAV v7B=]v Hzw/筵wL[ҹsܿ?) A%WC))[9սh:M 3Ȧd1,;ta iLHb:$?P=&I`P-Eƃf-lxp< ?Qkj:4T| 0̃UC+zWҺe2z[Q#EĦM(O #A!G8;C%'ۖ=hl#[m۷;7!hmepphܟ9/{K)9@vB3R,7?ueeɵf&z꼕c}j'֬ilf|\db# ۢ}o4[MF#)k-?'4m~|ޤ'_a@yU]`ul^ &\}7'q#`!7Ʈ Q IZԳ y0HJ{|邬&b+aESw5WtbN7VeTjUn%룁,A ދ 2=>H9)'Xn/uXpF/p$S* w12hD7qPy2JQ3=[4sqr_U˼ +e/EՍ̋rF77HFd4%;Yut[AbV+%GgO+@hjž84 * ʑ9~erH/jeeڬk0cv-Zk= nl.k.Yjl5r  `y0oWXk .]΃w(E@\չvϖ%H=-?Evz*H#SPI͏#2 ( h i8ɔ>rPԊJC,fA[nf}Ŵ\cU21 yp/52Ft*7H4S`qv4"o<3+UMZ#$I_A<Z$, `*CJ*KRA40aD$gڤ{c7b̀הY j`| сgr 5g5lPsv>Y{ @yPyu༜;`kB,v*RpZ m/b>X4|:0>TA MHU^R<1AuzRa0pb|9"*OjE2=& \$"kN7{EFq90^$ ȷOBz >Х OyZ) _UI@/L_AZ*m>4!ETB$]U.Mxw5bFI^9VN,IG-Bq:'\ Yyr!]_zN&~o[oRupS?I>)l8],ˆ% yZ%t bɨ, =p- נ 8'c~"WsnG8wy5B()V#k<7년o"ܐ&{vSW@ x$%P!fa,`a.`aЕ,taJhS ~l@?Z']^%'r] nu(϶u1cz`vrtp>&UW_k~mO_Fl%viE--kB> txTQ稻Fx}`QUΊMF*(N'}g  ˏ !w~tt[9:\{t^K9y #K%ْ݂O'7b.ȳFCm7!,Fޑ߇lpeRd[QT 3sre{i_Sƹ2i,[lΙT5RIr%;j T`dqсן*ԯ>`Ua#NG|m֞Z׊aTu)zSٟ kFKtQUbXig dcItA^1:v*T+2+f0 ,߻CN@C(ʏ52-gswuȏi}uȏG  OK|yxo^3|i^ZpA4MpIc/8F{Άl vIY/&P7Q9 uEI?~8;#Ewu*YeB۶k>^>·|<޴>YL ]jŌ_c<~W[78z,c ;'~fbؖ|:G9l§4Ut}ӟ;7?^'õg`݊K|"-#'wmc<Y ="ζ|Hf.ɲ>sn%MP!б'CҚJ`T`,ӕERMLxI5lZ a`2?fki}sr{ģ5WL]8C2y^z(&(6_Iɡ 5k5Af ag.{!)Z`g_j9eI<xm MfͬM(g۵~.¿%ߵVl$]gڎw_Wn~3?U1ҽÝ\&s:ʈֹՀEn=9Cyq>\1wRL;:_liqSK`jatK-Hsr0.CQYTְϦP&|7;Թ}~™4R*_'4z׾Alվ wul Q}OjE]0oKR/5VCR%Z]MtjWFG+ԩX7&2LpXϓeIT,iV(UN󮬀' "{cތY4pKfG, ~-5 ^tXs5p~ 07$09e3Z7}pjp}(CP}kٻ΍?mS*R| ~<&ds-RK~mqT%P+l"tIXZw]Ӭ` LC2;&E7C @%~cFM]:Ȗ=̆sd#OGqHfX;r?~%hǼ^;Y |WuL^)^"?/Vr%voaA3?F:ϑaWgqYtv_G~T*S ;JBKG&J( >ꂓ>y$83Lq\ǹ7q l\qA|Eca!u\9nwzP!$Q:e~SdG!lxt Ԕ3Mlҍ @` ^ϯY!9w#T +QWYn֢Ln̎:6x&f$yOu_g { VP9.o|X#q6w rTv ]E/3*AM[ Vn&Θi+DNf73A<}᛼4=4R(31ܦje>54n ֙]? vyE{?/ FFV=OōA;jn QK6{vPC~VQwlIMwi蕒q4u*EaV -=J nDe;0E-uL]ӂZ/}\uR(B~(0]#bUx Jۂ=o;ڎ;:~9kJ]b$$5$m<4}$w!_"#Kɏw,\oƅO3 2Igw1 2';bewDF}g<ٱɼd4x`@LnڑUOУSjF՚y^X ;ɞ1pm2ɪPI+ Yhz$-'bW<:;>`mҾ>b{ι3ݞd7$&!!@j(*HP򞨈~nq,rV!& s-~Wd~NiM aYց =ڭpl!ތCxL;9|pnl.7ipzZGd2r^Z%rAe>Qj<>ґQ:r _e4K 'ոC(<߽Zu( ~q2M\ᨙ|k__n#IEņ]JPGC>3@&BYvL}uy N;y<}<yd_3 YG,#vH 2n 41٠PP-0i<߮Ax,iBcɸ f./C€wx`JGϏ4*3N\\Ƞ08't~9͛` t_lWt]I*n!M7HnKn~ >pN[@ [ gB%Q;JFm r B2b,w* ES^p~oC#p7/ߵ DnN2jKczki CMyy3Ѩ4ۈW@2pBρ饌+^'irGb{]3 7و!tҶ]ybq @kǹJՍ ‹^eɟXNi@\u{#J҉|rFp$-{wUiү$ylS0;W5x@k3$Rۙ8y8FC8YI-sCu|8ansZZ.kl̕ytY|#A~U^$y;ۙqCs($Z?f.v-# >jԂ~Ssn;]|cCjo?k_^sPFږ9sf>3A{f9*RRM-c38Mh aƘ8>3r0{yWqǼ<G=ֽnP70ɉ[Tws EǦcJ׺>/g.?yT[vgK4]@{ 6"pO?k?TZd{+v߿:wFQz׹Oi'ΡҖn ^'vmYKᶳi7|Ρ[j'w3Oo팓>jFww7ewjn =S%/ݚp۩{jvNiP h};Oylm?D4T~:o|ΊZ/!PELHHwG>?tw~?iބS*LWly8c QL&NKa]5nGIpئs9gҷ3?uަz8OD}}M@#9A[ltߴGo"כH#ov\%=POs3nsJF#^G]Vm0{ \u.G5(/잰پ`\ZaN!L28ӈ","C9_օ`2vkos6{z?Fb7ޱ[4 -\=8iqK/5O~瀿&~$-Kű-KsS1v&XЌ-(浯(U5Eĉ;4Qg#Io(cyxJ#@!224*R#.#5:;-ްQ[d_#KxHҗjf'p7SN7>]F|pͳ|?>w1[4N?:]֣^|[k/nih[jCCVBQ$?oux#>)!﹮yRC0|mW:w׺hD9Rɬ#UaYèԧ;3&k22X(dxO:+FyP%zw9o} a =N{%i'sZ?9݆+]yx9YIԀ@nS22`!zQeOTfw5$GBl+!f3SFXu%7BLߟ)Lk`TU ?Y(;8&@6XF` JU2ɛ81@e~y1N*YHg83Oa΂Ym2ć x5㾬CzwyVΛ u[@0F,}#~<}2']{Uo"k^臈*@jN9hva&ͱe![ѕK٧3n?j^2 ׏f`yte)^̵O(x_)G h?\zd9;>}_'Ruݙ cHiƎ:*ra^̶ r)-YY-\>3pBɯ匦,d^srw*H?9y]{5?C!+_p{E7uk6r *kQqx1|(' hOP&CNjWL&(K$? ubБ䣰 ͒*22R > +Ñ,oĦe2)3u "Tq"7$EܳLz7yz[o#70iтٵZM?]5q Ap_hFQR,hd)~YB u>ozy[Dp[:m" aHgŽ}"iVTfO4xE Y"nqt; D!NF4)Ӛ&yi`sE]a٠YOXh6٤!:o~w[ozAʻz0"*?$Ktۢf1P~0rzo~DʲQ=ZG,Y^$Tfe}lPr,%zFȂQs5Ykmf&( t@Ǚ?b0弒J Z%zO/',e Y+r {ؠMZZ"k:cQ¬F@.M.$\1 Fe9 &_ 'P\O(Z"3m~fDӿ#|xA$ODw2*IX]^˽ߚru†I$&0@_YMfm\$Tj^jJ4Y4G$lXVN&I-W(pˢOGz3R/e?]soBh}g!~˥֓g:{5=(grE>1Fz]3ۻsS/Mn\=DIl 4'f 7-DZƒIa!?Hz?>eBY ^l#7Tq"UW}'~g5A>w뢺^۪~#[rOGSi~FY&^rxmx} ["tX]9M^BB5J\Ru5| gؐ;)D[@>2*Ib =$O@;r'֢Ks7mZ0jr=at*Se3/ :y{ЉhP'+3$P#jMSAUjV C;ݜ9NHHBڎ&dgJiиgchdS`Z 8?;ʼnsNks t3>>nE&FR.0&L6DQ0@JDIltU7͈/EÌ|?ڇE_#xB /C|98mJ|WpgdnrD`dQ"Es4 1ů W]~+?J)AO 3P!4E"w]#ՈA@|G{UJ_Km42! ݴnԐZsZ=;*xcys|Q@s#|Ds?d{7f[ ɟ;gASHBFq+fo-ʤQd)1E)~hhXRۙj+ PLϭqh5bGE Hqf_ Co9XRs)?P s'х'~t)?yaVOAK(sJ6GS"?OE\Psৈ8sඳ@:_R A1*pE>SEmJ;Hz/ѭpc\i [| j JGn|g^1|{Oڝɱ!G>Ӷ r-!u^Yv `WG(D/rlOFI1iIr$ҟKmw vn;z<fnȋ?w{}A;$i[so'A[769Bv䱜@}'PS#vO>ɟ>>߳q݉NmTٵm[CNspxE/gDoS^Eؔ_D #^kҲ{yz[ T*G9!@&5{U]AKB*9|SP{IJy`UA6R0=ԉ(*yA!0Z'hvׯA8vƋ?QH.O8Mı|g~E7ϦM?d^KfM>A+x\K,)}*DiPizoRkBiJ}ʁhJN%W=szgq."~#J~JPV dS `Xf)YX̋1Ɗcz}]g5̀D?·P.qv >BP$+ŮSU]G<Gp& w?9yyuGvI>Rz)nUA5'.Zⶳde_pێz {kX}Yk/4Ip25|H_I(P9YX?&T%9@Z }P *A M{š8ܵq z/p?/n痾py޷~"tWLJ%'i!~v/8ʽ<%- `SNr{kq/6G~"G(YWח\0fi2>w^⡦O4c ׋ކ/3Aʣ뱈7#ƺ^9&oq2E[/ģNI+AJۂbQ[$D, J׀ٿp_ 7s>#Yly' sʴr}iswGe4L<_,6Q+O O[ X9?S'*Z1;'ڛAiU5I=#^p]_G]/^^ʶ Cq%(VAY %㯼l(b[.'tB!u3a[N+.cIZAͽؒY\ΐ?⩠?@7UKUU?fW?:hZ)SXO~i7:dd]/oz8o<ɖo\^-q(=ؒS x6ovG}1r')=Zz xow~߿Rz-ۑ_߿[o ~•}<3\)߷BC/%r> kS.mw^E;oLA|*N.m\ʼni~=l& KŦ=J:E\9[5{5|j[,JIRijniu>"._`O~%qu<狓C#FPiv U.F>㩦Wkd"g<׬I^qiX4GÝz%t6L3;U'Q~ϮCF7 7@9HA?v?=9yC`%yqG{uZIwx(kdTj$̗WnE=B=~Z6X;œ2@Ҳ5 >xRk j`YbBǢ6 }[H9& / FJgwg~/&ף? E u/vI9h7vՅ<${oyLC †$SҤ;Z=jAc\MSQFi#)}=Hwh0.}A5&NQyPcD^ 7(j?{W?x  mX+[b\PPCGS܇)>K])u|G"vxNITU!Zkh:N9؎4g[]rw7O/F;;~CjZqE>BvMRs$}DzMORJ"$}Z.;3oDg_" j}{wvXoDž -mCOHٴ3iy34SJW 4>аEHu̫^6hIuDݧ#ӒќOԄT?/C!yejQ h Ca l^F8Da !\AH֣)"t g _1BA7EZoqv -3E CG=lGTKAF6[yEB[8`0A]ņxVuJk9/?QO{I4qZ:Cx)s Yh~(B~[g ЫqC=_ba~,6b?BJ,zq\g8累I]Z$:RnEz9g>˞M͇kʌm7,^0WdH# [ /Lm5 4HN1bä~^/yFmJb4Z20YF\dp< mݰ9A)䫠12"J NyhƕU~ES>%4,#`1?O>vQ=7!B oX}&{g @6#I-͊Ip9VƟҏA=01#@H DQff6DaA-CATũ"xhq3±Q\YHU8$rA?Eng#׼-bbVR/u\:9WPNIR=tOn?vĿG7BЫk,{>תWz f>ErUsE @n)*^s\lNcH%zW>ӿ9_p9w3w?4whX@ d3B ũR-V]RYtbJ.uhus)WAϹ6,ntv~qag 074K5&ptOϜSxGz-YȻr9[(Pdⳕ=]㑆!~*z}ضu*H,rqȼ uz6Ƙw_v7uʊ4¿{%ę*?n+/}3d>8r~D$S4hqv^~rOZ,/ӒO!zACAa*0"l V%<47PQZ?.!dϸ, QrC6E;b췐ynujL!ɾ,fV'>-Wi\XiWnA81nXĴfi%YCcsls]ܵ4o߉[4o>y=߯[vW圈J[vrDž3iLZ&MBDQp"ա(s5ipk(G,CEZٸMVw댫Y.=4CE[$pu^f>e,DOlBµib\;JR2b"J}^'+>LEf&cᢑ)d|\GȨnh~ZЏMb߯2㷁U%%3p=Mbi.=3K"0eaRnhb~%Ƅ~ 7<:M"7|na 6,J.F 綻'8FYzLژmCN~8eo@ђ+PgQ; wF˜dN) g/κ$;d< Jdҏ,MI2(ԘTS_Ex3hH~#u|'.iǡį/$#9 xC󚬃]|Dp8icme{.!^Db?H~ۨNm8qnƧLD J}zq#Ϋyaj~"φ&KJ/!B"a5,J%RaX(׋? Q&js6D} .0 VWO(2=X"fn>θ؞sW#q_i>}>)}Wkp fhd%rz{G@2vkO"; 7j E/7!C7g3'xer./VkDs4n{!IғIHVdJTsɕ7J?ՕٺFst.אCf<ţ(2"*U2{fNh*\UcE xO{{]ϭ曉PcDZ/h;QpC#ߥ{AK[;86ɿͤy5:q>=Ao\7"aCp쁢+vgw7,l^PzVӊ&QcLh8(aO!mKE(y"Q(?{"N_%#}VFB؎f8(8Rw!oG#>7?7 W{cwzm-D8:+(,-mcTCxoI_%o#kg'/-yRaw lVv+~J^ ǞȅWrU grxu!N%qhz$ ڟDQOTIFIYKX ı/V$f!jYfৱ@&,/3|g;|4Ƨw=ː͋=w?p G IxH&@P吟9U/(4?~<I[93OT)Opx~;y>Bs?^+~zˆ:ubI{JBg@iy6olV"yJOG=~uG=z8B3;_}|/ ;8+/0"WQl,#.AnL$-ɬlPwN6~̐)U3p3Mo5×*U#I:={ _nH(YOH᮹Us7 >:I(@F+E,_9PT~̙LmN 4䥳;ˈ08ۣȁws4g*9e~Cˌ/ ǣ=T@z"iYnR#+U|<0 LT:4_Ў\JF>Z[Icǔ+JD*4rC>n-򐵣z_B0|e]EPoLAumg>7Q?M(ar/+pmg_c[Aunì#XLe1ׁ5f>c~<q$r np<-xQHOu" R-~="H` @= n.p3}:Xk\)ķ~.s7;`$=A緛gb娄pԆlv2PzTPu: ~_bz' ~Eqˮ|W讳1*rIp[$3#lX4yODY HK/U9)͟ R!0ďAh;9~ٳɉC | ۃUZ'S|*1 م$W<2&+VFe7|ZQ8P%xA%] דW#/9y !9^i(R+俼Zʌ*d_},"68O79O#|ɹSG$r+P 鎛x=vTPvbz.Au.)<Χ}# =VU 4{`A`?UJ[B]Fn/wXT֏QvqkLip{^3!Ze+}@Y/4; /q1n !v;dX!qAK,"/#>VTwq!WLoo䳆ˆpBUiWoʏ>"vn%VVB}Eܚ."S|r?0]ޏܐy"3 M4'o˺oFs:ORS zYj#O%2ԃmv6X)Re!|lD|?}*Pul)Pֶ”ouj ODMKBz{ TXb껋e8FMe"ge:кyr)QQ~dMZ{¯,#nڪ²"KWleߴ5ցZku-6y~;㔍aSm)e?m5ΔkkTe m$Rw Ne^4;#9߉*D%DPup2{ 3.jbi%I$QGB%CP,st̿u1d=b屻+U~=+|$%t|f&,gT>1zu11/#)lz1caOF8U }ƾʻDaE%SYr gp(>ThP1MْJL͢N"$Rŝ y>T@KƊ+>sT>d1S+vo5Z WGGgC3'[Or9@B2̓ Zv8:Z;W/j/<A9jRG|_H >B^ʓ+pDj.顿pM""f}Dijɔ#Hה$D p](y &~e$dD8O8ΉTQ/G)o\ akʓ!yjzԆZ> IR\ ΡO,{mnaε9#yRۏtWMD C~,D|qQSlp4G;D~C1B>`].n'sC`U7*2.-n~M% ]0Ͻgۺ|7z)?aV[=]ac?cfeVa{ТE N}q >ϩ4Zx,DGg33z<8'GbW8do/#z|̖LY)UaH-Ebw@}Qx\=M}orvrr"C?%djօKO3pH.)W.( "サ/yļϴKȥW/ux2{it< ~uѪ>ze1 c~`:Ƨ/ˈ95Y G vVGI쌔Hp_2+m`\?6˥ +s~]pHk\¢ U|0A豳sYGpNY U8[2 @DfOwUHJ!,~W\Bv p#Z~y1 XU)J(ك\CR֥IQ|Xp񵳶|/)3)2rNY`ujh-~bߧ¿uΧS3x<ۗ*(-ri `ls_vG_1tf:/AFs 5gRĖ10A>.V+E3uΐӛi\6e4ӕ$GGdxLPT5BTF"̗XN8{.. |a՝f{맴3wH?oF~:YgvG+K$)HQ|ذy;/aҪ~I% R*ŒHvoe$eg+a+r%VrJP<\~__ݗdwvӪe;|S Hy:xЗl*i"Ҏzly3ƴU5gNtsr=(y}=:8\S{Uwꡞ@}qL^UoOJ'dB\3=C <0'}KC#C/H_#=B'د\з'{ˑSNLpOQ$ r$}9K/దOuE9fb\p#jF*>"ipYS#S BF@tƩ ZoEk7͒gy;n24;LLKJ=̖VOp^M:U"zLTB}4Qn+vEM\iCgsz{63=$G瑈A89wOww;S$[NdC.MW$j|pͦ5r^Y]:⾮<,Ibbr,6_n|+.7aG8%ytjC| y.~|4alLy?߷v60,-ixUstH&7Er!ӕyb1tI:NZk7 A*Ѿ$县[OeƹEDGV]\z΃z b/ u%3[]4L$2O=!{*8kTmFK*Tuׇ^]|6MiBslz.q<4L(hsJZ}kY kCٸXm<306-39.( ~ɫ:_^Eٙ.}Wި#EUvgqƁ<@w4|8w`&OF8s7"6a[{4Te':h{͈ߍG+z+ez&/lgc Ha8$\S)(֥Wi\58Єp8?)?Tϛ~ 9K͸DpC3=aн7;|7w։"!}9tׇc?E pt \q5]*"SU$U\]z hm'3PWM)TXr3a޸+ﰮC[?i/G\\ߴ^|r8{RJ)K_m$QjUJ)=(= (9X(,Vu2xvFc !WwS~{NW<'.zI:Xӓ}J<:5q1]%.+,`B猐d>^N2&qǵ/jo>~@|'B޵g^u&8wwS:p/¬@H}k%%`F?`8rr3~5ė",w ^=wfKj6%3MR "R_Y91f(&I(ϭd+Gx i>_:bg=W|}yb rTYxR~J^EV/WU]weOǞ4X%;MSȓFc.wDLX8sѡJFztoQ-^Wr{7L]ΥֳKX'^yoa',t n4}=*qؖ[dk'#+R2lGn `BXEZ0Plw&=1vKHG>[#߲ȕL"faŔbGmrr"_!ǯs"G/N]vd6+4@s/O(oߎѫ{ogna~<7׼IC\mg3j$% fr?]uW׿Zyx;^]4KϷFr4#"B;N)m;}G4-!Ԏqz^򙿫?UoY4LמY-o]V/NodBڽBARҊ6L%j[? Gi{g]&geiòrYFO*v΢ rr]eZ$FgP` F aM#xa~`ri҂B@xqc,Hc=zum\^6E:}9`.f!.DmU!Bw2t$(5/ܫVlHwj;{xHZsC|q{q[mCg=P0۫mj/._.W:I' ^EL;ƭ~Pr_rJC:p{5ΣDF?.##<Ρcda/zMC6']y0O1`u8iQǩ g 8{kł'< W)l#$Ní@X`AWr C[dŰ/R^|2H6Bdr^y3^=Mo/kx^qhe.ȶ`au߿'|^|=vaOlf˥ݗrXHCTwXCFmk^◯hJ0F|:S >ʉoO67ͪDvSZ:&yBԙ)*M׃?麗&QH33[x 4+}׮o"!'SbT6V~,E/ ?K Ă1T*ba'%{s$z_zA_C#CM)}—NJ=BrנXذ+#Eg6hnUZU# ̲(^[$(iv+\BlHd3)j%Vld'~QjPM'M~3 fE-5Y%!ȓ#d}$;_8#a!rQ41R]n䫑#{G;G׎!9#c ej] ;cȣcCcR%i ׹BUDgY^x̀Gw` bG#<#nI$JT+/g=syx쵢7s~)e7-tNn^y mҺY,A0 K[ďMnƛDWs,]i-%W!dO+Ɛ'$J`Pbd蓪-ߙj3VdXXMɉ0̸aN Z Q0m]7nm,;B eq+-DdlҨa+ fm ("ˡ #4WyF0Zְ -~q$q\6E|>'ogqr.YJOU żf,c#(tu֌3Ï+ &E9Gȷ!5U`hj2۬W @JLd= Kyr ܨ5Mp8Ct̳+z+0J-ji dH$8Q8DjQTK){R.3ko&"0YZKSDl[E>zo7#eC=Lz̈́/KӰ zy\""H&EJcT-R('Z"e e?30BX:^'m֗ftmG|/¯gB3OLyi拝Ptqn҂Ӑ:%_;l< }P8XP;MT es v8Jhj$a t=q&n|~p W"|rjOGO'2oDnd2)i^ULTߞLdAU ~No/EEޙ$fIBhSX'g$ea_.K2w%\ W"864[ΎLĚ4ȃXԄwr t5Q|cR4gj>?D0LqSuU  x$,ƉΉge||p2M58EADإ/]|v{d mETlt.vf;I̎HT*8$ ]͔ !,9fRR'$ü)[/֯W>=` {`aЕ}@\RkO.4sO;uvϭug%O?1e9n"L IbjR_' )0U ĄdSuݝpֈsB ̄ ۣbRm}g|2:#o™ @XzGm.򍸚S;(5%Ą#/7(9B3ZxC|!`O'"zLC?2}SSuPdq*ɉ:P¶o ;'UMbE% 2ba4,!'JP.뱬Ygڨ`YDհף8( KINr`2AE<IccGD>?".qD &a^6Mfmh",x 3&&"lN1>QMP}:8 t$ĝXHĆ83hO,ԋF6w"ku pQgCS>^߃hwuҎi8XG45΁Èdzv$ ޗ{-ֻ[ʼ]ȪL'AmMn-u*L  @ಬ#7p]s!oUS(K*+* [?`m!Ua:aKAbk 'r_>\>s}g[ѢJ7)?JN) pos"'f,UA8\U^9t/NV`Nn"%QԶH 29Vv0K~^QW r1va埁5Ґ0-*W *pEAYTTu [eXө꟦j <ot02J8ۧj;%0ACы6wMc]ߴa=9r̙{} RX6'B?Z%+M88B,'ʡrʞrI?ݣrX9?TNDYkO%yt98_B0Iwr ֶҤgf%8v#崄sWFLTa35I1W!5 gw|^G~!n){|C#`/IPhe`]oY|b[^:证W׃5a)s * WN\Mߐ7dr&!fRQĭcjzD|.95A=l?? pf,kR&l13l!]`9i-ii1]p& /$ՐaK1F9xIFahmGyRKz^H'!,*DaE A9YOOfdv- j iUHzmu:a 1&dp pË́Yصw >|hG?/|!T.h՞&H̒AH31" !LMAMTFy iHZrC{eh3z|ҡ~|{hWyS%7;rp3ysF*m TvyڿqKw$٥!Dׅ yM[K~65RQ5ּ~0Div\xb󼴪S\~~_"E!3O9%TYpSadSSp^3,5B=٫'I>- i㢟{m ܹeU!O`vR'AC#JhzvmrQ~\ķ@buAyNGAQ⎪(WP]L3 u@Dpmk=%|CpI~@׃d֟X_~|v ^|/]PiK+L}2MQb3ۆ * ؟}:NoM.Wq6oaFcbX94ArWdzF}W}r:LS~/o~ud j/XJ2F")'LHHJlTy_{> '.9oLwI9ˆ}<,h6B@<*Pgɓ)+CGC@gC}񣒔u鉭k}8?.k::p#[ҜaHkt хn"YdWtDK&^B2p$X"ML^q=Xеv/ g[ڽ]^smSs$ Ub x"r5,ȅث#r.$tƸVy{la7̧'AӬ|輠0G^=g*mlSUC(P+6c6E$YjH$<89KT '5$!UK6iu!]ÚIf3OƧQezH&Q/LV <7 ]JI{;67߸ȣ[(4IH:r"<"G2Hp8`|FS/Gx_8˻\Xg%݀{ &)EX7 Ƥ5ǘc/'W"_zrr/7XsF"=FҞ!jY1גS}AzdϛG| %VL7aTx\3¿*=B^m55T-ٺ,<ʽ JH1 }!fL2tY>iTc] qS6bbnN{+>+X$ ~ͼq Sȕ+$Is)BRh`+61S]lt7ƂY^CPE/}DT~@iAXP8y DIZX=O =MdqVM y ) C0P!ͯ<7liÜhŒBdohYCZ} //@KKj*Idp",`E|™"V#\{I7Zyříy_ K mqg"~;Y}{Զ>t#kI)-8?44qa09Od+@+!:w1Z6$ӌX"D+beMaEZlX& =Y>o,ͦ6J6А\$ZZ2̻}`$1oU0~\w7OcCsusmy׉#"}W6b=+jgy?LC\+G.>G=nf"?/ .i Zu[WttbGϚeK6vXwk[8ES].o7u}{Ү.ovq׷v}{P׷wykbtZ.me~wtyW,=e'NFJJ CC<=q^S9Yd .̐ Kz:rxFG%JMq$UX>Ts!!a.jΰHаDZlHI!ZbIFRA͔QN4JR$;14M ̬N/cxSD Csb2D%fAv-rmAAƤT2Y7JqBb9:4=>4ͽN>Y^ wϽu}’Ay`O5=/)qC'slx{:ҽ_1|{5CܗWeN^Dz_X_?vՍu}YӢqyn7N8T]%Nkc>,B(  ꞑ T2ND0N", ( bv<Ε{Q(Q/QW24RFsw WB⪬9<;0~ylOl",b J4 'dUU.c!rءt J8wSkEG˂ ][G^Rku-4Y]S(NrTMz+|l=)L5OPT h%QPbBQFRV&BƧ b^B~}ty0O#WՄ +8utkFb]%뛄YtYäou͓7xVTxHiGT^P Xo%!% V]5P~ ?cTnHC6(ZJO0Jri`w3f1p>),48C:yn9 rLq˒TE$X=QQ,,AɆW?ڠ'#>aڠwΒ zUw{5U\ذ}{mYS|lQeXX$e1(b!fȸ^vel*kBX]|+QCte@?WψNWìK1M+w[:\:UWIVT'}loy) Ԃ%)]óeoKes_ƭbJ a==N2Qډ*ԯ_DmD;B˧ ~b-=Qv^IuͿW7_[z`X<Q!tt.W P. uwj;ǩMy,]>ōH^?W/%!9J&rh1= Jlm/A'0@Iqcʚעwa9LnPv)!y#LP!2=P"oiYz*@ ZXq|S"k^5PqU7]?v~W4a^6Yt^kN5,kjծ qonVed|-mN'yE-_I)kB"\t@~yWzDQYEBLxYa35PTɞ.Ps|VY)`3s#nAIo$I+,еDoW\| H !lyo8s]ik3rTUh4ΟJ-y"Eئ=Vq{lt74}jg=:{6~y~QB^7 p{ ۶- K1_7)fs5R=铜%v26)lmf\"[\uqG 9xNw(5g^!6YҥB}ù^(J]yLu \lJXe aQA>j,kڎN00Z[xCYb~^F`pzfpޮLMMtNsЇD OhP9xL)`o*-6MGsEiZoL'jPz?#oims dpʋ*d\5=g@u4/EP<]an9!f9\sř S7S,Iqp䭪MU*pA=/T§'z{92lI*b=z׉+T_QZ֦\5"K]*QTa5_WǡƢ6 x\X>@{"]?d:؈ DT"g;vH䄋c4Zbj& k&af.zHnvs<\koaM<-T4p hvZbVuRx2M`c3yV9ߍN/6y|HC>:Es݉ɍw~|ܢ 7ܫ%K:G]o =zDL&s"˗ QGag!PElȕ~ԳAIgzv]Ub:+ ɐ{SĸGzOMi.M<RU p0dS ]\]f@ַE?E%9N L!-*WG]mu^fSb5ٸ`ρSQ/&! f#<3u4ա }2GmgtDF0mmjkUDusG#ނys0/FM7XI߭ذěEtWQm9\@|k>4l0Y&6I6Q1T1(db^j2rbnRFܬ'>Lt牗QyԈi=xL}_!IOsןX^"jU4/# 5ޣm;lyt-dpBbR23|US7QbѳubI4-]^Sa+|$dz]GsC8C(]k$?vѢB17V:Lֈ.w= [.Hoy݁R}gэAnl]n<вx]U 5BX.m"6Đ<%Yĵ6ݢkFD (Cț$_,{κ)/ ^pManwmR˗WW ,lpQzK`%))p8 ,!1[X` LFvm Hkj6<lPCi!YBja$mnEG zsO!>v_p'VuCfO*gW]^f-(:6:yLF*,DP/h}:-+ u$U鞵ZݝfyKZ!_zQYz@V _l%-Dc݆*qj.qc&C >@>xi]/Х R!vfF4zbnVhn /Y[viTT@j lfb"ȿ10%"3 0{2@' ;vH=M=pk@vcI?w'zמ)Ѕ^;t\lGp|:u.*F"% W\;kI2$Ub( Uޛߖ`\ju[:[zۖ;>`UW|Wfe *a}=c͸&SD|8/50skPᾭ'~x\jk:K6bOea5z>tsui7ڼ`fՑ&:qd9 9WP2,FD{He XJk"3ɐ> $«\~%:2ȨH-g#^P[G{εx}m^}v:'$iq]pz{qm}7̯UGU-=v43%2ph$s-!d-BQ4|eU^xy}o976,Ho]SeƊ7&vOy&vrxH6R2\1r:'G:b(fZAIREp}J_K~E&iae:QD&^#1$+9 [@@40e%z3F_4'?߁~,Axt|%4nB62iIznw5` d9IY_vWS@%BYGj"WRQ뷵$3t2,Id;cJ4-100%LK 1"۩(됒1Z3t5NNcF2D %}\==捴;AD}g^vNn`o,uMͭtϔZ3f$4'PJp@{&֕GB=`HLILX5ZD (;}Ǻ)r.nXX,JGA*JeUpN]w!~6w "||WGyu}-E964,^D;u.mnzym$gf0328C15C Rb F/F2Lr'* mdf~wҸ] s)0 a ktLTFsVWUww>n?G'noAc\'E4UL2tcaD1E.+pv`ߥɾ~{OP?7#~{D+^ω.g~E (_"2Ph7"P:=et ˎrw4r5J SK~=3;ip+eTj(/8$wk+=ٟS?i&_mK<.3>1ZfTJv =uDRfWJg=Vi"A/SM _V' G&F-㋉`95Y<}n*(]XT~C*6KU*ΉgFB2Cg=6ϫ];Kw~v}k;v /uY_ v]~8m)ɐZneٕV⪢ꊓX+tswQп%v?C}JF[iEb\c >/J. p"K@5nIzIE9)<}ݝZMjB< %0FBKp&aXLȦ ږ\Hga+1$2/8(#i㡠>! Y< g3K-9[E[{`_A*.nxp<ߺ_x_|qDߐ7DՎ@'>FzR$40d4Q=Yd)<r2'j&B$A+d:3[jzz{X@kMnGđZn&Ub'G"!iG$ǍAE#TQOaĚ *Y($1Jz ;Ud/`<˨;ui[5i?sJ侊 jL0D5|fNش/obٴUYVyu/5&?g"4/1z x̏C˿;!O=)Pm[}.O!xr^ui:@E^rӝDg›RsLc1+BQTX6M <Qy_Y+')\GX DuvL'W?>15Ίx:Ċa`S[(_F]CH6aB1b;aɫ*p9A鷄̈́A?DN%KD9xҐS} 'x.7 ~'}t_3VwOQhnr7V @m=]ߎ _\*.D%TQNfF.F{A2HŽd:iRB6 MntNFsn4PO^lj*QSm iyv"!cAr |t Z}HPD$"LO.Ub Kg40O&+6VkPGP{H/uղS3$s(Ot9I@cQJYaOFv0U!yx]ԍ}Ğ͵ M~xeu`W8?NRvb& t-MH\[4dSl[JG> ? .? %m^y3 E ؕDAWO ` 1yx3 vKg!Oz2?jٍWuu*UW6C1ɠ姐_ $?Ǧi$^Ox},iugZ!jؐHy6d!IR Bu@2ŊZJqYb$kţ3}CT /۪PgbmWΡtXP尪#,dPæ,,{WB&LdUt @}꧃8A f5ľ FDD:,Jb P2γYGG!n|*†7;t ~C$Yv#X|!=07-11A.=:[4dkJmrhf"e8P~6w҇ċd=CmgILgm}'ek{M+ece:GqP;J gő!'nD]> J+_"~3[3u Vᅪ5GAͦIb^)i5Or|LA"5yqϗcCn~d%:wtZl#'*8c 6aD2'!UMYR!2 =)/{*#* =rN_)~%N`50Qd0Qo7*Q_P:ק ?Gٟ34*FHY#o, K1Z ֑D|+=COw-]}|y,dE0BB[:1*Hta6!PdVݤraRt YfH4}xdRMIVTE Q7D̝3P0}(վs=J@8\@ߌA: y~Ft/`'ϯ^8:vH7[{zq{\`2 BE&UiB(+ԐͰHx,1^' TD_7[72ƨҭGtrƙiFz2mkyxd6`ʩ2gu2^W$JQp$98Hֱ?2DLXGpun' T tf)[ȅ`( &pW-3ώ 4gOI立:W~c~ @9ה:Z coay>׬hM(] 7m:ǣsK|3w R0? g'^vשcNSf}[5sRЏ̗AJCz9` |)z5-]\.չ#LV;*y~kPvߋrmx97EQ'ڕyHV\Ӫ[fUvkM Ym/̺i IUB' "=\^ g) \JxHW~ _n]٢Pllv7s}3ŶrV9J 35A]Q>,jk%.V'yHEGoj@O?G {]^$4~buKO[nk%Uz-|*/*:Õe i_t/Fi^ LjSr; P6ߍ}Ո?bqwhR:o4w7aa<8!Wz]]d.{m~=L;Nf*R Na}+p̓u{8pKҷx5f [k!EGPm;[NDe^Ue5$G'!DQ7_Z82T\CYN)U1C6 ['㵠]@akAy/~Ǐ=} Tt rP=ěZskzIx`g,?W;=?K@/yt"Q}!JdPe( j2{^_9# s}ev٥oK}!~ƒ1mC;SKòJd^$s!%1de''#Ker <ˬ8rұ8_GCg(_vmo6g˗ۍFkh3j8Z50&);z{95#:?|0}+.}[~YҫFFI,p9+AReO/t7;^>SV3hw^ּ遯0QqdI߀%`OwJ̹_WzK?"^=:ǨQ#ad7#!KȓiXB]s$fvo ~סSAv[_zYW,k@ xBGrqk_>ںonUF6-`,9h|w9n7574-]:?,>W)I2vCcK]=i]I*Jq?=]F1q,(I)VbRұttTxpt^t<$I6ݟqw8:$1sW4,o]rZfCIbs;qB\~:]nƥ]JRJpisxo0o?NjIMNN ĎX F12t<̕=ĿB0ݣ.~^t@&-T5t}sT'_}|O-T-h$>{qARE![GbįE> 祭r/Nպ#uXm~[4a%ؽӇsx;pB(A젅Nj 5 ?) DseQvi~dF6P^MsTM=0:BIŵ\I`ZrEIZ.OǞ9v<.S'q8COE>9$|'|мz5rr"5]e-|SqVF)lAP-#|#eȸdP):~#g+ү8SXko+F4u^ ;: Qi\"Y0 2߱<(?Ĥ>=Lr[VAT"ȇK 5_$-iLb şLS|)iYf,33[>N؇x=iGXpQ"znc5rяQɊRo:hQ-5j"zI(Op6Z| 陉=3st#-7?4htLF=^Lz!4L }- LO zk_W{aGNwljêP.[;9p>T ׼ϤupRB7r!EIR,L"PF1lTIӕ$'2Vn%ԍ$#He=DЌ㈖nJYAH :3l "dG_wBsDQd|FZS1OF X(CƇ"-(߯)&[HUn<Ǟ| A|BǞwu߂6 tQvUE(򀃎X*sIvծ~"4Z.P8̵:vkoSպvꦪI"\F"8PgFH(8SS2M6[#9ܿHDJfMA<' bd|\F!i,z9~҉|*[gT//&s?==>@(ke#ޯ3lU|ħFG3؆} So ~8E5jL'ဌ)'́r"T@aS^*w+ Ʈu()F?@=dJo0!BMqϱ,Qd`Y¢;O=xz|0>P;G^缗74ԯݰ!FwgT;hef \OMC0]2qK.N':Vh,:M0U gdoO\g3&k#Qί|+} #8i-D:)]ө> 5yB[aC7ׅj Ȝ̦h6;rkHn=O6RuMPOj;8C_R(jv?reM (0[sݰcN?ϼvrN;hSώ*fNe^9F5<SU[DsA`֢覊i":HG=QJ/X .6apsx:ReM D;Y-Q6mhAdVJU x!~h3XA~x} |u7By8% [W6wdh1ptG\t"2?\TmYH[9}")dHL?I7{t>SJM7F){ _#V.B[ts5ae$Dt4-\ TJ֨&c탊mu!4Ƃyvw~vP2GO#~>y '2Bt3;oW 2/~K _GȣL~~~Ô 2j\?YB3q}ap:FQ ߵ,@\Bp#]ܦδM&@v8c0԰@;B}1 VD0^$Nw~޽?xIC|{(VyqKP)yc9S Y#dKOя82 6~ĿCP~7>ΡnjzHzm2M' m=׽J:ӫ5v+߾k@.,Qʶ>en_#כ8*HCC .3$$B=aɍlJ3L@?6//hwm8zCot'7U`M˕ݶ<"'#U[Z{+HJ8ꮝf@QYAxo:!%#3|p2R^T?{:%4V^ckl8󔜪Jly'3Qs"hpBt1t~[, ~/_m8$#d^%]V~7R\F- H\i^(t\6RYMgYt<’!iRQ],$Rf()xs/^; {7w:ۊ"4) `}bmn~Ʊ9/B8W Oof{Ca@sPE sopxޢѿh h|n_yz,k3HlRVt}Pg R+s쨩744;,] f* 5DJSNrwv>GBtcxHƋCWs}lqM %t@1h YmeBP^W|=e^_)ʳJr`4~71UR.E;oGG=Io]mkArvb~$Iυgmd{F:]/u>޸4ن!u)O"=a>;{8?ǿI"|H3_ۻg}; z>L LL:TsN}^mE*W6Qs,0?20ylAM4֯@ȹx.z%Z}(v䖿-ᣘg=Ohl)@aksδJm0 4lQle:8#5S8b6?XsQO'`u< w~{)ğGOO{Լ<ܔ1FX4qaӘx8x]K22gYtlJw2ehos{iL.9ZVkpf|&|*_ %z8}N"J iv,W9a=)*~P[Y8k"rtԳ}h_1H…~kܼ[ţ=_csvڰlɂ+ꛛV}vƫO|ݷ:Դ?7AʠO)r/Rm٫X/ <'o?1}ɳPy12ͼ*Ӱ<>! (dSk+h-ҷ쟪`,]#i#,  1Y!~Z%ͬL /#jf}i`=ȓ [9͕TÎS>K@@X1e-$j:|,Iy @3)_8vݩhlX5qQ (*CDY~0&꿑 _  L x%X|44t1eJʇ&P'(y!.BѲsmg"_SDBſ(dȓ BX},ܿSkV0!otk"Z¹G #E?!r&@l}4=-Pͯ b hub֤4d^жQn:hwRxR5>* |B 1MuvQζ󇁦 x_UCslm , t{*h["YB{&X 1,W3 n*. ?[jOrK. g`#V$2t(?*ԻxCQo`Ol4WJ+IT,-;]]@׭F>P^lmo~+#E( f-7Pm>t@ـQ4d8c#PLHYɊ9]Àbfc_E< jX_OLBB쒟 B?yQ7Ca7\\Z\uhG5؟i4_4uIj^LfK0BcPHjJWSRB1>#^ 7XMO&b SXV~;-+6 B b)ibՒ Ի]W3|94|ho sjBMR  kz E鎗8<;̳{D ӝ\9t`,ܜ0Q(%չF_rT:yK4ѫTl67l$DKwnE2/F( F"~)BDNiTSck<@GqUm_ѝr ym3>V@ ~O )y`f l_C",4TpB܏\V,m Uƿ={5h}PhuW+~ź =\]f9l_0 ۞l 'K2ZZ\H/V Ỳ^zN؃ I:huy=컝\fB ';[Oa/s|v{\OE]v#>2Gw7#y;^'k|("Зs:S7h\@e ¥! G~K5E}#A$6OAQqR$߈ Uf:t'K"f{93\w;կF| caG\DI8O:ȉ75q&My=mJоnْf)/ _9Sd0Gsn&$+9,C{3K/,%(B>G -">E{w'ޒr`#HoqӍ58;p =a_! K8+%9=ޡg(TE"gpD|^!~ ºDoلU-3FN6({(yUtMv;WZ3$&I?PvXH :~|(St>uR<{ uSX_><9ҹ9ًM+V.ilXF| l~qRp})OubIɻ2m܅E'~oNO/4b`2n7B!RLZΧ3` 1sKQ< |T?oE؝gV#.Hsq83Zw>퐞/@ePx5(97Qt:;pru @1W>#~USW><=P/ 2HL菬I`%f@hPa!1S/a@ _mYf/b?$ܓNB+%`"Hjs>B0;f&)aRՀCμ_=x tO/x7x~U Vڹҿ3;,^~><_V6AM,ǃ3 #`)I1*C 'u"Ym_z#B#yRqu[:q~C=5 ݪξhס%{@v;'m\I r_HЙ7EZD #Z8$5C{MÜB!x.AB {"| )!Wh; .京SN=ceKδû*iUv<<-+P\8^t;P df{Ի 쯭m_;ΑPhnC (=0ߞ@'h2,Dpܜmr'7|@><v4qs[hnU=4bWiqS0~ %8|o^Mi/ӳIOO8'!tS ǡ8ԣ մ|yS#(*DE%F3r 3syo?&u?/s<\FRr=ɼ~D6.joQzUy!A2B Wfj5>F F2;z_cA^&oF_\}{ēz0ҫWpeWzcU84mGi-MNk٫kbR[hXa~+>jqćz~;C: 3?ȸ44COs%?qEJ_@>/B >\sǬFBY$5PRHDv}Sȣ]S>͔? '%?/hvu3۩p<وBAG.~_==~MF| ™==~nOz~#NX;6nЎqz,d-G. Q S<$k2P?4ud}ק,زfst!SEyuͫ( oQrʩ+:ë4_=}1{ qd>;5y{~7id/2y¤GA|i*a-L/GkD_W|;.4AqJ [OEZn>OAP=,@TzNF}(rmt /İJ &)z.,/KbB ?:'>fVt$_G[ a\Aa+IR焇)րn٩!~_oFkY1#"l,v 緞`T~Y X^x_";xƹK#rz:NSzc}d_3?|k_q~t~B:썝=MX/ <UjZ%L% EZB+ TA;lxO=nׇkpPJK@4I(r7I1ϪD)?yH ׊,]hӓ9c2&$ᠧY"UC&4*=U$J)4 (|է#ZKd)ye"ƄҢT+J/_v5 D*> QIۄ7)iJmڔ5%v Gv|ՋY9.hٓ%atQHZ Cp6! P&(J]Ηfz)6S ='4009:*7<чR5zT+ MgiuaXXwGT M`1fj$@p8-;!-|T0ɮumU_OG|)BS_ϿH?N/!֎_bI $)5Az=,=R;F#=[e ;UNu%^#~šOo%=ƖQr uһԠ~zj7Dn7/y:NtftksE\: ;б>߉+ )uT㇬Z=ټdݪQIIzs" >t?ƧKU2?$=u4 3'@~PlAe^i./QŢ^TX'ijR*c~6!^*%R?F|R?eu>o㞃Z|MX({sִ&/FΌ"҆&M悐Y~Mӷ&oP#ihCo@$L*Q:a9EN}˼ߎVWߤrFz -wsy:R}si%ʬv/:`yKvA.7^SerU}W6A|| Ջ $.oqXǏldC_?O`2{0M^UrrD%LJ'o#l_y|w Z|>EIhMȈȈ.A |R~bf$>}"Jd}t*|Ѽ=n<>(,_YîQ5F}{8z%HOv+2wZa\_#)t/y/f8RU*X&iIY %DӰ鳳 ]ó vx X}ncğFYUE:6|供gNۺ>~;]ae[F5>Rd!Y5"f=-W+pd[:0~ +Z64w$K5*crP+vʻ>]wGn6FX\$ =آƐJ RuDQPANCP2:[  g ~5XӃUw/k5K|dH:8u1ȿioRJf =JG5K#X-#7aw@d]`wa֫bWB8eW-km;a|hͪn~2s@I&UNgn/'(haw0T!F*dUiu4ԕcC<:x;Ÿx|Ga:V: n+##˨FDmGGʇzt C|#1a%aɑB3H2BQb2$1Ukб=CvK{ma$;:}ھhm¥8DhμEi2EO-0Oԩ~F0ЈcAwLJyZ:yV͝!{0?}p'gSfs7ƱBױC?Q ȴdWݿ+FL'\:nCϺʇ<)gkHZ(f#׾GEr'_;Bs)7rpFӊLF3+j~BA7SGD(п*/& "B[B qJc{5׈JFzVzz=;NS#CvǤp 3o8@1(J-LAZEoV $*:dAؤͲzS\c~ ]='_]><5ޑ)mY&xHci}乺c0Hd%(kɐhU{^ʑ(4UTL{!J ־jǜ*pB?"~3ƒ|*>tW,]8'Fn8m|29 \ïkr'UQU|REW-7G Fz " QVu?[D( g;x{.t`dW  Fx8\" 1V?^h6s}aɧ54.KH.Y_D&G#.F!-8(8><,v(&=}R}8B6MZhQѢB3* َ rjkj50~ACk3tM%M-rg~ `5nbc#t͟Pa>iyÌy+Dy}X7.0S%B/ @;v/hƺc]G׍߅51tS n(atJW<@e|DH1R0-V8dw6p5-c=ykG.6.ƙxOm67zS[)v0vƂ)viLI~J̅s`:$<}O еl'P_KgF6(Sq@HST?ҜJEqOC8u/nx F\- u`Oh:2;.W B[|`7鷨ʋ^Ej*<*xooρ -|Lt *\rŧ?73D[4IG7ۿr>!lC~;9~>e슦]D:*7bْESW.G{)ef۩l-N~ (|d7 8%1J}oB\jFAգ"냠 銢g}Çjt:08F&#/F⸣ð{D+ȟ s>_@G4԰mL׭/!UwSJYMIΧrktuXÇblyǎg@3`m?4IDj(Acs&BC.jg^k?z;sy튦s'jKXC/R(w'r&xU`je#%9߁QtJzm?w{EԵ.{}_8l\xGG/KOXMNrӳ$J18lb)H2X2dÐ:1?&z|S'1R8x{ŋ5ng'fo~!j4oh4+b$OfB JK#h7J>aBԷ8nJELy/#YHo'SO=,5UCSjs10j -fmJ;i.ݬ`|-fsxܨKsm*|3TI:;lt 4C *@6F3XOB /ﶱD|'5 ))edXѭ?`{ Ƞ!sG[F/4ڇAB7YPC92~"|_E/z#r2$c*$˙ P췃hY-Վ=8]@kȳ_{ ZTKxqJZVb搭F&`}T'dV49KGdO>:Ni˵Ft zY5 %-JBz}MXOL׀}=VNlpr2LUn5tՀ_0'ǧZ 3I趙,a>Ieԃ\İ`Z_ Sm/}Mp烦xD)S2u2gsr헉{3jaePr Gc6DE \68B??[Gɬ_XZ+m\ ~6%\`$6=hzgL1?KŔQ#%}l!ʷ;Eto~#dwx$᳸/I4ͣg23fOYnȥlɎ!dmD.Eۮ0r 5~翌ncqvB;4g4X๳uk%\yƇu.2;FbT÷H.XQ;ݥ/Q25x/t'9Q'x/ 7K[iAjA#OB:D@dI%KL%TN7ӽOBaaKEUIFJǓi޲ǔs5>]xJE˖zcɳRΒ N~>ër_A00ڰBֺ'oJC'JvˇSiG_6+)rc[pL:ė͌OG,bL\! זUв޶&Ûi9zvSNn-+:-ƝF'.4\ן.⬓4Z꒳ޓ+hrKa>05XhI,-LS {O;4cObB5>8+]s:IDE]:?'p ұ$?:Dɺ׵gzA4ѵnyb=|Sf4;1g6f$/ W!QcN!@_3coOΘuyޅ:DvHpjשL}cqu6#ٞ\yfe\ڃ?z,hhi]tu={mYܹ\*#ޭ sp2T{U9 \׎po;>?r2#'wW=yg*YETALL'O//n^ o]Fwo7"`Ωt_L_$VYїZHT۷T!%)n|fg%%i7qd?S0G? nG@DU4dWQMnw9/:W1*"M@|o^h3f͏gHJh 8U4DC.U.IIȕ[zȴEfa>U7 Ac|_v/d'ǩxѝU`9&m<,XZ-q&!\+ZoFؼ4wc5McOr2Ej'Tr>HRuK1 FI 4GzE/:րv=,]\k.vBK(g'Z\|Emm'ļg8TS :O %RY px;o͎IMx2QOk $[Q2Ip7#4W _xsVZB&=MJdC j%C krڽQ^Il G_b$&=І<]Z߿I4 D("iF4p;w eUˬ ]5O'YgUΨ2&ͧa'OTx wmxy6V |6ѿ)x|k6x.xt[yN'kgh9$l[7d|?^;bב]3cQąs#+7H|ozM3fWWUh&L]:73|wҸoWOTΪ2Nyn?q>߈do^re 7}au]d+ *WU3gVWWQ" /|0;{?jfT@( ït[pWRjEB+P9}ia÷5o}R[]5z&eV$2 )&g7}wJz2_R5b @Hhe_sA[g{}~Q[I9 PR[a\4!7t_V&?0˻Kޟ$۬;j I~+WCz͘]{ O1˹sCEH9?*ZGxWWCQ:A<պ/y 79,A{R($~)ֻeB7&ߋ|)ۍ2n3?L#V%l("٘S {Dwlp-QpX*{/&pJW\H*+N J &coh0ʤ XG ]v+mp]|G33UջwhUp~|h>c\Q1꿯 V=eҵ?3l僼yҪ[Fz{Q|ގ_<0?fM%O8wZqܙg1lpmpC45^Fz*M8}68Y6Pᜲ8?Nc VykYv tlȋ3=i3LbY̾e{wW>*a pY̎;">ӽ6 ^kmE0ep8KQ!-}[]g]zڃtpRR0/h\xQN#>k _FCbu)l^p8OT!zH?޼9mu ZӿW(bBӻJu"Nƕ!*!c 5P2,>gL?kpAs!7+,F# IE*&up#Hw{)m.EGܫ%$$X|/s)=24Arθ8j)4]q ephUڜeZpKpkHnMOr]kv-ssmi8yR2K)4qʬ 0HMZ_ Ubè歍) ([UVLS%bƧnhӬWY5aiK?yߌpq[rYz"|_Qz]{_|؀/3J~5IYIQ-i n~ߙ5v`ZOsTrhpM8?fdIOAOHOz$yI!g_l`$2vK+_I_D{mƉD0` IYZ=K[M~ >6|kd:tT52cd|ad&+r{! ɬ['RDӱɚiX(EU`uU w¿d;~xNƊ4gDuUvO}ViB9~T27m݆n6$9ѿAD-I^1RU3eanjx |SosZo<#Fn ᕐ n"QM |ZiOπ /y~?љ7CmmOG8(2-y/FZLUr?U$KYTp1Z?V&+ͯWa-n6pkBȢ-"]Q> ~ˬ{։"$'L vIwX)OLNߵMEr]&$VkpV+n*p27o>‹Vm[md8[/ѪLjm_Ias~NP N'>hW"<rŃVΥ`*V.99}rYw7! dx63`kz֩:LdSfN=hm|mHdc i$6|)j<ٔHZ؏ܪ{Qo[R]>og5O@NĒ%'&8Gu#F&onf[w2ah&W[s:~ iߛ&Vv ܟ?`#u;~Б7$ eHVW2) wX3gՎf 7>lw# &~"n)m%ax hI&ak (;K|[gCXwyWF >=tN$f:Cs륅 YA|q2u46:$=&O G2y«[y7fuH'O~Nz.vjk8zf /h aų:[O 3qvF8tD,ìH:QG;ϐ@`]n AH.COgz/J+gL-gRphî3'bp J2_ۈ+!HSV_ff Di$*}L vzv;HW{*n{a{%}߄b{@ Ul fn 4d#Z}I~7B{1uaw2S ը}qGvQm_Gx!Gwβ/ot^4vtU1\+;qs]-ϝ q]}S+ߓ<}ƦJ_R/@B#!US!. c=j4@njt"<21gZdIo7'SJJKX%e[63zI㒱kKY:kUJceC7QwcgIZR;wp;|A7p/ARȕs^>i͙>m 4@q<)8>,io8'柭kٳT? ̮`   ᥐ#|{G(o({Yq;Sr.9O> _߰N!xʞ0x"\ixYĖ<8hߢpPD : %Śu&wodN 67ν[3W.!yB]j^"77;cJqP"]Epp.]{;"_%}F!w c\ck _8UD0'ѐ= |(苎bwh*Қmw?JȬ_ѕ]lh:%hls4+ 5ʀ]^Կ35߁jߞFC6ȣ9FP$[}JvgKس]l-8`z2;~'}}Oyϕˌͷ42Y^]xEa!A!!o  2 ӣ {,\(אݐ (Cm4A* k!;! lR wv4_T؏!OBV@A}́L>x@6C! #!x}q*DC ;酴B  @}Va@B  {rr9d4佃 ;g'bp8:=I&6+OQOߋ1ѓE7 7:0p,@8^ |ip]]:~\s,7 qx-_,Pt|52Bn,rR]GZRmmvLh FDX{AJIEBN)^`[ȧH0MShE,g7dh"02Y;FR洦Y-->Q.4Z: =r=yK"VM:ۻΓ4_<w:}CBE{>XYEUӚs.h(vجjg8 vT[. 0 ϭ~qh6m}e)Ш{G6=%ИQÄޭK#˚bфSD<+^"oD-WR\\' Im)&rZk4jKšg7[:8u(Ȏky.? PtӁΫNkg afF' _TVЍ(?)"\Hq SO$BD~?#?lIlP/p=D78!_?i4vuln-1.0.1/internal/vulncheck/packages.go000066400000000000000000000104521446745451500204210ustar00rootroot00000000000000// Copyright 2023 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 vulncheck import ( "fmt" "strings" "golang.org/x/tools/go/packages" "golang.org/x/vuln/internal" "golang.org/x/vuln/internal/semver" ) // PackageGraph holds a complete module and package graph. // Its primary purpose is to allow fast access to the nodes by path. type PackageGraph struct { modules map[string]*packages.Module packages map[string]*packages.Package } func NewPackageGraph(goVersion string) *PackageGraph { graph := &PackageGraph{ modules: map[string]*packages.Module{}, packages: map[string]*packages.Package{}, } graph.AddModules(&packages.Module{ Path: internal.GoStdModulePath, Version: semver.GoTagToSemver(goVersion), }) return graph } // AddModules adds the modules and any replace modules provided. // It will ignore modules that have duplicate paths to ones the graph already holds. func (g *PackageGraph) AddModules(mods ...*packages.Module) { for _, mod := range mods { if _, found := g.modules[mod.Path]; found { //TODO: check duplicates are okay? continue } g.modules[mod.Path] = mod if mod.Replace != nil { g.AddModules(mod.Replace) } } } // . func (g *PackageGraph) GetModule(path string) *packages.Module { if mod, ok := g.modules[path]; ok { return mod } mod := &packages.Module{ Path: path, Version: "", } g.AddModules(mod) return mod } // AddPackages adds the packages and the full graph of imported packages. // It will ignore packages that have duplicate paths to ones the graph already holds. func (g *PackageGraph) AddPackages(pkgs ...*packages.Package) { for _, pkg := range pkgs { if _, found := g.packages[pkg.PkgPath]; found { //TODO: check duplicates are okay? continue } g.packages[pkg.PkgPath] = pkg g.fixupPackage(pkg) for _, child := range pkg.Imports { g.AddPackages(child) } } } func (g *PackageGraph) fixupPackage(pkg *packages.Package) { if pkg.Module != nil { g.AddModules(pkg.Module) return } pkg.Module = g.findModule(pkg.PkgPath) } // findModule finds a module for package. // It does a longest prefix search amongst the existing modules, if that does // not find anything, it returns the "unknown" module. func (g *PackageGraph) findModule(pkgPath string) *packages.Module { //TODO: better stdlib test if !strings.Contains(pkgPath, ".") { return g.GetModule(internal.GoStdModulePath) } for _, m := range g.modules { //TODO: not first match, best match... if pkgPath == m.Path || strings.HasPrefix(pkgPath, m.Path+"/") { return m } } return g.GetModule(internal.UnknownModulePath) } // GetPackage returns the package matching the path. // If the graph does not already know about the package, a new one is added. func (g *PackageGraph) GetPackage(path string) *packages.Package { if pkg, ok := g.packages[path]; ok { return pkg } pkg := &packages.Package{ PkgPath: path, } g.AddPackages(pkg) return pkg } // LoadPackages loads the packages specified by the patterns into the graph. // See golang.org/x/tools/go/packages.Load for details of how it works. func (g *PackageGraph) LoadPackages(cfg *packages.Config, tags []string, patterns []string) ([]*packages.Package, error) { if len(tags) > 0 { cfg.BuildFlags = []string{fmt.Sprintf("-tags=%s", strings.Join(tags, ","))} } cfg.Mode |= packages.NeedDeps | packages.NeedImports | packages.NeedModule | packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedName pkgs, err := packages.Load(cfg, patterns...) if err != nil { return nil, err } var perrs []packages.Error packages.Visit(pkgs, nil, func(p *packages.Package) { perrs = append(perrs, p.Errors...) }) if len(perrs) > 0 { err = &packageError{perrs} } g.AddPackages(pkgs...) return pkgs, err } // packageError contains errors from loading a set of packages. type packageError struct { Errors []packages.Error } func (e *packageError) Error() string { var b strings.Builder fmt.Fprintln(&b, "\nThere are errors with the provided package patterns:") fmt.Fprintln(&b, "") for _, e := range e.Errors { fmt.Fprintln(&b, e) } fmt.Fprintln(&b, "\nFor details on package patterns, see https://pkg.go.dev/cmd/go#hdr-Package_lists_and_patterns.") return b.String() } vuln-1.0.1/internal/vulncheck/slicing.go000066400000000000000000000024151446745451500202730ustar00rootroot00000000000000// Copyright 2021 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 vulncheck import ( "golang.org/x/tools/go/callgraph" "golang.org/x/tools/go/ssa" ) // forwardSlice computes the transitive closure of functions forward reachable // via calls in cg or referred to in an instruction starting from `sources`. func forwardSlice(sources map[*ssa.Function]bool, cg *callgraph.Graph) map[*ssa.Function]bool { seen := make(map[*ssa.Function]bool) var visit func(f *ssa.Function) visit = func(f *ssa.Function) { if seen[f] { return } seen[f] = true if n := cg.Nodes[f]; n != nil { for _, e := range n.Out { if e.Site != nil { visit(e.Callee.Func) } } } var buf [10]*ssa.Value // avoid alloc in common case for _, b := range f.Blocks { for _, instr := range b.Instrs { for _, op := range instr.Operands(buf[:0]) { if fn, ok := (*op).(*ssa.Function); ok { visit(fn) } } } } } for source := range sources { visit(source) } return seen } // pruneSet removes functions in `set` that are in `toPrune`. func pruneSet(set, toPrune map[*ssa.Function]bool) { for f := range set { if !toPrune[f] { delete(set, f) } } } vuln-1.0.1/internal/vulncheck/slicing_test.go000066400000000000000000000042111446745451500213260ustar00rootroot00000000000000// Copyright 2021 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 vulncheck import ( "path" "reflect" "testing" "golang.org/x/tools/go/callgraph/cha" "golang.org/x/tools/go/packages/packagestest" "golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa/ssautil" ) // funcNames returns a set of function names for `funcs`. func funcNames(funcs map[*ssa.Function]bool) map[string]bool { fs := make(map[string]bool) for f := range funcs { fs[dbFuncName(f)] = true } return fs } func TestSlicing(t *testing.T) { // test program p := ` package slice func X() {} func Y() {} // not reachable func id(i int) int { return i } // not reachable func inc(i int) int { return i + 1 } func Apply(b bool, h func()) { if b { func() { print("applied") }() return } h() } type I interface { Foo() } type A struct{} func (a A) Foo() {} // not reachable func (a A) Bar() {} type B struct{} func (b B) Foo() {} func debug(s string) { print(s) } func Do(i I, input string) { debug(input) i.Foo() func(x string) { func(l int) { print(l) }(len(x)) }(input) }` e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{ { Name: "some/module", Files: map[string]interface{}{"slice/slice.go": p}, }, }) pkgs, err := loadTestPackages(e, path.Join(e.Temp(), "/module/slice")) if err != nil { t.Fatal(err) } prog, ssaPkgs := ssautil.AllPackages(pkgs, 0) prog.Build() pkg := ssaPkgs[0] sources := map[*ssa.Function]bool{pkg.Func("Apply"): true, pkg.Func("Do"): true} fs := funcNames(forwardSlice(sources, cha.CallGraph(prog))) want := map[string]bool{ "Apply": true, "Apply$1": true, "X": true, "Y": true, "Do": true, "Do$1": true, "Do$1$1": true, "debug": true, "A.Foo": true, "B.Foo": true, } if !reflect.DeepEqual(want, fs) { t.Errorf("want %v; got %v", want, fs) } } vuln-1.0.1/internal/vulncheck/source.go000066400000000000000000000254121446745451500201450ustar00rootroot00000000000000// Copyright 2021 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 vulncheck import ( "context" "fmt" "go/token" "sync" "golang.org/x/tools/go/callgraph" "golang.org/x/tools/go/packages" "golang.org/x/tools/go/ssa" "golang.org/x/vuln/internal/client" "golang.org/x/vuln/internal/govulncheck" "golang.org/x/vuln/internal/osv" ) // Source detects vulnerabilities in packages. The result will contain: // // 1) An ImportGraph related to an import of a package with some known // vulnerabilities. // // 2) A RequireGraph related to a require of a module with a package that has // some known vulnerabilities. // // 3) A CallGraph leading to the use of a known vulnerable function or method. func Source(ctx context.Context, pkgs []*packages.Package, cfg *govulncheck.Config, client *client.Client, graph *PackageGraph) (_ *Result, err error) { // buildSSA builds a whole program that assumes all packages use the same FileSet. // Check all packages in pkgs are using the same FileSet. // TODO(https://go.dev/issue/59729): take FileSet out of Package and // let Source take a single FileSet. That will make the enforcement // clearer from the API level. var fset *token.FileSet for _, p := range pkgs { if fset == nil { fset = p.Fset } else { if fset != p.Fset { return nil, fmt.Errorf("[]*Package must have created with the same FileSet") } } } ctx, cancel := context.WithCancel(ctx) defer cancel() // If we are building the callgraph, build ssa and the callgraph in parallel // with fetching vulnerabilities. If the vulns set is empty, return without // waiting for SSA construction or callgraph to finish. var ( wg sync.WaitGroup // guards entries, cg, and buildErr entries []*ssa.Function cg *callgraph.Graph buildErr error ) if cfg.ScanLevel.WantSymbols() { wg.Add(1) go func() { defer wg.Done() prog, ssaPkgs := buildSSA(pkgs, fset) entries = entryPoints(ssaPkgs) cg, buildErr = callGraph(ctx, prog, entries) }() } mods := extractModules(pkgs) mv, err := FetchVulnerabilities(ctx, client, mods) if err != nil { return nil, err } modVulns := moduleVulnerabilities(mv) modVulns = modVulns.filter("", "") result := &Result{} vulnPkgModSlice(pkgs, modVulns, result) // Return result immediately if not in symbol mode or // if there are no vulnerable packages. if !cfg.ScanLevel.WantSymbols() || len(result.EntryPackages) == 0 { return result, nil } wg.Wait() // wait for build to finish if buildErr != nil { return nil, err } vulnCallGraphSlice(entries, modVulns, cg, result, graph) return result, nil } // vulnPkgModSlice computes the slice of pkgs imports and requires graph // leading to imports/requires of vulnerable packages/modules in modVulns // and stores the computed slices to result. func vulnPkgModSlice(pkgs []*packages.Package, modVulns moduleVulnerabilities, result *Result) { // analyzedPkgs contains information on packages analyzed thus far. // If a package is mapped to false, this means it has been visited // but it does not lead to a vulnerable imports. Otherwise, a // visited package is mapped to true. analyzedPkgs := make(map[*packages.Package]bool) for _, pkg := range pkgs { // Top level packages that lead to vulnerable imports are // stored as result.EntryPackages graph entry points. if vulnerable := vulnImportSlice(pkg, modVulns, result, analyzedPkgs); vulnerable { result.EntryPackages = append(result.EntryPackages, pkg) } } } // vulnImportSlice checks if pkg has some vulnerabilities or transitively imports // a package with known vulnerabilities. If that is the case, populates result.Imports // graph with this reachability information and returns the result.Imports package // node for pkg. Otherwise, returns nil. func vulnImportSlice(pkg *packages.Package, modVulns moduleVulnerabilities, result *Result, analyzed map[*packages.Package]bool) bool { if vulnerable, ok := analyzed[pkg]; ok { return vulnerable } analyzed[pkg] = false // Recursively compute which direct dependencies lead to an import of // a vulnerable package and remember the nodes of such dependencies. transitiveVulnerable := false for _, imp := range pkg.Imports { if impVulnerable := vulnImportSlice(imp, modVulns, result, analyzed); impVulnerable { transitiveVulnerable = true } } // Check if pkg has known vulnerabilities. vulns := modVulns.vulnsForPackage(pkg.PkgPath) // If pkg is not vulnerable nor it transitively leads // to vulnerabilities, jump out. if !transitiveVulnerable && len(vulns) == 0 { return false } // Create Vuln entry for each symbol of known OSV entries for pkg. for _, osv := range vulns { for _, affected := range osv.Affected { for _, p := range affected.EcosystemSpecific.Packages { if p.Path != pkg.PkgPath { continue } symbols := p.Symbols if len(symbols) == 0 { symbols = allSymbols(pkg.Types) } for _, symbol := range symbols { vuln := &Vuln{ OSV: osv, Symbol: symbol, ImportSink: pkg, } result.Vulns = append(result.Vulns, vuln) } } } } analyzed[pkg] = true return true } // vulnCallGraphSlice checks if known vulnerabilities are transitively reachable from sources // via call graph cg. If so, populates result.Calls graph with this reachability information. func vulnCallGraphSlice(sources []*ssa.Function, modVulns moduleVulnerabilities, cg *callgraph.Graph, result *Result, graph *PackageGraph) { sinksWithVulns := vulnFuncs(cg, modVulns) // Compute call graph backwards reachable // from vulnerable functions and methods. var sinks []*callgraph.Node for n := range sinksWithVulns { sinks = append(sinks, n) } bcg := callGraphSlice(sinks, false) // Interesect backwards call graph with forward // reachable graph to remove redundant edges. var filteredSources []*callgraph.Node for _, e := range sources { if n, ok := bcg.Nodes[e]; ok { filteredSources = append(filteredSources, n) } } fcg := callGraphSlice(filteredSources, true) // Get the sinks that are in fact reachable from entry points. filteredSinks := make(map[*callgraph.Node][]*osv.Entry) for n, vs := range sinksWithVulns { if fn, ok := fcg.Nodes[n.Func]; ok { filteredSinks[fn] = vs } } // Transform the resulting call graph slice into // vulncheck representation and store it to result. vulnCallGraph(filteredSources, filteredSinks, result, graph) } // callGraphSlice computes a slice of callgraph beginning at starts // in the direction (forward/backward) controlled by forward flag. func callGraphSlice(starts []*callgraph.Node, forward bool) *callgraph.Graph { g := &callgraph.Graph{Nodes: make(map[*ssa.Function]*callgraph.Node)} visited := make(map[*callgraph.Node]bool) var visit func(*callgraph.Node) visit = func(n *callgraph.Node) { if visited[n] { return } visited[n] = true var edges []*callgraph.Edge if forward { edges = n.Out } else { edges = n.In } for _, edge := range edges { nCallee := g.CreateNode(edge.Callee.Func) nCaller := g.CreateNode(edge.Caller.Func) callgraph.AddEdge(nCaller, edge.Site, nCallee) if forward { visit(edge.Callee) } else { visit(edge.Caller) } } } for _, s := range starts { visit(s) } return g } // vulnCallGraph creates vulnerability call graph from sources -> sinks reachability info. func vulnCallGraph(sources []*callgraph.Node, sinks map[*callgraph.Node][]*osv.Entry, result *Result, graph *PackageGraph) { nodes := make(map[*ssa.Function]*FuncNode) // First create entries and sinks and store relevant information. for _, s := range sources { fn := createNode(nodes, s.Func, graph) result.EntryFunctions = append(result.EntryFunctions, fn) } for s, vulns := range sinks { f := s.Func funNode := createNode(nodes, s.Func, graph) // Populate CallSink field for each detected vuln symbol. for _, osv := range vulns { if vulnMatchesPackage(osv, funNode.Package.PkgPath) { addCallSinkForVuln(funNode, osv, dbFuncName(f), funNode.Package.PkgPath, result) } } } visited := make(map[*callgraph.Node]bool) var visit func(*callgraph.Node) visit = func(n *callgraph.Node) { if visited[n] { return } visited[n] = true for _, edge := range n.In { nCallee := createNode(nodes, edge.Callee.Func, graph) nCaller := createNode(nodes, edge.Caller.Func, graph) call := edge.Site cs := &CallSite{ Parent: nCaller, Name: call.Common().Value.Name(), RecvType: callRecvType(call), Resolved: resolved(call), Pos: instrPosition(call), } nCallee.CallSites = append(nCallee.CallSites, cs) visit(edge.Caller) } } for s := range sinks { visit(s) } } // vulnFuncs returns vulnerability information for vulnerable functions in cg. func vulnFuncs(cg *callgraph.Graph, modVulns moduleVulnerabilities) map[*callgraph.Node][]*osv.Entry { m := make(map[*callgraph.Node][]*osv.Entry) for f, n := range cg.Nodes { vulns := modVulns.vulnsForSymbol(pkgPath(f), dbFuncName(f)) if len(vulns) > 0 { m[n] = vulns } } return m } // pkgPath returns the path of the f's enclosing package, if any. // Otherwise, returns "". func pkgPath(f *ssa.Function) string { if f.Package() != nil && f.Package().Pkg != nil { return f.Package().Pkg.Path() } return "" } func createNode(nodes map[*ssa.Function]*FuncNode, f *ssa.Function, graph *PackageGraph) *FuncNode { if fn, ok := nodes[f]; ok { return fn } fn := &FuncNode{ Name: f.Name(), Package: graph.GetPackage(pkgPath(f)), RecvType: funcRecvType(f), Pos: funcPosition(f), } nodes[f] = fn return fn } // addCallSinkForVuln adds callID as call sink to vuln of result.Vulns // identified with . func addCallSinkForVuln(call *FuncNode, osv *osv.Entry, symbol, pkg string, result *Result) { for _, vuln := range result.Vulns { if vuln.OSV == osv && vuln.Symbol == symbol && vuln.ImportSink.PkgPath == pkg { vuln.CallSink = call return } } } // extractModules collects modules in `pkgs` up to uniqueness of // module path and version. func extractModules(pkgs []*packages.Package) []*packages.Module { modMap := map[string]*packages.Module{} seen := map[*packages.Package]bool{} var extract func(*packages.Package, map[string]*packages.Module) extract = func(pkg *packages.Package, modMap map[string]*packages.Module) { if pkg == nil || seen[pkg] { return } if pkg.Module != nil { if pkg.Module.Replace != nil { modMap[pkg.Module.Replace.Path] = pkg.Module } else { modMap[pkg.Module.Path] = pkg.Module } } seen[pkg] = true for _, imp := range pkg.Imports { extract(imp, modMap) } } for _, pkg := range pkgs { extract(pkg, modMap) } modules := []*packages.Module{} for _, mod := range modMap { modules = append(modules, mod) } return modules } vuln-1.0.1/internal/vulncheck/source_test.go000066400000000000000000000261311446745451500212030ustar00rootroot00000000000000// Copyright 2021 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 vulncheck import ( "context" "path" "reflect" "testing" "golang.org/x/tools/go/packages/packagestest" "golang.org/x/vuln/internal/client" "golang.org/x/vuln/internal/govulncheck" "golang.org/x/vuln/internal/osv" ) // TestCalls checks for call graph vuln slicing correctness. // The inlined test code has the following call graph // // x.X // / | \ // / d.D1 avuln.VulnData.Vuln1 // / / | // c.C1 d.internal.Vuln1 // | // avuln.VulnData.Vuln2 // // --------------------y.Y------------------------------- // / / \ \ \ \ // / / \ \ \ \ // / / \ \ \ \ // c.C4 c.vulnWrap.V.Vuln1(=nil) c.C2 bvuln.Vuln c.C3 c.C3$1 // | | | // y.benign e.E // // and this slice // // x.X // / | \ // / d.D1 avuln.VulnData.Vuln1 // / / // c.C1 // | // avuln.VulnData.Vuln2 // // y.Y // | // bvuln.Vuln // | | // e.E // // related to avuln.VulnData.{Vuln1, Vuln2} and bvuln.Vuln vulnerabilities. func TestCalls(t *testing.T) { e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{ { Name: "golang.org/entry", Files: map[string]interface{}{ "x/x.go": ` package x import ( "golang.org/cmod/c" "golang.org/dmod/d" ) func X(x bool) { if x { c.C1().Vuln1() // vuln use: Vuln1 } else { d.D1() // no vuln use } } `, "y/y.go": ` package y import ( "golang.org/cmod/c" ) func Y(y bool) { if y { c.C2()() // vuln use: bvuln.Vuln } else { c.C3()() w := c.C4(benign) w.V.Vuln1() // no vuln use: Vuln1 does not belong to vulnerable type } } func benign(i c.I) {} `}}, { Name: "golang.org/cmod@v1.1.3", Files: map[string]interface{}{"c/c.go": ` package c import ( "golang.org/amod/avuln" "golang.org/bmod/bvuln" ) type I interface { Vuln1() } func C1() I { v := avuln.VulnData{} v.Vuln2() // vuln use return v } func C2() func() { return bvuln.Vuln } func C3() func() { return func() {} } type vulnWrap struct { V I } func C4(f func(i I)) vulnWrap { f(avuln.VulnData{}) return vulnWrap{} } `}, }, { Name: "golang.org/dmod@v0.5.0", Files: map[string]interface{}{"d/d.go": ` package d import ( "golang.org/cmod/c" ) type internal struct{} func (i internal) Vuln1() {} func D1() { c.C1() // transitive vuln use var i c.I i = internal{} i.Vuln1() // no vuln use } `}, }, { Name: "golang.org/amod@v1.1.3", Files: map[string]interface{}{"avuln/avuln.go": ` package avuln type VulnData struct {} func (v VulnData) Vuln1() {} func (v VulnData) Vuln2() {} `}, }, { Name: "golang.org/bmod@v0.5.0", Files: map[string]interface{}{"bvuln/bvuln.go": ` package bvuln import ( "golang.org/emod/e" ) func Vuln() { e.E(Vuln) } `}, }, { Name: "golang.org/emod@v1.5.0", Files: map[string]interface{}{"e/e.go": ` package e func E(f func()) { f() } `}, }, }) defer e.Cleanup() // Load x and y as entry packages. graph := NewPackageGraph("go1.18") pkgs, err := graph.LoadPackages(e.Config, nil, []string{path.Join(e.Temp(), "entry/x"), path.Join(e.Temp(), "entry/y")}) if err != nil { t.Fatal(err) } if len(pkgs) != 2 { t.Fatal("failed to load x and y test packages") } c, err := newTestClient() if err != nil { t.Fatal(err) } cfg := &govulncheck.Config{ScanLevel: "symbol"} result, err := Source(context.Background(), pkgs, cfg, c, graph) if err != nil { t.Fatal(err) } // Check that we find the right number of vulnerabilities. // There should be three entries as there are three vulnerable // symbols in the two import-reachable OSVs. if len(result.Vulns) != 3 { t.Errorf("want 3 Vulns, got %d", len(result.Vulns)) } // Check that call graph entry points are present. if got := len(result.EntryFunctions); got != 2 { t.Errorf("want 2 call graph entry points; got %v", got) } // Check that vulnerabilities are connected to the call graph. // For the test example, all vulns should have a call sink. for _, v := range result.Vulns { if v.CallSink == nil { t.Errorf("want CallSink !=0 for %v; got 0", v.Symbol) } } wantCalls := map[string][]string{ "golang.org/entry/x.X": {"golang.org/amod/avuln.VulnData.Vuln1", "golang.org/cmod/c.C1", "golang.org/dmod/d.D1"}, "golang.org/cmod/c.C1": {"golang.org/amod/avuln.VulnData.Vuln2"}, "golang.org/dmod/d.D1": {"golang.org/cmod/c.C1"}, "golang.org/entry/y.Y": {"golang.org/bmod/bvuln.Vuln"}, "golang.org/bmod/bvuln.Vuln": {"golang.org/emod/e.E"}, "golang.org/emod/e.E": {"golang.org/bmod/bvuln.Vuln"}, } if callStrMap := callGraphToStrMap(result); !reflect.DeepEqual(wantCalls, callStrMap) { t.Errorf("want %v call graph; got %v", wantCalls, callStrMap) } } func TestAllSymbolsVulnerable(t *testing.T) { e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{ { Name: "golang.org/entry", Files: map[string]interface{}{ "x/x.go": ` package x import "golang.org/vmod/vuln" func X() { vuln.V1() }`, }, }, { Name: "golang.org/vmod@v1.2.3", Files: map[string]interface{}{"vuln/vuln.go": ` package vuln func V1() {} func V2() {} func v() {} type a struct{} func (x a) foo() {} func (x *a) bar() {} `}, }, }) defer e.Cleanup() client, err := client.NewInMemoryClient( []*osv.Entry{ { ID: "V", Affected: []osv.Affected{{ Module: osv.Module{Path: "golang.org/vmod"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "1.2.0"}}}}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ Path: "golang.org/vmod/vuln", Symbols: []string{}, }}, }, }}, }, }, ) if err != nil { t.Fatal(err) } // Load x as entry package. graph := NewPackageGraph("go1.18") pkgs, err := graph.LoadPackages(e.Config, nil, []string{path.Join(e.Temp(), "entry/x")}) if err != nil { t.Fatal(err) } if len(pkgs) != 1 { t.Fatal("failed to load x test package") } cfg := &govulncheck.Config{ScanLevel: "symbol"} result, err := Source(context.Background(), pkgs, cfg, client, graph) if err != nil { t.Fatal(err) } if len(result.Vulns) != 5 { t.Errorf("want 5 Vulns, got %d", len(result.Vulns)) } for _, v := range result.Vulns { if v.Symbol == "V1" && v.CallSink == nil { t.Errorf("expected a call sink for V1; got none") } else if v.Symbol != "V1" && v.CallSink != nil { t.Errorf("expected no call sink for %v; got %v", v.Symbol, v.CallSink) } } } // TestNoSyntheticNodes checks that removing synthetic wrappers from // call graph still produces correct results. func TestNoSyntheticNodes(t *testing.T) { e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{ { Name: "golang.org/entry", Files: map[string]interface{}{ "x/x.go": ` package x import "golang.org/amod/avuln" type i interface { Vuln1() } func X() { v := &avuln.VulnData{} var x i = v // to force creatation of wrapper method *avuln.VulnData.Vuln1 x.Vuln1() }`, }, }, { Name: "golang.org/amod@v1.1.3", Files: map[string]interface{}{"avuln/avuln.go": ` package avuln type VulnData struct {} func (v VulnData) Vuln1() {} func (v VulnData) Vuln2() {} `}, }, }) defer e.Cleanup() // Load x as entry package. graph := NewPackageGraph("go1.18") pkgs, err := graph.LoadPackages(e.Config, nil, []string{path.Join(e.Temp(), "entry/x")}) if err != nil { t.Fatal(err) } if len(pkgs) != 1 { t.Fatal("failed to load x test package") } c, err := newTestClient() if err != nil { t.Fatal(err) } cfg := &govulncheck.Config{ScanLevel: "symbol"} result, err := Source(context.Background(), pkgs, cfg, c, graph) if err != nil { t.Fatal(err) } if len(result.Vulns) != 2 { t.Errorf("want 2 Vulns, got %d", len(result.Vulns)) } var vuln *Vuln for _, v := range result.Vulns { if v.Symbol == "VulnData.Vuln1" && v.CallSink != nil { vuln = v } } if vuln == nil { t.Fatal("VulnData.Vuln1 should be deemed a called vulnerability") } stack := CallStacks(result)[vuln] // We don't want the call stack X -> *VulnData.Vuln1 (wrapper) -> VulnData.Vuln1. // We want X -> VulnData.Vuln1. if len(stack) != 2 { t.Errorf("want stack of length 2; got stack of length %v", len(stack)) } } func TestRecursion(t *testing.T) { e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{ { Name: "golang.org/entry", Files: map[string]interface{}{ "x/x.go": ` package x import "golang.org/bmod/bvuln" func X() { y() bvuln.Vuln() z() } func y() { X() } func z() {} `, }, }, { Name: "golang.org/bmod@v0.5.0", Files: map[string]interface{}{"bvuln/bvuln.go": ` package bvuln func Vuln() {} `}, }, }) defer e.Cleanup() // Load x as entry package. graph := NewPackageGraph("go1.18") pkgs, err := graph.LoadPackages(e.Config, nil, []string{path.Join(e.Temp(), "entry/x")}) if err != nil { t.Fatal(err) } if len(pkgs) != 1 { t.Fatal("failed to load x test package") } c, err := newTestClient() if err != nil { t.Fatal(err) } cfg := &govulncheck.Config{ScanLevel: "symbol"} result, err := Source(context.Background(), pkgs, cfg, c, graph) if err != nil { t.Fatal(err) } wantCalls := map[string][]string{ "golang.org/entry/x.X": {"golang.org/bmod/bvuln.Vuln", "golang.org/entry/x.y"}, "golang.org/entry/x.y": {"golang.org/entry/x.X"}, } if callStrMap := callGraphToStrMap(result); !reflect.DeepEqual(wantCalls, callStrMap) { t.Errorf("want %v call graph; got %v", wantCalls, callStrMap) } } func TestIssue57174(t *testing.T) { e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{ { Name: "golang.org/entry", Files: map[string]interface{}{ "x/x.go": ` package x import "golang.org/bmod/bvuln" func P(d [][3]int) { p(d) } func p[E interface{ [3]int | [4]int }](d []E) { c := d[0] if c[0] > 0 { bvuln.Vuln() } } `, }, }, { Name: "golang.org/bmod@v0.5.0", Files: map[string]interface{}{"bvuln/bvuln.go": ` package bvuln func Vuln() {} `}, }, }) defer e.Cleanup() // Load x as entry package. graph := NewPackageGraph("go1.18") pkgs, err := graph.LoadPackages(e.Config, nil, []string{path.Join(e.Temp(), "entry/x")}) if err != nil { t.Fatal(err) } if len(pkgs) != 1 { t.Fatal("failed to load x test package") } c, err := newTestClient() if err != nil { t.Fatal(err) } cfg := &govulncheck.Config{ScanLevel: "symbol"} _, err = Source(context.Background(), pkgs, cfg, c, graph) if err != nil { t.Fatal(err) } } vuln-1.0.1/internal/vulncheck/utils.go000066400000000000000000000166421446745451500200120ustar00rootroot00000000000000// Copyright 2021 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 vulncheck import ( "bytes" "context" "go/token" "go/types" "strings" "golang.org/x/tools/go/callgraph" "golang.org/x/tools/go/callgraph/cha" "golang.org/x/tools/go/callgraph/vta" "golang.org/x/tools/go/packages" "golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/go/types/typeutil" "golang.org/x/vuln/internal/osv" "golang.org/x/tools/go/ssa" ) // buildSSA creates an ssa representation for pkgs. Returns // the ssa program encapsulating the packages and top level // ssa packages corresponding to pkgs. func buildSSA(pkgs []*packages.Package, fset *token.FileSet) (*ssa.Program, []*ssa.Package) { // TODO(https://go.dev/issue/57221): what about entry functions that are generics? prog := ssa.NewProgram(fset, ssa.InstantiateGenerics) imports := make(map[*packages.Package]*ssa.Package) var createImports func(map[string]*packages.Package) createImports = func(pkgs map[string]*packages.Package) { for _, p := range pkgs { if _, ok := imports[p]; !ok { i := prog.CreatePackage(p.Types, p.Syntax, p.TypesInfo, true) imports[p] = i createImports(p.Imports) } } } for _, tp := range pkgs { createImports(tp.Imports) } var ssaPkgs []*ssa.Package for _, tp := range pkgs { if sp, ok := imports[tp]; ok { ssaPkgs = append(ssaPkgs, sp) } else { sp := prog.CreatePackage(tp.Types, tp.Syntax, tp.TypesInfo, false) ssaPkgs = append(ssaPkgs, sp) } } prog.Build() return prog, ssaPkgs } // callGraph builds a call graph of prog based on VTA analysis. func callGraph(ctx context.Context, prog *ssa.Program, entries []*ssa.Function) (*callgraph.Graph, error) { entrySlice := make(map[*ssa.Function]bool) for _, e := range entries { entrySlice[e] = true } if err := ctx.Err(); err != nil { // cancelled? return nil, err } initial := cha.CallGraph(prog) allFuncs := ssautil.AllFunctions(prog) fslice := forwardSlice(entrySlice, initial) // Keep only actually linked functions. pruneSet(fslice, allFuncs) if err := ctx.Err(); err != nil { // cancelled? return nil, err } vtaCg := vta.CallGraph(fslice, initial) // Repeat the process once more, this time using // the produced VTA call graph as the base graph. fslice = forwardSlice(entrySlice, vtaCg) pruneSet(fslice, allFuncs) if err := ctx.Err(); err != nil { // cancelled? return nil, err } cg := vta.CallGraph(fslice, vtaCg) cg.DeleteSyntheticNodes() return cg, nil } // dbTypeFormat formats the name of t according how types // are encoded in vulnerability database: // - pointer designation * is skipped // - full path prefix is skipped as well func dbTypeFormat(t types.Type) string { switch tt := t.(type) { case *types.Pointer: return dbTypeFormat(tt.Elem()) case *types.Named: return tt.Obj().Name() default: return types.TypeString(t, func(p *types.Package) string { return "" }) } } // dbFuncName computes a function name consistent with the namings used in vulnerability // databases. Effectively, a qualified name of a function local to its enclosing package. // If a receiver is a pointer, this information is not encoded in the resulting name. The // name of anonymous functions is simply "". The function names are unique subject to the // enclosing package, but not globally. // // Examples: // // func (a A) foo (...) {...} -> A.foo // func foo(...) {...} -> foo // func (b *B) bar (...) {...} -> B.bar func dbFuncName(f *ssa.Function) string { selectBound := func(f *ssa.Function) types.Type { // If f is a "bound" function introduced by ssa for a given type, return the type. // When "f" is a "bound" function, it will have 1 free variable of that type within // the function. This is subject to change when ssa changes. if len(f.FreeVars) == 1 && strings.HasPrefix(f.Synthetic, "bound ") { return f.FreeVars[0].Type() } return nil } selectThunk := func(f *ssa.Function) types.Type { // If f is a "thunk" function introduced by ssa for a given type, return the type. // When "f" is a "thunk" function, the first parameter will have that type within // the function. This is subject to change when ssa changes. params := f.Signature.Params() // params.Len() == 1 then params != nil. if strings.HasPrefix(f.Synthetic, "thunk ") && params.Len() >= 1 { if first := params.At(0); first != nil { return first.Type() } } return nil } var qprefix string if recv := f.Signature.Recv(); recv != nil { qprefix = dbTypeFormat(recv.Type()) } else if btype := selectBound(f); btype != nil { qprefix = dbTypeFormat(btype) } else if ttype := selectThunk(f); ttype != nil { qprefix = dbTypeFormat(ttype) } if qprefix == "" { return f.Name() } return qprefix + "." + f.Name() } // dbTypesFuncName is dbFuncName defined over *types.Func. func dbTypesFuncName(f *types.Func) string { sig := f.Type().(*types.Signature) if sig.Recv() == nil { return f.Name() } return dbTypeFormat(sig.Recv().Type()) + "." + f.Name() } // memberFuncs returns functions associated with the `member`: // 1) `member` itself if `member` is a function // 2) `member` methods if `member` is a type // 3) empty list otherwise func memberFuncs(member ssa.Member, prog *ssa.Program) []*ssa.Function { switch t := member.(type) { case *ssa.Type: methods := typeutil.IntuitiveMethodSet(t.Type(), &prog.MethodSets) var funcs []*ssa.Function for _, m := range methods { if f := prog.MethodValue(m); f != nil { funcs = append(funcs, f) } } return funcs case *ssa.Function: return []*ssa.Function{t} default: return nil } } // funcPosition gives the position of `f`. Returns empty token.Position // if no file information on `f` is available. func funcPosition(f *ssa.Function) *token.Position { pos := f.Prog.Fset.Position(f.Pos()) return &pos } // instrPosition gives the position of `instr`. Returns empty token.Position // if no file information on `instr` is available. func instrPosition(instr ssa.Instruction) *token.Position { pos := instr.Parent().Prog.Fset.Position(instr.Pos()) return &pos } func resolved(call ssa.CallInstruction) bool { if call == nil { return true } return call.Common().StaticCallee() != nil } func callRecvType(call ssa.CallInstruction) string { if !call.Common().IsInvoke() { return "" } buf := new(bytes.Buffer) types.WriteType(buf, call.Common().Value.Type(), nil) return buf.String() } func funcRecvType(f *ssa.Function) string { v := f.Signature.Recv() if v == nil { return "" } buf := new(bytes.Buffer) types.WriteType(buf, v.Type(), nil) return buf.String() } // allSymbols returns all top-level functions and methods defined in pkg. func allSymbols(pkg *types.Package) []string { var names []string scope := pkg.Scope() for _, name := range scope.Names() { o := scope.Lookup(name) switch o := o.(type) { case *types.Func: names = append(names, dbTypesFuncName(o)) case *types.TypeName: ms := types.NewMethodSet(types.NewPointer(o.Type())) for i := 0; i < ms.Len(); i++ { if f, ok := ms.At(i).Obj().(*types.Func); ok { names = append(names, dbTypesFuncName(f)) } } } } return names } // vulnMatchesPackage reports whether an entry applies to pkg (an import path). func vulnMatchesPackage(v *osv.Entry, pkg string) bool { for _, a := range v.Affected { for _, p := range a.EcosystemSpecific.Packages { if p.Path == pkg { return true } } } return false } vuln-1.0.1/internal/vulncheck/vulncheck.go000066400000000000000000000214331446745451500206260ustar00rootroot00000000000000// Copyright 2021 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 vulncheck import ( "fmt" "go/token" "strings" "time" "golang.org/x/tools/go/packages" "golang.org/x/vuln/internal" "golang.org/x/vuln/internal/osv" "golang.org/x/vuln/internal/semver" ) // Result contains information on how known vulnerabilities are reachable // in the call graph, package imports graph, and module requires graph of // the user code. type Result struct { // EntryFunctions are a subset of Functions representing vulncheck entry points. EntryFunctions []*FuncNode // EntryPackages are a subset of Packages representing packages of vulncheck entry points. EntryPackages []*packages.Package // Vulns contains information on detected vulnerabilities and their place in // the above graphs. Only vulnerabilities whose symbols are reachable in Calls, // or whose packages are imported in Imports, or whose modules are required in // Requires, have an entry in Vulns. Vulns []*Vuln } // Vuln provides information on how a vulnerability is affecting user code by // connecting it to the Result.{Calls,Imports,Requires} graphs. Vulnerabilities // detected in Go binaries do not appear in the Result graphs. type Vuln struct { // OSV contains information on the detected vulnerability in the shared // vulnerability format. // // OSV, Symbol, PkgPath, and ModPath identify a vulnerability. // // Note that *osv.Entry may describe multiple symbols from multiple // packages. OSV *osv.Entry // Symbol is the name of the detected vulnerable function or method. Symbol string // CallSink is the FuncNode in Result.Calls corresponding to Symbol. // // When analyzing binaries, Symbol is not reachable, or cfg.ScanLevel // is symbol, CallSink will be unavailable and set to 0. CallSink *FuncNode // ImportSink is the PkgNode in Result.Imports corresponding to PkgPath. // // When analyzing binaries or PkgPath is not imported, ImportSink will be // unavailable and set to 0. ImportSink *packages.Package } // A FuncNode describes a function in the call graph. type FuncNode struct { // Name is the name of the function. Name string // RecvType is the receiver object type of this function, if any. RecvType string // Package is the package the function is part of. Package *packages.Package // Position describes the position of the function in the file. Pos *token.Position // CallSites is a set of call sites where this function is called. CallSites []*CallSite } func (fn *FuncNode) String() string { if fn.RecvType == "" { return fmt.Sprintf("%s.%s", fn.Package.PkgPath, fn.Name) } return fmt.Sprintf("%s.%s", fn.RecvType, fn.Name) } // Receiver returns the FuncNode's receiver, with package path removed. // Pointers are preserved if present. func (fn *FuncNode) Receiver() string { return strings.Replace(fn.RecvType, fmt.Sprintf("%s.", fn.Package.PkgPath), "", 1) } // A CallSite describes a function call. type CallSite struct { // Parent is the enclosing function where the call is made. Parent *FuncNode // Name stands for the name of the function (variable) being called. Name string // RecvType is the full path of the receiver object type, if any. RecvType string // Position describes the position of the function in the file. Pos *token.Position // Resolved indicates if the called function can be statically resolved. Resolved bool } // moduleVulnerabilities is an internal structure for // holding and querying vulnerabilities provided by a // vulnerability database client. type moduleVulnerabilities []*ModVulns // ModVulns groups vulnerabilities per module. type ModVulns struct { Module *packages.Module Vulns []*osv.Entry } func (mv moduleVulnerabilities) filter(os, arch string) moduleVulnerabilities { now := time.Now() var filteredMod moduleVulnerabilities for _, mod := range mv { module := mod.Module modVersion := module.Version if module.Replace != nil { modVersion = module.Replace.Version } // TODO(https://golang.org/issues/49264): if modVersion == "", try vcs? var filteredVulns []*osv.Entry for _, v := range mod.Vulns { // Ignore vulnerabilities that have been withdrawn if v.Withdrawn != nil && v.Withdrawn.Before(now) { continue } var filteredAffected []osv.Affected for _, a := range v.Affected { // Vulnerabilities from some databases might contain // information on related but different modules that // were, say, reported in the same CVE. We filter such // information out as it might lead to incorrect results: // Computing a latest fix could consider versions of these // different packages. if a.Module.Path != module.Path { continue } // A module version is affected if // - it is included in one of the affected version ranges // - and module version is not "" if modVersion == "" { // Module version of "" means the module version is not available, // and so we don't want to spam users with potential false alarms. continue } if !semver.Affects(a.Ranges, modVersion) { continue } var filteredImports []osv.Package for _, p := range a.EcosystemSpecific.Packages { if matchesPlatform(os, arch, p) { filteredImports = append(filteredImports, p) } } if len(a.EcosystemSpecific.Packages) != 0 && len(filteredImports) == 0 { continue } a.EcosystemSpecific.Packages = filteredImports filteredAffected = append(filteredAffected, a) } if len(filteredAffected) == 0 { continue } // save the non-empty vulnerability with only // affected symbols. newV := *v newV.Affected = filteredAffected filteredVulns = append(filteredVulns, &newV) } filteredMod = append(filteredMod, &ModVulns{ Module: module, Vulns: filteredVulns, }) } return filteredMod } func matchesPlatform(os, arch string, e osv.Package) bool { return matchesPlatformComponent(os, e.GOOS) && matchesPlatformComponent(arch, e.GOARCH) } // matchesPlatformComponent reports whether a GOOS (or GOARCH) // matches a list of GOOS (or GOARCH) values from an osv.EcosystemSpecificImport. func matchesPlatformComponent(s string, ps []string) bool { // An empty input or an empty GOOS or GOARCH list means "matches everything." if s == "" || len(ps) == 0 { return true } for _, p := range ps { if s == p { return true } } return false } // vulnsForPackage returns the vulnerabilities for the module which is the most // specific prefix of importPath, or nil if there is no matching module with // vulnerabilities. func (mv moduleVulnerabilities) vulnsForPackage(importPath string) []*osv.Entry { isStd := isStdPackage(importPath) var mostSpecificMod *ModVulns for _, mod := range mv { md := mod if isStd && mod.Module.Path == internal.GoStdModulePath { // standard library packages do not have an associated module, // so we relate them to the artificial stdlib module. mostSpecificMod = md } else if strings.HasPrefix(importPath, md.Module.Path) { if mostSpecificMod == nil || len(mostSpecificMod.Module.Path) < len(md.Module.Path) { mostSpecificMod = md } } } if mostSpecificMod == nil { return nil } if mostSpecificMod.Module.Replace != nil { // standard libraries do not have a module nor replace module importPath = fmt.Sprintf("%s%s", mostSpecificMod.Module.Replace.Path, strings.TrimPrefix(importPath, mostSpecificMod.Module.Path)) } vulns := mostSpecificMod.Vulns packageVulns := []*osv.Entry{} Vuln: for _, v := range vulns { for _, a := range v.Affected { for _, p := range a.EcosystemSpecific.Packages { if p.Path == importPath { packageVulns = append(packageVulns, v) continue Vuln } } } } return packageVulns } // vulnsForSymbol returns vulnerabilities for `symbol` in `mv.VulnsForPackage(importPath)`. func (mv moduleVulnerabilities) vulnsForSymbol(importPath, symbol string) []*osv.Entry { vulns := mv.vulnsForPackage(importPath) if vulns == nil { return nil } symbolVulns := []*osv.Entry{} vulnLoop: for _, v := range vulns { for _, a := range v.Affected { for _, p := range a.EcosystemSpecific.Packages { if p.Path != importPath { continue } if len(p.Symbols) > 0 && !contains(p.Symbols, symbol) { continue } symbolVulns = append(symbolVulns, v) continue vulnLoop } } } return symbolVulns } func contains(symbols []string, target string) bool { for _, s := range symbols { if s == target { return true } } return false } func isStdPackage(pkg string) bool { if pkg == "" { return false } // std packages do not have a "." in their path. For instance, see // Contains in pkgsite/+/refs/heads/master/internal/stdlbib/stdlib.go. if i := strings.IndexByte(pkg, '/'); i != -1 { pkg = pkg[:i] } return !strings.Contains(pkg, ".") } vuln-1.0.1/internal/vulncheck/vulncheck_test.go000066400000000000000000000345341446745451500216730ustar00rootroot00000000000000// Copyright 2021 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 vulncheck import ( "path" "reflect" "testing" "time" "github.com/google/go-cmp/cmp" "golang.org/x/tools/go/packages" "golang.org/x/tools/go/packages/packagestest" "golang.org/x/vuln/internal/osv" ) func TestFilterVulns(t *testing.T) { past := time.Now().Add(-3 * time.Hour) mv := moduleVulnerabilities{ { Module: &packages.Module{ Path: "example.mod/a", Version: "v1.0.0", }, Vulns: []*osv.Entry{ {ID: "a", Affected: []osv.Affected{ {Module: osv.Module{Path: "example.mod/a"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "2.0.0"}}}}}, {Module: osv.Module{Path: "a.example.mod/a"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "2.0.0"}}}}}, // should be filtered out {Module: osv.Module{Path: "example.mod/a"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0"}, {Fixed: "0.9.0"}}}}}, // should be filtered out }}, {ID: "b", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/a"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.1"}}}}, EcosystemSpecific: osv.EcosystemSpecific{Packages: []osv.Package{{ GOOS: []string{"windows", "linux"}, }}, }}}}, {ID: "c", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/a"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "1.0.1"}}}}, EcosystemSpecific: osv.EcosystemSpecific{Packages: []osv.Package{{ GOARCH: []string{"arm64", "amd64"}, }}, }}}}, {ID: "d", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/a"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ GOOS: []string{"windows"}, }}, }}}}, }, }, { Module: &packages.Module{ Path: "example.mod/b", Version: "v1.0.0", }, Vulns: []*osv.Entry{ {ID: "e", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ GOARCH: []string{"arm64"}, }}, }}}}, {ID: "f", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ GOOS: []string{"linux"}, }}, }}}}, {ID: "g", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ GOARCH: []string{"amd64"}, }}, }, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0.0.1"}, {Fixed: "2.0.1"}}}}}}}, {ID: "h", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ GOOS: []string{"windows"}, GOARCH: []string{"amd64"}, }}, }}}}, }, }, { Module: &packages.Module{ Path: "example.mod/c", }, Vulns: []*osv.Entry{ {ID: "i", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/c"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ GOARCH: []string{"amd64"}, }}, }, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0.0.0"}}}}}}}, {ID: "j", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/c"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ GOARCH: []string{"amd64"}, }}, }, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Fixed: "3.0.0"}}}}}}}, {ID: "k"}, }, }, { Module: &packages.Module{ Path: "example.mod/d", Version: "v1.2.0", }, Vulns: []*osv.Entry{ {ID: "l", Affected: []osv.Affected{ {Module: osv.Module{Path: "example.mod/d"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ GOOS: []string{"windows"}, // should be filtered out }}, }}, {Module: osv.Module{Path: "example.mod/d"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ GOOS: []string{"linux"}, }}, }}, }}, }, }, { Module: &packages.Module{ Path: "example.mod/w", Version: "v1.3.0", }, Vulns: []*osv.Entry{ {ID: "m", Withdrawn: &past, Affected: []osv.Affected{ // should be filtered out {Module: osv.Module{Path: "example.mod/w"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ GOOS: []string{"linux"}, }}, }}, }}, {ID: "n", Affected: []osv.Affected{ {Module: osv.Module{Path: "example.mod/w"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ GOOS: []string{"linux"}, }}, }}, }}, }, }, } expected := moduleVulnerabilities{ { Module: &packages.Module{ Path: "example.mod/a", Version: "v1.0.0", }, Vulns: []*osv.Entry{ {ID: "a", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/a"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "2.0.0"}}}}}}}, {ID: "c", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/a"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ GOARCH: []string{"arm64", "amd64"}, }}, }, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "1.0.1"}}}}}}}, }, }, { Module: &packages.Module{ Path: "example.mod/b", Version: "v1.0.0", }, Vulns: []*osv.Entry{ {ID: "f", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ GOOS: []string{"linux"}, }}, }}}}, {ID: "g", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ GOARCH: []string{"amd64"}, }}, }, Ranges: []osv.Range{{Type: osv.RangeTypeSemver, Events: []osv.RangeEvent{{Introduced: "0.0.1"}, {Fixed: "2.0.1"}}}}}}}, }, }, { Module: &packages.Module{ Path: "example.mod/c", }, }, { Module: &packages.Module{ Path: "example.mod/d", Version: "v1.2.0", }, Vulns: []*osv.Entry{ {ID: "l", Affected: []osv.Affected{{Module: osv.Module{Path: "example.mod/d"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ GOOS: []string{"linux"}, }}, }}}}, }, }, { Module: &packages.Module{ Path: "example.mod/w", Version: "v1.3.0", }, Vulns: []*osv.Entry{ {ID: "n", Affected: []osv.Affected{ {Module: osv.Module{Path: "example.mod/w"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ GOOS: []string{"linux"}, }}, }}, }}, }, }, } filtered := mv.filter("linux", "amd64") if diff := diffModuleVulnerabilities(expected, filtered); diff != "" { t.Fatalf("Filter returned unexpected results (-want,+got):\n%s", diff) } } func diffModuleVulnerabilities(a, b moduleVulnerabilities) string { return cmp.Diff(a, b, cmp.Exporter(func(t reflect.Type) bool { return reflect.TypeOf(moduleVulnerabilities{}) == t || reflect.TypeOf(ModVulns{}) == t })) } func TestVulnsForPackage(t *testing.T) { mv := moduleVulnerabilities{ { Module: &packages.Module{ Path: "example.mod/a", Version: "v1.0.0", }, Vulns: []*osv.Entry{ {ID: "a", Affected: []osv.Affected{{ Module: osv.Module{Path: "example.mod/a"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ Path: "example.mod/a/b/c", }}, }, }}}, }, }, { Module: &packages.Module{ Path: "example.mod/a/b", Version: "v1.0.0", }, Vulns: []*osv.Entry{ {ID: "b", Affected: []osv.Affected{{ Module: osv.Module{Path: "example.mod/a/b"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ Path: "example.mod/a/b/c", }}, }, }}}, }, }, { Module: &packages.Module{ Path: "example.mod/d", Version: "v0.0.1", }, Vulns: []*osv.Entry{ {ID: "d", Affected: []osv.Affected{{ Module: osv.Module{Path: "example.mod/d"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ Path: "example.mod/d", }}, }, }}}, }, }, } filtered := mv.vulnsForPackage("example.mod/a/b/c") expected := []*osv.Entry{ {ID: "b", Affected: []osv.Affected{{ Module: osv.Module{Path: "example.mod/a/b"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ Path: "example.mod/a/b/c", }}, }, }}}, } if !reflect.DeepEqual(filtered, expected) { t.Fatalf("VulnsForPackage returned unexpected results, got:\n%s\nwant:\n%s", vulnsToString(filtered), vulnsToString(expected)) } } func TestVulnsForPackageReplaced(t *testing.T) { mv := moduleVulnerabilities{ { Module: &packages.Module{ Path: "example.mod/a", Version: "v1.0.0", }, Vulns: []*osv.Entry{ {ID: "a", Affected: []osv.Affected{{ Module: osv.Module{Path: "example.mod/a"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ Path: "example.mod/a/b/c", }}, }, }}}, }, }, { Module: &packages.Module{ Path: "example.mod/a/b", Replace: &packages.Module{ Path: "example.mod/b", }, Version: "v1.0.0", }, Vulns: []*osv.Entry{ {ID: "c", Affected: []osv.Affected{{ Module: osv.Module{Path: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ Path: "example.mod/b/c", }}, }, }}}, }, }, } filtered := mv.vulnsForPackage("example.mod/a/b/c") expected := []*osv.Entry{ {ID: "c", Affected: []osv.Affected{{ Module: osv.Module{Path: "example.mod/b"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ Path: "example.mod/b/c", }}, }, }}}, } if !reflect.DeepEqual(filtered, expected) { t.Fatalf("VulnsForPackage returned unexpected results, got:\n%s\nwant:\n%s", vulnsToString(filtered), vulnsToString(expected)) } } func TestVulnsForSymbol(t *testing.T) { mv := moduleVulnerabilities{ { Module: &packages.Module{ Path: "example.mod/a", Version: "v1.0.0", }, Vulns: []*osv.Entry{ {ID: "a", Affected: []osv.Affected{{ Module: osv.Module{Path: "example.mod/a"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ Path: "example.mod/a/b/c", }}, }, }}}, }, }, { Module: &packages.Module{ Path: "example.mod/a/b", Version: "v1.0.0", }, Vulns: []*osv.Entry{ {ID: "b", Affected: []osv.Affected{{ Module: osv.Module{Path: "example.mod/a/b"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ Path: "example.mod/a/b/c", Symbols: []string{"a"}, }}, }, }}}, {ID: "c", Affected: []osv.Affected{{ Module: osv.Module{Path: "example.mod/a/b"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ Path: "example.mod/a/b/c", Symbols: []string{"b"}, }}, }, }}}, }, }, } filtered := mv.vulnsForSymbol("example.mod/a/b/c", "a") expected := []*osv.Entry{ {ID: "b", Affected: []osv.Affected{{ Module: osv.Module{Path: "example.mod/a/b"}, EcosystemSpecific: osv.EcosystemSpecific{ Packages: []osv.Package{{ Path: "example.mod/a/b/c", Symbols: []string{"a"}, }}, }, }}}, } if !reflect.DeepEqual(filtered, expected) { t.Fatalf("VulnsForPackage returned unexpected results, got:\n%s\nwant:\n%s", vulnsToString(filtered), vulnsToString(expected)) } } func TestConvert(t *testing.T) { e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{ { Name: "golang.org/entry", Files: map[string]interface{}{ "x/x.go": ` package x import _ "golang.org/amod/avuln" `}}, { Name: "golang.org/zmod@v0.0.0", Files: map[string]interface{}{"z/z.go": ` package z `}, }, { Name: "golang.org/amod@v1.1.3", Files: map[string]interface{}{"avuln/avuln.go": ` package avuln import _ "golang.org/wmod/w" `}, }, { Name: "golang.org/bmod@v0.5.0", Files: map[string]interface{}{"bvuln/bvuln.go": ` package bvuln `}, }, { Name: "golang.org/wmod@v0.0.0", Files: map[string]interface{}{"w/w.go": ` package w import _ "golang.org/bmod/bvuln" `}, }, }) defer e.Cleanup() // Load x as entry package. pkgs, err := loadTestPackages(e, path.Join(e.Temp(), "entry/x")) if err != nil { t.Fatal(err) } wantPkgs := map[string][]string{ "golang.org/amod/avuln": {"golang.org/wmod/w"}, "golang.org/bmod/bvuln": nil, "golang.org/entry/x": {"golang.org/amod/avuln"}, "golang.org/wmod/w": {"golang.org/bmod/bvuln"}, } if got := pkgPathToImports(pkgs); !reflect.DeepEqual(got, wantPkgs) { t.Errorf("want %v;got %v", wantPkgs, got) } wantMods := map[string]string{ "golang.org/amod": "v1.1.3", "golang.org/bmod": "v0.5.0", "golang.org/entry": "", "golang.org/wmod": "v0.0.0", } if got := modulePathToVersion(pkgs); !reflect.DeepEqual(got, wantMods) { t.Errorf("want %v;got %v", wantMods, got) } } func TestReceiver(t *testing.T) { tcs := []struct { name string fn *FuncNode want string }{ { name: "empty", fn: &FuncNode{ RecvType: "", Package: &packages.Package{PkgPath: "example.com/a/pkg"}, }, want: "", }, { name: "pointer", fn: &FuncNode{ RecvType: "*example.com/a/pkg.Atype", Package: &packages.Package{PkgPath: "example.com/a/pkg"}, }, want: "*Atype", }, { name: "not pointer", fn: &FuncNode{ RecvType: "example.com/a/pkg.Atype", Package: &packages.Package{PkgPath: "example.com/a/pkg"}, }, want: "Atype", }, { name: "no prefix", fn: &FuncNode{ RecvType: "Atype", Package: &packages.Package{PkgPath: "example.com/a/pkg"}, }, want: "Atype", }, } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { got := tc.fn.Receiver() if got != tc.want { t.Errorf("*FuncNode.Receiver() = %s, want %s", got, tc.want) } }) } } vuln-1.0.1/internal/vulncheck/witness.go000066400000000000000000000253541446745451500203460ustar00rootroot00000000000000// Copyright 2021 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 vulncheck import ( "container/list" "fmt" "go/ast" "go/token" "sort" "strconv" "strings" "sync" "golang.org/x/tools/go/packages" ) // CallStack is a call stack starting with a client // function or method and ending with a call to a // vulnerable symbol. type CallStack []StackEntry // StackEntry is an element of a call stack. type StackEntry struct { // Function whose frame is on the stack. Function *FuncNode // Call is the call site inducing the next stack frame. // nil when the frame represents the last frame in the stack. Call *CallSite } // CallStacks returns representative call stacks for each // vulnerability in res. The returned call stacks are heuristically // ordered by how seemingly easy is to understand them: shorter // call stacks with less dynamic call sites appear earlier in the // returned slices. // // CallStacks performs a breadth-first search of res.CallGraph starting // at the vulnerable symbol and going up until reaching an entry // function or method in res.CallGraph.Entries. During this search, // each function is visited at most once to avoid potential // exponential explosion. Hence, not all call stacks are analyzed. func CallStacks(res *Result) map[*Vuln]CallStack { var ( wg sync.WaitGroup mu sync.Mutex ) stackPerVuln := make(map[*Vuln]CallStack) for _, vuln := range res.Vulns { vuln := vuln wg.Add(1) go func() { cs := callStack(vuln, res) mu.Lock() stackPerVuln[vuln] = cs mu.Unlock() wg.Done() }() } wg.Wait() updateInitPositions(stackPerVuln) return stackPerVuln } // callStack finds a representative call stack for vuln. // This is a shortest unique call stack with the least // number of dynamic call sites. func callStack(vuln *Vuln, res *Result) CallStack { vulnSink := vuln.CallSink if vulnSink == nil { return nil } entries := make(map[*FuncNode]bool) for _, e := range res.EntryFunctions { entries[e] = true } seen := make(map[*FuncNode]bool) // Do a BFS from the vuln sink to the entry points // and find the representative call stack. This is // the shortest call stack that goes through the // least number of dynamic call sites. We first // collect all candidate call stacks of the shortest // length and then pick the best one accordingly. var candidates []CallStack candDepth := 0 queue := list.New() queue.PushBack(&callChain{f: vulnSink}) // We want to avoid call stacks that go through // other vulnerable symbols of the same package // for the same vulnerability. In other words, // we want unique call stacks. skipSymbols := make(map[*FuncNode]bool) for _, v := range res.Vulns { if v.CallSink != nil && v != vuln && v.OSV == vuln.OSV && v.ImportSink == vuln.ImportSink { skipSymbols[v.CallSink] = true } } for queue.Len() > 0 { front := queue.Front() c := front.Value.(*callChain) queue.Remove(front) f := c.f if seen[f] { continue } seen[f] = true // Pick a single call site for each function in determinstic order. // A single call site is sufficient as we visit a function only once. for _, cs := range callsites(f.CallSites, seen) { nStack := &callChain{f: cs.Parent, call: cs, child: c} if !skipSymbols[cs.Parent] { queue.PushBack(nStack) } if entries[cs.Parent] { ns := nStack.CallStack() if len(candidates) == 0 || len(ns) == candDepth { // The case where we either have not identified // any call stacks or just found one of the same // length as the previous ones. candidates = append(candidates, ns) candDepth = len(ns) } else { // We just found a candidate call stack whose // length is greater than what we previously // found. We can thus safely disregard this // call stack and stop searching since we won't // be able to find any better candidates. queue.Init() // clear the list, effectively exiting the outer loop } } } } // Sort candidate call stacks by their number of dynamic call // sites and return the first one. sort.SliceStable(candidates, func(i int, j int) bool { s1, s2 := candidates[i], candidates[j] if w1, w2 := weight(s1), weight(s2); w1 != w2 { return w1 < w2 } // At this point, the stableness/determinism of // sorting is guaranteed by the determinism of // the underlying call graph and the call stack // search algorithm. return true }) if len(candidates) == 0 { return nil } return candidates[0] } // callsites picks a call site from sites for each non-visited function. // For each such function, the smallest (posLess) call site is chosen. The // returned slice is sorted by caller functions (funcLess). Assumes callee // of each call site is the same. func callsites(sites []*CallSite, visited map[*FuncNode]bool) []*CallSite { minCs := make(map[*FuncNode]*CallSite) for _, cs := range sites { if visited[cs.Parent] { continue } if csLess(cs, minCs[cs.Parent]) { minCs[cs.Parent] = cs } } var fs []*FuncNode for _, cs := range minCs { fs = append(fs, cs.Parent) } sort.SliceStable(fs, func(i, j int) bool { return funcLess(fs[i], fs[j]) }) var css []*CallSite for _, f := range fs { css = append(css, minCs[f]) } return css } // callChain models a chain of function calls. type callChain struct { call *CallSite // nil for entry points f *FuncNode child *callChain } // CallStack converts callChain to CallStack type. func (c *callChain) CallStack() CallStack { if c == nil { return nil } return append(CallStack{StackEntry{Function: c.f, Call: c.call}}, c.child.CallStack()...) } // weight computes an approximate measure of how easy is to understand the call // stack when presented to the client as a witness. The smaller the value, the more // understandable the stack is. Currently defined as the number of unresolved // call sites in the stack. func weight(stack CallStack) int { w := 0 for _, e := range stack { if e.Call != nil && !e.Call.Resolved { w += 1 } } return w } // csLess compares two call sites by their locations and, if needed, // their string representation. func csLess(cs1, cs2 *CallSite) bool { if cs2 == nil { return true } // fast code path if p1, p2 := cs1.Pos, cs2.Pos; p1 != nil && p2 != nil { if posLess(*p1, *p2) { return true } if posLess(*p2, *p1) { return false } // for sanity, should not occur in practice return fmt.Sprintf("%v.%v", cs1.RecvType, cs2.Name) < fmt.Sprintf("%v.%v", cs2.RecvType, cs2.Name) } // code path rarely exercised if cs2.Pos == nil { return true } if cs1.Pos == nil { return false } // should very rarely occur in practice return fmt.Sprintf("%v.%v", cs1.RecvType, cs2.Name) < fmt.Sprintf("%v.%v", cs2.RecvType, cs2.Name) } // posLess compares two positions by their line and column number, // and filename if needed. func posLess(p1, p2 token.Position) bool { if p1.Line < p2.Line { return true } if p2.Line < p1.Line { return false } if p1.Column < p2.Column { return true } if p2.Column < p1.Column { return false } return strings.Compare(p1.Filename, p2.Filename) == -1 } // funcLess compares two function nodes by locations of // corresponding functions and, if needed, their string representation. func funcLess(f1, f2 *FuncNode) bool { if p1, p2 := f1.Pos, f2.Pos; p1 != nil && p2 != nil { if posLess(*p1, *p2) { return true } if posLess(*p2, *p1) { return false } // for sanity, should not occur in practice return f1.String() < f2.String() } if f2.Pos == nil { return true } if f1.Pos == nil { return false } // should happen only for inits return f1.String() < f2.String() } // updateInitPositions populates non-existing positions of init functions // and their respective calls in callStacks (see #51575). func updateInitPositions(callStacks map[*Vuln]CallStack) { for _, cs := range callStacks { for i := range cs { updateInitPosition(&cs[i]) if i != len(cs)-1 { updateInitCallPosition(&cs[i], cs[i+1]) } } } } // updateInitCallPosition updates the position of a call to init in a stack frame, if // one already does not exist: // // P1.init -> P2.init: position of call to P2.init is the position of "import P2" // statement in P1 // // P.init -> P.init#d: P.init is an implicit init. We say it calls the explicit // P.init#d at the place of "package P" statement. func updateInitCallPosition(curr *StackEntry, next StackEntry) { call := curr.Call if !isInit(next.Function) || (call.Pos != nil && call.Pos.IsValid()) { // Skip non-init functions and inits whose call site position is available. return } var pos token.Position if curr.Function.Name == "init" && curr.Function.Package == next.Function.Package { // We have implicit P.init calling P.init#d. Set the call position to // be at "package P" statement position. pos = packageStatementPos(curr.Function.Package) } else { // Choose the beginning of the import statement as the position. pos = importStatementPos(curr.Function.Package, next.Function.Package.PkgPath) } call.Pos = &pos } func importStatementPos(pkg *packages.Package, importPath string) token.Position { var importSpec *ast.ImportSpec spec: for _, f := range pkg.Syntax { for _, impSpec := range f.Imports { // Import spec paths have quotation marks. impSpecPath, err := strconv.Unquote(impSpec.Path.Value) if err != nil { panic(fmt.Sprintf("import specification: package path has no quotation marks: %v", err)) } if impSpecPath == importPath { importSpec = impSpec break spec } } } if importSpec == nil { // for sanity, in case of a wild call graph imprecision return token.Position{} } // Choose the beginning of the import statement as the position. return pkg.Fset.Position(importSpec.Pos()) } func packageStatementPos(pkg *packages.Package) token.Position { if len(pkg.Syntax) == 0 { return token.Position{} } // Choose beginning of the package statement as the position. Pick // the first file since it is as good as any. return pkg.Fset.Position(pkg.Syntax[0].Package) } // updateInitPosition updates the position of P.init function in a stack frame if one // is not available. The new position is the position of the "package P" statement. func updateInitPosition(se *StackEntry) { fun := se.Function if !isInit(fun) || (fun.Pos != nil && fun.Pos.IsValid()) { // Skip non-init functions and inits whose position is available. return } pos := packageStatementPos(fun.Package) fun.Pos = &pos } func isInit(f *FuncNode) bool { // A source init function, or anonymous functions used in inits, will // be named "init#x" by vulncheck (more precisely, ssa), where x is a // positive integer. Implicit inits are named simply "init". return f.Name == "init" || strings.HasPrefix(f.Name, "init#") } vuln-1.0.1/internal/vulncheck/witness_test.go000066400000000000000000000157141446745451500214040ustar00rootroot00000000000000// Copyright 2021 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 vulncheck import ( "context" "fmt" "path" "path/filepath" "reflect" "strings" "testing" "github.com/google/go-cmp/cmp" "golang.org/x/tools/go/packages" "golang.org/x/tools/go/packages/packagestest" "golang.org/x/vuln/internal/client" "golang.org/x/vuln/internal/govulncheck" "golang.org/x/vuln/internal/osv" ) // stacksToString converts map *Vuln:stack to Vuln.Symbol:"f1->...->fN" // string representation. func stacksToString(stacks map[*Vuln]CallStack) map[string]string { m := make(map[string]string) for v, st := range stacks { var stStr []string for _, call := range st { stStr = append(stStr, call.Function.Name) } m[v.Symbol] = strings.Join(stStr, "->") } return m } func TestCallStacks(t *testing.T) { // Call graph structure for the test program // entry1 entry2 // | | // interm1 | // | \ / // | interm2(interface) // | / | // vuln1 vuln2 o := &osv.Entry{ID: "o"} e1 := &FuncNode{Name: "entry1"} e2 := &FuncNode{Name: "entry2"} i1 := &FuncNode{Name: "interm1", CallSites: []*CallSite{{Parent: e1, Resolved: true}}} i2 := &FuncNode{Name: "interm2", CallSites: []*CallSite{{Parent: e2, Resolved: true}, {Parent: i1, Resolved: true}}} v1 := &FuncNode{Name: "vuln1", CallSites: []*CallSite{{Parent: i1, Resolved: true}, {Parent: i2, Resolved: false}}} v2 := &FuncNode{Name: "vuln2", CallSites: []*CallSite{{Parent: i2, Resolved: false}}} vp := &packages.Package{PkgPath: "v1", Module: &packages.Module{Path: "m1"}} vuln1 := &Vuln{CallSink: v1, ImportSink: vp, OSV: o, Symbol: "vuln1"} vuln2 := &Vuln{CallSink: v2, ImportSink: vp, OSV: o, Symbol: "vuln2"} res := &Result{ EntryFunctions: []*FuncNode{e1, e2}, Vulns: []*Vuln{vuln1, vuln2}, } want := map[string]string{ "vuln1": "entry1->interm1->vuln1", "vuln2": "entry2->interm2->vuln2", } stacks := CallStacks(res) if got := stacksToString(stacks); !reflect.DeepEqual(want, got) { t.Errorf("want %v; got %v", want, got) } } func TestUniqueCallStack(t *testing.T) { // Call graph structure for the test program // entry1 entry2 // | | // vuln1 interm1 // | | // | interm2 // | / // vuln2 o := &osv.Entry{ID: "o"} e1 := &FuncNode{Name: "entry1"} e2 := &FuncNode{Name: "entry2"} i1 := &FuncNode{Name: "interm1", CallSites: []*CallSite{{Parent: e2}}} i2 := &FuncNode{Name: "interm2", CallSites: []*CallSite{{Parent: i1}}} v1 := &FuncNode{Name: "vuln1", CallSites: []*CallSite{{Parent: e1}}} v2 := &FuncNode{Name: "vuln2", CallSites: []*CallSite{{Parent: v1}, {Parent: i2}}} vp := &packages.Package{PkgPath: "v1", Module: &packages.Module{Path: "m1"}} vuln1 := &Vuln{CallSink: v1, ImportSink: vp, OSV: o, Symbol: "vuln1"} vuln2 := &Vuln{CallSink: v2, ImportSink: vp, OSV: o, Symbol: "vuln2"} res := &Result{ EntryFunctions: []*FuncNode{e1, e2}, Vulns: []*Vuln{vuln1, vuln2}, } want := map[string]string{ "vuln1": "entry1->vuln1", "vuln2": "entry2->interm1->interm2->vuln2", } stacks := CallStacks(res) if got := stacksToString(stacks); !reflect.DeepEqual(want, got) { t.Errorf("want %v; got %v", want, got) } } // TestInits checks for correct positions of init functions // and their respective calls (see #51575). func TestInits(t *testing.T) { testClient, err := client.NewInMemoryClient( []*osv.Entry{ { ID: "A", Affected: []osv.Affected{{Module: osv.Module{Path: "golang.org/amod"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver}}, EcosystemSpecific: osv.EcosystemSpecific{Packages: []osv.Package{{ Path: "golang.org/amod/avuln", Symbols: []string{"A"}}, }}, }}, }, { ID: "C", Affected: []osv.Affected{{Module: osv.Module{Path: "golang.org/cmod"}, Ranges: []osv.Range{{Type: osv.RangeTypeSemver}}, EcosystemSpecific: osv.EcosystemSpecific{Packages: []osv.Package{{ Path: "golang.org/cmod/cvuln", Symbols: []string{"C"}}, }}, }}, }, }) if err != nil { t.Fatal(err) } e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{ { Name: "golang.org/entry", Files: map[string]interface{}{ "x/x.go": ` package x import ( _ "golang.org/amod/avuln" _ "golang.org/bmod/b" ) `, }, }, { Name: "golang.org/amod@v0.5.0", Files: map[string]interface{}{"avuln/avuln.go": ` package avuln func init() { A() } func A() {} `}, }, { Name: "golang.org/bmod@v0.5.0", Files: map[string]interface{}{"b/b.go": ` package b import _ "golang.org/cmod/cvuln" `}, }, { Name: "golang.org/cmod@v0.5.0", Files: map[string]interface{}{"cvuln/cvuln.go": ` package cvuln var x int = C() func C() int { return 0 } `}, }, }) defer e.Cleanup() // Load x as entry package. graph := NewPackageGraph("go1.18") pkgs, err := graph.LoadPackages(e.Config, nil, []string{path.Join(e.Temp(), "entry/x")}) if err != nil { t.Fatal(err) } if len(pkgs) != 1 { t.Fatal("failed to load x test package") } cfg := &govulncheck.Config{ScanLevel: "symbol"} result, err := Source(context.Background(), pkgs, cfg, testClient, graph) if err != nil { t.Fatal(err) } cs := CallStacks(result) want := map[string][]string{ "A": { // Entry init's position is the package statement. // It calls avuln.init at avuln import statement. "N:golang.org/entry/x.init F:x.go:2:4 C:x.go:5:5", // implicit avuln.init is calls explicit init at the avuln // package statement. "N:golang.org/amod/avuln.init F:avuln.go:2:4 C:avuln.go:2:4", "N:golang.org/amod/avuln.init#1 F:avuln.go:4:9 C:avuln.go:5:6", "N:golang.org/amod/avuln.A F:avuln.go:8:9 C:", }, "C": { "N:golang.org/entry/x.init F:x.go:2:4 C:x.go:6:5", "N:golang.org/bmod/b.init F:b.go:2:4 C:b.go:4:11", "N:golang.org/cmod/cvuln.init F:cvuln.go:2:4 C:cvuln.go:4:17", "N:golang.org/cmod/cvuln.C F:cvuln.go:6:9 C:", }, } if diff := cmp.Diff(want, fullStacksToString(cs)); diff != "" { t.Errorf("modules mismatch (-want, +got):\n%s", diff) } } // fullStacksToString is like stacksToString but the stack stringification // is a slice of strings, each containing detailed information on each on // the corresponding frame. func fullStacksToString(callStacks map[*Vuln]CallStack) map[string][]string { m := make(map[string][]string) for v, cs := range callStacks { var scs []string for _, se := range cs { fPos := se.Function.Pos fp := fmt.Sprintf("%s:%d:%d", filepath.Base(fPos.Filename), fPos.Line, fPos.Column) var cp string if se.Call != nil && se.Call.Pos.IsValid() { cPos := se.Call.Pos cp = fmt.Sprintf("%s:%d:%d", filepath.Base(cPos.Filename), cPos.Line, cPos.Column) } sse := fmt.Sprintf("N:%s.%s\tF:%v\tC:%v", se.Function.Package.PkgPath, se.Function.Name, fp, cp) scs = append(scs, sse) } m[v.OSV.ID] = scs } return m } vuln-1.0.1/internal/web/000077500000000000000000000000001446745451500151055ustar00rootroot00000000000000vuln-1.0.1/internal/web/url.go000066400000000000000000000076001446745451500162410ustar00rootroot00000000000000// Copyright 2022 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. // Code copied from // https://github.com/golang/go/blob/2ebe77a2fda1ee9ff6fd9a3e08933ad1ebaea039/src/cmd/go/internal/web/url.go // TODO(https://go.dev/issue/32456): if accepted, use the new API. package web import ( "errors" "net/url" "path/filepath" "runtime" "strings" ) var errNotAbsolute = errors.New("path is not absolute") // URLToFilePath converts a file-scheme url to a file path. func URLToFilePath(u *url.URL) (string, error) { if u.Scheme != "file" { return "", errors.New("non-file URL") } checkAbs := func(path string) (string, error) { if !filepath.IsAbs(path) { return "", errNotAbsolute } return path, nil } if u.Path == "" { if u.Host != "" || u.Opaque == "" { return "", errors.New("file URL missing path") } return checkAbs(filepath.FromSlash(u.Opaque)) } path, err := convertFileURLPath(u.Host, u.Path) if err != nil { return path, err } return checkAbs(path) } // URLFromFilePath converts the given absolute path to a URL. func URLFromFilePath(path string) (*url.URL, error) { if !filepath.IsAbs(path) { return nil, errNotAbsolute } // If path has a Windows volume name, convert the volume to a host and prefix // per https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/. if vol := filepath.VolumeName(path); vol != "" { if strings.HasPrefix(vol, `\\`) { path = filepath.ToSlash(path[2:]) i := strings.IndexByte(path, '/') if i < 0 { // A degenerate case. // \\host.example.com (without a share name) // becomes // file://host.example.com/ return &url.URL{ Scheme: "file", Host: path, Path: "/", }, nil } // \\host.example.com\Share\path\to\file // becomes // file://host.example.com/Share/path/to/file return &url.URL{ Scheme: "file", Host: path[:i], Path: filepath.ToSlash(path[i:]), }, nil } // C:\path\to\file // becomes // file:///C:/path/to/file return &url.URL{ Scheme: "file", Path: "/" + filepath.ToSlash(path), }, nil } // /path/to/file // becomes // file:///path/to/file return &url.URL{ Scheme: "file", Path: filepath.ToSlash(path), }, nil } func convertFileURLPath(host, path string) (string, error) { if runtime.GOOS == "windows" { return convertFileURLPathWindows(host, path) } switch host { case "", "localhost": default: return "", errors.New("file URL specifies non-local host") } return filepath.FromSlash(path), nil } func convertFileURLPathWindows(host, path string) (string, error) { if len(path) == 0 || path[0] != '/' { return "", errNotAbsolute } path = filepath.FromSlash(path) // We interpret Windows file URLs per the description in // https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/. // The host part of a file URL (if any) is the UNC volume name, // but RFC 8089 reserves the authority "localhost" for the local machine. if host != "" && host != "localhost" { // A common "legacy" format omits the leading slash before a drive letter, // encoding the drive letter as the host instead of part of the path. // (See https://blogs.msdn.microsoft.com/freeassociations/2005/05/19/the-bizarre-and-unhappy-story-of-file-urls/.) // We do not support that format, but we should at least emit a more // helpful error message for it. if filepath.VolumeName(host) != "" { return "", errors.New("file URL encodes volume in host field: too few slashes?") } return `\\` + host + path, nil } // If host is empty, path must contain an initial slash followed by a // drive letter and path. Remove the slash and verify that the path is valid. if vol := filepath.VolumeName(path[1:]); vol == "" || strings.HasPrefix(vol, `\\`) { return "", errors.New("file URL missing drive letter") } return path[1:], nil } vuln-1.0.1/internal/web/url_test.go000066400000000000000000000123651446745451500173040ustar00rootroot00000000000000// Copyright 2022 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 web import ( "net/url" "runtime" "testing" ) func TestURLToFilePath(t *testing.T) { for _, tc := range urlTests() { if tc.url == "" { continue } tc := tc t.Run(tc.url, func(t *testing.T) { u, err := url.Parse(tc.url) if err != nil { t.Fatalf("url.Parse(%q): %v", tc.url, err) } path, err := URLToFilePath(u) if err != nil { if err.Error() == tc.wantErr { return } if tc.wantErr == "" { t.Fatalf("urlToFilePath(%v): %v; want ", u, err) } else { t.Fatalf("urlToFilePath(%v): %v; want %s", u, err, tc.wantErr) } } if path != tc.filePath || tc.wantErr != "" { t.Fatalf("urlToFilePath(%v) = %q, ; want %q, %s", u, path, tc.filePath, tc.wantErr) } }) } } func TestURLFromFilePath(t *testing.T) { for _, tc := range urlTests() { if tc.filePath == "" { continue } tc := tc t.Run(tc.filePath, func(t *testing.T) { u, err := URLFromFilePath(tc.filePath) if err != nil { if err.Error() == tc.wantErr { return } if tc.wantErr == "" { t.Fatalf("urlFromFilePath(%v): %v; want ", tc.filePath, err) } else { t.Fatalf("urlFromFilePath(%v): %v; want %s", tc.filePath, err, tc.wantErr) } } if tc.wantErr != "" { t.Fatalf("urlFromFilePath(%v) = ; want error: %s", tc.filePath, tc.wantErr) } wantURL := tc.url if tc.canonicalURL != "" { wantURL = tc.canonicalURL } if u.String() != wantURL { t.Errorf("urlFromFilePath(%v) = %v; want %s", tc.filePath, u, wantURL) } }) } } func urlTests() []urlTest { if runtime.GOOS == "windows" { return urlTestsWindows } return urlTestsOthers } type urlTest struct { url string filePath string canonicalURL string // If empty, assume equal to url. wantErr string } var urlTestsOthers = []urlTest{ // Examples from RFC 8089: { url: `file:///path/to/file`, filePath: `/path/to/file`, }, { url: `file:/path/to/file`, filePath: `/path/to/file`, canonicalURL: `file:///path/to/file`, }, { url: `file://localhost/path/to/file`, filePath: `/path/to/file`, canonicalURL: `file:///path/to/file`, }, // We reject non-local files. { url: `file://host.example.com/path/to/file`, wantErr: "file URL specifies non-local host", }, } var urlTestsWindows = []urlTest{ // Examples from https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/: { url: `file://laptop/My%20Documents/FileSchemeURIs.doc`, filePath: `\\laptop\My Documents\FileSchemeURIs.doc`, }, { url: `file:///C:/Documents%20and%20Settings/davris/FileSchemeURIs.doc`, filePath: `C:\Documents and Settings\davris\FileSchemeURIs.doc`, }, { url: `file:///D:/Program%20Files/Viewer/startup.htm`, filePath: `D:\Program Files\Viewer\startup.htm`, }, { url: `file:///C:/Program%20Files/Music/Web%20Sys/main.html?REQUEST=RADIO`, filePath: `C:\Program Files\Music\Web Sys\main.html`, canonicalURL: `file:///C:/Program%20Files/Music/Web%20Sys/main.html`, }, { url: `file://applib/products/a-b/abc_9/4148.920a/media/start.swf`, filePath: `\\applib\products\a-b\abc_9\4148.920a\media\start.swf`, }, { url: `file:////applib/products/a%2Db/abc%5F9/4148.920a/media/start.swf`, wantErr: "file URL missing drive letter", }, { url: `C:\Program Files\Music\Web Sys\main.html?REQUEST=RADIO`, wantErr: "non-file URL", }, // The example "file://D:\Program Files\Viewer\startup.htm" errors out in // url.Parse, so we substitute a slash-based path for testing instead. { url: `file://D:/Program Files/Viewer/startup.htm`, wantErr: "file URL encodes volume in host field: too few slashes?", }, // The blog post discourages the use of non-ASCII characters because they // depend on the user's current codepage. However, when we are working with Go // strings we assume UTF-8 encoding, and our url package refuses to encode // URLs to non-ASCII strings. { url: `file:///C:/exampleㄓ.txt`, filePath: `C:\exampleㄓ.txt`, canonicalURL: `file:///C:/example%E3%84%93.txt`, }, { url: `file:///C:/example%E3%84%93.txt`, filePath: `C:\exampleㄓ.txt`, }, // Examples from RFC 8089: // We allow the drive-letter variation from section E.2, because it is // simpler to support than not to. However, we do not generate the shorter // form in the reverse direction. { url: `file:c:/path/to/file`, filePath: `c:\path\to\file`, canonicalURL: `file:///c:/path/to/file`, }, // We encode the UNC share name as the authority following section E.3.1, // because that is what the Microsoft blog post explicitly recommends. { url: `file://host.example.com/Share/path/to/file.txt`, filePath: `\\host.example.com\Share\path\to\file.txt`, }, // We decline the four- and five-slash variations from section E.3.2. // The paths in these URLs would change meaning under path.Clean. { url: `file:////host.example.com/path/to/file`, wantErr: "file URL missing drive letter", }, { url: `file://///host.example.com/path/to/file`, wantErr: "file URL missing drive letter", }, } vuln-1.0.1/scan/000077500000000000000000000000001446745451500134405ustar00rootroot00000000000000vuln-1.0.1/scan/scan.go000066400000000000000000000047731446745451500147260ustar00rootroot00000000000000// Copyright 2023 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 scan provides functionality for running govulncheck. See [cmd/govulncheck/main.go] as a usage example. [cmd/govulncheck/main.go]: https://go.googlesource.com/vuln/+/master/cmd/govulncheck/main.go */ package scan import ( "context" "errors" "io" "os" "golang.org/x/vuln/internal/scan" ) // Cmd represents an external govulncheck command being prepared or run, // similar to exec.Cmd. type Cmd struct { // Stdin specifies the standard input. If provided, it is expected to be // the output of govulncheck -json. Stdin io.Reader // Stdout specifies the standard output. If nil, Run connects os.Stdout. Stdout io.Writer // Stderr specifies the standard error. If nil, Run connects os.Stderr. Stderr io.Writer // Env is the environment to use. // If Env is nil, the current environment is used. // As in os/exec's Cmd, only the last value in the slice for // each environment key is used. To specify the setting of only // a few variables, append to the current environment, as in: // // opt.Env = append(os.Environ(), "GOOS=plan9", "GOARCH=386") // Env []string ctx context.Context args []string done chan struct{} err error } // Command returns the Cmd struct to execute govulncheck with the given // arguments. func Command(ctx context.Context, arg ...string) *Cmd { return &Cmd{ ctx: ctx, args: arg, } } // Start starts the specified command but does not wait for it to complete. // // After a successful call to Start the Wait method must be called in order to // release associated system resources. func (c *Cmd) Start() error { if c.done != nil { return errors.New("vuln: already started") } if c.Stdin == nil { c.Stdin = os.Stdin } if c.Stdout == nil { c.Stdout = os.Stdout } if c.Stderr == nil { c.Stderr = os.Stderr } if c.Env == nil { c.Env = os.Environ() } c.done = make(chan struct{}) go func() { defer close(c.done) c.err = c.scan() }() return nil } // Wait waits for the command to exit. The command must have been started by // Start. // // Wait releases any resources associated with the Cmd. func (c *Cmd) Wait() error { if c.done == nil { return errors.New("vuln: start must be called before wait") } <-c.done return c.err } func (c *Cmd) scan() error { if err := c.ctx.Err(); err != nil { return err } return scan.RunGovulncheck(c.ctx, c.Env, c.Stdin, c.Stdout, c.Stderr, c.args) }