Skip to content

Commit

Permalink
feat: Add workaround for Golang's stdlib (#72)
Browse files Browse the repository at this point in the history
* feat: Add workaround for Golang's stdlib

* fix: Success when no files are specified

* fix: Try writing file in /tmp
  • Loading branch information
mrfyda authored Jul 1, 2024
1 parent a83eb43 commit e687f3b
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 11 deletions.
34 changes: 31 additions & 3 deletions docs/multiple-tests/pattern-vulnerability/results.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,43 @@

<file
name="golang/go.mod">
<error source="vulnerability" line="5"
<error source="vulnerability" line="7"
message="Insecure dependency golang.org/x/net@v0.16.0 (CVE-2023-45288: golang: net/http, x/net/http2: unlimited number of CONTINUATION frames causes DoS) (update to 0.23.0)"
severity="error" />
<error source="vulnerability" line="5"
<error source="vulnerability" line="7"
message="Insecure dependency golang.org/x/net@v0.16.0 (CVE-2023-39325: golang: net/http, x/net/http2: rapid stream resets can cause excessive work (CVE-2023-44487)) (update to 0.17.0)"
severity="error" />
<error source="vulnerability" line="5"
<error source="vulnerability" line="7"
message="Insecure dependency golang.org/x/net@v0.16.0 (CVE-2023-44487: HTTP/2: Multiple HTTP/2 enabled web servers are vulnerable to a DDoS attack (Rapid Reset Attack)) (update to 0.17.0)"
severity="error" />
<!-- stdlib -->
<error source="vulnerability" line="3"
message="Insecure dependency stdlib@v1.21.4 (CVE-2023-39326: golang: net/http/internal: Denial of Service (DoS) via Resource Consumption via HTTP requests) (update to 1.21.5)"
severity="error" />
<error source="vulnerability" line="3"
message="Insecure dependency stdlib@v1.21.4 (CVE-2023-45288: golang: net/http, x/net/http2: unlimited number of CONTINUATION frames causes DoS) (update to 1.21.9)"
severity="error" />
<error source="vulnerability" line="3"
message="Insecure dependency stdlib@v1.21.4 (CVE-2023-45289: golang: net/http/cookiejar: incorrect forwarding of sensitive headers and cookies on HTTP redirect) (update to 1.21.8)"
severity="error" />
<error source="vulnerability" line="3"
message="Insecure dependency stdlib@v1.21.4 (CVE-2023-45290: golang: net/http: memory exhaustion in Request.ParseMultipartForm) (update to 1.21.8)"
severity="error" />
<error source="vulnerability" line="3"
message="Insecure dependency stdlib@v1.21.4 (CVE-2024-24783: golang: crypto/x509: Verify panics on certificates with an unknown public key algorithm) (update to 1.21.8)"
severity="error" />
<error source="vulnerability" line="3"
message="Insecure dependency stdlib@v1.21.4 (CVE-2024-24784: golang: net/mail: comments in display names are incorrectly handled) (update to 1.21.8)"
severity="error" />
<error source="vulnerability" line="3"
message="Insecure dependency stdlib@v1.21.4 (CVE-2024-24785: golang: html/template: errors returned from MarshalJSON methods may break template escaping) (update to 1.21.8)"
severity="error" />
<error source="vulnerability" line="3"
message="Insecure dependency stdlib@v1.21.4 (CVE-2024-24789: golang: archive/zip: Incorrect handling of certain ZIP files) (update to 1.21.11)"
severity="error" />
<error source="vulnerability" line="3"
message="Insecure dependency stdlib@v1.21.4 (CVE-2024-24790: golang: net/netip: Unexpected behavior from Is methods for IPv4-mapped IPv6 addresses) (update to 1.21.11)"
severity="error" />
</file>

<file name="gradle/gradle.lockfile">
Expand Down
4 changes: 3 additions & 1 deletion docs/multiple-tests/pattern-vulnerability/src/golang/go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module example

go 1.21.4
go 1.21.0

toolchain go1.21.4

require golang.org/x/net v0.16.0
128 changes: 128 additions & 0 deletions internal/tool/golang.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package tool

import (
"bufio"
"io"
"os"
"path/filepath"
"strings"

"github.com/samber/lo"
)

func patchGoModFilesForStdlib(srcDir string, files []string) string {
// Copy the files to a temporary directory because /src is read-only
dstDir := "/tmp/src"
if err := CopyFiles(files, srcDir, dstDir); err != nil {
return srcDir
}

// Find and patch the go.mod files
lo.ForEach(files, func(file string, _ int) {
if strings.HasSuffix(file, "go.mod") {
patchGoModFileForStdlib(filepath.Join(dstDir, file))
}
})

return dstDir
}

// Find lines in go.mod files that specify the Go version and replace them with a require statement for the stdlib module.
func patchGoModFileForStdlib(filename string) {
tempFilename := filename + ".tmp"

// Open the original file for reading
inputFile, err := os.Open(filename)
if err != nil {
return
}
defer inputFile.Close()

// Create a temporary file for writing
tempFile, err := os.Create(tempFilename)
if err != nil {
return
}
defer tempFile.Close()

scanner := bufio.NewScanner(inputFile)
writer := bufio.NewWriter(tempFile)

// Process the file line by line
for scanner.Scan() {
line := scanner.Text()
// Find go version statement
if strings.HasPrefix(line, "go ") {
version := strings.TrimPrefix(line, "go ")
line = "require stdlib v" + version
}
// Find toolchain statement
if strings.HasPrefix(line, "toolchain go") {
version := strings.TrimPrefix(line, "toolchain go")
line = "require stdlib v" + version
}

_, err := writer.WriteString(line + "\n")
if err != nil {
return
}
}

if err := scanner.Err(); err != nil {
return
}

// Flush the writer
if err := writer.Flush(); err != nil {
return
}

// Close both files
inputFile.Close()
tempFile.Close()

// Replace the original file with the temporary file
if err := os.Rename(tempFilename, filename); err != nil {
return
}
}

// CopyFiles copies specific files from the source directory to the destination directory.
func CopyFiles(files []string, srcDir string, dstDir string) error {
for _, file := range files {
srcPath := filepath.Join(srcDir, file)
dstPath := filepath.Join(dstDir, file)

// Ensure the destination directory exists
if err := os.MkdirAll(filepath.Dir(dstPath), os.ModePerm); err != nil {
return err
}

// Copy the file
if err := CopyFile(srcPath, dstPath); err != nil {
return err
}
}
return nil
}

// CopyFile copies a single file from src to dst.
func CopyFile(src, dst string) error {
sourceFile, err := os.Open(src)
if err != nil {
return err
}
defer sourceFile.Close()

destinationFile, err := os.Create(dst)
if err != nil {
return err
}
defer destinationFile.Close()

if _, err := io.Copy(destinationFile, sourceFile); err != nil {
return err
}

return nil
}
18 changes: 12 additions & 6 deletions internal/tool/tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"os"
"path"
"path/filepath"
"strings"

"github.com/aquasecurity/trivy/pkg/fanal/secret"
Expand Down Expand Up @@ -45,6 +46,11 @@ func (t codacyTrivy) Run(ctx context.Context, toolExecution codacy.ToolExecution
return []codacy.Result{}, nil
}

if toolExecution.Files == nil || len(*toolExecution.Files) == 0 {
// TODO Run for all files in the source dir?
return []codacy.Result{}, nil
}

err := newConfiguration(*toolExecution.Patterns)
if err != nil {
return nil, err
Expand Down Expand Up @@ -73,6 +79,11 @@ func (t codacyTrivy) runVulnerabilityScanning(ctx context.Context, toolExecution
return []codacy.Result{}, nil
}

// Workaround for detecting vulnerabilities in the Go standard library.
// Mimics the behavior of govulncheck by replacing the go version directive with a require statement for stdlib. https://go.dev/blog/govulncheck
// This is only supported by Trivy for Go binaries. https://github.com/aquasecurity/trivy/issues/4133
toolExecution.SourceDir = patchGoModFilesForStdlib(toolExecution.SourceDir, *toolExecution.Files)

config := flag.Options{
GlobalOptions: flag.GlobalOptions{
// CacheDir needs to be explicitly set and match the directory in the Dockerfile.
Expand Down Expand Up @@ -212,7 +223,7 @@ func filterIssuesFromKnownFiles(issues []codacy.Issue, knownFiles []string) []co
// If the line number is not available in the Trivy result, try to find it in the source file.
// Returns 0 if the line number is not found.
func fallbackSearchForLineNumber(sourceDir, fileName, pkgName string) (int, error) {
filePath := path.Join(sourceDir, fileName)
filePath := filepath.Join(sourceDir, fileName)
f, err := os.Open(filePath)
if err != nil {
return 0, err
Expand Down Expand Up @@ -270,11 +281,6 @@ func (t codacyTrivy) runSecretScanning(patterns []codacy.Pattern, files *[]strin
return []codacy.Result{}, nil
}

if files == nil || len(*files) == 0 {
// TODO Run for all files in the source dir?
return []codacy.Result{}, nil
}

scanner := secret.NewScanner(&secret.Config{})

results := []codacy.Result{}
Expand Down
13 changes: 12 additions & 1 deletion internal/tool/tool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,16 @@ func TestRunNoPatterns(t *testing.T) {

func TestRunConfigurationError(t *testing.T) {
// Arrange
file1 := "file-1"
file2 := "file-2"

toolExecution := codacy.ToolExecution{
Patterns: &[]codacy.Pattern{
{
ID: "unknown",
},
},
Files: &[]string{file1, file2},
}

underTest := codacyTrivy{}
Expand All @@ -233,13 +237,16 @@ func TestRunConfigurationError(t *testing.T) {

func TestRunNewRunnerError(t *testing.T) {
// Arrange
file1 := "file-1"
file2 := "file-2"

toolExecution := codacy.ToolExecution{
Patterns: &[]codacy.Pattern{
{
ID: ruleIDVulnerability,
},
},
Files: &[]string{},
Files: &[]string{file1, file2},
}

underTest := codacyTrivy{
Expand All @@ -261,6 +268,9 @@ func TestRunScanFilesystemError(t *testing.T) {
ctx := context.Background()
ctrl := gomock.NewController(t)

file1 := "file-1"
file2 := "file-2"

sourceDir := "src"
toolExecution := codacy.ToolExecution{
Patterns: &[]codacy.Pattern{
Expand All @@ -272,6 +282,7 @@ func TestRunScanFilesystemError(t *testing.T) {
},
},
SourceDir: sourceDir,
Files: &[]string{file1, file2},
}

config := flag.Options{
Expand Down

0 comments on commit e687f3b

Please sign in to comment.