diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..1391e832b --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,39 @@ +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + analyze: + name: Analyze + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go'] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + + - name: Make all + run: make all + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/lint-go.yaml b/.github/workflows/lint-go.yaml new file mode 100644 index 000000000..9fdd4ca04 --- /dev/null +++ b/.github/workflows/lint-go.yaml @@ -0,0 +1,50 @@ +name: Go Lint + +on: + push: + branches: + - master + pull_request: + +permissions: + contents: read + +jobs: + golangci: + name: golangci-lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: '1.21' + cache: false + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.54 + + # Optional: working directory, useful for monorepos + # working-directory: somedir + + # Optional: golangci-lint command line arguments. + # + # Note: By default, the `.golangci.yml` file should be at the root of the repository. + # The location of the configuration file can be changed by using `--config=` + args: --timeout=30m + + # Optional: show only new issues if it's a pull request. The default value is `false`. + only-new-issues: ${{ github.event_name == 'pull_request' }} + + # Optional: if set to true, then all caching functionality will be completely disabled, + # takes precedence over all other caching options. + skip-cache: true + + # Optional: if set to true, then the action won't cache or restore ~/go/pkg. + skip-pkg-cache: true + + # Optional: if set to true, then the action won't cache or restore ~/.cache/go-build. + skip-build-cache: true + + # Optional: The mode to install golangci-lint. It can be 'binary' or 'goinstall'. + # install-mode: "goinstall" diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 000000000..d0cc50c43 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,19 @@ +# Please refer to the official golangci-lint config documentation for more details: +# https://golangci-lint.run/usage/configuration/ +# https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml + +linters: + enable-all: true + +linters-settings: + gci: + sections: + - standard + - default + +run: + timeout: 10m + tests: false + +issues: + max-issues-per-linter: 1000 diff --git a/builtin/gen/bindata.go b/builtin/gen/bindata.go index 8f1ffbc5d..04c86073c 100644 --- a/builtin/gen/bindata.go +++ b/builtin/gen/bindata.go @@ -44,21 +44,28 @@ import ( "time" ) +const ( + maxDecompressedSize = 1 << 20 // 1MB +) + func bindataRead(data []byte, name string) ([]byte, error) { gz, err := gzip.NewReader(bytes.NewBuffer(data)) if err != nil { return nil, fmt.Errorf("Read %q: %v", name, err) } + // Limit the number of bytes read from the decompressor + limitedReader := io.LimitReader(gz, maxDecompressedSize) + var buf bytes.Buffer - _, err = io.Copy(&buf, gz) + _, err = io.Copy(&buf, limitedReader) clErr := gz.Close() if err != nil { return nil, fmt.Errorf("Read %q: %v", name, err) } if clErr != nil { - return nil, err + return nil, clErr } return buf.Bytes(), nil @@ -794,11 +801,13 @@ var _bindata = map[string]func() (*asset, error){ // directory embedded in the file by go-bindata. // For example if you run go-bindata on data/... and data contains the // following hierarchy: -// data/ -// foo.txt -// img/ -// a.png -// b.png +// +// data/ +// foo.txt +// img/ +// a.png +// b.png +// // then AssetDir("data") would return []string{"foo.txt", "img"} // AssetDir("data/img") would return []string{"a.png", "b.png"} // AssetDir("foo.txt") and AssetDir("notexist") would return an error diff --git a/cache/rnd_cache.go b/cache/rnd_cache.go index 661546497..c8aa7aa0d 100644 --- a/cache/rnd_cache.go +++ b/cache/rnd_cache.go @@ -6,15 +6,11 @@ package cache import ( - "math/rand" + "crypto/rand" + "math/big" "sync" - "time" ) -func init() { - rand.Seed(time.Now().UnixNano()) -} - // RandCache a simple cache which randomly evicts entries when // length exceeds limit. type RandCache struct { @@ -106,7 +102,13 @@ func (rc *RandCache) Pick() *Entry { if len(rc.s) == 0 { return nil } - ent := rc.s[rand.Intn(len(rc.s))] + + randomIndex, err := rand.Int(rand.Reader, big.NewInt(int64(len(rc.s)))) + if err != nil { + return nil + } + + ent := rc.s[randomIndex.Uint64()] cpy := ent.Entry return &cpy } @@ -141,6 +143,12 @@ func (rc *RandCache) randDrop() { if len(rc.s) == 0 { return } - ent := rc.s[rand.Intn(len(rc.s))] + + randomIndex, err := rand.Int(rand.Reader, big.NewInt(int64(len(rc.s)))) + if err != nil { + return + } + + ent := rc.s[randomIndex.Uint64()] rc.remove(ent.Key) } diff --git a/cmd/thor/utils.go b/cmd/thor/utils.go index 9765705c5..728545b94 100644 --- a/cmd/thor/utils.go +++ b/cmd/thor/utils.go @@ -203,6 +203,10 @@ func selectGenesis(ctx *cli.Context) (*genesis.Genesis, thor.ForkConfig, error) gene := genesis.NewMainnet() return gene, thor.GetForkConfig(gene.ID()), nil default: + network = filepath.Clean(network) + if _, err := os.Stat(network); os.IsNotExist(err) { + return nil, thor.ForkConfig{}, errors.New(fmt.Sprintf("the specified genesis file [%v] does not exist", network)) + } file, err := os.Open(network) if err != nil { return nil, thor.ForkConfig{}, errors.Wrap(err, "open genesis file") @@ -444,8 +448,12 @@ func newP2PComm(ctx *cli.Context, repo *chain.Repository, txPool *txpool.TxPool, } peersCachePath := filepath.Join(instanceDir, "peers.cache") + peersCachePath = filepath.Clean(peersCachePath) + if _, err := os.Stat(peersCachePath); os.IsNotExist(err) { + log.Warn("failed to load peers cache", "err", err) + } - if data, err := ioutil.ReadFile(peersCachePath); err != nil { + if data, err := os.ReadFile(peersCachePath); err != nil { if !os.IsNotExist(err) { log.Warn("failed to load peers cache", "err", err) } @@ -518,7 +526,7 @@ func startAPIServer(ctx *cli.Context, handler http.Handler, genesisID thor.Bytes handler = handleXGenesisID(handler, genesisID) handler = handleXThorestVersion(handler) handler = requestBodyLimit(handler) - srv := &http.Server{Handler: handler} + srv := &http.Server{Handler: handler, ReadHeaderTimeout: time.Millisecond * time.Duration(timeout)} var goes co.Goes goes.Go(func() { srv.Serve(listener) diff --git a/comm/peer.go b/comm/peer.go index b5f397a84..4f60c9415 100644 --- a/comm/peer.go +++ b/comm/peer.go @@ -6,7 +6,9 @@ package comm import ( - "math/rand" + "crypto/rand" + "math/big" + mathRand "math/rand" "sync" "time" @@ -24,10 +26,6 @@ const ( maxKnownBlocks = 1024 // Maximum block IDs to keep in the known list (prevent DOS) ) -func init() { - rand.Seed(time.Now().UnixNano()) -} - // Peer extends p2p.Peer with RPC integrated. type Peer struct { *p2p.Peer @@ -84,7 +82,15 @@ func (p *Peer) UpdateHead(id thor.Bytes32, totalScore uint64) { // MarkTransaction marks a transaction to known. func (p *Peer) MarkTransaction(hash thor.Bytes32) { // that's 10~100 block intervals - expiration := mclock.AbsTime(time.Second * time.Duration(thor.BlockInterval*uint64(rand.Intn(91)+10))) + randomValue, err := rand.Int(rand.Reader, big.NewInt(91)) + if err != nil { + log.Warn("failed to generate random value", "err", err) + return + } + + // Add 10 to the random value to get a number in the range [10, 100]. + interval := randomValue.Int64() + 10 + expiration := mclock.AbsTime(time.Second * time.Duration(thor.BlockInterval*uint64(interval))) deadline := mclock.Now() + expiration p.knownTxs.Add(hash, deadline) @@ -183,7 +189,7 @@ func (ps *PeerSet) Slice() Peers { defer ps.lock.Unlock() ret := make(Peers, len(ps.m)) - perm := rand.Perm(len(ps.m)) + perm := mathRand.Perm(len(ps.m)) i := 0 for _, s := range ps.m { // randomly diff --git a/p2psrv/rpc/rpc.go b/p2psrv/rpc/rpc.go index a7d6fdecf..f76dc91d5 100644 --- a/p2psrv/rpc/rpc.go +++ b/p2psrv/rpc/rpc.go @@ -7,7 +7,8 @@ package rpc import ( "context" - "math/rand" + "crypto/rand" + "math/big" "sync" "time" @@ -17,11 +18,6 @@ import ( "github.com/pkg/errors" ) -func init() { - // required when generate call id - rand.Seed(time.Now().UnixNano()) -} - const ( rpcDefaultTimeout = time.Second * 10 ) @@ -154,12 +150,22 @@ func (r *RPC) handleResult(callID uint32, msg *p2p.Msg) error { return nil } +// generateRandomID generates a cryptographically secure random uint32. +func generateRandomID() (uint32, error) { + maxInt := big.NewInt(1<<32 - 1) + randomID, err := rand.Int(rand.Reader, maxInt) + if err != nil { + return 0, err + } + return uint32(randomID.Int64()), nil +} + func (r *RPC) prepareCall(msgCode uint64, onResult func(*p2p.Msg) error) uint32 { r.lock.Lock() defer r.lock.Unlock() for { - id := rand.Uint32() - if id == 0 { + id, err := generateRandomID() + if id == 0 || err != nil { // 0 id is taken by Notify continue } diff --git a/txpool/blocklist.go b/txpool/blocklist.go index b87238410..adcb4fea2 100644 --- a/txpool/blocklist.go +++ b/txpool/blocklist.go @@ -13,6 +13,7 @@ import ( "io/ioutil" "net/http" "os" + "path/filepath" "strings" "sync" @@ -27,6 +28,10 @@ type blocklist struct { // Load load list from local file. func (bl *blocklist) Load(path string) error { + path = filepath.Clean(path) + if _, err := os.Stat(path); os.IsNotExist(err) { + return fmt.Errorf("the path [%v] does not exist", path) + } file, err := os.Open(path) if err != nil { return err @@ -47,6 +52,7 @@ func (bl *blocklist) Load(path string) error { // Save save list to local file. func (bl *blocklist) Save(path string) error { + path = filepath.Clean(path) file, err := os.Create(path) if err != nil { return err diff --git a/txpool/tx_pool.go b/txpool/tx_pool.go index c8170b571..0251957a1 100644 --- a/txpool/tx_pool.go +++ b/txpool/tx_pool.go @@ -7,7 +7,9 @@ package txpool import ( "context" - "math/rand" + "crypto/rand" + "errors" + "math/big" "os" "sync/atomic" "time" @@ -161,7 +163,7 @@ func (p *TxPool) fetchBlocklistLoop() { var eTag string fetch := func() { if err := p.blocklist.Fetch(p.ctx, url, &eTag); err != nil { - if err == context.Canceled { + if errors.Is(err, context.Canceled) { return } log.Warn("blocklist fetch failed", "error", err, "url", url) @@ -180,8 +182,14 @@ func (p *TxPool) fetchBlocklistLoop() { fetch() for { + // delay 1~2 min - delay := time.Second * time.Duration(rand.Int()%60+60) + randomNum, err := rand.Int(rand.Reader, new(big.Int).Sub(big.NewInt(121), big.NewInt(60))) + if err != nil { + randomNum = big.NewInt(90) + } + + delay := time.Second * time.Duration(randomNum.Int64()) select { case <-p.ctx.Done(): return diff --git a/vm/bn256/cloudflare/gfp_decl.go b/vm/bn256/cloudflare/gfp_decl.go index fdea5c11a..d35c23c80 100644 --- a/vm/bn256/cloudflare/gfp_decl.go +++ b/vm/bn256/cloudflare/gfp_decl.go @@ -12,7 +12,7 @@ import ( //nolint:varcheck var hasBMI2 = cpu.X86.HasBMI2 -// go:noescape +//go:noescape func gfpNeg(c, a *gfP) //go:noescape