Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Support user-defined fuzz functions (GoLang) in fuzzing check #1979

Merged
merged 36 commits into from Jun 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f21d341
temp save 05262022
May 26, 2022
1edd76b
Merge branch 'main' into feat/897_go_fuzzer_check
May 26, 2022
2804c75
Merge branch 'ossf:main' into feat/897_go_fuzzer_check
aidenwang9867 May 26, 2022
d42b58d
Merge branch 'feat/897_go_fuzzer_check' of https://github.com/aidenwa…
May 26, 2022
091f311
finished golang fuzz func check, getLang interface to be done next week
May 27, 2022
949b6df
Merge branch 'main' into feat/897_go_fuzzer_check
May 31, 2022
3a225fa
temp save 05/31/2022
Jun 1, 2022
162de28
temp save 06/01/2022
Jun 2, 2022
a9f8fcb
Merge branch 'main' into feat/897_go_fuzzer_check
Jun 2, 2022
724e70c
Merge branch 'ossf:main' into feat/897_go_fuzzer_check
aidenwang9867 Jun 2, 2022
1c1c25a
Merge branch 'feat/897_go_fuzzer_check' of https://github.com/aidenwa…
Jun 2, 2022
69b807b
temp save-2 06/01/2022
Jun 2, 2022
000d9c3
temp save-1 06032022
Jun 2, 2022
f62dc64
temp save-2 06022022
Jun 2, 2022
4b0178d
temp save
Jun 3, 2022
69f6b18
temp save 06032022
Jun 3, 2022
ffe1bf1
temp save 06032022 (2)
Jun 3, 2022
739de16
update err def
Jun 3, 2022
06a0373
temp save 3
Jun 3, 2022
f999d7c
update docs for fuzzing
Jun 6, 2022
3d10b94
update docs for fuzzing
Jun 6, 2022
f481803
update checks.yaml to gen docs
Jun 6, 2022
531b103
Merge branch 'main' into feat/897_go_fuzzer_check
aidenwang9867 Jun 6, 2022
68a6cb2
temp save 0606
Jun 6, 2022
06e46ac
Merge branch 'feat/897_go_fuzzer_check' of https://github.com/aidenwa…
Jun 6, 2022
40ad2a0
temp save-2 0606
Jun 7, 2022
a918025
temp save-3 0606
Jun 7, 2022
b41e82f
temp save-4 0606
Jun 7, 2022
543ff10
fix linter errors
Jun 7, 2022
56d7d3e
fix linter errs-2
Jun 7, 2022
e043558
Merge branch 'main' into feat/897_go_fuzzer_check
aidenwang9867 Jun 7, 2022
ff02e79
fix e2e errors
Jun 7, 2022
a2a61f0
Merge branch 'feat/897_go_fuzzer_check' of https://github.com/aidenwa…
Jun 7, 2022
20b192d
0608
Jun 8, 2022
658059f
Merge branch 'ossf:main' into feat/897_go_fuzzer_check
aidenwang9867 Jun 8, 2022
af0c157
0608-2
Jun 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 4 additions & 4 deletions checker/raw_result.go
Expand Up @@ -174,10 +174,10 @@ type BranchProtectionsData struct {

// Tool represents a tool.
type Tool struct {
URL *string
Desc *string
File *File
Name string
URL *string
Desc *string
Files []File
Name string
// Runs of the tool.
Runs []Run
// Issues created by the tool.
Expand Down
21 changes: 11 additions & 10 deletions checks/evaluation/dependency_update_tool.go
Expand Up @@ -49,19 +49,20 @@ func DependencyUpdateTool(name string, dl checker.DetailLogger,
return checker.CreateRuntimeErrorResult(name, e)
}

if r.Tools[0].File == nil {
e := sce.WithMessage(sce.ErrScorecardInternal, "File is nil")
if r.Tools[0].Files == nil {
e := sce.WithMessage(sce.ErrScorecardInternal, "Files are nil")
return checker.CreateRuntimeErrorResult(name, e)
}

// Note: only one file per tool is present,
// so we do not iterate thru all entries.
dl.Info(&checker.LogMessage{
Path: r.Tools[0].File.Path,
Type: r.Tools[0].File.Type,
Offset: r.Tools[0].File.Offset,
Text: fmt.Sprintf("%s detected", r.Tools[0].Name),
})
// Iterate over all the files, since a Tool can contain multiple files.
for _, file := range r.Tools[0].Files {
dl.Info(&checker.LogMessage{
Path: file.Path,
Type: file.Type,
Offset: file.Offset,
Text: fmt.Sprintf("%s detected", r.Tools[0].Name),
})
}

// High score result.
return checker.CreateMaxScoreResult(name, "update tool detected")
Expand Down
11 changes: 6 additions & 5 deletions checks/evaluation/dependency_update_tool_test.go
Expand Up @@ -88,14 +88,15 @@ func TestDependencyUpdateTool(t *testing.T) {
Tools: []checker.Tool{
{
Name: "DependencyUpdateTool",
File: &checker.File{
Path: "/etc/dependency-update-tool.conf",
Snippet: `
Files: []checker.File{
{
Path: "/etc/dependency-update-tool.conf",
Snippet: `
[dependency-update-tool]
enabled = true
`,
Offset: 0,
Type: 0,
Type: checker.FileTypeSource,
},
},
},
},
Expand Down
22 changes: 18 additions & 4 deletions checks/evaluation/fuzzing.go
Expand Up @@ -30,11 +30,25 @@ func Fuzzing(name string, dl checker.DetailLogger,
return checker.CreateRuntimeErrorResult(name, e)
}

if len(r.Fuzzers) == 0 {
return checker.CreateMinScoreResult(name, "project is not fuzzed")
}
fuzzers := []string{}
for i := range r.Fuzzers {
fuzzer := r.Fuzzers[i]
return checker.CreateMaxScoreResult(name,
fmt.Sprintf("project is fuzzed with %s", fuzzer.Name))
for _, f := range fuzzer.Files {
msg := checker.LogMessage{
Path: f.Path,
Type: f.Type,
Offset: f.Offset,
}
aidenwang9867 marked this conversation as resolved.
Show resolved Hide resolved
if f.Snippet != "" {
msg.Text = f.Snippet
}
dl.Info(&msg)
}
fuzzers = append(fuzzers, fuzzer.Name)
}

return checker.CreateMinScoreResult(name, "project is not fuzzed")
return checker.CreateMaxScoreResult(name,
fmt.Sprintf("project is fuzzed with %v", fuzzers))
}
27 changes: 23 additions & 4 deletions checks/fuzzing_test.go
Expand Up @@ -34,6 +34,7 @@ func TestFuzzing(t *testing.T) {
tests := []struct {
name string
want checker.CheckResult
langs map[clients.Language]int
response clients.SearchResponse
wantErr bool
wantFuzzErr bool
Expand All @@ -44,13 +45,20 @@ func TestFuzzing(t *testing.T) {
{
name: "empty response",
response: clients.SearchResponse{},
wantErr: false,
langs: map[clients.Language]int{
clients.Go: 300,
},
wantErr: false,
},
{
name: "hits 1",
response: clients.SearchResponse{
Hits: 1,
},
langs: map[clients.Language]int{
clients.Go: 100,
clients.Java: 70,
},
wantErr: false,
want: checker.CheckResult{Score: 10},
expected: scut.TestReturn{
Expand All @@ -61,7 +69,10 @@ func TestFuzzing(t *testing.T) {
},
},
{
name: "nil response",
name: "nil response",
langs: map[clients.Language]int{
clients.Python: 256,
},
wantErr: true,
want: checker.CheckResult{Score: -1},
expected: scut.TestReturn{
Expand All @@ -73,7 +84,15 @@ func TestFuzzing(t *testing.T) {
},
},
{
name: " error",
name: "min score since lang not supported",
langs: map[clients.Language]int{
clients.Language("not_supported_lang"): 1490,
},
wantFuzzErr: false,
want: checker.CheckResult{Score: 0},
},
{
name: "error",
wantFuzzErr: true,
want: checker.CheckResult{},
},
Expand All @@ -94,7 +113,7 @@ func TestFuzzing(t *testing.T) {
}
return tt.response, nil
}).AnyTimes()

mockFuzz.EXPECT().ListProgrammingLanguages().Return(tt.langs, nil).AnyTimes()
mockFuzz.EXPECT().ListFiles(gomock.Any()).Return(tt.fileName, nil).AnyTimes()
mockFuzz.EXPECT().GetFileContent(gomock.Any()).DoAndReturn(func(f string) (string, error) {
if tt.wantErr {
Expand Down
20 changes: 12 additions & 8 deletions checks/raw/dependency_update_tool.go
Expand Up @@ -51,10 +51,12 @@ var checkDependencyFileExists fileparser.DoWhileTrueOnFilename = func(name strin
Name: "Dependabot",
URL: asPointer("https://github.com/dependabot"),
Desc: asPointer("Automated dependency updates built into GitHub"),
File: &checker.File{
Path: name,
Type: checker.FileTypeSource,
Offset: checker.OffsetDefault,
Files: []checker.File{
{
Path: name,
Type: checker.FileTypeSource,
Offset: checker.OffsetDefault,
},
},
})

Expand All @@ -65,10 +67,12 @@ var checkDependencyFileExists fileparser.DoWhileTrueOnFilename = func(name strin
Name: "Renovabot",
URL: asPointer("https://github.com/renovatebot/renovate"),
Desc: asPointer("Automated dependency updates. Multi-platform and multi-language."),
File: &checker.File{
Path: name,
Type: checker.FileTypeSource,
Offset: checker.OffsetDefault,
Files: []checker.File{
{
Path: name,
Type: checker.FileTypeSource,
Offset: checker.OffsetDefault,
},
},
})
default:
Expand Down
153 changes: 151 additions & 2 deletions checks/raw/fuzzing.go
Expand Up @@ -15,14 +15,51 @@
package raw

import (
"bytes"
"fmt"
"regexp"
"strings"

"github.com/ossf/scorecard/v4/checker"
"github.com/ossf/scorecard/v4/checks/fileparser"
"github.com/ossf/scorecard/v4/clients"
sce "github.com/ossf/scorecard/v4/errors"
)

const (
fuzzerOSSFuzz = "OSSFuzz"
fuzzerClusterFuzzLite = "ClusterFuzzLite"
fuzzerBuiltInGo = "GoBuiltInFuzzer"
// TODO: add more fuzzing check supports.
)

type filesWithPatternStr struct {
pattern string
files []checker.File
}

// Configurations for language-specified fuzzers.
type languageFuzzConfig struct {
URL, Desc *string
filePattern, funcPattern, Name string
//TODO: add more language fuzzing-related fields.
}

// Contains fuzzing speficications for programming languages.
// Please use the type Language defined in clients/languages.go rather than a raw string.
var languageFuzzSpecs = map[clients.Language]languageFuzzConfig{
// Default fuzz patterns for Go.
clients.Go: {
filePattern: "*_test.go",
funcPattern: `func\s+Fuzz\w+\s*\(\w+\s+\*testing.F\)`,
Name: fuzzerBuiltInGo,
URL: asPointer("https://go.dev/doc/fuzz/"),
Desc: asPointer(
"Go fuzzing intelligently walks through the source code to report failures and find vulnerabilities."),
},
// TODO: add more language-specific fuzz patterns & configs.
}

// Fuzzing runs Fuzzing check.
func Fuzzing(c *checker.CheckRequest) (checker.FuzzingData, error) {
var fuzzers []checker.Tool
Expand All @@ -33,7 +70,7 @@ func Fuzzing(c *checker.CheckRequest) (checker.FuzzingData, error) {
if usingCFLite {
fuzzers = append(fuzzers,
checker.Tool{
Name: "ClusterFuzzLite",
Name: fuzzerClusterFuzzLite,
URL: asPointer("https://github.com/google/clusterfuzzlite"),
Desc: asPointer("continuous fuzzing solution that runs as part of Continuous Integration (CI) workflows"),
// TODO: File.
Expand All @@ -48,14 +85,36 @@ func Fuzzing(c *checker.CheckRequest) (checker.FuzzingData, error) {
if usingOSSFuzz {
fuzzers = append(fuzzers,
checker.Tool{
Name: "OSS-Fuzz",
Name: fuzzerOSSFuzz,
URL: asPointer("https://github.com/google/oss-fuzz"),
Desc: asPointer("Continuous Fuzzing for Open Source Software"),
// TODO: File.
},
)
}

langMap, err := c.RepoClient.ListProgrammingLanguages()
if err != nil {
return checker.FuzzingData{}, fmt.Errorf("cannot get langs of repo: %w", err)
}
prominentLangs := getProminentLanguages(langMap)

for _, lang := range prominentLangs {
usingFuzzFunc, files, e := checkFuzzFunc(c, lang)
if e != nil {
return checker.FuzzingData{}, fmt.Errorf("%w", e)
}
if usingFuzzFunc {
fuzzers = append(fuzzers,
checker.Tool{
Name: languageFuzzSpecs[lang].Name,
URL: languageFuzzSpecs[lang].URL,
Desc: languageFuzzSpecs[lang].Desc,
Files: files,
},
)
}
}
return checker.FuzzingData{Fuzzers: fuzzers}, nil
}

Expand Down Expand Up @@ -91,3 +150,93 @@ func checkOSSFuzz(c *checker.CheckRequest) (bool, error) {
}
return result.Hits > 0, nil
}

func checkFuzzFunc(c *checker.CheckRequest, lang clients.Language) (bool, []checker.File, error) {
if c.RepoClient == nil {
return false, nil, nil
}
data := filesWithPatternStr{
files: make([]checker.File, 0),
}
// Search language-specified fuzz func patterns in the hashmap.
pattern, found := languageFuzzSpecs[lang]
if !found {
// If the fuzz patterns for the current language not supported yet,
// we return it as false (not found), nil (no files), and nil (no errors).
return false, nil, nil
}
// Get patterns for file and func.
// We use the file pattern in the matcher to match the test files,
// and put the func pattern in var data to match file contents (func names).
filePattern, funcPattern := pattern.filePattern, pattern.funcPattern
matcher := fileparser.PathMatcher{
Pattern: filePattern,
CaseSensitive: false,
}
data.pattern = funcPattern
err := fileparser.OnMatchingFileContentDo(c.RepoClient, matcher, getFuzzFunc, &data)
if err != nil {
return false, nil, fmt.Errorf("error when OnMatchingFileContentDo: %w", err)
}

if len(data.files) == 0 {
// This means no fuzz funcs matched for this language.
return false, nil, nil
}
return true, data.files, nil
}

// This is the callback func for interface OnMatchingFileContentDo
// used for matching fuzz functions in the file content,
// and return a list of files (or nil for not found).
var getFuzzFunc fileparser.DoWhileTrueOnFileContent = func(
path string, content []byte, args ...interface{}) (bool, error) {
if len(args) != 1 {
return false, fmt.Errorf("getFuzzFunc requires exactly one argument: %w", errInvalidArgLength)
}
pdata, ok := args[0].(*filesWithPatternStr)
if !ok {
return false, errInvalidArgType
}
r := regexp.MustCompile(pdata.pattern)
lines := bytes.Split(content, []byte("\n"))
for i, line := range lines {
found := r.FindString(string(line))
if found != "" {
// If fuzz func is found in the file, add it to the file array,
// with its file path as Path, func name as Snippet,
// FileTypeFuzz as Type, and # of lines as Offset.
pdata.files = append(pdata.files, checker.File{
Path: path,
Type: checker.FileTypeSource,
Snippet: found,
Offset: uint(i + 1), // Since the # of lines starts from zero.
})
}
}
return true, nil
}

func getProminentLanguages(langs map[clients.Language]int) []clients.Language {
numLangs := len(langs)
if numLangs == 0 {
return nil
}
totalLoC := 0
for _, LoC := range langs {
totalLoC += LoC
}
// Var avgLoC calculates the average lines of code in the current repo,
// and it can stay as an int, no need for a float value.
avgLoC := totalLoC / numLangs

// Languages that have lines of code above average will be considered prominent.
ret := []clients.Language{}
for lang, LoC := range langs {
if LoC >= avgLoC {
lang = clients.Language(strings.ToLower(string(lang)))
ret = append(ret, lang)
}
}
return ret
}