diff options
Diffstat (limited to 'vendor/github.com/securego')
28 files changed, 1333 insertions, 128 deletions
diff --git a/vendor/github.com/securego/gosec/v2/.gitignore b/vendor/github.com/securego/gosec/v2/.gitignore index f6c8065b4..45460260f 100644 --- a/vendor/github.com/securego/gosec/v2/.gitignore +++ b/vendor/github.com/securego/gosec/v2/.gitignore @@ -37,3 +37,4 @@ _testmain.go # SBOMs generated during CI /bom.json +1
\ No newline at end of file diff --git a/vendor/github.com/securego/gosec/v2/.golangci.yml b/vendor/github.com/securego/gosec/v2/.golangci.yml index d591dc249..c63a7cc5a 100644 --- a/vendor/github.com/securego/gosec/v2/.golangci.yml +++ b/vendor/github.com/securego/gosec/v2/.golangci.yml @@ -2,11 +2,11 @@ linters: enable: - asciicheck - bodyclose + - copyloopvar - dogsled - durationcheck - errcheck - errorlint - - exportloopref - gci - ginkgolinter - gochecknoinits @@ -18,7 +18,6 @@ linters: - govet - importas - ineffassign - - megacheck - misspell - nakedret - nolintlint @@ -36,6 +35,11 @@ linters-settings: - standard - default - prefix(github.com/securego) + staticcheck: + checks: + - all + - '-SA1019' + revive: rules: - name: dot-imports diff --git a/vendor/github.com/securego/gosec/v2/Makefile b/vendor/github.com/securego/gosec/v2/Makefile index 4f6cce765..bcfda6c1c 100644 --- a/vendor/github.com/securego/gosec/v2/Makefile +++ b/vendor/github.com/securego/gosec/v2/Makefile @@ -17,7 +17,7 @@ GOSEC ?= $(GOBIN)/gosec GINKGO ?= $(GOBIN)/ginkgo GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2) GOVULN_MIN_VERSION = 17 -GO_VERSION = 1.22 +GO_VERSION = 1.23 default: $(MAKE) build @@ -68,7 +68,7 @@ build-race: go build -race -o $(BIN) ./cmd/gosec/ clean: - rm -rf build vendor dist coverage.txt + rm -rf build vendor dist coverage.out rm -f release image $(BIN) release: @@ -92,4 +92,7 @@ image-push: image tlsconfig: go generate ./... -.PHONY: test build clean release image image-push tlsconfig +perf-diff: + ./perf-diff.sh + +.PHONY: test build clean release image image-push tlsconfig perf-diff diff --git a/vendor/github.com/securego/gosec/v2/README.md b/vendor/github.com/securego/gosec/v2/README.md index f7b41df2e..7b8b526a8 100644 --- a/vendor/github.com/securego/gosec/v2/README.md +++ b/vendor/github.com/securego/gosec/v2/README.md @@ -139,6 +139,7 @@ directory you can supply `./...` as the input argument. - G112: Potential slowloris attack - G113: Usage of Rat.SetString in math/big with an overflow (CVE-2022-23772) - G114: Use of net/http serve function that has no support for setting timeouts +- G115: Potential integer overflow when converting between integer types - G201: SQL query construction using format string - G202: SQL query construction using string concatenation - G203: Use of unescaped data in HTML templates @@ -150,16 +151,21 @@ directory you can supply `./...` as the input argument. - G305: File traversal when extracting zip/tar archive - G306: Poor file permissions used when writing to a new file - G307: Poor file permissions used when creating a file with os.Create -- G401: Detect the usage of DES, RC4, MD5 or SHA1 +- G401: Detect the usage of MD5 or SHA1 - G402: Look for bad TLS connection settings - G403: Ensure minimum RSA key length of 2048 bits - G404: Insecure random number source (rand) +- G405: Detect the usage of DES or RC4 +- G406: Detect the usage of MD4 or RIPEMD160 +- G407: Detect the usage of hardcoded Initialization Vector(IV)/Nonce - G501: Import blocklist: crypto/md5 - G502: Import blocklist: crypto/des - G503: Import blocklist: crypto/rc4 - G504: Import blocklist: net/http/cgi - G505: Import blocklist: crypto/sha1 -- G601: Implicit memory aliasing of items from a range statement +- G506: Import blocklist: golang.org/x/crypto/md4 +- G507: Import blocklist: golang.org/x/crypto/ripemd160 +- G601: Implicit memory aliasing of items from a range statement (only for Go 1.21 or lower) - G602: Slice access out of bounds ### Retired rules @@ -230,6 +236,12 @@ You can also configure the hard-coded credentials rule `G101` with additional pa } ``` +#### Go version + +Some rules require a specific Go version which is retrieved from the Go module file present in the project. If this version cannot be found, it will fallback to Go runtime version. + +The Go module version is parsed using the `go list` command which in some cases might lead to performance degradation. In this situation, the go module version can be easily provided by setting the environment variable `GOSECGOVERSION=go1.21.1`. + ### Dependencies gosec will fetch automatically the dependencies of the code which is being analyzed when go module is turned on (e.g.`GO111MODULE=on`). If this is not the case, @@ -263,6 +275,19 @@ gosec can ignore generated go files with default generated code comment. gosec -exclude-generated ./... ``` +### Auto fixing vulnerabilities +gosec can suggest fixes based on AI recommendation. It will call an AI API to receive a suggestion for a security finding. + +You can enable this feature by providing the following command line arguments: +- `ai-api-provider`: the name of the AI API provider, currently only `gemini`is supported. +- `ai-api-key` or set the environment variable `GOSEC_AI_API_KEY`: the key to access the AI API, +For gemini, you can create an API key following [these instructions](https://ai.google.dev/gemini-api/docs/api-key). +- `ai-endpoint`: the endpoint of the AI provider, this is optional argument. + + +```bash +gosec -ai-api-provider="gemini" -ai-api-key="your_key" ./... +``` ### Annotating code @@ -348,7 +373,7 @@ file. The output format is controlled by the `-fmt` flag, and the output file is $ gosec -fmt=json -out=results.json *.go ``` -Results will be reported to stdout as well as to the provided output file by `-stdout` flag. The `-verbose` flag overrides the +Results will be reported to stdout as well as to the provided output file by `-stdout` flag. The `-verbose` flag overrides the output format when stdout the results while saving them in the output file ```bash # Write output in json format to results.json as well as stdout @@ -407,14 +432,14 @@ git push origin v1.0.0 The GitHub [release workflow](.github/workflows/release.yml) triggers immediately after the tag is pushed upstream. This flow will release the binaries using the [goreleaser](https://goreleaser.com/actions/) action and then it will build and publish the docker image into Docker Hub. -The released artifacts are signed using [cosign](https://docs.sigstore.dev/). You can use the public key from [cosign.pub](cosign.pub) +The released artifacts are signed using [cosign](https://docs.sigstore.dev/). You can use the public key from [cosign.pub](cosign.pub) file to verify the signature of docker image and binaries files. The docker image signature can be verified with the following command: ``` cosign verify --key cosign.pub securego/gosec:<TAG> ``` - + The binary files signature can be verified with the following command: ``` cosign verify-blob --key cosign.pub --signature gosec_<VERSION>_darwin_amd64.tar.gz.sig gosec_<VERSION>_darwin_amd64.tar.gz diff --git a/vendor/github.com/securego/gosec/v2/action.yml b/vendor/github.com/securego/gosec/v2/action.yml index 3097075ce..d4bc351c8 100644 --- a/vendor/github.com/securego/gosec/v2/action.yml +++ b/vendor/github.com/securego/gosec/v2/action.yml @@ -10,7 +10,7 @@ inputs: runs: using: 'docker' - image: 'docker://securego/gosec:2.18.2' + image: 'docker://securego/gosec:2.21.1' args: - ${{ inputs.args }} diff --git a/vendor/github.com/securego/gosec/v2/analyzer.go b/vendor/github.com/securego/gosec/v2/analyzer.go index 0b1225b9b..bfa7e1940 100644 --- a/vendor/github.com/securego/gosec/v2/analyzer.go +++ b/vendor/github.com/securego/gosec/v2/analyzer.go @@ -31,11 +31,12 @@ import ( "strings" "sync" - "github.com/securego/gosec/v2/analyzers" - "github.com/securego/gosec/v2/issue" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/buildssa" "golang.org/x/tools/go/packages" + + "github.com/securego/gosec/v2/analyzers" + "github.com/securego/gosec/v2/issue" ) // LoadMode controls the amount of details to return when loading the packages @@ -55,8 +56,6 @@ const externalSuppressionJustification = "Globally suppressed." const aliasOfAllRules = "*" -var generatedCodePattern = regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`) - type ignore struct { start int end int @@ -123,7 +122,7 @@ func (i ignores) get(file string, line string) map[string][]issue.SuppressionInf start, end := i.parseLine(line) if is, ok := i[file]; ok { for _, i := range is { - if start <= i.start && end >= i.end { + if i.start <= start && i.end >= end || start <= i.start && end >= i.end { return i.suppressions } } @@ -183,7 +182,7 @@ type Analyzer struct { showIgnored bool trackSuppressions bool concurrency int - analyzerList []*analysis.Analyzer + analyzerSet *analyzers.AnalyzerSet mu sync.Mutex } @@ -214,7 +213,7 @@ func NewAnalyzer(conf Config, tests bool, excludeGenerated bool, trackSuppressio concurrency: concurrency, excludeGenerated: excludeGenerated, trackSuppressions: trackSuppressions, - analyzerList: analyzers.BuildDefaultAnalyzers(), + analyzerSet: analyzers.NewAnalyzerSet(), } } @@ -237,6 +236,15 @@ func (gosec *Analyzer) LoadRules(ruleDefinitions map[string]RuleBuilder, ruleSup } } +// LoadAnalyzers instantiates all the analyzers to be used when analyzing source +// packages +func (gosec *Analyzer) LoadAnalyzers(analyzerDefinitions map[string]analyzers.AnalyzerDefinition, analyzerSuppressed map[string]bool) { + for id, def := range analyzerDefinitions { + r := def.Create(def.ID, def.Description) + gosec.analyzerSet.Register(r, analyzerSuppressed[id]) + } +} + // Process kicks off the analysis process for a given package func (gosec *Analyzer) Process(buildTags []string, packagePaths ...string) error { config := &packages.Config{ @@ -376,7 +384,7 @@ func (gosec *Analyzer) CheckRules(pkg *packages.Package) { if filepath.Ext(checkedFile) != ".go" { continue } - if gosec.excludeGenerated && isGeneratedFile(file) { + if gosec.excludeGenerated && ast.IsGenerated(file) { gosec.logger.Println("Ignoring generated file:", checkedFile) continue } @@ -391,7 +399,6 @@ func (gosec *Analyzer) CheckRules(pkg *packages.Package) { gosec.context.PkgFiles = pkg.Syntax gosec.context.Imports = NewImportTracker() gosec.context.PassedValues = make(map[string]interface{}) - gosec.context.Ignores = newIgnores() gosec.updateIgnores() ast.Walk(gosec, file) gosec.stats.NumFiles++ @@ -417,7 +424,7 @@ func (gosec *Analyzer) CheckAnalyzers(pkg *packages.Package) { generatedFiles := gosec.generatedFiles(pkg) - for _, analyzer := range gosec.analyzerList { + for _, analyzer := range gosec.analyzerSet.Analyzers { pass := &analysis.Pass{ Analyzer: analyzer, Fset: pkg.Fset, @@ -459,7 +466,7 @@ func (gosec *Analyzer) CheckAnalyzers(pkg *packages.Package) { func (gosec *Analyzer) generatedFiles(pkg *packages.Package) map[string]bool { generatedFiles := map[string]bool{} for _, file := range pkg.Syntax { - if isGeneratedFile(file) { + if ast.IsGenerated(file) { fp := pkg.Fset.File(file.Pos()) if fp == nil { // skip files which cannot be located @@ -500,17 +507,6 @@ func (gosec *Analyzer) buildSSA(pkg *packages.Package) (interface{}, error) { return ssaPass.Analyzer.Run(ssaPass) } -func isGeneratedFile(file *ast.File) bool { - for _, comment := range file.Comments { - for _, row := range comment.List { - if generatedCodePattern.MatchString(row.Text) { - return true - } - } - } - return false -} - // ParseErrors parses the errors from given package func (gosec *Analyzer) ParseErrors(pkg *packages.Package) error { if len(pkg.Errors) == 0 { @@ -679,7 +675,7 @@ func (gosec *Analyzer) getSuppressionsAtLineInFile(file string, line string, id suppressions := append(generalSuppressions, ruleSuppressions...) // Track external suppressions of this rule. - if gosec.ruleset.IsRuleSuppressed(id) { + if gosec.ruleset.IsRuleSuppressed(id) || gosec.analyzerSet.IsSuppressed(id) { ignored = true suppressions = append(suppressions, issue.SuppressionInfo{ Kind: "external", @@ -718,4 +714,5 @@ func (gosec *Analyzer) Reset() { gosec.issues = make([]*issue.Issue, 0, 16) gosec.stats = &Metrics{} gosec.ruleset = NewRuleSet() + gosec.analyzerSet = analyzers.NewAnalyzerSet() } diff --git a/vendor/github.com/securego/gosec/v2/analyzers/analyzers_set.go b/vendor/github.com/securego/gosec/v2/analyzers/analyzers_set.go new file mode 100644 index 000000000..e2fe51c92 --- /dev/null +++ b/vendor/github.com/securego/gosec/v2/analyzers/analyzers_set.go @@ -0,0 +1,38 @@ +// (c) Copyright gosec's authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package analyzers + +import "golang.org/x/tools/go/analysis" + +type AnalyzerSet struct { + Analyzers []*analysis.Analyzer + AnalyzerSuppressedMap map[string]bool +} + +// NewAnalyzerSet constructs a new AnalyzerSet +func NewAnalyzerSet() *AnalyzerSet { + return &AnalyzerSet{nil, make(map[string]bool)} +} + +// Register adds a trigger for the supplied analyzer +func (a *AnalyzerSet) Register(analyzer *analysis.Analyzer, isSuppressed bool) { + a.Analyzers = append(a.Analyzers, analyzer) + a.AnalyzerSuppressedMap[analyzer.Name] = isSuppressed +} + +// IsSuppressed will return whether the Analyzer is suppressed. +func (a *AnalyzerSet) IsSuppressed(ruleID string) bool { + return a.AnalyzerSuppressedMap[ruleID] +} diff --git a/vendor/github.com/securego/gosec/v2/analyzers/analyzerslist.go b/vendor/github.com/securego/gosec/v2/analyzers/analyzerslist.go new file mode 100644 index 000000000..f2157442f --- /dev/null +++ b/vendor/github.com/securego/gosec/v2/analyzers/analyzerslist.go @@ -0,0 +1,95 @@ +// (c) Copyright gosec's authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package analyzers + +import ( + "golang.org/x/tools/go/analysis" +) + +// AnalyzerDefinition contains the description of an analyzer and a mechanism to +// create it. +type AnalyzerDefinition struct { + ID string + Description string + Create AnalyzerBuilder +} + +// AnalyzerBuilder is used to register an analyzer definition with the analyzer +type AnalyzerBuilder func(id string, description string) *analysis.Analyzer + +// AnalyzerList contains a mapping of analyzer ID's to analyzer definitions and a mapping +// of analyzer ID's to whether analyzers are suppressed. +type AnalyzerList struct { + Analyzers map[string]AnalyzerDefinition + AnalyzerSuppressed map[string]bool +} + +// AnalyzersInfo returns all the create methods and the analyzer suppressed map for a +// given list +func (al *AnalyzerList) AnalyzersInfo() (map[string]AnalyzerDefinition, map[string]bool) { + builders := make(map[string]AnalyzerDefinition) + for _, def := range al.Analyzers { + builders[def.ID] = def + } + return builders, al.AnalyzerSuppressed +} + +// AnalyzerFilter can be used to include or exclude an analyzer depending on the return +// value of the function +type AnalyzerFilter func(string) bool + +// NewAnalyzerFilter is a closure that will include/exclude the analyzer ID's based on +// the supplied boolean value. +func NewAnalyzerFilter(action bool, analyzerIDs ...string) AnalyzerFilter { + analyzerlist := make(map[string]bool) + for _, analyzer := range analyzerIDs { + analyzerlist[analyzer] = true + } + return func(analyzer string) bool { + if _, found := analyzerlist[analyzer]; found { + return action + } + return !action + } +} + +var defaultAnalyzers = []AnalyzerDefinition{ + {"G115", "Type conversion which leads to integer overflow", newConversionOverflowAnalyzer}, + {"G602", "Possible slice bounds out of range", newSliceBoundsAnalyzer}, + {"G407", "Use of hardcoded IV/nonce for encryption", newHardCodedNonce}, +} + +// Generate the list of analyzers to use +func Generate(trackSuppressions bool, filters ...AnalyzerFilter) *AnalyzerList { + analyzerMap := make(map[string]AnalyzerDefinition) + analyzerSuppressedMap := make(map[string]bool) + + for _, analyzer := range defaultAnalyzers { + analyzerSuppressedMap[analyzer.ID] = false + addToAnalyzerList := true + for _, filter := range filters { + if filter(analyzer.ID) { + analyzerSuppressedMap[analyzer.ID] = true + if !trackSuppressions { + addToAnalyzerList = false + } + } + } + if addToAnalyzerList { + analyzerMap[analyzer.ID] = analyzer + } + } + return &AnalyzerList{Analyzers: analyzerMap, AnalyzerSuppressed: analyzerSuppressedMap} +} diff --git a/vendor/github.com/securego/gosec/v2/analyzers/conversion_overflow.go b/vendor/github.com/securego/gosec/v2/analyzers/conversion_overflow.go new file mode 100644 index 000000000..3ef4825af --- /dev/null +++ b/vendor/github.com/securego/gosec/v2/analyzers/conversion_overflow.go @@ -0,0 +1,526 @@ +// (c) Copyright gosec's authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package analyzers + +import ( + "fmt" + "go/token" + "math" + "regexp" + "strconv" + "strings" + + "golang.org/x/exp/constraints" + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/buildssa" + "golang.org/x/tools/go/ssa" + + "github.com/securego/gosec/v2/issue" +) + +type integer struct { + signed bool + size int + min int + max uint +} + +type rangeResult struct { + minValue int + maxValue uint + explixitPositiveVals []uint + explicitNegativeVals []int + isRangeCheck bool + convertFound bool +} + +type branchResults struct { + minValue *int + maxValue *uint + explixitPositiveVals []uint + explicitNegativeVals []int + convertFound bool +} + +func newConversionOverflowAnalyzer(id string, description string) *analysis.Analyzer { + return &analysis.Analyzer{ + Name: id, + Doc: description, + Run: runConversionOverflow, + Requires: []*analysis.Analyzer{buildssa.Analyzer}, + } +} + +func runConversionOverflow(pass *analysis.Pass) (interface{}, error) { + ssaResult, err := getSSAResult(pass) + if err != nil { + return nil, fmt.Errorf("building ssa representation: %w", err) + } + + issues := []*issue.Issue{} + for _, mcall := range ssaResult.SSA.SrcFuncs { + for _, block := range mcall.DomPreorder() { + for _, instr := range block.Instrs { + switch instr := instr.(type) { + case *ssa.Convert: + src := instr.X.Type().Underlying().String() + dst := instr.Type().Underlying().String() + if isIntOverflow(src, dst) { + if isSafeConversion(instr) { + continue + } + issue := newIssue(pass.Analyzer.Name, + fmt.Sprintf("integer overflow conversion %s -> %s", src, dst), + pass.Fset, + instr.Pos(), + issue.High, + issue.Medium, + ) + issues = append(issues, issue) + } + } + } + } + } + + if len(issues) > 0 { + return issues, nil + } + return nil, nil +} + +func isIntOverflow(src string, dst string) bool { + srcInt, err := parseIntType(src) + if err != nil { + return false + } + + dstInt, err := parseIntType(dst) + if err != nil { + return false + } + + return srcInt.min < dstInt.min || srcInt.max > dstInt.max +} + +func parseIntType(intType string) (integer, error) { + re := regexp.MustCompile(`^(?P<type>u?int)(?P<size>\d{1,2})?$`) + matches := re.FindStringSubmatch(intType) + if matches == nil { + return integer{}, fmt.Errorf("no integer type match found for %s", intType) + } + + it := matches[re.SubexpIndex("type")] + is := matches[re.SubexpIndex("size")] + + signed := it == "int" + + // use default system int type in case size is not present in the type. + intSize := strconv.IntSize + if is != "" { + var err error + intSize, err = strconv.Atoi(is) + if err != nil { + return integer{}, fmt.Errorf("failed to parse the integer type size: %w", err) + } + } + + if intSize != 8 && intSize != 16 && intSize != 32 && intSize != 64 && is != "" { + return integer{}, fmt.Errorf("invalid bit size: %d", intSize) + } + + var min int + var max uint + + if signed { + shiftAmount := intSize - 1 + + // Perform a bounds check. + if shiftAmount < 0 { + return integer{}, fmt.Errorf("invalid shift amount: %d", shiftAmount) + } + + max = (1 << uint(shiftAmount)) - 1 + min = -1 << (intSize - 1) + + } else { + max = (1 << uint(intSize)) - 1 + min = 0 + } + + return integer{ + signed: signed, + size: intSize, + min: min, + max: max, + }, nil +} + +func isSafeConversion(instr *ssa.Convert) bool { + dstType := instr.Type().Underlying().String() + + // Check for constant conversions. + if constVal, ok := instr.X.(*ssa.Const); ok { + if isConstantInRange(constVal, dstType) { + return true + } + } + + // Check for string to integer conversions with specified bit size. + if isStringToIntConversion(instr, dstType) { + return true + } + + // Check for explicit range checks. + if hasExplicitRangeCheck(instr, dstType) { + return true + } + + return false +} + +func isConstantInRange(constVal *ssa.Const, dstType string) bool { + value, err := strconv.ParseInt(constVal.Value.String(), 10, 64) + if err != nil { + return false + } + + dstInt, err := parseIntType(dstType) + if err != nil { + return false + } + + if dstInt.signed { + return value >= -(1<<(dstInt.size-1)) && value <= (1<<(dstInt.size-1))-1 + } + return value >= 0 && value <= (1<<dstInt.size)-1 +} + +func isStringToIntConversion(instr *ssa.Convert, dstType string) bool { + // Traverse the SSA instructions to find the original variable. + original := instr.X + for { + switch v := original.(type) { + case *ssa.Call: + if v.Call.StaticCallee() != nil && (v.Call.StaticCallee().Name() == "ParseInt" || v.Call.StaticCallee().Name() == "ParseUint") { + if len(v.Call.Args) == 3 { + if bitSize, ok := v.Call.Args[2].(*ssa.Const); ok { + signed := v.Call.StaticCallee().Name() == "ParseInt" + bitSizeValue, err := strconv.Atoi(bitSize.Value.String()) + if err != nil { + return false + } + dstInt, err := parseIntType(dstType) + if err != nil { + return false + } + isSafe := bitSizeValue <= dstInt.size && signed == dstInt.signed + return isSafe + } + } + } + return false + case *ssa.Phi: + original = v.Edges[0] + case *ssa.Extract: + original = v.Tuple + default: + return false + } + } +} + +func hasExplicitRangeCheck(instr *ssa.Convert, dstType string) bool { + dstInt, err := parseIntType(dstType) + if err != nil { + return false + } + + srcInt, err := parseIntType(instr.X.Type().String()) + if err != nil { + return false + } + + minValue := srcInt.min + maxValue := srcInt.max + explicitPositiveVals := []uint{} + explicitNegativeVals := []int{} + + if minValue > dstInt.min && maxValue < dstInt.max { + return true + } + + visitedIfs := make(map[*ssa.If]bool) + for _, block := range instr.Parent().Blocks { + for _, blockInstr := range block.Instrs { + switch v := blockInstr.(type) { + case *ssa.If: + result := getResultRange(v, instr, visitedIfs) + if result.isRangeCheck { + minValue = max(minValue, &result.minValue) + maxValue = min(maxValue, &result.maxValue) + explicitPositiveVals = append(explicitPositiveVals, result.explixitPositiveVals...) + explicitNegativeVals = append(explicitNegativeVals, result.explicitNegativeVals...) + } + case *ssa.Call: + // These function return an int of a guaranteed size. + if v != instr.X { + continue + } + if fn, isBuiltin := v.Call.Value.(*ssa.Builtin); isBuiltin { + switch fn.Name() { + case "len", "cap": + minValue = 0 + } + } + } + + if explicitValsInRange(explicitPositiveVals, explicitNegativeVals, dstInt) { + return true + } else if minValue >= dstInt.min && maxValue <= dstInt.max { + return true + } + } + } + return false +} + +// getResultRange is a recursive function that walks the branches of the if statement to find the range of the variable. +func getResultRange(ifInstr *ssa.If, instr *ssa.Convert, visitedIfs map[*ssa.If]bool) rangeResult { + if visitedIfs[ifInstr] { + return rangeResult{minValue: math.MinInt, maxValue: math.MaxUint} + } + visitedIfs[ifInstr] = true + + cond := ifInstr.Cond + binOp, ok := cond.(*ssa.BinOp) + if !ok || !isRangeCheck(binOp, instr.X) { + return rangeResult{minValue: math.MinInt, maxValue: math.MaxUint} + } + + result := rangeResult{ + minValue: math.MinInt, + maxValue: math.MaxUint, + isRangeCheck: true, + } + + thenBounds := walkBranchForConvert(ifInstr.Block().Succs[0], instr, visitedIfs) + elseBounds := walkBranchForConvert(ifInstr.Block().Succs[1], instr, visitedIfs) + + updateResultFromBinOp(&result, binOp, instr, thenBounds.convertFound) + + if thenBounds.convertFound { + result.convertFound = true + result.minValue = max(result.minValue, thenBounds.minValue) + result.maxValue = min(result.maxValue, thenBounds.maxValue) + result.explixitPositiveVals = append(result.explixitPositiveVals, thenBounds.explixitPositiveVals...) + result.explicitNegativeVals = append(result.explicitNegativeVals, thenBounds.explicitNegativeVals...) + } else if elseBounds.convertFound { + result.convertFound = true + result.minValue = max(result.minValue, elseBounds.minValue) + result.maxValue = min(result.maxValue, elseBounds.maxValue) + result.explixitPositiveVals = append(result.explixitPositiveVals, elseBounds.explixitPositiveVals...) + result.explicitNegativeVals = append(result.explicitNegativeVals, elseBounds.explicitNegativeVals...) + } + + return result +} + +// updateResultFromBinOp updates the rangeResult based on the BinOp instruction and the location of the Convert instruction. +func updateResultFromBinOp(result *rangeResult, binOp *ssa.BinOp, instr *ssa.Convert, successPathConvert bool) { + x, y := binOp.X, binOp.Y + operandsFlipped := false + + compareVal, op := getRealValueFromOperation(instr.X) + if x != compareVal { + y, operandsFlipped = x, true + } + + constVal, ok := y.(*ssa.Const) + if !ok { + return + } + + switch binOp.Op { + case token.LEQ, token.LSS: + updateMinMaxForLessOrEqual(result, constVal, binOp.Op, operandsFlipped, successPathConvert) + case token.GEQ, token.GTR: + updateMinMaxForGreaterOrEqual(result, constVal, binOp.Op, operandsFlipped, successPathConvert) + case token.EQL: + if !successPathConvert { + break + } + + // Determine if the constant value is positive or negative. + if strings.Contains(constVal.String(), "-") { + result.explicitNegativeVals = append(result.explicitNegativeVals, int(constVal.Int64())) + } else { + result.explixitPositiveVals = append(result.explixitPositiveVals, uint(constVal.Uint64())) + } + + case token.NEQ: + if successPathConvert { + break + } + + // Determine if the constant value is positive or negative. + if strings.Contains(constVal.String(), "-") { + result.explicitNegativeVals = append(result.explicitNegativeVals, int(constVal.Int64())) + } else { + result.explixitPositiveVals = append(result.explixitPositiveVals, uint(constVal.Uint64())) + } + } + + if op == "neg" { + min := result.minValue + max := result.maxValue + + if min >= 0 { + result.maxValue = uint(min) + } + if max <= math.MaxInt { + result.minValue = int(max) //nolint:gosec + } + } +} + +func updateMinMaxForLessOrEqual(result *rangeResult, constVal *ssa.Const, op token.Token, operandsFlipped bool, successPathConvert bool) { + // If the success path has a conversion and the operands are not flipped, then the constant value is the maximum value. + if successPathConvert && !operandsFlipped { + result.maxValue = uint(constVal.Uint64()) + if op == token.LEQ { + result.maxValue-- + } + } else { + result.minValue = int(constVal.Int64()) + if op == token.GTR { + result.minValue++ + } + } +} + +func updateMinMaxForGreaterOrEqual(result *rangeResult, constVal *ssa.Const, op token.Token, operandsFlipped bool, successPathConvert bool) { + // If the success path has a conversion and the operands are not flipped, then the constant value is the minimum value. + if successPathConvert && !operandsFlipped { + result.minValue = int(constVal.Int64()) + if op == token.GEQ { + result.minValue++ + } + } else { + result.maxValue = uint(constVal.Uint64()) + if op == token.LSS { + result.maxValue-- + } + } +} + +// walkBranchForConvert walks the branch of the if statement to find the range of the variable and where the conversion is. +func walkBranchForConvert(block *ssa.BasicBlock, instr *ssa.Convert, visitedIfs map[*ssa.If]bool) branchResults { + bounds := branchResults{} + + for _, blockInstr := range block.Instrs { + switch v := blockInstr.(type) { + case *ssa.If: + result := getResultRange(v, instr, visitedIfs) + bounds.convertFound = bounds.convertFound || result.convertFound + + if result.isRangeCheck { + bounds.minValue = toPtr(max(result.minValue, bounds.minValue)) + bounds.maxValue = toPtr(min(result.maxValue, bounds.maxValue)) + } + case *ssa.Call: + if v == instr.X { + if fn, isBuiltin := v.Call.Value.(*ssa.Builtin); isBuiltin && (fn.Name() == "len" || fn.Name() == "cap") { + bounds.minValue = toPtr(0) + } + } + case *ssa.Convert: + if v == instr { + bounds.convertFound = true + return bounds + } + } + } + + return bounds +} + +func isRangeCheck(v ssa.Value, x ssa.Value) bool { + compareVal, _ := getRealValueFromOperation(x) + + switch op := v.(type) { + case *ssa.BinOp: + switch op.Op { + case token.LSS, token.LEQ, token.GTR, token.GEQ, + token.EQL, token.NEQ: + return op.X == compareVal || op.Y == compareVal + } + } + return false +} + +func getRealValueFromOperation(v ssa.Value) (ssa.Value, string) { + switch v := v.(type) { + case *ssa.UnOp: + if v.Op == token.SUB { + return v.X, "neg" + } + } + return v, "" +} + +func explicitValsInRange(explicitPosVals []uint, explicitNegVals []int, dstInt integer) bool { + if len(explicitPosVals) == 0 && len(explicitNegVals) == 0 { + return false + } + + for _, val := range explicitPosVals { + if val > dstInt.max { + return false + } + } + + for _, val := range explicitNegVals { + if val < dstInt.min { + return false + } + } + + return true +} + +func min[T constraints.Integer](a T, b *T) T { + if b == nil { + return a + } + if a < *b { + return a + } + return *b +} + +func max[T constraints.Integer](a T, b *T) T { + if b == nil { + return a + } + if a > *b { + return a + } + return *b +} + +func toPtr[T any](a T) *T { + return &a +} diff --git a/vendor/github.com/securego/gosec/v2/analyzers/hardcodedNonce.go b/vendor/github.com/securego/gosec/v2/analyzers/hardcodedNonce.go new file mode 100644 index 000000000..b07363388 --- /dev/null +++ b/vendor/github.com/securego/gosec/v2/analyzers/hardcodedNonce.go @@ -0,0 +1,246 @@ +// (c) Copyright gosec's authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package analyzers + +import ( + "errors" + "fmt" + "go/token" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/buildssa" + "golang.org/x/tools/go/ssa" + + "github.com/securego/gosec/v2/issue" +) + +const defaultIssueDescription = "Use of hardcoded IV/nonce for encryption" + +func newHardCodedNonce(id string, description string) *analysis.Analyzer { + return &analysis.Analyzer{ + Name: id, + Doc: description, + Run: runHardCodedNonce, + Requires: []*analysis.Analyzer{buildssa.Analyzer}, + } +} + +func runHardCodedNonce(pass *analysis.Pass) (interface{}, error) { + ssaResult, err := getSSAResult(pass) + if err != nil { + return nil, fmt.Errorf("building ssa representation: %w", err) + } + + // Holds the function name as key, the number of arguments that the function accepts, and at which index of those accepted arguments is the nonce/IV + // Example "Test" 3, 1 -- means the function "Test" which accepts 3 arguments, and has the nonce arg as second argument + calls := map[string][]int{ + "(crypto/cipher.AEAD).Seal": {4, 1}, + "(crypto/cipher.AEAD).Open": {4, 1}, + "crypto/cipher.NewCBCDecrypter": {2, 1}, + "crypto/cipher.NewCBCEncrypter": {2, 1}, + "crypto/cipher.NewCFBDecrypter": {2, 1}, + "crypto/cipher.NewCFBEncrypter": {2, 1}, + "crypto/cipher.NewCTR": {2, 1}, + "crypto/cipher.NewOFB": {2, 1}, + } + ssaPkgFunctions := ssaResult.SSA.SrcFuncs + args := getArgsFromTrackedFunctions(ssaPkgFunctions, calls) + if args == nil { + return nil, errors.New("no tracked functions found, resulting in no variables to track") + } + var issues []*issue.Issue + for _, arg := range args { + if arg == nil { + continue + } + i, err := raiseIssue(*arg, calls, ssaPkgFunctions, pass, "") + if err != nil { + return issues, fmt.Errorf("raising issue error: %w", err) + } + issues = append(issues, i...) + } + return issues, nil +} + +func raiseIssue(val ssa.Value, funcsToTrack map[string][]int, ssaFuncs []*ssa.Function, + pass *analysis.Pass, issueDescription string, +) ([]*issue.Issue, error) { + if issueDescription == "" { + issueDescription = defaultIssueDescription + } + var err error + var allIssues []*issue.Issue + var issues []*issue.Issue + switch valType := (val).(type) { + case *ssa.Slice: + issueDescription += " by passing hardcoded slice/array" + issues, err = iterateThroughReferrers(val, funcsToTrack, pass.Analyzer.Name, issueDescription, pass.Fset, issue.High) + allIssues = append(allIssues, issues...) + case *ssa.UnOp: + // Check if it's a dereference operation (a.k.a pointer) + if valType.Op == token.MUL { + issueDescription += " by passing pointer which points to hardcoded variable" + issues, err = iterateThroughReferrers(val, funcsToTrack, pass.Analyzer.Name, issueDescription, pass.Fset, issue.Low) + allIssues = append(allIssues, issues...) + } + // When the value assigned to a variable is a function call. + // It goes and check if this function contains call to crypto/rand.Read + // in it's body(Assuming that calling crypto/rand.Read in a function, + // is used for the generation of nonce/iv ) + case *ssa.Call: + if callValue := valType.Call.Value; callValue != nil { + if calledFunction, ok := callValue.(*ssa.Function); ok { + if contains, funcErr := isFuncContainsCryptoRand(calledFunction); !contains && funcErr == nil { + issueDescription += " by passing a value from function which doesn't use crypto/rand" + issues, err = iterateThroughReferrers(val, funcsToTrack, pass.Analyzer.Name, issueDescription, pass.Fset, issue.Medium) + allIssues = append(allIssues, issues...) + } else if funcErr != nil { + err = funcErr + } + } + } + // only checks from strings->[]byte + // might need to add additional types + case *ssa.Convert: + if valType.Type().String() == "[]byte" && valType.X.Type().String() == "string" { + issueDescription += " by passing converted string" + issues, err = iterateThroughReferrers(val, funcsToTrack, pass.Analyzer.Name, issueDescription, pass.Fset, issue.High) + allIssues = append(allIssues, issues...) + } + case *ssa.Parameter: + // arg given to tracked function is wrapped in another function, example: + // func encrypt(..,nonce,...){ + // aesgcm.Seal(nonce) + // } + // save parameter position, by checking the name of the variable used in + // tracked functions and comparing it with the name of the arg + if valType.Parent() != nil { + trackedFunctions := make(map[string][]int) + for index, funcArgs := range valType.Parent().Params { + if funcArgs.Name() == valType.Name() && funcArgs.Type() == valType.Type() { + trackedFunctions[valType.Parent().String()] = []int{len(valType.Parent().Params), index} + } + } + args := getArgsFromTrackedFunctions(ssaFuncs, trackedFunctions) + + issueDescription += " by passing a parameter to a function and" + // recursively backtrack to where the origin of a variable passed to multiple functions is + for _, arg := range args { + if arg == nil { + continue + } + issues, err = raiseIssue(*arg, trackedFunctions, ssaFuncs, pass, issueDescription) + allIssues = append(allIssues, issues...) + } + } + } + return allIssues, err +} + +// iterateThroughReferrers iterates through all places that use the `variable` argument and check if it's used in one of the tracked functions. +func iterateThroughReferrers(variable ssa.Value, funcsToTrack map[string][]int, + analyzerID string, issueDescription string, + fileSet *token.FileSet, issueConfidence issue.Score, +) ([]*issue.Issue, error) { + if funcsToTrack == nil || variable == nil || analyzerID == "" || issueDescription == "" || fileSet == nil { + return nil, errors.New("received a nil object") + } + var gosecIssues []*issue.Issue + refs := variable.Referrers() + if refs == nil { + return gosecIssues, nil + } + // Go trough all functions that use the given arg variable + for _, ref := range *refs { + // Iterate trough the functions we are interested + for trackedFunc := range funcsToTrack { + + // Split the functions we are interested in, by the '.' because we will use the function name to do the comparison + // MIGHT GIVE SOME FALSE POSITIVES THIS WAY + trackedFuncParts := strings.Split(trackedFunc, ".") + trackedFuncPartsName := trackedFuncParts[len(trackedFuncParts)-1] + if strings.Contains(ref.String(), trackedFuncPartsName) { + gosecIssues = append(gosecIssues, newIssue(analyzerID, issueDescription, fileSet, ref.Pos(), issue.High, issueConfidence)) + } + } + } + return gosecIssues, nil +} + +// isFuncContainsCryptoRand checks whether a function contains a call to crypto/rand.Read in it's function body. +func isFuncContainsCryptoRand(funcCall *ssa.Function) (bool, error) { + if funcCall == nil { + return false, errors.New("passed ssa.Function object is nil") + } + for _, block := range funcCall.Blocks { + for _, instr := range block.Instrs { + if call, ok := instr.(*ssa.Call); ok { + if calledFunction, ok := call.Call.Value.(*ssa.Function); ok { + if calledFunction.Pkg != nil && calledFunction.Pkg.Pkg.Path() == "crypto/rand" && calledFunction.Name() == "Read" { + return true, nil + } + } + } + } + } + return false, nil +} + +func addToVarsMap(value ssa.Value, mapToAddTo map[string]*ssa.Value) { + key := value.Name() + value.Type().String() + value.String() + value.Parent().String() + mapToAddTo[key] = &value +} + +func isContainedInMap(value ssa.Value, mapToCheck map[string]*ssa.Value) bool { + key := value.Name() + value.Type().String() + value.String() + value.Parent().String() + _, contained := mapToCheck[key] + return contained +} + +func getArgsFromTrackedFunctions(ssaFuncs []*ssa.Function, trackedFunc map[string][]int) map[string]*ssa.Value { + values := make(map[string]*ssa.Value) + for _, pkgFunc := range ssaFuncs { + for _, funcBlock := range pkgFunc.Blocks { + for _, funcBlocInstr := range funcBlock.Instrs { + iterateTrackedFunctionsAndAddArgs(funcBlocInstr, trackedFunc, values) + } + } + } + return values +} + +func iterateTrackedFunctionsAndAddArgs(funcBlocInstr ssa.Instruction, trackedFunc map[string][]int, values map[string]*ssa.Value) { + if funcCall, ok := (funcBlocInstr).(*ssa.Call); ok { + for trackedFuncName, trackedFuncArgsInfo := range trackedFunc { + // only process functions that have the same number of arguments as the ones we track + if len(funcCall.Call.Args) == trackedFuncArgsInfo[0] { + tmpArg := funcCall.Call.Args[trackedFuncArgsInfo[1]] + // check if the function is called from an object or directly from the package + if funcCall.Call.Method != nil { + if methodFullname := funcCall.Call.Method.FullName(); methodFullname == trackedFuncName { + if !isContainedInMap(tmpArg, values) { + addToVarsMap(tmpArg, values) + } + } + } else if funcCall.Call.Value.String() == trackedFuncName { + if !isContainedInMap(tmpArg, values) { + addToVarsMap(tmpArg, values) + } + } + } + } + } +} diff --git a/vendor/github.com/securego/gosec/v2/analyzers/slice_bounds.go b/vendor/github.com/securego/gosec/v2/analyzers/slice_bounds.go index 08a55eb42..968102f26 100644 --- a/vendor/github.com/securego/gosec/v2/analyzers/slice_bounds.go +++ b/vendor/github.com/securego/gosec/v2/analyzers/slice_bounds.go @@ -118,32 +118,45 @@ func runSliceBounds(pass *analysis.Pass) (interface{}, error) { if i == 1 { bound = invBound(bound) } - for _, instr := range block.Instrs { - if _, ok := issues[instr]; ok { - switch bound { - case lowerUnbounded: - break - case upperUnbounded, unbounded: - delete(issues, instr) - case upperBounded: - switch tinstr := instr.(type) { - case *ssa.Slice: - lower, upper := extractSliceBounds(tinstr) - if isSliceInsideBounds(0, value, lower, upper) { - delete(issues, instr) - } - case *ssa.IndexAddr: - indexValue, err := extractIntValue(tinstr.Index.String()) - if err != nil { - break - } - if isSliceIndexInsideBounds(0, value, indexValue) { - delete(issues, instr) + var processBlock func(block *ssa.BasicBlock, depth int) + processBlock = func(block *ssa.BasicBlock, depth int) { + if depth == maxDepth { + return + } + depth++ + for _, instr := range block.Instrs { + if _, ok := issues[instr]; ok { + switch bound { + case lowerUnbounded: + break + case upperUnbounded, unbounded: + delete(issues, instr) + case upperBounded: + switch tinstr := instr.(type) { + case *ssa.Slice: + lower, upper := extractSliceBounds(tinstr) + if isSliceInsideBounds(0, value, lower, upper) { + delete(issues, instr) + } + case *ssa.IndexAddr: + indexValue, err := extractIntValue(tinstr.Index.String()) + if err != nil { + break + } + if isSliceIndexInsideBounds(0, value, indexValue) { + delete(issues, instr) + } } } + } else if nestedIfInstr, ok := instr.(*ssa.If); ok { + for _, nestedBlock := range nestedIfInstr.Block().Succs { + processBlock(nestedBlock, depth) + } } } } + + processBlock(block, 0) } } diff --git a/vendor/github.com/securego/gosec/v2/analyzers/util.go b/vendor/github.com/securego/gosec/v2/analyzers/util.go index 5941184aa..e7961a56b 100644 --- a/vendor/github.com/securego/gosec/v2/analyzers/util.go +++ b/vendor/github.com/securego/gosec/v2/analyzers/util.go @@ -38,7 +38,9 @@ type SSAAnalyzerResult struct { // BuildDefaultAnalyzers returns the default list of analyzers func BuildDefaultAnalyzers() []*analysis.Analyzer { return []*analysis.Analyzer{ + newConversionOverflowAnalyzer("G115", "Type conversion which leads to integer overflow"), newSliceBoundsAnalyzer("G602", "Possible slice bounds out of range"), + newHardCodedNonce("G407", "Use of hardcoded IV/nonce for encryption"), } } diff --git a/vendor/github.com/securego/gosec/v2/cwe/data.go b/vendor/github.com/securego/gosec/v2/cwe/data.go index 79a6b9d23..a9568ba4d 100644 --- a/vendor/github.com/securego/gosec/v2/cwe/data.go +++ b/vendor/github.com/securego/gosec/v2/cwe/data.go @@ -18,6 +18,31 @@ const ( ) var idWeaknesses = map[string]*Weakness{ + "22": { + ID: "22", + Description: "The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the software does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.", + Name: "Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')", + }, + "78": { + ID: "78", + Description: "The software constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.", + Name: "Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')", + }, + "79": { + ID: "79", + Description: "The software does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.", + Name: "Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')", + }, + "88": { + ID: "88", + Description: "The software constructs a string for a command to executed by a separate component\nin another control sphere, but it does not properly delimit the\nintended arguments, options, or switches within that command string.", + Name: "Improper Neutralization of Argument Delimiters in a Command ('Argument Injection')", + }, + "89": { + ID: "89", + Description: "The software constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component.", + Name: "Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')", + }, "118": { ID: "118", Description: "The software does not restrict or incorrectly restricts operations within the boundaries of a resource that is accessed using an index or pointer, such as memory or files.", @@ -33,11 +58,6 @@ var idWeaknesses = map[string]*Weakness{ Description: "The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.", Name: "Exposure of Sensitive Information to an Unauthorized Actor", }, - "22": { - ID: "22", - Description: "The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the software does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.", - Name: "Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')", - }, "242": { ID: "242", Description: "The program calls a function that can never be guaranteed to work safely.", @@ -73,6 +93,11 @@ var idWeaknesses = map[string]*Weakness{ Description: "The use of a broken or risky cryptographic algorithm is an unnecessary risk that may result in the exposure of sensitive information.", Name: "Use of a Broken or Risky Cryptographic Algorithm", }, + "328": { + ID: "328", + Description: "The product uses an algorithm that produces a digest (output value) that does not meet security expectations for a hash function that allows an adversary to reasonably determine the original input (preimage attack), find another input that can produce the same hash (2nd preimage attack), or find multiple inputs that evaluate to the same hash (birthday attack). ", + Name: "Use of Weak Hash", + }, "338": { ID: "338", Description: "The product uses a Pseudo-Random Number Generator (PRNG) in a security context, but the PRNG's algorithm is not cryptographically strong.", @@ -93,40 +118,25 @@ var idWeaknesses = map[string]*Weakness{ Description: "The software does not handle or incorrectly handles a compressed input with a very high compression ratio that produces a large output.", Name: "Improper Handling of Highly Compressed Data (Data Amplification)", }, + "676": { + ID: "676", + Description: "The program invokes a potentially dangerous function that could introduce a vulnerability if it is used incorrectly, but the function can also be used safely.", + Name: "Use of Potentially Dangerous Function", + }, "703": { ID: "703", Description: "The software does not properly anticipate or handle exceptional conditions that rarely occur during normal operation of the software.", Name: "Improper Check or Handling of Exceptional Conditions", }, - "78": { - ID: "78", - Description: "The software constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.", - Name: "Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')", - }, - "79": { - ID: "79", - Description: "The software does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.", - Name: "Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')", - }, "798": { ID: "798", Description: "The software contains hard-coded credentials, such as a password or cryptographic key, which it uses for its own inbound authentication, outbound communication to external components, or encryption of internal data.", Name: "Use of Hard-coded Credentials", }, - "88": { - ID: "88", - Description: "The software constructs a string for a command to executed by a separate component\nin another control sphere, but it does not properly delimit the\nintended arguments, options, or switches within that command string.", - Name: "Improper Neutralization of Argument Delimiters in a Command ('Argument Injection')", - }, - "89": { - ID: "89", - Description: "The software constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component.", - Name: "Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')", - }, - "676": { - ID: "676", - Description: "The program invokes a potentially dangerous function that could introduce a vulnerability if it is used incorrectly, but the function can also be used safely.", - Name: "Use of Potentially Dangerous Function", + "1204": { + ID: "1204", + Description: "The product uses a cryptographic primitive that uses an Initialization Vector (IV), but the product does not generate IVs that are sufficiently unpredictable or unique according to the expected cryptographic requirements for that primitive.", + Name: "Generation of Weak Initialization Vector (IV)", }, } diff --git a/vendor/github.com/securego/gosec/v2/helpers.go b/vendor/github.com/securego/gosec/v2/helpers.go index 15b2b5f3a..1089f52c0 100644 --- a/vendor/github.com/securego/gosec/v2/helpers.go +++ b/vendor/github.com/securego/gosec/v2/helpers.go @@ -15,12 +15,15 @@ package gosec import ( + "bytes" + "encoding/json" "errors" "fmt" "go/ast" "go/token" "go/types" "os" + "os/exec" "os/user" "path/filepath" "regexp" @@ -29,6 +32,9 @@ import ( "strings" ) +// envGoModVersion overrides the Go version detection. +const envGoModVersion = "GOSECGOVERSION" + // MatchCallByPackage ensures that the specified package is imported, // adjusts the name for any aliases and ignores cases that are // initialization only imports. @@ -260,7 +266,7 @@ func getIdentStringValues(ident *ast.Ident, stringFinder func(ast.Node) (string, return values } -// getIdentStringRecursive returns the string of values of an Ident if they can be resolved +// GetIdentStringValuesRecursive returns the string of values of an Ident if they can be resolved // The difference between this and GetIdentStringValues is that it will attempt to resolve the strings recursively, // if it is passed a *ast.BinaryExpr. See GetStringRecursive for details func GetIdentStringValuesRecursive(ident *ast.Ident) []string { @@ -493,19 +499,49 @@ func RootPath(root string) (string, error) { return filepath.Abs(root) } -// GoVersion returns parsed version of Go from runtime +// GoVersion returns parsed version of Go mod version and fallback to runtime version if not found. func GoVersion() (int, int, int) { - return parseGoVersion(runtime.Version()) + if env, ok := os.LookupEnv(envGoModVersion); ok { + return parseGoVersion(strings.TrimPrefix(env, "go")) + } + + goVersion, err := goModVersion() + if err != nil { + return parseGoVersion(strings.TrimPrefix(runtime.Version(), "go")) + } + + return parseGoVersion(goVersion) +} + +type goListOutput struct { + GoVersion string `json:"GoVersion"` +} + +func goModVersion() (string, error) { + cmd := exec.Command("go", "list", "-m", "-json") + + raw, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("command go list: %w: %s", err, string(raw)) + } + + var v goListOutput + err = json.NewDecoder(bytes.NewBuffer(raw)).Decode(&v) + if err != nil { + return "", fmt.Errorf("unmarshaling error: %w: %s", err, string(raw)) + } + + return v.GoVersion, nil } // parseGoVersion parses Go version. // example: -// - go1.19rc2 -// - go1.19beta2 -// - go1.19.4 -// - go1.19 +// - 1.19rc2 +// - 1.19beta2 +// - 1.19.4 +// - 1.19 func parseGoVersion(version string) (int, int, int) { - exp := regexp.MustCompile(`go(\d+).(\d+)(?:.(\d+))?.*`) + exp := regexp.MustCompile(`(\d+).(\d+)(?:.(\d+))?.*`) parts := exp.FindStringSubmatch(version) if len(parts) <= 1 { return 0, 0, 0 diff --git a/vendor/github.com/securego/gosec/v2/import_tracker.go b/vendor/github.com/securego/gosec/v2/import_tracker.go index 7984e99f4..0d9ebfe16 100644 --- a/vendor/github.com/securego/gosec/v2/import_tracker.go +++ b/vendor/github.com/securego/gosec/v2/import_tracker.go @@ -15,9 +15,12 @@ package gosec import ( "go/ast" "go/types" + "regexp" "strings" ) +var versioningPackagePattern = regexp.MustCompile(`v[0-9]+$`) + // ImportTracker is used to normalize the packages that have been imported // by a source file. It is able to differentiate between plain imports, aliased // imports and init only imports. @@ -66,5 +69,10 @@ func importName(importPath string) string { if len(parts) > 0 { name = parts[len(parts)-1] } + // If the last segment of the path is version information, consider the second to last segment as the package name. + // (e.g., `math/rand/v2` would be `rand`) + if len(parts) > 1 && versioningPackagePattern.MatchString(name) { + name = parts[len(parts)-2] + } return name } diff --git a/vendor/github.com/securego/gosec/v2/issue/issue.go b/vendor/github.com/securego/gosec/v2/issue/issue.go index 1000b2042..6227db670 100644 --- a/vendor/github.com/securego/gosec/v2/issue/issue.go +++ b/vendor/github.com/securego/gosec/v2/issue/issue.go @@ -67,6 +67,7 @@ var ruleToCWE = map[string]string{ "G112": "400", "G113": "190", "G114": "676", + "G115": "190", "G201": "89", "G202": "89", "G203": "79", @@ -77,32 +78,38 @@ var ruleToCWE = map[string]string{ "G304": "22", "G305": "22", "G306": "276", - "G401": "326", + "G401": "328", "G402": "295", "G403": "310", "G404": "338", + "G405": "327", + "G406": "328", + "G407": "1204", "G501": "327", "G502": "327", "G503": "327", "G504": "327", "G505": "327", + "G506": "327", + "G507": "327", "G601": "118", "G602": "118", } // Issue is returned by a gosec rule if it discovers an issue with the scanned code. type Issue struct { - Severity Score `json:"severity"` // issue severity (how problematic it is) - Confidence Score `json:"confidence"` // issue confidence (how sure we are we found it) - Cwe *cwe.Weakness `json:"cwe"` // Cwe associated with RuleID - RuleID string `json:"rule_id"` // Human readable explanation - What string `json:"details"` // Human readable explanation - File string `json:"file"` // File name we found it in - Code string `json:"code"` // Impacted code line - Line string `json:"line"` // Line number in file - Col string `json:"column"` // Column number in line - NoSec bool `json:"nosec"` // true if the issue is nosec - Suppressions []SuppressionInfo `json:"suppressions"` // Suppression info of the issue + Severity Score `json:"severity"` // issue severity (how problematic it is) + Confidence Score `json:"confidence"` // issue confidence (how sure we are we found it) + Cwe *cwe.Weakness `json:"cwe"` // Cwe associated with RuleID + RuleID string `json:"rule_id"` // Human readable explanation + What string `json:"details"` // Human readable explanation + File string `json:"file"` // File name we found it in + Code string `json:"code"` // Impacted code line + Line string `json:"line"` // Line number in file + Col string `json:"column"` // Column number in line + NoSec bool `json:"nosec"` // true if the issue is nosec + Suppressions []SuppressionInfo `json:"suppressions"` // Suppression info of the issue + Autofix string `json:"autofix,omitempty"` // Proposed auto fix the issue } // SuppressionInfo object is to record the kind and the justification that used diff --git a/vendor/github.com/securego/gosec/v2/perf-diff.sh b/vendor/github.com/securego/gosec/v2/perf-diff.sh new file mode 100644 index 000000000..cf3084cbf --- /dev/null +++ b/vendor/github.com/securego/gosec/v2/perf-diff.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +BIN="gosec" +BUILD_DIR="/tmp/securego" + +# Scan the current folder and measure the duration. +function scan() { + local scan_cmd=$1 + s=$(date +%s%3N) + $scan_cmd -quiet ./... + e=$(date +%s%3N) + res=$(expr $e - $s) + echo $res +} + +# Build the master reference version. +mkdir -p ${BUILD_DIR} +git clone --quiet https://github.com/securego/gosec.git ${BUILD_DIR} >/dev/null +make -C ${BUILD_DIR} >/dev/null + +# Scan once with the main reference. +duration_master=$(scan "${BUILD_DIR}/${BIN}") +echo "gosec reference time: ${duration_master}ms" + +# Build the current version. +make -C . >/dev/null + +# Scan once with the current version. +duration=$(scan "./${BIN}") +echo "gosec time: ${duration}ms" + +# Compute the difference of the execution time. +diff=$(($duration - $duration_master)) +if [[ diff -lt 0 ]]; then + diff=$(($diff * -1)) +fi +echo "diff: ${diff}ms" +perf=$((100 - ($duration * 100) / $duration_master)) +echo "perf diff: ${perf}%" + +# Fail the build if there is a performance degradation of more than 10%. +if [[ $perf -lt -10 ]]; then + exit 1 +fi diff --git a/vendor/github.com/securego/gosec/v2/rules/blocklist.go b/vendor/github.com/securego/gosec/v2/rules/blocklist.go index 5e03cf7a0..a4376b19a 100644 --- a/vendor/github.com/securego/gosec/v2/rules/blocklist.go +++ b/vendor/github.com/securego/gosec/v2/rules/blocklist.go @@ -93,3 +93,17 @@ func NewBlocklistedImportSHA1(id string, conf gosec.Config) (gosec.Rule, []ast.N "crypto/sha1": "Blocklisted import crypto/sha1: weak cryptographic primitive", }) } + +// NewBlocklistedImportMD4 fails if MD4 is imported +func NewBlocklistedImportMD4(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { + return NewBlocklistedImports(id, conf, map[string]string{ + "golang.org/x/crypto/md4": "Blocklisted import golang.org/x/crypto/md4: deprecated and weak cryptographic primitive", + }) +} + +// NewBlocklistedImportRIPEMD160 fails if RIPEMD160 is imported +func NewBlocklistedImportRIPEMD160(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { + return NewBlocklistedImports(id, conf, map[string]string{ + "golang.org/x/crypto/ripemd160": "Blocklisted import golang.org/x/crypto/ripemd160: deprecated and weak cryptographic primitive", + }) +} diff --git a/vendor/github.com/securego/gosec/v2/rules/fileperms.go b/vendor/github.com/securego/gosec/v2/rules/fileperms.go index 5311f74c6..eb1fa2eee 100644 --- a/vendor/github.com/securego/gosec/v2/rules/fileperms.go +++ b/vendor/github.com/securego/gosec/v2/rules/fileperms.go @@ -61,7 +61,7 @@ func (r *filePermissions) Match(n ast.Node, c *gosec.Context) (*issue.Issue, err for _, pkg := range r.pkgs { if callexpr, matched := gosec.MatchCallByPackage(n, c, pkg, r.calls...); matched { modeArg := callexpr.Args[len(callexpr.Args)-1] - if mode, err := gosec.GetInt(modeArg); err == nil && !modeIsSubset(mode, r.mode) { + if mode, err := gosec.GetInt(modeArg); err == nil && !modeIsSubset(mode, r.mode) || isOsPerm(modeArg) { return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil } } @@ -69,6 +69,18 @@ func (r *filePermissions) Match(n ast.Node, c *gosec.Context) (*issue.Issue, err return nil, nil } +// isOsPerm check if the provide ast node contains a os.PermMode symbol +func isOsPerm(n ast.Node) bool { + if node, ok := n.(*ast.SelectorExpr); ok { + if identX, ok := node.X.(*ast.Ident); ok { + if identX.Name == "os" && node.Sel != nil && node.Sel.Name == "ModePerm" { + return true + } + } + } + return false +} + // NewWritePerms creates a rule to detect file Writes with bad permissions. func NewWritePerms(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { mode := getConfiguredMode(conf, id, 0o600) diff --git a/vendor/github.com/securego/gosec/v2/rules/hardcoded_credentials.go b/vendor/github.com/securego/gosec/v2/rules/hardcoded_credentials.go index ed1fb947d..c10d18b30 100644 --- a/vendor/github.com/securego/gosec/v2/rules/hardcoded_credentials.go +++ b/vendor/github.com/securego/gosec/v2/rules/hardcoded_credentials.go @@ -154,10 +154,6 @@ var secretsPatterns = [...]secretPattern{ regexp: regexp.MustCompile(`sk_live_[0-9a-zA-Z]{24}`), }, { - name: "Stripe API Key", - regexp: regexp.MustCompile(`sk_live_[0-9a-zA-Z]{24}`), - }, - { name: "Stripe Restricted API Key", regexp: regexp.MustCompile(`rk_live_[0-9a-zA-Z]{24}`), }, diff --git a/vendor/github.com/securego/gosec/v2/rules/implicit_aliasing.go b/vendor/github.com/securego/gosec/v2/rules/implicit_aliasing.go index a7eabb20b..75de4ed8c 100644 --- a/vendor/github.com/securego/gosec/v2/rules/implicit_aliasing.go +++ b/vendor/github.com/securego/gosec/v2/rules/implicit_aliasing.go @@ -47,6 +47,12 @@ func doGetIdentExpr(expr ast.Expr, hasSelector bool) (*ast.Ident, bool) { } func (r *implicitAliasing) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { + // This rule does not apply for Go 1.22, see https://tip.golang.org/doc/go1.22#language. + major, minor, _ := gosec.GoVersion() + if major >= 1 && minor >= 22 { + return nil, nil + } + switch node := n.(type) { case *ast.RangeStmt: // When presented with a range statement, get the underlying Object bound to diff --git a/vendor/github.com/securego/gosec/v2/rules/rand.go b/vendor/github.com/securego/gosec/v2/rules/rand.go index 4491fd928..fe34ca9c3 100644 --- a/vendor/github.com/securego/gosec/v2/rules/rand.go +++ b/vendor/github.com/securego/gosec/v2/rules/rand.go @@ -23,8 +23,7 @@ import ( type weakRand struct { issue.MetaData - funcNames []string - packagePath string + blocklist map[string][]string } func (w *weakRand) ID() string { @@ -32,8 +31,8 @@ func (w *weakRand) ID() string { } func (w *weakRand) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { - for _, funcName := range w.funcNames { - if _, matched := gosec.MatchCallByPackage(n, c, w.packagePath, funcName); matched { + for pkg, funcs := range w.blocklist { + if _, matched := gosec.MatchCallByPackage(n, c, pkg, funcs...); matched { return c.NewIssue(n, w.ID(), w.What, w.Severity, w.Confidence), nil } } @@ -43,17 +42,22 @@ func (w *weakRand) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { // NewWeakRandCheck detects the use of random number generator that isn't cryptographically secure func NewWeakRandCheck(id string, _ gosec.Config) (gosec.Rule, []ast.Node) { + calls := make(map[string][]string) + calls["math/rand"] = []string{ + "New", "Read", "Float32", "Float64", "Int", "Int31", "Int31n", + "Int63", "Int63n", "Intn", "NormFloat64", "Uint32", "Uint64", + } + calls["math/rand/v2"] = []string{ + "New", "Float32", "Float64", "Int", "Int32", "Int32N", + "Int64", "Int64N", "IntN", "N", "NormFloat64", "Uint32", "Uint32N", "Uint64", "Uint64N", "UintN", + } return &weakRand{ - funcNames: []string{ - "New", "Read", "Float32", "Float64", "Int", "Int31", - "Int31n", "Int63", "Int63n", "Intn", "NormalFloat64", "Uint32", "Uint64", - }, - packagePath: "math/rand", + blocklist: calls, MetaData: issue.MetaData{ ID: id, Severity: issue.High, Confidence: issue.Medium, - What: "Use of weak random number generator (math/rand instead of crypto/rand)", + What: "Use of weak random number generator (math/rand or math/rand/v2 instead of crypto/rand)", }, }, []ast.Node{(*ast.CallExpr)(nil)} } diff --git a/vendor/github.com/securego/gosec/v2/rules/readfile.go b/vendor/github.com/securego/gosec/v2/rules/readfile.go index 7ef4bbad1..da6b9c965 100644 --- a/vendor/github.com/securego/gosec/v2/rules/readfile.go +++ b/vendor/github.com/securego/gosec/v2/rules/readfile.go @@ -143,6 +143,7 @@ func NewReadFile(id string, _ gosec.Config) (gosec.Rule, []ast.Node) { rule.pathJoin.Add("path", "Join") rule.clean.Add("path/filepath", "Clean") rule.clean.Add("path/filepath", "Rel") + rule.clean.Add("path/filepath", "EvalSymlinks") rule.Add("io/ioutil", "ReadFile") rule.Add("os", "ReadFile") rule.Add("os", "Open") diff --git a/vendor/github.com/securego/gosec/v2/rules/rulelist.go b/vendor/github.com/securego/gosec/v2/rules/rulelist.go index f9ca4f52c..13f29f71a 100644 --- a/vendor/github.com/securego/gosec/v2/rules/rulelist.go +++ b/vendor/github.com/securego/gosec/v2/rules/rulelist.go @@ -94,10 +94,12 @@ func Generate(trackSuppressions bool, filters ...RuleFilter) RuleList { {"G307", "Poor file permissions used when creating a file with os.Create", NewOsCreatePerms}, // crypto - {"G401", "Detect the usage of DES, RC4, MD5 or SHA1", NewUsesWeakCryptography}, + {"G401", "Detect the usage of MD5 or SHA1", NewUsesWeakCryptographyHash}, {"G402", "Look for bad TLS connection settings", NewIntermediateTLSCheck}, {"G403", "Ensure minimum RSA key length of 2048 bits", NewWeakKeyStrength}, {"G404", "Insecure random number source (rand)", NewWeakRandCheck}, + {"G405", "Detect the usage of DES or RC4", NewUsesWeakCryptographyEncryption}, + {"G406", "Detect the usage of deprecated MD4 or RIPEMD160", NewUsesWeakDeprecatedCryptographyHash}, // blocklist {"G501", "Import blocklist: crypto/md5", NewBlocklistedImportMD5}, @@ -105,6 +107,8 @@ func Generate(trackSuppressions bool, filters ...RuleFilter) RuleList { {"G503", "Import blocklist: crypto/rc4", NewBlocklistedImportRC4}, {"G504", "Import blocklist: net/http/cgi", NewBlocklistedImportCGI}, {"G505", "Import blocklist: crypto/sha1", NewBlocklistedImportSHA1}, + {"G506", "Import blocklist: golang.org/x/crypto/md4", NewBlocklistedImportMD4}, + {"G507", "Import blocklist: golang.org/x/crypto/ripemd160", NewBlocklistedImportRIPEMD160}, // memory safety {"G601", "Implicit memory aliasing in RangeStmt", NewImplicitAliasing}, diff --git a/vendor/github.com/securego/gosec/v2/rules/templates.go b/vendor/github.com/securego/gosec/v2/rules/templates.go index 728766f45..3d5f9a977 100644 --- a/vendor/github.com/securego/gosec/v2/rules/templates.go +++ b/vendor/github.com/securego/gosec/v2/rules/templates.go @@ -45,9 +45,12 @@ func (t *templateCheck) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error // find use of templates where HTML/JS escaping is not being used func NewTemplateCheck(id string, _ gosec.Config) (gosec.Rule, []ast.Node) { calls := gosec.NewCallList() + calls.Add("html/template", "CSS") calls.Add("html/template", "HTML") calls.Add("html/template", "HTMLAttr") calls.Add("html/template", "JS") + calls.Add("html/template", "JSStr") + calls.Add("html/template", "Srcset") calls.Add("html/template", "URL") return &templateCheck{ calls: calls, diff --git a/vendor/github.com/securego/gosec/v2/rules/weakcrypto.go b/vendor/github.com/securego/gosec/v2/rules/weakcrypto.go index 4f2ab11d1..143f67d4e 100644 --- a/vendor/github.com/securego/gosec/v2/rules/weakcrypto.go +++ b/vendor/github.com/securego/gosec/v2/rules/weakcrypto.go @@ -21,16 +21,16 @@ import ( "github.com/securego/gosec/v2/issue" ) -type usesWeakCryptography struct { +type usesWeakCryptographyEncryption struct { issue.MetaData blocklist map[string][]string } -func (r *usesWeakCryptography) ID() string { +func (r *usesWeakCryptographyEncryption) ID() string { return r.MetaData.ID } -func (r *usesWeakCryptography) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { +func (r *usesWeakCryptographyEncryption) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { for pkg, funcs := range r.blocklist { if _, matched := gosec.MatchCallByPackage(n, c, pkg, funcs...); matched { return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil @@ -39,14 +39,12 @@ func (r *usesWeakCryptography) Match(n ast.Node, c *gosec.Context) (*issue.Issue return nil, nil } -// NewUsesWeakCryptography detects uses of des.* md5.* or rc4.* -func NewUsesWeakCryptography(id string, _ gosec.Config) (gosec.Rule, []ast.Node) { +// NewUsesWeakCryptographyEncryption detects uses of des.*, rc4.* +func NewUsesWeakCryptographyEncryption(id string, _ gosec.Config) (gosec.Rule, []ast.Node) { calls := make(map[string][]string) calls["crypto/des"] = []string{"NewCipher", "NewTripleDESCipher"} - calls["crypto/md5"] = []string{"New", "Sum"} - calls["crypto/sha1"] = []string{"New", "Sum"} calls["crypto/rc4"] = []string{"NewCipher"} - rule := &usesWeakCryptography{ + rule := &usesWeakCryptographyEncryption{ blocklist: calls, MetaData: issue.MetaData{ ID: id, diff --git a/vendor/github.com/securego/gosec/v2/rules/weakcryptohash.go b/vendor/github.com/securego/gosec/v2/rules/weakcryptohash.go new file mode 100644 index 000000000..298555de1 --- /dev/null +++ b/vendor/github.com/securego/gosec/v2/rules/weakcryptohash.go @@ -0,0 +1,55 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rules + +import ( + "go/ast" + + "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" +) + +type usesWeakCryptographyHash struct { + issue.MetaData + blocklist map[string][]string +} + +func (r *usesWeakCryptographyHash) ID() string { + return r.MetaData.ID +} + +func (r *usesWeakCryptographyHash) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { + for pkg, funcs := range r.blocklist { + if _, matched := gosec.MatchCallByPackage(n, c, pkg, funcs...); matched { + return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil + } + } + return nil, nil +} + +// NewUsesWeakCryptographyHash detects uses of md5.*, sha1.* +func NewUsesWeakCryptographyHash(id string, _ gosec.Config) (gosec.Rule, []ast.Node) { + calls := make(map[string][]string) + calls["crypto/md5"] = []string{"New", "Sum"} + calls["crypto/sha1"] = []string{"New", "Sum"} + rule := &usesWeakCryptographyHash{ + blocklist: calls, + MetaData: issue.MetaData{ + ID: id, + Severity: issue.Medium, + Confidence: issue.High, + What: "Use of weak cryptographic primitive", + }, + } + return rule, []ast.Node{(*ast.CallExpr)(nil)} +} diff --git a/vendor/github.com/securego/gosec/v2/rules/weakdepricatedcryptohash.go b/vendor/github.com/securego/gosec/v2/rules/weakdepricatedcryptohash.go new file mode 100644 index 000000000..68297355c --- /dev/null +++ b/vendor/github.com/securego/gosec/v2/rules/weakdepricatedcryptohash.go @@ -0,0 +1,57 @@ +// (c) Copyright 2024 Mercedes-Benz Tech Innovation GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rules + +import ( + "go/ast" + + "github.com/securego/gosec/v2" + "github.com/securego/gosec/v2/issue" +) + +type usesWeakDeprecatedCryptographyHash struct { + issue.MetaData + blocklist map[string][]string +} + +func (r *usesWeakDeprecatedCryptographyHash) ID() string { + return r.MetaData.ID +} + +func (r *usesWeakDeprecatedCryptographyHash) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) { + for pkg, funcs := range r.blocklist { + if _, matched := gosec.MatchCallByPackage(n, c, pkg, funcs...); matched { + return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil + } + } + return nil, nil +} + +// NewUsesWeakCryptographyHash detects uses of md4.New, ripemd160.New +func NewUsesWeakDeprecatedCryptographyHash(id string, _ gosec.Config) (gosec.Rule, []ast.Node) { + calls := make(map[string][]string) + calls["golang.org/x/crypto/md4"] = []string{"New"} + calls["golang.org/x/crypto/ripemd160"] = []string{"New"} + rule := &usesWeakDeprecatedCryptographyHash{ + blocklist: calls, + MetaData: issue.MetaData{ + ID: id, + Severity: issue.Medium, + Confidence: issue.High, + What: "Use of deprecated weak cryptographic primitive", + }, + } + return rule, []ast.Node{(*ast.CallExpr)(nil)} +} |
