diff --git a/rules/rulelist.go b/rules/rulelist.go index 6783957..6fd1511 100644 --- a/rules/rulelist.go +++ b/rules/rulelist.go @@ -117,6 +117,7 @@ func Generate(filters ...RuleFilter) RuleList { {"G703", "Errors that don't result in rollback", sdk.NewErrorNotPropagated}, {"G704", "Strconv invalid bitSize and cast", sdk.NewStrconvIntBitSizeOverflow}, {"G705", "Iterating over maps undeterministically", sdk.NewMapRangingCheck}, + {"G706", "Use of time.Now() in consensus code could lead to chain halt", sdk.NewTimeNowRefusal}, } ruleMap := make(map[string]RuleDefinition) diff --git a/rules/rules_test.go b/rules/rules_test.go index f508e90..d5024ba 100644 --- a/rules/rules_test.go +++ b/rules/rules_test.go @@ -95,6 +95,10 @@ var _ = Describe("gosec rules", func() { runner("G705", testutils.SampleCodeMapRangingNonDeterministic) }) + It("should detect use of time.Now() in consensus code which could lead to chain halt", func() { + runner("G706", testutils.SampleCodeTimeNowNonConsensusAware) + }) + It("should detect DoS vulnerability via decompression bomb", func() { runner("G110", testutils.SampleCodeG110) }) diff --git a/rules/sdk/time_now.go b/rules/sdk/time_now.go new file mode 100644 index 0000000..a6dbc84 --- /dev/null +++ b/rules/sdk/time_now.go @@ -0,0 +1,84 @@ +// (c) Copyright 2021 Hewlett Packard Enterprise Development LP +// +// 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 sdk + +import ( + "errors" + "go/ast" + + "github.com/cosmos/gosec/v2" +) + +// This static analyzer discourages the use of time.Now() as it was discovered that +// its usage caused local non-determinism as reported and detailed at +// https://forum.cosmos.network/t/cosmos-sdk-vulnerability-retrospective-security-advisory-jackfruit-october-12-2021/5349 + +type timeNowCheck struct { + gosec.MetaData + calls gosec.CallList +} + +func (tmc *timeNowCheck) ID() string { return tmc.MetaData.ID } + +func (tmc *timeNowCheck) Match(node ast.Node, ctx *gosec.Context) (*gosec.Issue, error) { + // We want to catch all function invocations as well as assignments of any of the form: + // .Value = time.Now().* + // fn := time.Now + callExpr, ok := node.(*ast.CallExpr) + if !ok { + return nil, nil + } + + sel, ok := callExpr.Fun.(*ast.SelectorExpr) + if !ok { + return nil, nil + } + + if sel.Sel.Name != "Now" { + return nil, nil + } + + switch x := sel.X.(type) { + case *ast.Ident: + if x.Name != "time" { + return nil, nil + } + + case *ast.SelectorExpr: + if x.Sel.Name != "time" { + return nil, nil + } + } + + // By this point issue the error. + return nil, errors.New("time.Now() is non-deterministic for distributed consensus, you should use the current Block's timestamp") +} + +func NewTimeNowRefusal(id string, config gosec.Config) (rule gosec.Rule, nodes []ast.Node) { + calls := gosec.NewCallList() + + tnc := &timeNowCheck{ + MetaData: gosec.MetaData{ + ID: id, + Severity: gosec.High, + Confidence: gosec.High, + What: "Non-determinism from using non-consensus aware time.Now()", + }, + calls: calls, + } + + nodes = append(nodes, (*ast.CallExpr)(nil)) + return tnc, nodes +} diff --git a/testutils/source.go b/testutils/source.go index e61a609..43dd8ef 100644 --- a/testutils/source.go +++ b/testutils/source.go @@ -2528,4 +2528,25 @@ func noop(keys []string) []string {return keys} `}, 13, gosec.NewConfig(), }, } + + // SampleCodeTimeNowNonConsensusAware is a sample in which we + // should flag the usage of time.Now() which can lead to chain halt. + SampleCodeTimeNowNonConsensusAware = []CodeSample{ + { + []string{` +package main + +func main() { + _ = time.Now() +}`}, 3, gosec.NewConfig(), + }, + { + []string{` +package main + +func main() { + _ = time.Now().Unix() +}`}, 3, gosec.NewConfig(), + }, + } )