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

Refactor to support duplicate imports with different aliases #865

Merged
merged 1 commit into from
Oct 17, 2022

Conversation

thaJeztah
Copy link
Contributor

The existing code assumed imports to be either imported, or imported with an
alias. Badly formatted files may have duplicate imports for a package, using
different aliases.

This patch refactors the code, and;

Introduces a new GetImportedNames function, which returns all name(s) and
aliase(s) for a package, which effectively combines GetAliasedName and
GetImportedName, but adding support for duplicate imports.

The old GetAliasedName and GetImportedName functions have been rewritten to
use the new function and marked deprecated, but could be removed if there are no
external consumers.

With this patch, the linter is able to detect issues in files such as;

package main

import (
    crand "crypto/rand"
    "math/big"
    "math/rand"
    rand2 "math/rand"
    rand3 "math/rand"
)

func main() {
    _, _ = crand.Int(crand.Reader, big.NewInt(int64(2))) // good

    _ = rand.Intn(2) // bad
    _ = rand2.Intn(2)  // bad
    _ = rand3.Intn(2)  // bad
}

Before this patch, only a single issue would be detected:

gosec --quiet .

[main.go:14] - G404 (CWE-338): Use of weak random number generator (math/rand instead of crypto/rand) (Confidence: MEDIUM, Severity: HIGH)
    13:
  > 14: 	_ = rand.Intn(2) // bad
    15: 	_ = rand2.Intn(2)  // bad

With this patch, all issues are identified:

gosec --quiet .

[main.go:16] - G404 (CWE-338): Use of weak random number generator (math/rand instead of crypto/rand) (Confidence: MEDIUM, Severity: HIGH)
    15: 	_ = rand2.Intn(2)  // bad
  > 16: 	_ = rand3.Intn(2)  // bad
    17: }

[main.go:15] - G404 (CWE-338): Use of weak random number generator (math/rand instead of crypto/rand) (Confidence: MEDIUM, Severity: HIGH)
    14: 	_ = rand.Intn(2) // bad
  > 15: 	_ = rand2.Intn(2)  // bad
    16: 	_ = rand3.Intn(2)  // bad

[main.go:14] - G404 (CWE-338): Use of weak random number generator (math/rand instead of crypto/rand) (Confidence: MEDIUM, Severity: HIGH)
    13:
  > 14: 	_ = rand.Intn(2) // bad
    15: 	_ = rand2.Intn(2)  // bad

Imported map[string]string
Aliased map[string]string
// Imported is a map of imports with their associated names/aliases.
Imported map[string][]string
InitOnly map[string]bool
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I should mention that this change is not backward compatible if there's external users of the ImportTracker type; not sure if there's users, and if that's a concern (if it is, we can probably add back the old fields, and introduce a new field (which could be non-exported); https://grep.app/search?q=%22github.com/securego/gosec/v2

Copy link
Member

Choose a reason for hiding this comment

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

I think is fine, this is not supposed to be used outside of the project. We didn't do a good job to isolate this as internal.

helpers.go Outdated
Comment on lines 269 to 270
// Deprecated: use GetImportedNames
func GetImportedName(path string, ctx *Context) (string, bool) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I deprecated the old ones, but not sure if there's external consumers (if we don't expect there to be, these could be removed).

Copy link
Member

Choose a reason for hiding this comment

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

I think this can be removed, it is not supposed to be used outside of the project.

@thaJeztah
Copy link
Contributor Author

Alright, this needs some work; have a false positive on

package main

import (
	"crypto/rand"
	mrand "math/rand"
)

func main() {
	good, _ := rand.Read(nil)
	println(good)
	bad := mrand.Int31()
	println(bad)
}

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.

Thanks for working on this. It looks good so far.

helpers.go Outdated
Comment on lines 269 to 270
// Deprecated: use GetImportedNames
func GetImportedName(path string, ctx *Context) (string, bool) {
Copy link
Member

Choose a reason for hiding this comment

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

I think this can be removed, it is not supposed to be used outside of the project.

Imported map[string]string
Aliased map[string]string
// Imported is a map of imports with their associated names/aliases.
Imported map[string][]string
InitOnly map[string]bool
Copy link
Member

Choose a reason for hiding this comment

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

I think is fine, this is not supposed to be used outside of the project. We didn't do a good job to isolate this as internal.

@mauriciocm9
Copy link

any update?

@ccojocar
Copy link
Member

Hi @thaJeztah, Do you still plan to finish this pull request? Thanks

@thaJeztah thaJeztah force-pushed the improve_import_matching branch 3 times, most recently from d52d68c to 98158f4 Compare October 16, 2022 23:15
@thaJeztah
Copy link
Contributor Author

@ccojocar I think I fixed it, but looks like CI is failing due to older Go versions being installed, and an outdated module; I opened #880 to fix / work around that, and temporarily rebased this PR on top of that (will rebase once the other PR is merged)

@codecov-commenter
Copy link

Codecov Report

Base: 73.89% // Head: 74.19% // Increases project coverage by +0.30% 🎉

Coverage data is based on head (98158f4) compared to base (6cd9e62).
Patch coverage: 88.88% of modified lines in pull request are covered.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #865      +/-   ##
==========================================
+ Coverage   73.89%   74.19%   +0.30%     
==========================================
  Files          51       51              
  Lines        3195     3186       -9     
==========================================
+ Hits         2361     2364       +3     
+ Misses        763      752      -11     
+ Partials       71       70       -1     
Impacted Files Coverage Δ
analyzer.go 89.40% <ø> (-0.04%) ⬇️
import_tracker.go 75.60% <82.60%> (+9.98%) ⬆️
helpers.go 43.18% <100.00%> (+0.42%) ⬆️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

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

Comment on lines 42 to 39
path := strings.Trim(imp.Path.Value, `"`)
parts := strings.Split(path, "/")
if len(parts) > 0 {
name := parts[len(parts)-1]
t.Imported[path] = name
importPath := strings.Trim(imp.Path.Value, `"`)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was part of the problem; the old code needed to manually track the file, to get the "un-aliased" import, and then used the ImportTracker.TrackImport to get the aliased versions. But the TrackFile would ignore aliases.

Expect(tracker.Imported).Should(Equal(map[string]string{"fmt": "fmt"}))
Expect(tracker.Imported).Should(Equal(map[string][]string{"fmt": {"fm"}}))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Which can be seen here (the file tested has fm as alias for fmt, but TrackFile() doesn't detect that.

Comment on lines -299 to 300
gosec.context.Imports.TrackFile(file)
gosec.context.PassedValues = make(map[string]interface{})
ast.Walk(gosec, file)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

...and because Analyzer.Check() called both TrackFile() and ast.Walk(), the incorrect import name would be added (so a non-aliased import would be added, even though the package used an alias), which broke detection.

With the new code, CheckImports() finds both, so calling TrackFile is no longer needed.

I wasn't sure if that function could be removed altogether (it's slightly easier to use than starting an Analyzer, so perhaps it's useful as a utility still).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wasn't sure if that function could be removed altogether (it's slightly easier to use than starting an Analyzer, so perhaps it's useful as a utility still).

Actually, perhaps it should be the reverse; use TrackFile once, instead of during Visit() (we only need it once per file). Could perhaps be as part as Visit() though.

Comment on lines -269 to -275
if !imported {
return "", false
}

if _, initonly := ctx.Imports.InitOnly[path]; initonly {
return "", false
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The initOnly seemed redundant; all tests seem to pass without it, so I removed it, but maybe there was a reason it was there 😅

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.

lgtm, please could you please rebase it now? Is this still a draft?

@thaJeztah
Copy link
Contributor Author

Working on some last changes; perhaps it's better after all to handle imports at the File level, otherwise we're calling it for every Node; let me push those changes 👍

@thaJeztah thaJeztah force-pushed the improve_import_matching branch from 98158f4 to 61bbb83 Compare October 17, 2022 08:46
The existing code assumed imports to be either imported, or imported with an
alias. Badly formatted files may have duplicate imports for a package, using
different aliases.

This patch refactors the code, and;

Introduces a new `GetImportedNames` function, which returns all name(s) and
aliase(s) for a package, which effectively combines `GetAliasedName` and
`GetImportedName`, but adding support for duplicate imports.

The old `GetAliasedName` and `GetImportedName` functions have been rewritten to
use the new function and marked deprecated, but could be removed if there are no
external consumers.

With this patch, the linter is able to detect issues in files such as;

    package main

    import (
        crand "crypto/rand"
        "math/big"
        "math/rand"
        rand2 "math/rand"
        rand3 "math/rand"
    )

    func main() {
        _, _ = crand.Int(crand.Reader, big.NewInt(int64(2))) // good

        _ = rand.Intn(2) // bad
        _ = rand2.Intn(2)  // bad
        _ = rand3.Intn(2)  // bad
    }

Before this patch, only a single issue would be detected:

    gosec --quiet .

    [main.go:14] - G404 (CWE-338): Use of weak random number generator (math/rand instead of crypto/rand) (Confidence: MEDIUM, Severity: HIGH)
        13:
      > 14: 	_ = rand.Intn(2) // bad
        15: 	_ = rand2.Intn(2)  // bad

With this patch, all issues are identified:

    gosec --quiet .

    [main.go:16] - G404 (CWE-338): Use of weak random number generator (math/rand instead of crypto/rand) (Confidence: MEDIUM, Severity: HIGH)
        15: 	_ = rand2.Intn(2)  // bad
      > 16: 	_ = rand3.Intn(2)  // bad
        17: }

    [main.go:15] - G404 (CWE-338): Use of weak random number generator (math/rand instead of crypto/rand) (Confidence: MEDIUM, Severity: HIGH)
        14: 	_ = rand.Intn(2) // bad
      > 15: 	_ = rand2.Intn(2)  // bad
        16: 	_ = rand3.Intn(2)  // bad

    [main.go:14] - G404 (CWE-338): Use of weak random number generator (math/rand instead of crypto/rand) (Confidence: MEDIUM, Severity: HIGH)
        13:
      > 14: 	_ = rand.Intn(2) // bad
        15: 	_ = rand2.Intn(2)  // bad

While working on this change, I noticed that ImportTracker.TrackFile() was not able
to find import aliases;  Analyser.Check() called both ImportTracker.TrackFile() and
ast.Walk(), which (with the updated ImportTracker) resulted in importes to be in-
correctly included multiple times (once with the correct alias, once with the default).

I updated ImportTracker.TrackFile() to fix this, but with the updated ImportTracker,
Analyser.Check() no longer has to call ImportTracker.TrackFile() separately, as ast.Walk()
already handles the file, and will find all imports.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
@thaJeztah thaJeztah force-pushed the improve_import_matching branch from 61bbb83 to 473f548 Compare October 17, 2022 08:52
@thaJeztah thaJeztah marked this pull request as ready for review October 17, 2022 08:58
@thaJeztah
Copy link
Contributor Author

@ccojocar should be good now; PTAL 🤗

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.

Perfect! Thanks!

@ccojocar ccojocar merged commit 0ae0174 into securego:master Oct 17, 2022
@thaJeztah thaJeztah deleted the improve_import_matching branch October 17, 2022 08:59
@thaJeztah
Copy link
Contributor Author

YW! Was fun digging a bit into how the ast Parsing works 😄

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.

4 participants