The Wayback Machine - https://web.archive.org/web/20250523002908/https://github.com/github/codeql-go/pull/256
Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.

Polish CWE-327 (weak TLS config) query #256

Merged
merged 30 commits into from
Aug 3, 2020

Conversation

smowton
Copy link
Contributor

@smowton smowton commented Jul 10, 2020

  • Minor style cleanups
  • Exclude paths that extend beyond their first sink
  • Use the same heuristics as DisabledCertificateCheck to exclude some instances which are likely deliberately insecure
  • Extend those heuristics to look for more syntactic signifiers of intentional insecurity, and to include signifiers of a legacy mode (DisabledCertificateCheck does not use these, as disabling checking is a laxness thing, not an age thing)
  • Exclude cases where a good TLS version also flows to the same variable or definition
  • Exclude cases where the ostensibly bad version is a dummy error return value

Still to-do: check if isTestFile can be made broader to exclude some more FPs

@smowton smowton requested a review from a team July 10, 2020 15:29

predicate isSource(DataFlow::Node source, int val) {
val = source.getIntValue() and
unsafeTlsVersion(val, _)
}

predicate isSink(DataFlow::Node sink, Field fld) {
predicate isSink(DataFlow::Node sink, Field fld, DataFlow::Node base, Write fieldWrite) {
fld.hasQualifiedName("crypto/tls", "Config", ["MinVersion", "MaxVersion"]) and
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be beneficial to not consider MaxVersion here; is there any case where a bad MaxVersion is not accompanied by a bad MinVersion?

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 guess the only case would be if MinVersion is left as its implicit default, which we wouldn't otherwise spot. I haven't seen any cases in the wild (yet) where MaxVersion is a problem.

@smowton smowton force-pushed the smowton/admin/cwe-327-cleanup branch from 32053b4 to 0c05636 Compare July 15, 2020 14:47
@smowton
Copy link
Contributor Author

smowton commented Jul 15, 2020

@sauyon your suggested change is in, along with various other changes updated in the description; please re-review

@smowton
Copy link
Contributor Author

smowton commented Jul 15, 2020

CI failures are spurious, caused by checking out and testing an old revision of this PR, in turn caused by an ongoing Github incident

Copy link
Contributor

@owen-mc owen-mc left a comment

Choose a reason for hiding this comment

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

Most of my comments are minor. The big thing you haven't done is actually move the query out of the experimental folder. (Don't forget to move the tests too.)

or
suiteName = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"
)
suiteName =
Copy link
Contributor

Choose a reason for hiding this comment

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

Trivial comment (feel free to ignore): I think it looks slightly nicer to use suiteName in [...] instead of suiteName = [...].

Comment on lines 156 to 161
// Exclude sinks guarded by a feature flag
not getAFeatureFlagCheck().dominatesNode(sink.getNode().asInstruction()) and
// Exclude results in functions whose name documents the insecurity
not exists(FuncDef fn | fn = sink.getNode().asInstruction().getRoot() |
isFeatureFlagName(fn.getEnclosingFunction*().getName())
) and
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be better to put these in the definition of the sink? I assume it would be slightly more efficient to rule out some sinks earlier in the calculation.

Comment on lines 138 to 96
predicate secureTlsVersionFlowsToSink(DataFlow::PathNode sink, Field fld) {
secureTlsVersionFlow(_, _, sink, fld)
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be neater to just inline this in the one place it's used?

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 is used in secureTlsVersionFlowsToSink and secureTlsVersionFlowsToField

Copy link
Contributor

Choose a reason for hiding this comment

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

I was talking about secureTlsVersionFlowsToSink, which I've just double-checked is only used in one place. I'm guessing you thought I was talking about secureTlsVersionFlow.

@@ -65,7 +65,7 @@ module InsecureFeatureFlag {
/**
* Flags suggesting support for an old or legacy feature.
*/
string legacyFlag() { result = "feature" }
string legacyFlag() { result = "legacy" }
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be in the previous commit, I guess, but it doesn't really matter

@gagliardetto
Copy link
Contributor

Wow, you've put hella lot of work into it. This might easily be the most advanced query I've seen in here.


I realized that the default MinVersion in a &tls.Config{} is an insecure version (TLSv1.0).

So, if there is a &tls.Config{}, and there is no write to MinVersion field with a secure version, it means that it will default to use the lowest supported version, which is TLSv1.0.

Pretty much anything Go and Google-related (golang.org, proxy.golang.org, google.com, cloud.google.com, console.developers.google.com, etc.) all support TLSv1.0 (along with the secure versions).

The biggest issue is when MinVersion and MaxVersion are both set to an insecure version, which means only insecure versions are available to be used.

@@ -0,0 +1,2 @@
lgtm,codescanning
* Query "Insecure TLS configuration" (`go/insecure-tls`) is promoted from experimental status. This checks for use of insecure SSL/TLS versions and cipher suites.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there an already existing change note for this query?
If not, why mention that it is promoted from experimental status?

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 suppose to inform anyone that has used it before that their query has moved, but I defer to anyone that knows the proper convention to describe a promotion.

Copy link
Contributor

Choose a reason for hiding this comment

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

We had a similar change note when go/email-injection was promoted from the experimental folder, so I think it's fine: https://github.com/github/codeql-go/blob/master/change-notes/2020-06-16-email-injection.md .

@smowton
Copy link
Contributor Author

smowton commented Jul 20, 2020

@gagliardetto good thought -- I think we should add that as a different PR, as it will require a wholly different strategy. To avoid false-positives we'll have to look for something like a config where we're confident we know all of its aliases, and that none of those aliases ever see a MinVersion write.

// or the highest available version setting for MaxVersion.
val = 0 and name = ""
(
val = 768 and name = "VersionSSL30"
Copy link
Contributor

Choose a reason for hiding this comment

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

Hm, is there a reason we're checking the implementation-specific tls enum values here? If it's because there's not way to get them, that's probably something that we should have.

Copy link
Contributor

Choose a reason for hiding this comment

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

Some people use an uint16 (eg 0x0301 for tls.VersionTLS10) instead of the hardcoded version consts from the tls package; this approach catches them both.

Those version values are not golang-specific, but part of the tls protocol spec.

*
* For example, `0` in `func tryGetInt() (int, error) { return 0, errors.New("no good") }`
*/
predicate isReturnedWithError(DataFlow::Node node) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems like it would be useful in general, though I'm not entirely sure where it might go.

* @id go/insecure-tls
* @tags security
* external/cwe/cwe-327
*/

import go
import DataFlow::PathGraph
import semmle.go.security.InsecureFeatureFlag::InsecureFeatureFlag

/**
* Check whether the file where the node is located is a test file.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the predicate below should be replaced by instanceof TestFile a la DisabledCertificateCheck.

We could make files that match the heuristic into TestFiles, or if we don't think that's a good idea, move the heuristic logic into frameworks/Testing and add a predicate we can use in all queries where we exclude test file results.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we want to keep this filter at all? I think filtering out boring results in tests is the job of the presentation layer (Code Scanning or LGTM), not of the query. There are exceptions (usually if the thing the query flags is legitimately OK in tests), but I'm not sure this is one of them.

/**
* Holds if `fieldWrite` writes `sink` to `base`.`fld`, where `fld` is a TLS version field.
*/
predicate isSink(DataFlow::Node sink, Field fld, DataFlow::Node base, Write fieldWrite) {
fld.hasQualifiedName("crypto/tls", "Config", ["MinVersion", "MaxVersion"]) and
Copy link
Contributor

Choose a reason for hiding this comment

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

For some reason I can't add a comment to the discussion above, but FWIW in my opinion it's fine to flag any project with an insecure TLS MinVersion, including the default, since it is actually vulnerable to a downgrade attack and therefore a valid result.

That would mean we could probably not worry about MaxVersion at all and simplify the query, as a bonus.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You mean the discussion with @gagliardetto? I agree we should flag the default, it's just harder to detect since we're looking for a negative property (that no write reaches that field or any of its aliases), so it's more likely to throw false-positives. That's why I wanted to pursue that separately.

* Holds if `fieldWrite` writes `sink` to `base`.`fld`, and `fld` is `tls.Config.CipherSuites`,
* and no parent of `base` is named suggesting an intentionally insecure configuration.
*/
predicate isSink(DataFlow::Node sink, Field fld, DataFlow::Node base, Write fieldWrite) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It doesn't seem to me like fieldWrite is used anywhere, can it be removed?

predicate checkTlsInsecureCipherSuites(
DataFlow::PathNode source, DataFlow::PathNode sink, string message
) {
predicate isInsecureTlsCipherFlow(DataFlow::PathNode source, DataFlow::PathNode sink, string message) {
exists(TlsInsecureCipherSuitesFlowConfig cfg | cfg.hasFlowPath(source, sink) |
exists(string name | cfg.isSourceValueEntity(source.getNode(), name) |
message = "Use of an insecure cipher suite: " + name + "."
Copy link
Contributor

Choose a reason for hiding this comment

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

I can't comment on it directly, but I think the message on line 239 doesn't need to mention InsecureCipherSuites().

| UnsafeTLS.go:304:18:306:4 | slice literal | UnsafeTLS.go:305:5:305:47 | selection of TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 : uint16 | UnsafeTLS.go:304:18:306:4 | slice literal | Use of an insecure cipher suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256. |
| UnsafeTLS.go:312:18:314:4 | slice literal | UnsafeTLS.go:313:5:313:45 | selection of TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 : uint16 | UnsafeTLS.go:312:18:314:4 | slice literal | Use of an insecure cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256. |
| UnsafeTLS.go:329:25:329:94 | call to append | UnsafeTLS.go:329:53:329:93 | selection of TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 : uint16 | UnsafeTLS.go:329:25:329:94 | call to append | Use of an insecure cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256. |
| UnsafeTLS.go:362:18:364:4 | slice literal | UnsafeTLS.go:363:5:363:47 | selection of TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 : uint16 | UnsafeTLS.go:362:18:364:4 | slice literal | Use of an insecure cipher suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256. |
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like this is missing results for the tests you marked bad on lines 336, 346, and 355?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, thanks -- that's because they were using a variable name that suggests deliberate insecurity. Now fixed.

not [getAFeatureFlagCheck(), getALegacyVersionCheck()]
.dominatesNode([source, sink].getNode().asInstruction()) and
// Exclude sources or sinks that occur lexically within a block related to a feature or legacy flag
not astNodeIsFlag([source, sink].getNode().asExpr().getParent*(), [featureFlag(), legacyFlag()]) and
Copy link
Contributor

Choose a reason for hiding this comment

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

Out of interest, what kinds of blocks are you excluding with this as opposed to the above checks logic?

Suggested change
not astNodeIsFlag([source, sink].getNode().asExpr().getParent*(), [featureFlag(), legacyFlag()]) and
not astNodeIsFlag([source, sink].getNode().asExpr().getParent*(), [any(FeatureFlag f), any(LegacyFlag l)]) and

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Lexically related but not dominated by a test:

insecureSetup := getBadCiphers()

Dominated but not lexically surrounded:

if !insecureConfig {
  return
}
config.MinVersion = 0

FeatureFlag() { this = "feature" }

bindingset[result]
override string getAFlagName() { isFeatureFlagName(result) }
Copy link
Contributor

Choose a reason for hiding this comment

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

Feels a bit odd to expose both this and isFeatureFlagName.

Maybe you could have a utility predicate like getFlagFunction for the use of isFeatureFlagName above? Or maybe you could just do any(FeatureFlag f).getAFlagName(), though that's a bit clunkier

/**
* Gets a control-flow node that represents a (likely) feature-flag check for certificate checking.
*/
ControlFlow::ConditionGuardNode getAFeatureFlagCheck() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it might be nicer to add this and getAFlag as a member predicates of Flag, like so:

  ControlFlow::ConditionGuardNode getACheck() { result.ensures(this.getAFlag().getANode(), _) }

I think that would also eliminate the need for featureFlag and legacyFlag.

Copy link
Contributor

@max-schaefer max-schaefer 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 tackling this! A few comments from me as well; I haven't read all of it yet.

deleteme Outdated
@@ -0,0 +1 @@
sdlkfjdskl;fjdfls
Copy link
Contributor

Choose a reason for hiding this comment

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

🤨

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Workaround for github breakage, will drop

* @id go/insecure-tls
* @tags security
* external/cwe/cwe-327
*/

import go
import DataFlow::PathGraph
import semmle.go.security.InsecureFeatureFlag::InsecureFeatureFlag

/**
* Check whether the file where the node is located is a test file.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we want to keep this filter at all? I think filtering out boring results in tests is the job of the presentation layer (Code Scanning or LGTM), not of the query. There are exceptions (usually if the thing the query flags is legitimately OK in tests), but I'm not sure this is one of them.

predicate isSource(DataFlow::Node source, int val) {
val = source.getIntValue() and
unsafeTlsVersion(val, _)
not isReturnedWithError(source)
Copy link
Contributor

Choose a reason for hiding this comment

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

So, does this mean we are now tracking all integer constants anywhere? That seems like it might easily blow up.

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 was concerned too, but the all-go-projects run on lgtm.com doesn't seem to have extraordinary cost. Discussing with Pavel suggests a broad source and a narrow sink performs better than you might suppose. I can restrict it to more specific constants, but we'll always care about zero since that specifies the default (TLS1.0)

Copy link
Contributor

Choose a reason for hiding this comment

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

Fair enough. I note, though, that all-projects runs are a very coarse indication of performance at best; all they tell you is whether something timed out, but not whether it went from very fast to almost-timing-out. I personally wouldn't rely on the data-flow library being clever enough to prune the search space aggressively enough, particularly since it seems that in this case we could help it fairly easily be restricting the set of sources ourselves.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you mean just restricting to zero + the interesting values, or do you have something cleverer in mind?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pushed a commit just now doing the simple restriction. Will do a distcompare once my current distcompare for SSH-host-keys is done.

* Holds if an insecure TLS version flows from `source` to `sink`, which is in turn written
* to a field of `base`. `message` describes the specific problem found.
*/
query predicate isInsecureTlsVersionFlow(
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this a query predicate? Having extra query predicates confuses parts of our toolchain, so I don't think this is safe to add.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Doh, debugging stuff left over

/**
* Returns `node` or an implicit-deref node referring to it
*/
DataFlow::Node nodeOrDeref(DataFlow::Node node) {
Copy link
Contributor

Choose a reason for hiding this comment

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

What is this a workaround for? I'd rather fix it properly.

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 can't remember 100% anymore, but it has to do with isSink using writesField which in turn will return both the implicit-deref and the underlying as base candidates. A secure-version flow then needs to eliminate them both. An alternative would be to filter out implicit-deref nodes as base candidates in isSink or its caller?

)
/**
* Holds if `fieldWrite` writes `sink` to `base`.`fld`, and `fld` is `tls.Config.CipherSuites`,
* and no parent of `base` is named suggesting an intentionally insecure configuration.
Copy link
Contributor

Choose a reason for hiding this comment

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

Where is the latter bit ("no parent (...) is named (...)") checked?

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 moved into the top-level query; fixed.

// Exclude sources or sinks that occur lexically within a block related to a feature or legacy flag
not astNodeIsFlag([source, sink].getNode().asExpr().getParent*(), [featureFlag(), legacyFlag()]) and
// Exclude results in functions whose name documents insecurity
not exists(FuncDef fn | fn = sink.getNode().asInstruction().getRoot() |
Copy link
Contributor

Choose a reason for hiding this comment

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

Having a predicate Node.getEnclosingFunction() would be convenient, I think. With that in hand, I'd suggest rewriting this to

Suggested change
not exists(FuncDef fn | fn = sink.getNode().asInstruction().getRoot() |
not exists(FuncDef fn | fn = sink.getNode().getEnclosingFunction().getEnclosingFunction*() |

(which looks slightly redundant, but isn't equivalent to getEnclosingFunction+(), since the two getEnclosingFunction() predicates are actually different)

and then just use is*FlagName(fn.getName()) below.

/**
* Holds if `name` suggests an old or legacy version.
*
* We accept 'intermediate' because it appears to be common for TLS users
Copy link
Contributor

Choose a reason for hiding this comment

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

Makes sense, but seems very specific to the TLS query. Move there instead?

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've split the flags into 3 pieces. I can't see an easy way to split the code actually into the TLS query and yet allow using the shared infrastructure though.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why is that? The shared infrastructure just needs to know about FlagKind, the concrete subclass can be moved into the query.

smowton added 19 commits July 28, 2020 10:31
For this particular query it's hardly ever interesting to complain about a bad cipher suite being configured, then read from the list and re-added elsewhere. In such a case the longer path will be detected when the shorter one is fixed in any case.
…g a good version

I'm supposing these usually indicate something configurable, rather than a hard-coded insecure choice. The *default* being insecure is still a problem, but probably not amenable to automated analyses.
It is common to return 0 has a dummy value with an error; these are very likely not going to be used as a real TLS version.
Note: if accepted, merge this into a previous commit before submitting the PR
The same path is now used to classify flags relating to old/legacy versions.
These are now checked of all source *and* sink nodes, and the checks are factored with similar paths for is-insecure and is-old flags.
This now checks various carve-outs for probable feature / compatibility flags
smowton added 4 commits July 28, 2020 11:55
These stopped producing an alert because they used a variable name that acknowledges an insecure setup
The case noted works fine.
There are now three categories: general security or option flags, those related to TLS version selection, and those related to certificate configuration. The TLS and disabled-certificate-check queries use two categories each.
@smowton smowton force-pushed the smowton/admin/cwe-327-cleanup branch from ae8a688 to 88cb435 Compare July 28, 2020 12:54
@smowton
Copy link
Contributor Author

smowton commented Jul 28, 2020

@max-schaefer review comments addressed for the most part. I wasn't sure how best to structure the shared InsecureFeatureFlag stuff; any pointers appreciated.

@smowton smowton force-pushed the smowton/admin/cwe-327-cleanup branch from 445c2dd to abfae43 Compare July 28, 2020 14:47
Copy link
Contributor

@max-schaefer max-schaefer left a comment

Choose a reason for hiding this comment

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

The handling of flag kinds looks a little awkward to me, but perhaps I'm not understanding the difficulties that gave rise to the current design, so I've added a few questions about that.

*/
abstract class FlagKind extends string {
FlagKind() {
this = "securityFeature" or this = "legacyTlsVersion" or this = "insecureCertificate"
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you need this? It's an abstract class, so this should just fall out from the usual semantics.

Of course, since it extends string you'll still have to put a binding-set annotation on the (trivial) characteristic predicate, like this: https://github.com/github/codeql-go/blob/master/ql/src/semmle/go/dataflow/internal/DataFlowImpl.qll#L51.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Aha, that's the very demonic incantation I was looking for, thanks

/**
* Flags suggesting an optional feature, perhaps deliberately insecure.
*/
string securityFeatureFlag() { result = "securityFeature" }
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you need this? Couldn't you just use any(SecurityFeatureFlag f)?

}

/**
* Holds if `node` suggests an old TLS version according to `flagKind`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Outdated comment.

* Holds if `name` may be the name of a feature flag that controls a security feature.
*/
bindingset[name]
predicate isSecurityFlagName(string name) { name.regexpMatch("(?i).*(secure|(en|dis)able).*") }
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you need this both as a top-level predicate and as a member predicate of SecurityFeatureFlag below?

/**
* Holds if `name` suggests an old or legacy version.
*
* We accept 'intermediate' because it appears to be common for TLS users
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is that? The shared infrastructure just needs to know about FlagKind, the concrete subclass can be moved into the query.

smowton added 3 commits July 29, 2020 15:15
Move the flag regexes inline, use `any` instead of a constructor function to select a particular flag kind, and remove explicit limitation on the common superclass FlagKind.
@smowton
Copy link
Contributor Author

smowton commented Jul 29, 2020

@max-schaefer I believe all comments are now addressed

Copy link
Contributor

@max-schaefer max-schaefer left a comment

Choose a reason for hiding this comment

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

The handling of flags looks much nicer now, thanks! A few more minor comments.

/**
* @name Insecure TLS configuration
* @description If an application supports insecure TLS versions or ciphers, it may be vulnerable to
* man-in-the-middle and other attacks.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* man-in-the-middle and other attacks.
* machine-in-the-middle and other attacks.

cf #234 (comment)

import semmle.go.security.InsecureFeatureFlag::InsecureFeatureFlag

/**
* Holds if it is insecure to assign TLS version `val` named `named` to `tls.Config` field `fieldName`
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* Holds if it is insecure to assign TLS version `val` named `named` to `tls.Config` field `fieldName`
* Holds if it is insecure to assign TLS version `val` named `name` to `tls.Config` field `fieldName`.

*/
predicate isInsecureTlsVersion(int val, string name, string fieldName) {
(fieldName = "MinVersion" or fieldName = "MaxVersion") and
// tls.VersionSSL30
Copy link
Contributor

Choose a reason for hiding this comment

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

Hm, did autoformat put that here?

* Integer values corresponding to versions are defined at https://golang.org/pkg/crypto/tls/#pkg-constants
* Zero means the default version; at the time of writing, TLS 1.0.
*/
int getATlsVersion() { result in [768, 769, 770, 771, 772, 0] }
Copy link
Contributor

Choose a reason for hiding this comment

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

It's a bit unfortunate to have two copies of these magic constants (though admittedly they are close enough to each other that this shouldn't be a major problem). How about defining an isSecureTlsVersion predicate and then have

int getATlsVersion() { isInsecureTlsVersion(result, _) or isSecureTlsVersion(result) }

exists(ReturnStmt ret |
ret.getExpr(0) = node.asExpr() and
ret.getNumExpr() = 2 and
ret.getExpr(1).getType().implements(Builtin::error().getType().getUnderlyingType()) and
Copy link
Contributor

Choose a reason for hiding this comment

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

(Optional:) This seems worth having in the standard library, perhaps as

class ErrorType extends Type {
  ErrorType() { this.implements(Builtin::error().getType().getUnderlyingType()) }
}

(We are using the same idiom in at least one other place.)

ret.getExpr(0) = node.asExpr() and
ret.getNumExpr() = 2 and
ret.getExpr(1).getType().implements(Builtin::error().getType().getUnderlyingType()) and
ret.getExpr(1) != Builtin::nil().getAReference()
Copy link
Contributor

Choose a reason for hiding this comment

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

nil doesn't implement error, so I don't think this is necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Replaced with a comment

}

/**
* Holds if a secure TLS version may reach `base`.`fld`
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* Holds if a secure TLS version may reach `base`.`fld`
* Holds if a secure TLS version may reach `accessPath`.`fld`.

}

/** Gets a global value number representing a (likely) security flag. */
GVN getAFlag(FlagKind flagKind) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it perhaps make sense for this to be a member predicate of FlagKind?

@smowton
Copy link
Contributor Author

smowton commented Jul 30, 2020

@max-schaefer all changes applied, including the optional ones (as separate commits)

@max-schaefer
Copy link
Contributor

@owen-mc, @sauyon: are you fine with me merging this, or do you still have outstanding changes you'd like to see addressed?

@sauyon
Copy link
Contributor

sauyon commented Jul 31, 2020

I haven't looked at this very carefully since the changes were made, but generally I think my comments have been addressed.

@max-schaefer
Copy link
Contributor

OK, let's merge this, then. (@owen-mc, if you have any follow-up questions or comments feel free to post here, we can address them in a future PR.)

@max-schaefer max-schaefer merged commit b057cbe into github:master Aug 3, 2020
@sauyon
Copy link
Contributor

sauyon commented Aug 20, 2020

Did we ever get @shati-patel to take a look at the docs for this one?

@shati-patel
Copy link
Contributor

Did we ever get @shati-patel to take a look at the docs for this one?

From a quick glance, the docs are all good! It looks like previous reviewers caught any issues ☀️

@sauyon
Copy link
Contributor

sauyon commented Aug 21, 2020

Great, thanks!

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

Successfully merging this pull request may close these issues.

7 participants