-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add static analysis for unsafe uint casting #10318
Conversation
Note this closes #9958 if all exceptions are taken care of |
// Defensive check to prevent potential issues when casting to int64. | ||
if length > math.MaxInt64 { | ||
return 0, errors.Errorf("invalid length provided: %d", length) | ||
il, err := math.Int(length) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This validation has moved to math.Int
@@ -2,6 +2,7 @@ load("@prysm//tools/go:def.bzl", "go_library") | |||
|
|||
go_library( | |||
name = "go_default_library", | |||
testonly = True, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found this in my journey. More test code not marked as testonly :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found a test case the analyzer does not catch: when doing the cast during struct initialization:
func Uint64CastInStruct() {
type S struct {
a int
}
s := S{
a: int(uint64(5)), // want "Unsafe cast from uint64 to int."
}
_ = s
}
this fails when running the Analyzer test
// maxBalanceCacheSize defines the max number of active balances can cache. | ||
maxBalanceCacheSize = uint64(4) | ||
maxBalanceCacheSize = int(4) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was brought up offline cc @uncdr , but is there any reason we have cache sizes as a signed integer here ? It can't be negative in any case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah k, I see it is due to the lruwrpr
, maybe we can change the method signature to take a uint64
. And it does the safe casting internally in the lru constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah exactly, I moved these to const
so that we are assured it can never change. I thought this was the easier route vs changing 3rd party API
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not 3rd party though, this lruwrpr
package is under prysm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh! OK, I'll take a look!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we even have lruwrpr
? It's a flow through to github.com/hashicorp/golang-lru
with panics. I'm going to leave it out of scope for this PR. It seems strange to have this package when we could use lru directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we wanted the lru package to not return an error mostly. The package returns an error if the cache size is negative.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this, it might be best to handle this in a follow up PR. The LRU cache is used across prysm and changing the api of it, will require changes across prysm
This is a pretty contrived example. I will see if I can reasonably add to the SA to solve this, but I might not be able to do it in this PR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm still not ok where int, uint, uint64 should be used
// maxCommitteesCacheSize defines the max number of shuffled committees on per randao basis can cache. | ||
// Due to reorgs and long finality, it's good to keep the old cache around for quickly switch over. | ||
maxCommitteesCacheSize = uint64(32) | ||
maxCommitteesCacheSize = int(32) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as in another thread, why signed int?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the 3rd party API. I'm not sure. This is a constant now so it can't overflow/underflow. I think we can address cache API in another PR or issue.
beacon-chain/p2p/message_id.go
Outdated
@@ -92,7 +92,7 @@ func postAltairMsgID(pmsg *pubsub_pb.Message, fEpoch types.Epoch) string { | |||
h := hash.Hash(combinedData) | |||
return string(h[:20]) | |||
} | |||
totalLength := len(params.BeaconNetworkConfig().MessageDomainValidSnappy) + len(topicLenBytes) + int(topicLen) + len(decodedData) | |||
totalLength := len(params.BeaconNetworkConfig().MessageDomainValidSnappy) + len(topicLenBytes) + int(topicLen) + len(decodedData) // lint:ignore uintcast -- Message length cannot exceed int64 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if totalLength exceeds int64? overflow and a negative value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add a SafeAddInt64 method for this
int and uint are now guaranteed to be 64 bits. Go automatically sets int and uint to the max size available to the system and I have added a runtime check to panic on start if the max size of int/uint is not 64 bits. In general, int should be used to interact with an API that we do not control.
There are a few other use cases where int is allowed when negative numbers are expected, like balance deltas in fork choice. In most cases, uint64 is the canonical type for numbers in Ethereum. |
Removing OK to merge until after someone approves this. Since this PR adds new static analysis, there is no cache hits between develop based branches and this one. So this PR is contributing to high build agent disk usage until it can be merged. |
* Add static analysis for unsafe uint casting * Fix violations of uintcast * go mod tidy * Add exclusion to nogo for darwin build * Add test for math.Int * Move some things to const so they are assured not to exceed int64 * Self review * lint * fix tests * fix test * Add init check for non 64 bit OS * Move new deps from WORKSPACE to deps.bzl * fix bazel build for go analysis runs * Update BUILD.bazel Remove TODO * add math.AddInt method * Add new test casts * Add case where builtin functions and declared functions are covered * Fix new findings * cleanup Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com> Co-authored-by: Nishant Das <nishdas93@gmail.com>
What type of PR is this?
Other
What does this PR do? Why is it needed?
Casting uint types to int types can lead to unexpected results, i.e. overflows.
This PR adds a static analysis check for these scenarios and resolves all found issues.
Which issues(s) does this PR fix?
Other notes for review
One thing I thought about is that it's technically not a big deal to cast a smaller uint to a larger int (uint32 -> int64), but we typically only deal with uint64 so I decided to warn on any conversion from uint to int.