-
Notifications
You must be signed in to change notification settings - Fork 286
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
Fix security errors in lint steps #2061
Conversation
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.
there's another go.mod in e2e as well
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.
See additional comments
@@ -68,7 +68,7 @@ func (gc *fakeGC) DeleteBeforeTx(_ context.Context, rev datastore.Revision) (Del | |||
|
|||
revInt := rev.(revisions.TransactionIDRevision).TransactionID() | |||
|
|||
return gc.deleter.DeleteBeforeTx(int64(revInt)) |
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 seemed like a type mismatch, so I propagated it through. I'd welcome a check of that assumption.
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, seems so
internal/datastore/common/changes.go
Outdated
// We checked for underflow above, so the current byte size | ||
// should fit in a uint64 | ||
currentByteSize, _ := safecast.ToUint64(ch.currentByteSize) | ||
|
||
if currentByteSize > ch.maxByteSize { |
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.
As I was going through these, I tended to go one of four ways:
- Something about the context makes it obvious that the cast is safe, so I used
safecast
to mark it as such by ignoring the error - There's potential for a miscast and it seems like the situation would represent a bug, so I return a
MustBugF
- There's potential for a miscast and it seems like it could realistically happen, so I return a normal error
- It was possible to redeclare the code in such a way that largely avoided casting issues
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 should return the error as a MustBugf even if we know it can be safe; it is "extra" code but it means if anything changes we're guaranteed to catch the error vs having a silent bug appear
for seed == 0 { | ||
// Sum64 returns a uint64, and safecast will return 0 if it's not castable, | ||
// which will happen about half the time (?). We just keep running it until | ||
// we get a seed that fits in the box. | ||
// Subtracting math.MaxInt64 should mean that we retain the entire range of | ||
// possible values. | ||
seed, _ = safecast.ToInt64(new(maphash.Hash).Sum64() - math.MaxInt64) | ||
} |
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 felt a little wonky but seemed to capture the desired effect.
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 should just make the seed a uint64 then? @ecordell?
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.
NewSource()
and Seed()
both take an int64, which is what this value is used for.
interpreting the uint64 bits as int64 bits doesn't really matter for this application. the requirements for this use of rand are super minimal, we really just want "don't do the same exact thing on every replica 100% of the time".
can we just divide it by 2 and then cast? or is there a way to mark this specific "unsafe" cast as "I don't care about wraparound at all"?
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, we can // ignore gosec
for this particular case, or we can use the approach I came up with. I'm fine with either.
maxConns, err := safecast.ToInt32(*opts.MaxOpenConns) | ||
if err != nil { | ||
return err | ||
} | ||
pgxConfig.MaxConns = maxConns |
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 would require a user to put a very specific value in, but we might as well catch it.
xminInt := int64(decoded.Xmin) | ||
xminInt, err := safecast.ToInt64(decoded.Xmin) | ||
if err != nil { | ||
return datastore.NoRevision, spiceerrors.MustBugf("Could not cast xmin to 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.
This seemed like it would be a bug since we were the ones who stored the revision in the first place
uintPageSize, err := safecast.ToUint32(pageSize) | ||
require.NoError(err) |
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 made a bunch of these changes to tests. They're overkill, especially since the values are statically defined in most cases, so I can walk these back out if desired.
// We make an assumption that if the cast fails, it's because the value | ||
// was too big, so we set to maxint in that case. | ||
uintCost = math.MaxUint32 | ||
} |
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.
Please check this
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.
Log it as well, but this seems fine
maxRetries, err := safecast.ToUint8(opts.MaxRetries) | ||
if err != nil { | ||
return nil, errors.New("max-retries could not be cast to uint8") | ||
} |
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.
Another branch that we're unlikely to hit, but it could result in very weird behavior if we do.
@@ -42,14 +43,18 @@ func runAssertions(devContext *DevContext, assertions []blocks.Assertion, expect | |||
for _, assertion := range assertions { | |||
tpl := tuple.MustFromRelationship[*v1t.ObjectReference, *v1t.SubjectReference, *v1t.ContextualizedCaveat](assertion.Relationship) | |||
|
|||
// NOTE: zeroes are fine here to mean "unknown" |
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 safecast can't successfully cast, it returns zero. For pointing at line numbers, I think this should be fine (especially since you'd have to do something weird to end up out of bounds here).
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.
We should either log the error or have it panic
@@ -13,16 +17,18 @@ func MustEnsureUInt32(value int) uint32 { | |||
|
|||
// EnsureUInt32 ensures that the specified value can be represented as a uint32. | |||
func EnsureUInt32(value int) (uint32, error) { |
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.
These functions are basically doing safecast
things anyway, so I rewrote them in terms of safecast to silence the errors. We can upstream it later.
internal/datastore/common/changes.go
Outdated
if ch.currentByteSize > int64(ch.maxByteSize) { | ||
// We checked for underflow above, so the current byte size | ||
// should fit in a uint64 | ||
currentByteSize, _ := safecast.ToUint64(ch.currentByteSize) |
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.
return the error even if we think it cannot be hit; its safer than ignoring it
internal/datastore/common/changes.go
Outdated
// We checked for underflow above, so the current byte size | ||
// should fit in a uint64 | ||
currentByteSize, _ := safecast.ToUint64(ch.currentByteSize) | ||
|
||
if currentByteSize > ch.maxByteSize { |
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 should return the error as a MustBugf even if we know it can be safe; it is "extra" code but it means if anything changes we're guaranteed to catch the error vs having a silent bug appear
@@ -68,7 +68,7 @@ func (gc *fakeGC) DeleteBeforeTx(_ context.Context, rev datastore.Revision) (Del | |||
|
|||
revInt := rev.(revisions.TransactionIDRevision).TransactionID() | |||
|
|||
return gc.deleter.DeleteBeforeTx(int64(revInt)) |
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, seems so
for seed == 0 { | ||
// Sum64 returns a uint64, and safecast will return 0 if it's not castable, | ||
// which will happen about half the time (?). We just keep running it until | ||
// we get a seed that fits in the box. | ||
// Subtracting math.MaxInt64 should mean that we retain the entire range of | ||
// possible values. | ||
seed, _ = safecast.ToInt64(new(maphash.Hash).Sum64() - math.MaxInt64) | ||
} |
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 should just make the seed a uint64 then? @ecordell?
@@ -141,12 +142,16 @@ func (p *RetryPool) ID() string { | |||
|
|||
// MaxConns returns the MaxConns configured on the underlying pool | |||
func (p *RetryPool) MaxConns() uint32 { | |||
return uint32(p.pool.Config().MaxConns) | |||
// This should be non-negative | |||
maxConns, _ := safecast.ToUint32(p.pool.Config().MaxConns) |
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 we have enough of these examples, we should add our own MustToUint32 method and just have it panic if it fails
internal/datastore/crdb/readwrite.go
Outdated
if delLimit > 0 && uint64(modified.RowsAffected()) == delLimit { | ||
rowsAffected, err := safecast.ToUint64(modified.RowsAffected()) | ||
if err != nil { | ||
return false, spiceerrors.MustBugf("could not cast RowsAffected to uint64") |
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.
always wrap the error when using MustBugf
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.
In fact, we should probably add a custom linter to ensure this at some point. Might be worth filing an issue
// We make an assumption that if the cast fails, it's because the value | ||
// was too big, so we set to maxint in that case. | ||
uintCost = math.MaxUint32 | ||
} |
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.
Log it as well, but this seems fine
@@ -42,14 +43,18 @@ func runAssertions(devContext *DevContext, assertions []blocks.Assertion, expect | |||
for _, assertion := range assertions { | |||
tpl := tuple.MustFromRelationship[*v1t.ObjectReference, *v1t.SubjectReference, *v1t.ContextualizedCaveat](assertion.Relationship) | |||
|
|||
// NOTE: zeroes are fine here to mean "unknown" |
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.
We should either log the error or have it panic
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.
LGTM
Description
A new go version was published that fixes a security vulnerability. This takes that patch.
Changes
Testing
Review. See that things pass.