1515package raw
1616
1717import (
18+ "bytes"
1819 "fmt"
20+ "regexp"
21+ "strings"
1922
2023 "github.com/ossf/scorecard/v4/checker"
2124 "github.com/ossf/scorecard/v4/checks/fileparser"
2225 "github.com/ossf/scorecard/v4/clients"
2326 sce "github.com/ossf/scorecard/v4/errors"
2427)
2528
29+ const (
30+ fuzzerOSSFuzz = "OSSFuzz"
31+ fuzzerClusterFuzzLite = "ClusterFuzzLite"
32+ fuzzerBuiltInGo = "GoBuiltInFuzzer"
33+ // TODO: add more fuzzing check supports.
34+ )
35+
36+ type filesWithPatternStr struct {
37+ pattern string
38+ files []checker.File
39+ }
40+
41+ // Configurations for language-specified fuzzers.
42+ type languageFuzzConfig struct {
43+ URL , Desc * string
44+ filePattern , funcPattern , Name string
45+ //TODO: add more language fuzzing-related fields.
46+ }
47+
48+ // Contains fuzzing speficications for programming languages.
49+ // Please use the type Language defined in clients/languages.go rather than a raw string.
50+ var languageFuzzSpecs = map [clients.Language ]languageFuzzConfig {
51+ // Default fuzz patterns for Go.
52+ clients .Go : {
53+ filePattern : "*_test.go" ,
54+ funcPattern : `func\s+Fuzz\w+\s*\(\w+\s+\*testing.F\)` ,
55+ Name : fuzzerBuiltInGo ,
56+ URL : asPointer ("https://go.dev/doc/fuzz/" ),
57+ Desc : asPointer (
58+ "Go fuzzing intelligently walks through the source code to report failures and find vulnerabilities." ),
59+ },
60+ // TODO: add more language-specific fuzz patterns & configs.
61+ }
62+
2663// Fuzzing runs Fuzzing check.
2764func Fuzzing (c * checker.CheckRequest ) (checker.FuzzingData , error ) {
2865 var fuzzers []checker.Tool
@@ -33,7 +70,7 @@ func Fuzzing(c *checker.CheckRequest) (checker.FuzzingData, error) {
3370 if usingCFLite {
3471 fuzzers = append (fuzzers ,
3572 checker.Tool {
36- Name : "ClusterFuzzLite" ,
73+ Name : fuzzerClusterFuzzLite ,
3774 URL : asPointer ("https://github.com/google/clusterfuzzlite" ),
3875 Desc : asPointer ("continuous fuzzing solution that runs as part of Continuous Integration (CI) workflows" ),
3976 // TODO: File.
@@ -48,14 +85,36 @@ func Fuzzing(c *checker.CheckRequest) (checker.FuzzingData, error) {
4885 if usingOSSFuzz {
4986 fuzzers = append (fuzzers ,
5087 checker.Tool {
51- Name : "OSS-Fuzz" ,
88+ Name : fuzzerOSSFuzz ,
5289 URL : asPointer ("https://github.com/google/oss-fuzz" ),
5390 Desc : asPointer ("Continuous Fuzzing for Open Source Software" ),
5491 // TODO: File.
5592 },
5693 )
5794 }
5895
96+ langMap , err := c .RepoClient .ListProgrammingLanguages ()
97+ if err != nil {
98+ return checker.FuzzingData {}, fmt .Errorf ("cannot get langs of repo: %w" , err )
99+ }
100+ prominentLangs := getProminentLanguages (langMap )
101+
102+ for _ , lang := range prominentLangs {
103+ usingFuzzFunc , files , e := checkFuzzFunc (c , lang )
104+ if e != nil {
105+ return checker.FuzzingData {}, fmt .Errorf ("%w" , e )
106+ }
107+ if usingFuzzFunc {
108+ fuzzers = append (fuzzers ,
109+ checker.Tool {
110+ Name : languageFuzzSpecs [lang ].Name ,
111+ URL : languageFuzzSpecs [lang ].URL ,
112+ Desc : languageFuzzSpecs [lang ].Desc ,
113+ Files : files ,
114+ },
115+ )
116+ }
117+ }
59118 return checker.FuzzingData {Fuzzers : fuzzers }, nil
60119}
61120
@@ -91,3 +150,93 @@ func checkOSSFuzz(c *checker.CheckRequest) (bool, error) {
91150 }
92151 return result .Hits > 0 , nil
93152}
153+
154+ func checkFuzzFunc (c * checker.CheckRequest , lang clients.Language ) (bool , []checker.File , error ) {
155+ if c .RepoClient == nil {
156+ return false , nil , nil
157+ }
158+ data := filesWithPatternStr {
159+ files : make ([]checker.File , 0 ),
160+ }
161+ // Search language-specified fuzz func patterns in the hashmap.
162+ pattern , found := languageFuzzSpecs [lang ]
163+ if ! found {
164+ // If the fuzz patterns for the current language not supported yet,
165+ // we return it as false (not found), nil (no files), and nil (no errors).
166+ return false , nil , nil
167+ }
168+ // Get patterns for file and func.
169+ // We use the file pattern in the matcher to match the test files,
170+ // and put the func pattern in var data to match file contents (func names).
171+ filePattern , funcPattern := pattern .filePattern , pattern .funcPattern
172+ matcher := fileparser.PathMatcher {
173+ Pattern : filePattern ,
174+ CaseSensitive : false ,
175+ }
176+ data .pattern = funcPattern
177+ err := fileparser .OnMatchingFileContentDo (c .RepoClient , matcher , getFuzzFunc , & data )
178+ if err != nil {
179+ return false , nil , fmt .Errorf ("error when OnMatchingFileContentDo: %w" , err )
180+ }
181+
182+ if len (data .files ) == 0 {
183+ // This means no fuzz funcs matched for this language.
184+ return false , nil , nil
185+ }
186+ return true , data .files , nil
187+ }
188+
189+ // This is the callback func for interface OnMatchingFileContentDo
190+ // used for matching fuzz functions in the file content,
191+ // and return a list of files (or nil for not found).
192+ var getFuzzFunc fileparser.DoWhileTrueOnFileContent = func (
193+ path string , content []byte , args ... interface {}) (bool , error ) {
194+ if len (args ) != 1 {
195+ return false , fmt .Errorf ("getFuzzFunc requires exactly one argument: %w" , errInvalidArgLength )
196+ }
197+ pdata , ok := args [0 ].(* filesWithPatternStr )
198+ if ! ok {
199+ return false , errInvalidArgType
200+ }
201+ r := regexp .MustCompile (pdata .pattern )
202+ lines := bytes .Split (content , []byte ("\n " ))
203+ for i , line := range lines {
204+ found := r .FindString (string (line ))
205+ if found != "" {
206+ // If fuzz func is found in the file, add it to the file array,
207+ // with its file path as Path, func name as Snippet,
208+ // FileTypeFuzz as Type, and # of lines as Offset.
209+ pdata .files = append (pdata .files , checker.File {
210+ Path : path ,
211+ Type : checker .FileTypeSource ,
212+ Snippet : found ,
213+ Offset : uint (i + 1 ), // Since the # of lines starts from zero.
214+ })
215+ }
216+ }
217+ return true , nil
218+ }
219+
220+ func getProminentLanguages (langs map [clients.Language ]int ) []clients.Language {
221+ numLangs := len (langs )
222+ if numLangs == 0 {
223+ return nil
224+ }
225+ totalLoC := 0
226+ for _ , LoC := range langs {
227+ totalLoC += LoC
228+ }
229+ // Var avgLoC calculates the average lines of code in the current repo,
230+ // and it can stay as an int, no need for a float value.
231+ avgLoC := totalLoC / numLangs
232+
233+ // Languages that have lines of code above average will be considered prominent.
234+ ret := []clients.Language {}
235+ for lang , LoC := range langs {
236+ if LoC >= avgLoC {
237+ lang = clients .Language (strings .ToLower (string (lang )))
238+ ret = append (ret , lang )
239+ }
240+ }
241+ return ret
242+ }
0 commit comments