Skip to content
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

Feature: G602 Slice Bound Checking #973

Merged
merged 12 commits into from
Jun 21, 2023

Conversation

morgenm
Copy link
Contributor

@morgenm morgenm commented Jun 17, 2023

This PR addresses this issue: #954

This adds a new rule which checks slice bounds access (reslicing and indexing) to ensure that slices are not accessed out of bounds. This only works for slice bounds defined by a make([]..., len) or make([]..., len, capacity) where len/capacity is defined as a literal integer. For example it works for:

s := make([]float, 10, 20)

But not for:

s := make([]float, 10*3)

Since we don't evaluate expressions. It also will calculate and validate slice capacities for new slices made by reslicing when possible, such as:

s := make([]float, 10)
x := s[5:]

The capacity for the new slice, x, will be validated in any subsequent usage. In addition, slices passed to functions are validated when possible by storing function-call-specific capacities. For example:

func main() {
	s := make([]int, 0, 30)
	doStuff(s)
	x := make([]int, 20)
	y := x[10:]
	doStuff(y)
	z := y[5:]
	doStuff(z)
}

func doStuff(x []int) {
	newSlice := x[:10]
	fmt.Println(newSlice)
	newSlice2 := x[:6]
	fmt.Println(newSlice2)
}`

will cause an error on creation of newSlice and newSlice2, since when z is passed to it, it is accessed out of bounds.

return nil, nil
}

// NewSliceBoundsCheck attempts to find any slices being accessed out of bounds

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment on exported function NewSliceBoundCheck should be of the form "NewSliceBoundCheck ..."

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please fix the function name in the comment?

@codecov-commenter
Copy link

codecov-commenter commented Jun 17, 2023

Codecov Report

Patch coverage: 73.60% and project coverage change: +0.24 🎉

Comparison is base (abeab10) 71.84% compared to head (a7356c2) 72.08%.

❗ Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please install the Github App Integration for your organization. Read more.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #973      +/-   ##
==========================================
+ Coverage   71.84%   72.08%   +0.24%     
==========================================
  Files          50       51       +1     
  Lines        3317     3586     +269     
==========================================
+ Hits         2383     2585     +202     
- Misses        868      911      +43     
- Partials       66       90      +24     
Impacted Files Coverage Δ
issue/issue.go 0.00% <ø> (ø)
rules/slice_bounds.go 73.50% <73.50%> (ø)
rules/rulelist.go 100.00% <100.00%> (ø)

... and 1 file with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

@morgenm
Copy link
Contributor Author

morgenm commented Jun 17, 2023

Note: This solution is complex since it stores the constant capacities of all the slices made with make(). I am thinking of adding in a simple check for non-constant capacities (or capacities evaluated from expressions such as make([]int, 5*5)) by making sure the user adds in a bounds check such as if index < cap(slice). If it is better to simplify the whole check by only validating that the user has checked bounds, without storing the actual constant capacities in the map as I did in this PR, I can change the code.

Let me know your thoughts.

Copy link
Member

@ccojocar ccojocar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this great contribution. I left a few comments. It would be great if you could fix them before we merge this rule. Thanks again!

rules/slice_bounds.go Show resolved Hide resolved
rules/slice_bounds.go Show resolved Hide resolved
rules/slice_bounds.go Show resolved Hide resolved
rules/slice_bounds.go Outdated Show resolved Hide resolved
rules/slice_bounds.go Outdated Show resolved Hide resolved
continue
}

paramName := params[it].Names[0].Name
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would check Name[0] that is not nil before accessing the Name filed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I added that check.

s.sliceCaps[nil] = sliceMap
}

// Matches calls to make() and stores the capacity of the new slice in the map to compare against future slice usage
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

method name prefix in the comment

return caps
}

// Matches slice assignments, calculates capacity of slice if possible to store it in map
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function name prefix missing in the comment

func (s *sliceOutOfBounds) matchSliceAssignment(node *ast.SliceExpr, sliceName string, ctx *gosec.Context) (*issue.Issue, error) {
// First do the normal match that verifies the slice expr is not out of bounds
if i, err := s.matchSliceExpr(node, ctx); err != nil {
return i, err
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would wrap the err with some more context before returning.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay I wrapped it using fmt.Errorf.

return nil, nil
}

// NewSliceBoundsCheck attempts to find any slices being accessed out of bounds
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please fix the function name in the comment?

@ccojocar
Copy link
Member

Note: This solution is complex since it stores the constant capacities of all the slices made with make(). I am thinking of adding in a simple check for non-constant capacities (or capacities evaluated from expressions such as make([]int, 5*5)) by making sure the user adds in a bounds check such as if index < cap(slice). If it is better to simplify the whole check by only validating that the user has checked bounds, without storing the actual constant capacities in the map as I did in this PR, I can change the code.

I think the current solution which checks the constant bounds looks fine to me. It is non intrusive comparing with a bound check which is more enforced on user.

My impression is, that one will really check the bounds at runtime if she wants to be defensive but most of people won't do it. This will cause a lot of warnings which most likely will get ignored.

@morgenm
Copy link
Contributor Author

morgenm commented Jun 20, 2023

I believe I addressed all the reviews.

I think the current solution which checks the constant bounds looks fine to me. It is non intrusive comparing with a bound check which is more enforced on user.

My impression is, that one will really check the bounds at runtime if she wants to be defensive but most of people won't do it. This will cause a lot of warnings which most likely will get ignored.

Yes, thinking on it some more I agree with you.

@ccojocar ccojocar merged commit a018cf0 into securego:master Jun 21, 2023
6 checks passed
@razor-1
Copy link

razor-1 commented Aug 21, 2023

It would be nice if this didn't fire when it was protected by something like if len(slice) >= x

@franchb
Copy link

franchb commented Aug 22, 2023

Yes, this rule fires a lot of false positives on code protected with something like if len(slice) >= x

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants