diff --git a/config/identical-files.json b/config/identical-files.json index b066c443d9f..56aac560473 100644 --- a/config/identical-files.json +++ b/config/identical-files.json @@ -247,7 +247,8 @@ "javascript/ql/lib/semmle/javascript/security/internal/SensitiveDataHeuristics.qll", "python/ql/lib/semmle/python/security/internal/SensitiveDataHeuristics.qll", "ruby/ql/lib/codeql/ruby/security/internal/SensitiveDataHeuristics.qll", - "swift/ql/lib/codeql/swift/security/internal/SensitiveDataHeuristics.qll" + "swift/ql/lib/codeql/swift/security/internal/SensitiveDataHeuristics.qll", + "rust/ql/lib/codeql/rust/security/internal/SensitiveDataHeuristics.qll" ], "IncompleteUrlSubstringSanitization": [ "javascript/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.qll", diff --git a/rust/ql/lib/codeql/rust/security/SensitiveData.qll b/rust/ql/lib/codeql/rust/security/SensitiveData.qll new file mode 100644 index 00000000000..a8164e3ab7c --- /dev/null +++ b/rust/ql/lib/codeql/rust/security/SensitiveData.qll @@ -0,0 +1,86 @@ +/** + * Provides classes and predicates for identifying sensitive data. + * + * 'Sensitive' data is anything that should not be sent in unencrypted form. This library tries to + * guess where sensitive data may either be stored in a variable or produced by a method. + */ + +import rust +private import internal.SensitiveDataHeuristics +private import codeql.rust.dataflow.DataFlow + +/** + * A data flow node that might contain sensitive data. + */ +cached +abstract class SensitiveData extends DataFlow::Node { + /** + * Gets a classification of the kind of sensitive data this expression might contain. + */ + cached + abstract SensitiveDataClassification getClassification(); +} + +/** + * A function that might produce sensitive data. + */ +private class SensitiveDataFunction extends Function { + SensitiveDataClassification classification; + + SensitiveDataFunction() { + HeuristicNames::nameIndicatesSensitiveData(this.getName().getText(), classification) + } + + SensitiveDataClassification getClassification() { result = classification } +} + +/** + * A function call that might produce sensitive data. + */ +private class SensitiveDataCall extends SensitiveData { + SensitiveDataClassification classification; + + SensitiveDataCall() { + classification = + this.asExpr() + .getAstNode() + .(CallExprBase) + .getStaticTarget() + .(SensitiveDataFunction) + .getClassification() + } + + override SensitiveDataClassification getClassification() { result = classification } +} + +/** + * A variable that might contain sensitive data. + */ +private class SensitiveDataVariable extends Variable { + SensitiveDataClassification classification; + + SensitiveDataVariable() { + HeuristicNames::nameIndicatesSensitiveData(this.getName(), classification) + } + + SensitiveDataClassification getClassification() { result = classification } +} + +/** + * A variable access that might produce sensitive data. + */ +private class SensitiveVariableAccess extends SensitiveData { + SensitiveDataClassification classification; + + SensitiveVariableAccess() { + classification = + this.asExpr() + .getAstNode() + .(VariableAccess) + .getVariable() + .(SensitiveDataVariable) + .getClassification() + } + + override SensitiveDataClassification getClassification() { result = classification } +} diff --git a/rust/ql/lib/codeql/rust/security/internal/SensitiveDataHeuristics.qll b/rust/ql/lib/codeql/rust/security/internal/SensitiveDataHeuristics.qll new file mode 100644 index 00000000000..eb8a0c1fe75 --- /dev/null +++ b/rust/ql/lib/codeql/rust/security/internal/SensitiveDataHeuristics.qll @@ -0,0 +1,188 @@ +/** + * INTERNAL: Do not use. + * + * Provides classes and predicates for identifying strings that may indicate the presence of sensitive data. + * Such that we can share this logic across our CodeQL analysis of different languages. + * + * 'Sensitive' data in general is anything that should not be sent around in unencrypted form. + */ + +/** + * A classification of different kinds of sensitive data: + * + * - secret: generic secret or trusted data; + * - id: a user name or other account information; + * - password: a password or authorization key; + * - certificate: a certificate. + * - private: private data such as credit card numbers + * + * While classifications are represented as strings, this should not be relied upon. + * Instead, use the predicates in `SensitiveDataClassification::` to work with + * classifications. + */ +class SensitiveDataClassification extends string { + SensitiveDataClassification() { this in ["secret", "id", "password", "certificate", "private"] } +} + +/** + * Provides predicates to select the different kinds of sensitive data we support. + */ +module SensitiveDataClassification { + /** Gets the classification for secret or trusted data. */ + SensitiveDataClassification secret() { result = "secret" } + + /** Gets the classification for user names or other account information. */ + SensitiveDataClassification id() { result = "id" } + + /** Gets the classification for passwords or authorization keys. */ + SensitiveDataClassification password() { result = "password" } + + /** Gets the classification for certificates. */ + SensitiveDataClassification certificate() { result = "certificate" } + + /** Gets the classification for private data. */ + SensitiveDataClassification private() { result = "private" } +} + +/** + * INTERNAL: Do not use. + * + * Provides heuristics for identifying names related to sensitive information. + */ +module HeuristicNames { + /** + * Gets a regular expression that identifies strings that may indicate the presence of secret + * or trusted data. + */ + string maybeSecret() { result = "(?is).*((?; + +module SensitiveDataTest implements TestSig { + string getARelevantTag() { result = "sensitive" } + + predicate hasActualResult(Location location, string element, string tag, string value) { + exists(DataFlow::Node source, DataFlow::Node sink | + SensitiveDataFlow::flow(source, sink) and + location = sink.getLocation() and + element = sink.toString() and + tag = "sensitive" and + value = source.(SensitiveData).getClassification() + ) + } +} + +import MakeTest diff --git a/rust/ql/test/library-tests/sensitivedata/test.rs b/rust/ql/test/library-tests/sensitivedata/test.rs index e0c0c6aa50d..858526642c6 100644 --- a/rust/ql/test/library-tests/sensitivedata/test.rs +++ b/rust/ql/test/library-tests/sensitivedata/test.rs @@ -27,21 +27,21 @@ fn test_passwords( ms: &MyStruct ) { // passwords - sink(password); // $ MISSING: sensitive=password - sink(passwd); // $ MISSING: sensitive=password - sink(my_password); // $ MISSING: sensitive=password - sink(password_str); // $ MISSING: sensitive=password + sink(password); // $ sensitive=password + sink(passwd); // $ sensitive=password + sink(my_password); // $ sensitive=password + sink(password_str); // $ sensitive=password sink(pass_phrase); // $ MISSING: sensitive=password sink(auth_key); // $ MISSING: sensitive=password - sink(authenticationkey); // $ MISSING: sensitive=password - sink(authKey); // $ MISSING: sensitive=password + sink(authenticationkey); // $ sensitive=password + sink(authKey); // $ sensitive=password sink(ms); // $ MISSING: sensitive=password sink(ms.password.as_str()); // $ MISSING: sensitive=password - sink(get_password()); // $ MISSING: sensitive=password + sink(get_password()); // $ sensitive=password let password2 = get_string(); - sink(password2); // $ MISSING: sensitive=password + sink(password2); // $ sensitive=password // not passwords sink(harmless); @@ -69,25 +69,25 @@ fn test_credentials( ms: &MyStruct ) { // credentials - sink(account_key); // $ MISSING: sensitive=secret - sink(accnt_key); // $ MISSING: sensitive=secret + sink(account_key); // $ sensitive=id + sink(accnt_key); // $ sensitive=id sink(license_key); // $ MISSING: sensitive=secret - sink(secret_key); // $ MISSING: sensitive=secret + sink(secret_key); // $ sensitive=secret - sink(ms.get_certificate()); // $ MISSING: sensitive=certificate + sink(ms.get_certificate()); // $ sensitive=certificate - sink(generate_secret_key()); // $ MISSING: sensitive=secret + sink(generate_secret_key()); // $ sensitive=secret sink(get_secure_key()); // $ MISSING: sensitive=secret sink(get_private_key()); // $ MISSING: sensitive=secret - sink(get_secret_token()); // $ MISSING: sensitive=secret + sink(get_secret_token()); // $ sensitive=secret // not credentials sink(is_secret); - sink(num_accounts); - sink(uid); + sink(num_accounts); // $ SPURIOUS: sensitive=id + sink(uid); // $ SPURIOUS: sensitive=id - sink(ms.get_certificate_url()); - sink(ms.get_certificate_file()); + sink(ms.get_certificate_url()); // $ SPURIOUS: sensitive=certificate + sink(ms.get_certificate_file()); // $ SPURIOUS: sensitive=certificate sink(get_public_key()); sink(get_next_token());