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

Add a new rule to detect integer overflow when converting between integer types #1149

Merged
merged 5 commits into from
May 27, 2024

Conversation

ccojocar
Copy link
Member

fixes #1130

@ccojocar ccojocar merged commit 43bef71 into securego:master May 27, 2024
6 checks passed
@ccojocar ccojocar deleted the conversion-int-overflow branch May 27, 2024 11:04
@remyleone
Copy link

Could you add documentation about how to fix it and make the linter pass? 🙏

dst := instr.Type().String()
if isIntOverflow(src, dst) {
issue := newIssue(pass.Analyzer.Name,
fmt.Sprintf("integer overflow conversion %s -> %s", src, dst),
Copy link
Contributor

Choose a reason for hiding this comment

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

I might have missed something about the implementation, but I would like to suggest this

Suggested change
fmt.Sprintf("integer overflow conversion %s -> %s", src, dst),
fmt.Sprintf("possible integer overflow conversion %s -> %s", src, dst),

It might not happen depending on people code.

It's better to report a possible error, than affirming there is one. People would argue.

Copy link
Contributor

Choose a reason for hiding this comment

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

@ccojocar what do you think about the changes I suggested?

Do you agree with them?

Would you change it? Or do you want me to open a PR for them?

}

func parseIntType(intType string) (integer, error) {
re := regexp.MustCompile(`(?P<type>u?int)(?P<size>\d{2})?`)
Copy link
Contributor

Choose a reason for hiding this comment

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

This regexp will be computed each time parseInt will be called, it should be moved out the function

Comment on lines +85 to +86
it := matches[re.SubexpIndex("type")]
is := matches[re.SubexpIndex("size")]
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add something like this

Suggested change
it := matches[re.SubexpIndex("type")]
is := matches[re.SubexpIndex("size")]
// int64 => int 64, uint8 => uint 8
it := matches[re.SubexpIndex("type")]
is := matches[re.SubexpIndex("size")]

return true
}
// converting int to uint of a smaller size might lead to overflow
if srcInt.signed && !dstInt.signed && dstInt.size < srcInt.size && srcInt.size-dstInt.size > 8 {
Copy link
Contributor

Choose a reason for hiding this comment

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

This 8 is magical number here

return integer{signed: signed, size: intSize}, nil
}

func isIntOverflow(src string, dst string) bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this

Suggested change
func isIntOverflow(src string, dst string) bool {
func couldIntOverflow(src string, dst string) bool {


import "github.com/securego/gosec/v2"

var SampleCodeG115 = []CodeSample{
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a bit worried these tests are only focusing on things that could be reported.

I would expect to be added to cover conversion of things that should pass.

They are likely the ones to report false positive if something is invalid in the current implementation, or if a later refactoring screw everything

@FairlySadPanda
Copy link

FairlySadPanda commented Aug 21, 2024

This check has rolled out and ergo suddenly comments on it!

This check seems over-prescriptive. In the builtin functions, len returns type int, despite only returning zero or positive ints. Casting out of len into uint32 is thus more declarative. This is just one example where this change enforces a //nolint flag.

The act of casting is an assertion that the programmer knows what they are doing and are content with the potential for data loss. Whilst I'm sure that a zero-trust environment would want to scrutinize this sort of thing, it's overkill for normal programs.

@moitias
Copy link

moitias commented Aug 21, 2024

There is perfectly valid code that can be guaranteed not to overflow that this check catches. For example;

v,_ := strconv.ParseInt("1",10,32) 
v32 := int32(v) 

As the GoDoc states;

The bitSize argument specifies the integer type that the result must fit into.

Tbh, I'm not sure how one is supposed to cast bigger (or bare) ints/uints (for example, received from a library) into smaller size ints/uints at all with this check? Doing just uint32(v & 0xFFFF) is also flagged even though - as far as I understand - it's not possible for v&0xFFFF to not fit into uint32.

At the very least, it seems to me like this check should be opt-in instead of opt-out.

@ccojocar
Copy link
Member Author

ccojocar commented Aug 21, 2024

There is perfectly valid code that can be guaranteed not to overflow that this check catches. For example;

@moitias The type of v is still int which on a 64 bit system is 64 bits. Essentially the rule sees a conversion from int64 to int32. It is not so smart to detect the conversion done before using strconv.ParseInt. It think adding these extension can be useful but for now you can make the size explicit by selecting the int32 type for v instead of int. This will make the size explicit into the type.

See more discussions in #1185.

Also if this rule doesn't work for your use case, feel free to exclude it but in some cases integer overflow can lead to security issues and there isn't a protection for this in the go runtime at the moment.

gosec -exclude G115 ./...

@moitias
Copy link

moitias commented Aug 21, 2024

@moitias The type of v is still int which on a 64 bit system is 64 bits.

Well, if we're being pedantic, no, the type of v is not int but int64 because ParseInt and ParseUInt return explicit int64s/uint64s but yeah, this is just nitpicking.

It think adding these extension can be useful but for now you can make the size explicit by selecting the int32 type for v instead of int. This will make the size explicit into the type.

Without commit access to Go stdlib (and considering Go 1 compatibility promise), I don't think I can do that.

Essentially the rule sees a conversion from int64 to int32. It is not so smart to detect the conversion done before using strconv.ParseInt.

And that, indeed, is the problem. There are safe ways of doing the conversion (again pointing out the very straightforward v := uint8(x & 0xFF)), but this checker does not allow for them either.

As a sidenote (or, another nit); this also has nothing to do with what happens before strconv.ParseInt but rather that the method makes an explicit guarantee that it will not return values that will not fit into size n values which the linter obviously does not understand.

Also if this rule doesn't work for your use case, feel free to exclude it but in some cases integer overflow can lead to security issues and there isn't a protection for this in the go runtime at the moment.

Yeah, but this checker does not allow the developer to do it safely either, so they will need to disable the checker (locally or globally), making this checker essentially useless for any case that actually needs to do conversions and of course, nothing but lost CPU time for any case that doesn't do conversions. So, I'd argue that the usefulness of this check is dependent on it being 'smart'. If it is not, people will disable it and overflows might then creep in.

@ccojocar
Copy link
Member Author

ccojocar commented Aug 21, 2024

@moitias @FairlySadPanda @ccoVeille I am looking forward to your contributions to improve this rule. I think is more productive to do this instead of just complaining and using an OSS tool maintained by volunteers ;-).

@moitias
Copy link

moitias commented Aug 21, 2024

Look, I appreciate the work you are putting into this (and helping me out) but I'm trying to point out - to help you out in turn - that this rule in its current form seems to be a bit prematurely turned on because it seems to have no use cases at all. This is because if one has it enabled, there is no way of doing the conversion securely while in fact there obviously are.

Could you please, to clarify the reasoning for this rule, at least describe one way of doing a conversion to a smaller bit size safely or are you of the opinion that it is never acceptable/secure to do? I would hope we could at least agree that linter warnings should have a way of fixing them, right?

@ccoVeille
Copy link
Contributor

ccoVeille commented Aug 21, 2024

@moitias @FairlySadPanda @ccoVeille I am looking forward to your contributions to improve this rule. I think is more productive to do this instead of just complaining and using an OSS tool maintained by volunteers ;-).

I can understand your reaction. I could say Touché. But I'm more likely to reply you positively than being affected. I'm a smiling person.

Be sure I have nothing against you or the idea you got. I wish the issues we are facing now were identified before it was merged, but there were not 🤷

You can understand people are reacting when their CI is now a Christmas tree 🎄

I wish I had reviewed your PR earlier. I wish others may have reviewed earlier.
Someone may have raised the consequences of this PR.

There problems was not with the idea or the code, but with the review of your changes suggestions.

That said, sometimes shit happens. Let's try to find a solution.

@ccoVeille
Copy link
Contributor

ccoVeille commented Aug 22, 2024

For anyone interested, the discussion continued here

#1185

Then the remaing issues to be addressed are discussed here #1212

spiffcs added a commit to anchore/syft that referenced this pull request Sep 13, 2024
A change to the rule gosec(G115) made a large amount of FP for gosec appear when updating to the
latest golang-ci linter.

securego/gosec#1185
securego/gosec#1149

We're going to ignore this rule for the time being while waiting for gosec to get updates so that
bound checking and example snippets of `valid` code is added for this rule

Signed-off-by: Christopher Phillips <[email protected]>
spiffcs added a commit to anchore/syft that referenced this pull request Sep 13, 2024
* chore(deps): update tools to latest versions

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* chore: disable gosec(G115)

A change to the rule gosec(G115) made a large amount of FP for gosec appear when updating to the
latest golang-ci linter.

securego/gosec#1185
securego/gosec#1149

We're going to ignore this rule for the time being while waiting for gosec to get updates so that
bound checking and example snippets of `valid` code is added for this rule

Signed-off-by: Christopher Phillips <[email protected]>

---------

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Signed-off-by: Christopher Phillips <[email protected]>
Co-authored-by: spiffcs <[email protected]>
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.

Add detection of overflow during integer conversion
5 participants