From 5f6cb1684b012038ed64c219822def1addd48839 Mon Sep 17 00:00:00 2001
From: erik-krogh
+When a library function dynamically constructs code in a potentially unsafe way, then
+it's important to document to clients of the library that the function should only be
+used with trusted inputs.
+
+If the function is not documented as being potentially unsafe, then a client may
+incorrectly use inputs containing unsafe code fragments, and thereby leave the
+client vulnerable to code-injection attacks.
+
+Properly document library functions that construct code from unsanitized
+inputs, or avoid constructing code in the first place.
+
+The following example shows two methods implemented using `eval`: a simple
+deserialization routine and a getter method.
+If untrusted inputs are used with these methods,
+then an attacker might be able to execute arbitrary code on the system.
+
+To avoid this problem, either properly document that the function is potentially
+unsafe, or use an alternative solution such as `JSON.parse` or another library, like in the examples below,
+that does not allow arbitrary code to be executed.
+
-The following example shows two methods implemented using `eval`: a simple
+The following example shows two methods implemented using
To avoid this problem, either properly document that the function is potentially
-unsafe, or use an alternative solution such as `JSON.parse` or another library, like in the examples below,
+unsafe, or use an alternative solution such as
+As another example, consider the below code which dynamically constructs
+a class that has a getter method with a custom name.
+
+The example dynamically constructs a string which is then executed using
+A more robust implementation, that is also immune to code injection,
+can be made by using
+This example dynamically registers a method on another class which
+forwards its arguments to the registering module. This approach uses
+
+A safer approach is to use
This example dynamically registers a method on another class which
-forwards its arguments to the registering module. This approach uses
+forwards its arguments to a target class. This approach uses
eval: a simple
deserialization routine and a getter method.
If untrusted inputs are used with these methods,
then an attacker might be able to execute arbitrary code on the system.
@@ -35,7 +35,7 @@ then an attacker might be able to execute arbitrary code on the system.
JSON.parse or another library, like in the examples below,
that does not allow arbitrary code to be executed.
module_eval.
+This code will break if the specified name is not a valid Ruby identifier, and
+if the value is controlled by an attacker, then this could lead to code injection.
+module_eval with a block and using define_method
+to define the getter method.
+module_eval and string interpolation to construct class variables
+and methods.
+class_variable_set and
+class_variable_get along with define_method. String
+interpolation is still used to construct the class variable name, but this is
+safe because class_variable_set is not susceptible to code injection.
+define_method.
+class_variable_set.
+module_eval and string interpolation to construct class variables
and methods.
class_variable_set and
class_variable_get along with define_method. String
interpolation is still used to construct the class variable name, but this is
safe because class_variable_set is not susceptible to code injection.
+To construct a dynamic method call we use send, which is ulnerable
+to code injection: if an attacker can control the first argument, they can call
+any method on the receiver. However this is less powerful than being able to run
+arbitrary Ruby code, so it is an improvement in security. We also document to
+callers that they should not pass arbitrary user data to the name
+parameter.
class_variable_set and
class_variable_get along with define_method. String
interpolation is still used to construct the class variable name, but this is
-safe because class_variable_set is not susceptible to code injection.
+safe because class_variable_set is not susceptible to code injection.
To construct a dynamic method call we use send, which is ulnerable
to code injection: if an attacker can control the first argument, they can call
any method on the receiver. However this is less powerful than being able to run
@@ -101,10 +101,10 @@ OWASP:
Wikipedia: Code Injection.
-Ruby documentation: define_method.
+Ruby documentation: define_method.
-Ruby documentation: class_variable_set.
+Ruby documentation: class_variable_set.
From 3811eae67946eba9814c4945fd77e554890dc76c Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Mon, 2 Jan 2023 11:26:58 +0100
Subject: [PATCH 17/41] simplify the qhelp for unsafe-code-construction The
`send()` example is not flagged by any current query, so it was weird talking
about it as "vulnerable".
---
.../cwe-094/UnsafeCodeConstruction.qhelp | 19 ++++++++++---------
.../examples/UnsafeCodeConstruction3Safe.rb | 1 -
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/ruby/ql/src/queries/security/cwe-094/UnsafeCodeConstruction.qhelp b/ruby/ql/src/queries/security/cwe-094/UnsafeCodeConstruction.qhelp
index 63f566aaa57..6936aee149d 100644
--- a/ruby/ql/src/queries/security/cwe-094/UnsafeCodeConstruction.qhelp
+++ b/ruby/ql/src/queries/security/cwe-094/UnsafeCodeConstruction.qhelp
@@ -54,11 +54,11 @@ a class that has a getter method with a custom name.
The example dynamically constructs a string which is then executed using module_eval.
This code will break if the specified name is not a valid Ruby identifier, and
-if the value is controlled by an attacker, then this could lead to code injection.
+if the value is controlled by an attacker, then this could lead to code-injection.
-A more robust implementation, that is also immune to code injection,
+A more robust implementation, that is also immune to code-injection,
can be made by using module_eval with a block and using define_method
to define the getter method.
@@ -80,13 +80,14 @@ and methods.
A safer approach is to use class_variable_set and
class_variable_get along with define_method. String
interpolation is still used to construct the class variable name, but this is
-safe because class_variable_set is not susceptible to code injection.
-To construct a dynamic method call we use send, which is ulnerable
-to code injection: if an attacker can control the first argument, they can call
-any method on the receiver. However this is less powerful than being able to run
-arbitrary Ruby code, so it is an improvement in security. We also document to
-callers that they should not pass arbitrary user data to the name
-parameter.
+safe because class_variable_set is not susceptible to code-injection.
+
+
+
+send is used to dynamically call the method specified by name.
+This is a more robust alternative than the previous example, because it does not allow
+arbitrary code to be executed, but it does still allow for any method to be called
+on the target object.
diff --git a/ruby/ql/src/queries/security/cwe-094/examples/UnsafeCodeConstruction3Safe.rb b/ruby/ql/src/queries/security/cwe-094/examples/UnsafeCodeConstruction3Safe.rb
index 65608f8aa20..ac32224ce72 100644
--- a/ruby/ql/src/queries/security/cwe-094/examples/UnsafeCodeConstruction3Safe.rb
+++ b/ruby/ql/src/queries/security/cwe-094/examples/UnsafeCodeConstruction3Safe.rb
@@ -1,5 +1,4 @@
module Invoker
- # Do not pass arbitrary user input to +name+.
def attach(klass, name, target)
var = :"@@#{name}"
klass.class_variable_set(var, target)
From d9176541c63639425155986462f91b67c7f7d083 Mon Sep 17 00:00:00 2001
From: Erik Krogh Kristensen
Date: Thu, 5 Jan 2023 20:02:54 +0100
Subject: [PATCH 18/41] Apply suggestions from code review
Co-authored-by: Alex Ford
---
.../ruby/security/UnsafeCodeConstructionCustomizations.qll | 4 ++--
.../src/queries/security/cwe-094/UnsafeCodeConstruction.qhelp | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/ruby/ql/lib/codeql/ruby/security/UnsafeCodeConstructionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/UnsafeCodeConstructionCustomizations.qll
index b6c9c3b5219..68c894f4705 100644
--- a/ruby/ql/lib/codeql/ruby/security/UnsafeCodeConstructionCustomizations.qll
+++ b/ruby/ql/lib/codeql/ruby/security/UnsafeCodeConstructionCustomizations.qll
@@ -81,10 +81,10 @@ module UnsafeCodeConstruction {
* A string constructed from a string-literal (e.g. `"foo #{sink}"`),
* where the resulting string ends up being executed as a code.
*/
- class StringFormatAsSink extends Sink {
+ class StringInterpolationAsSink extends Sink {
Concepts::CodeExecution s;
- StringFormatAsSink() {
+ StringInterpolationAsSink() {
exists(Ast::StringlikeLiteral lit |
any(DataFlow::Node n | n.asExpr().getExpr() = lit) = getANodeExecutedAsCode(s) and
this.asExpr().getExpr() = lit.getComponent(_)
diff --git a/ruby/ql/src/queries/security/cwe-094/UnsafeCodeConstruction.qhelp b/ruby/ql/src/queries/security/cwe-094/UnsafeCodeConstruction.qhelp
index 6936aee149d..f6dcb452661 100644
--- a/ruby/ql/src/queries/security/cwe-094/UnsafeCodeConstruction.qhelp
+++ b/ruby/ql/src/queries/security/cwe-094/UnsafeCodeConstruction.qhelp
@@ -69,7 +69,7 @@ to define the getter method.
This example dynamically registers a method on another class which
-forwards its arguments to a target class. This approach uses
+forwards its arguments to a target object. This approach uses
module_eval and string interpolation to construct class variables
and methods.
From f98ff65b11c1c07565283c05ba4b759c10d65832 Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Thu, 5 Jan 2023 20:03:51 +0100
Subject: [PATCH 19/41] use eval() instead of send() in test
---
.../security/cwe-094/UnsafeCodeConstruction/impl/unsafeCode.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ruby/ql/test/query-tests/security/cwe-094/UnsafeCodeConstruction/impl/unsafeCode.rb b/ruby/ql/test/query-tests/security/cwe-094/UnsafeCodeConstruction/impl/unsafeCode.rb
index 64bba0aadc6..27dd1d81f01 100644
--- a/ruby/ql/test/query-tests/security/cwe-094/UnsafeCodeConstruction/impl/unsafeCode.rb
+++ b/ruby/ql/test/query-tests/security/cwe-094/UnsafeCodeConstruction/impl/unsafeCode.rb
@@ -22,7 +22,7 @@ class Foobar
end
def named_code(code)
- foo.send("def \n #{code} \n end") # OK - parameter is named code
+ eval("def \n #{code} \n end") # OK - parameter is named code
end
def joinStuff(my_arr)
From aad56097ac93e1b5c7d217d91e201e1f6141dd79 Mon Sep 17 00:00:00 2001
From: Tony Torralba
Date: Wed, 30 Nov 2022 13:04:10 +0100
Subject: [PATCH 20/41] Add Cleartext Loggin query for Swift.
With some caveats: see TODO comments and failing tests.
---
.../codeql/swift/dataflow/ExternalFlow.qll | 1 +
.../swift/security/CleartextLogging.qll | 67 +++++++++
.../swift/security/CleartextLoggingQuery.qll | 32 +++++
.../Security/CWE-312/CleartextLogging.qhelp | 50 +++++++
.../Security/CWE-312/CleartextLogging.ql | 24 ++++
.../CWE-312/CleartextLoggingBad.swift | 0
.../CWE-312/CleartextLoggingGood.swift | 0
.../CWE-312/CleartextLoggingTest.expected | 0
.../Security/CWE-312/CleartextLoggingTest.ql | 24 ++++
.../CWE-312/cleartextLoggingTest.swift | 130 ++++++++++++++++++
10 files changed, 328 insertions(+)
create mode 100644 swift/ql/lib/codeql/swift/security/CleartextLogging.qll
create mode 100644 swift/ql/lib/codeql/swift/security/CleartextLoggingQuery.qll
create mode 100644 swift/ql/src/queries/Security/CWE-312/CleartextLogging.qhelp
create mode 100644 swift/ql/src/queries/Security/CWE-312/CleartextLogging.ql
create mode 100644 swift/ql/src/queries/Security/CWE-312/CleartextLoggingBad.swift
create mode 100644 swift/ql/src/queries/Security/CWE-312/CleartextLoggingGood.swift
create mode 100644 swift/ql/test/query-tests/Security/CWE-312/CleartextLoggingTest.expected
create mode 100644 swift/ql/test/query-tests/Security/CWE-312/CleartextLoggingTest.ql
create mode 100644 swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift
diff --git a/swift/ql/lib/codeql/swift/dataflow/ExternalFlow.qll b/swift/ql/lib/codeql/swift/dataflow/ExternalFlow.qll
index 2c90d1e7351..60f8bc9782b 100644
--- a/swift/ql/lib/codeql/swift/dataflow/ExternalFlow.qll
+++ b/swift/ql/lib/codeql/swift/dataflow/ExternalFlow.qll
@@ -89,6 +89,7 @@ private module Frameworks {
private import codeql.swift.frameworks.StandardLibrary.UrlSession
private import codeql.swift.frameworks.StandardLibrary.WebView
private import codeql.swift.frameworks.Alamofire.Alamofire
+ private import codeql.swift.security.CleartextLogging
private import codeql.swift.security.PathInjection
private import codeql.swift.security.PredicateInjection
}
diff --git a/swift/ql/lib/codeql/swift/security/CleartextLogging.qll b/swift/ql/lib/codeql/swift/security/CleartextLogging.qll
new file mode 100644
index 00000000000..9e5e551effd
--- /dev/null
+++ b/swift/ql/lib/codeql/swift/security/CleartextLogging.qll
@@ -0,0 +1,67 @@
+/** Provides classes and predicates to reason about cleartext logging of sensitive data vulnerabilities. */
+
+import swift
+private import codeql.swift.dataflow.DataFlow
+private import codeql.swift.dataflow.ExternalFlow
+private import codeql.swift.security.SensitiveExprs
+
+/** A data flow sink for cleartext logging of sensitive data vulnerabilities. */
+abstract class CleartextLoggingSink extends DataFlow::Node { }
+
+/** A sanitizer for cleartext logging of sensitive data vulnerabilities. */
+abstract class CleartextLoggingSanitizer extends DataFlow::Node { }
+
+/**
+ * A unit class for adding additional taint steps.
+ *
+ * Extend this class to add additional taint steps that should apply to paths related to
+ * cleartext logging of sensitive data vulnerabilities.
+ */
+class CleartextLoggingAdditionalTaintStep extends Unit {
+ /**
+ * Holds if the step from `n1` to `n2` should be considered a taint
+ * step for flows related to cleartext logging of sensitive data vulnerabilities.
+ */
+ abstract predicate step(DataFlow::Node n1, DataFlow::Node n2);
+}
+
+private class DefaultCleartextLoggingSink extends CleartextLoggingSink {
+ DefaultCleartextLoggingSink() { sinkNode(this, "logging") }
+}
+
+// TODO: Remove this. It shouldn't be necessary.
+private class EncryptionCleartextLoggingSanitizer extends CleartextLoggingSanitizer {
+ EncryptionCleartextLoggingSanitizer() { this.asExpr() instanceof EncryptedExpr }
+}
+
+/*
+ * TODO: Add a sanitizer for the OsLogMessage interpolation with .private/.sensitive privacy options,
+ * or restrict the sinks to require .public interpolation depending on what the default behavior is.
+ */
+
+private class LoggingSinks extends SinkModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ ";;false;print(_:separator:terminator:);;;Argument[0].ArrayElement;logging",
+ ";;false;print(_:separator:terminator:);;;Argument[1..2];logging",
+ ";;false;print(_:separator:terminator:toStream:);;;Argument[0].ArrayElement;logging",
+ ";;false;print(_:separator:terminator:toStream:);;;Argument[1..2];logging",
+ ";;false;NSLog(_:_:);;;Argument[0];logging",
+ ";;false;NSLog(_:_:);;;Argument[1].ArrayElement;logging",
+ ";;false;NSLogv(_:_:);;;Argument[0];logging",
+ ";;false;NSLogv(_:_:);;;Argument[1].ArrayElement;logging",
+ ";;false;vfprintf(_:_:_:);;;Agument[1..2];logging",
+ ";Logger;true;log(_:);;;Argument[0];logging",
+ ";Logger;true;log(level:_:);;;Argument[1];logging",
+ ";Logger;true;trace(_:);;;Argument[1];logging",
+ ";Logger;true;debug(_:);;;Argument[1];logging",
+ ";Logger;true;info(_:);;;Argument[1];logging",
+ ";Logger;true;notice(_:);;;Argument[1];logging",
+ ";Logger;true;warning(_:);;;Argument[1];logging",
+ ";Logger;true;error(_:);;;Argument[1];logging",
+ ";Logger;true;critical(_:);;;Argument[1];logging",
+ ";Logger;true;fault(_:);;;Argument[1];logging",
+ ]
+ }
+}
diff --git a/swift/ql/lib/codeql/swift/security/CleartextLoggingQuery.qll b/swift/ql/lib/codeql/swift/security/CleartextLoggingQuery.qll
new file mode 100644
index 00000000000..d67f66e74b9
--- /dev/null
+++ b/swift/ql/lib/codeql/swift/security/CleartextLoggingQuery.qll
@@ -0,0 +1,32 @@
+/**
+ * Provides a taint-tracking configuration for reasoning about cleartext logging of
+ * sensitive data vulnerabilities.
+ */
+
+import swift
+private import codeql.swift.dataflow.DataFlow
+private import codeql.swift.dataflow.TaintTracking
+private import codeql.swift.security.CleartextLogging
+private import codeql.swift.security.SensitiveExprs
+
+/**
+ * A taint-tracking configuration for cleartext logging of sensitive data vulnerabilities.
+ */
+class CleartextLoggingConfiguration extends TaintTracking::Configuration {
+ CleartextLoggingConfiguration() { this = "CleartextLoggingConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) { source.asExpr() instanceof SensitiveExpr }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof CleartextLoggingSink }
+
+ override predicate isSanitizer(DataFlow::Node sanitizer) {
+ sanitizer instanceof CleartextLoggingSanitizer
+ }
+
+ // Disregard paths that contain other paths. This helps with performance.
+ override predicate isSanitizerIn(DataFlow::Node node) { this.isSource(node) }
+
+ override predicate isAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) {
+ any(CleartextLoggingAdditionalTaintStep s).step(n1, n2)
+ }
+}
diff --git a/swift/ql/src/queries/Security/CWE-312/CleartextLogging.qhelp b/swift/ql/src/queries/Security/CWE-312/CleartextLogging.qhelp
new file mode 100644
index 00000000000..fc5c7ed6f5e
--- /dev/null
+++ b/swift/ql/src/queries/Security/CWE-312/CleartextLogging.qhelp
@@ -0,0 +1,50 @@
+
+
+
+
+
+Sensitive information that is logged unencrypted is accessible to an attacker
+who gains access to the logs.
+
+
+
+
+
+Ensure that sensitive information is always encrypted or obfuscated before being
+logged.
+
+
+
+In general, decrypt sensitive information only at the point where it is
+necessary for it to be used in cleartext.
+
+
+
+Be aware that external processes often store the standard out and
+standard error streams of the application, causing logged sensitive
+information to be stored.
+
+
+
+
+
+The following example code logs user credentials (in this case, their password)
+in plain text:
+
+
+
+Instead, the credentials should be encrypted, obfuscated, or omitted entirely:
+
+
+
+
+
+
+M. Dowd, J. McDonald and J. Schuhm, The Art of Software Security Assessment, 1st Edition, Chapter 2 - 'Common Vulnerabilities of Encryption', p. 43. Addison Wesley, 2006.
+M. Howard and D. LeBlanc, Writing Secure Code, 2nd Edition, Chapter 9 - 'Protecting Secret Data', p. 299. Microsoft, 2002.
+OWASP: Password Plaintext Storage.
+
+
+
diff --git a/swift/ql/src/queries/Security/CWE-312/CleartextLogging.ql b/swift/ql/src/queries/Security/CWE-312/CleartextLogging.ql
new file mode 100644
index 00000000000..2b21b5da1bc
--- /dev/null
+++ b/swift/ql/src/queries/Security/CWE-312/CleartextLogging.ql
@@ -0,0 +1,24 @@
+/**
+ * @name Clear-text logging of sensitive information
+ * @description Logging sensitive information without encryption or hashing can
+ * expose it to an attacker.
+ * @kind path-problem
+ * @problem.severity error
+ * @security-severity 7.5
+ * @precision high
+ * @id swift/clear-text-logging
+ * @tags security
+ * external/cwe/cwe-312
+ * external/cwe/cwe-359
+ * external/cwe/cwe-532
+ */
+
+import swift
+import codeql.swift.dataflow.DataFlow
+import codeql.swift.security.CleartextLoggingQuery
+import DataFlow::PathGraph
+
+from DataFlow::PathNode src, DataFlow::PathNode sink
+where any(CleartextLoggingConfiguration c).hasFlowPath(src, sink)
+select sink.getNode(), src, sink, "This $@ is written to a log file.", src.getNode(),
+ "potentially sensitive information"
diff --git a/swift/ql/src/queries/Security/CWE-312/CleartextLoggingBad.swift b/swift/ql/src/queries/Security/CWE-312/CleartextLoggingBad.swift
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/swift/ql/src/queries/Security/CWE-312/CleartextLoggingGood.swift b/swift/ql/src/queries/Security/CWE-312/CleartextLoggingGood.swift
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/swift/ql/test/query-tests/Security/CWE-312/CleartextLoggingTest.expected b/swift/ql/test/query-tests/Security/CWE-312/CleartextLoggingTest.expected
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/swift/ql/test/query-tests/Security/CWE-312/CleartextLoggingTest.ql b/swift/ql/test/query-tests/Security/CWE-312/CleartextLoggingTest.ql
new file mode 100644
index 00000000000..d57dc15bbad
--- /dev/null
+++ b/swift/ql/test/query-tests/Security/CWE-312/CleartextLoggingTest.ql
@@ -0,0 +1,24 @@
+import swift
+import codeql.swift.dataflow.DataFlow
+import codeql.swift.security.CleartextLoggingQuery
+import TestUtilities.InlineExpectationsTest
+
+class CleartextLogging extends InlineExpectationsTest {
+ CleartextLogging() { this = "CleartextLogging" }
+
+ override string getARelevantTag() { result = "hasCleartextLogging" }
+
+ override predicate hasActualResult(Location location, string element, string tag, string value) {
+ exists(
+ CleartextLoggingConfiguration config, DataFlow::Node source, DataFlow::Node sink,
+ Expr sinkExpr
+ |
+ config.hasFlow(source, sink) and
+ sinkExpr = sink.asExpr() and
+ location = sinkExpr.getLocation() and
+ element = sinkExpr.toString() and
+ tag = "hasCleartextLogging" and
+ value = source.asExpr().getLocation().getStartLine().toString()
+ )
+ }
+}
diff --git a/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift b/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift
new file mode 100644
index 00000000000..659ff02840d
--- /dev/null
+++ b/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift
@@ -0,0 +1,130 @@
+// --- stubs ---
+
+func NSLog(_ format: String, _ args: CVarArg...) {}
+func NSLogv(_ format: String, _ args: CVaListPointer) {}
+
+struct OSLogType : RawRepresentable {
+ static let `default` = OSLogType(rawValue: 0)
+ let rawValue: UInt8
+ init(rawValue: UInt8) { self.rawValue = rawValue}
+}
+
+struct OSLogStringAlignment {
+ static var none = OSLogStringAlignment()
+}
+
+struct OSLogPrivacy {
+ enum Mask { case none }
+ static var auto = OSLogPrivacy()
+ static var `private` = OSLogPrivacy()
+ static var sensitive = OSLogPrivacy()
+
+ static func auto(mask: OSLogPrivacy.Mask) -> OSLogPrivacy { return .auto }
+ static func `private`(mask: OSLogPrivacy.Mask) -> OSLogPrivacy { return .private }
+ static func `sensitive`(mask: OSLogPrivacy.Mask) -> OSLogPrivacy { return .sensitive }
+}
+
+struct OSLogInterpolation : StringInterpolationProtocol {
+ typealias StringLiteralType = String
+ init(literalCapacity: Int, interpolationCount: Int) {}
+ func appendLiteral(_: Self.StringLiteralType) {}
+ mutating func appendInterpolation(_ argumentString: @autoclosure @escaping () -> String, align: OSLogStringAlignment = .none, privacy: OSLogPrivacy = .auto) {}
+ mutating func appendInterpolation(_ argumentString: @autoclosure @escaping () -> String, align: OSLogStringAlignment = .none, privacy: OSLogPrivacy = .auto, attributes: String) {}
+}
+
+struct OSLogMessage : ExpressibleByStringInterpolation {
+ typealias StringInterpolation = OSLogInterpolation
+ typealias StringLiteralType = String
+ typealias ExtendedGraphemeClusterLiteralType = String
+ typealias UnicodeScalarLiteralType = String
+ init(stringInterpolation: OSLogInterpolation) {}
+ init(stringLiteral: String) {}
+ init(extendedGraphemeClusterLiteral: String) {}
+ init(unicodeScalarLiteral: String) {}
+}
+
+struct Logger {
+ func log(_ message: OSLogMessage) {}
+ func log(level: OSLogType, _ message: OSLogMessage) {}
+ func notice(_: OSLogMessage) {}
+ func debug(_: OSLogMessage) {}
+ func trace(_: OSLogMessage) {}
+ func info(_: OSLogMessage) {}
+ func error(_: OSLogMessage) {}
+ func warning(_: OSLogMessage) {}
+ func fault(_: OSLogMessage) {}
+ func critical(_: OSLogMessage) {}
+
+}
+
+// --- tests ---
+
+func test1(password: String, passwordHash : String) {
+ print(password) // $ MISSING: hasCleartextLogging=63
+ print(password, separator: "") // $ MISSING: $ hasCleartextLogging=64
+ print("", separator: password) // $ hasCleartextLogging=65
+ print(password, separator: "", terminator: "") // $ MISSING: hasCleartextLogging=66
+ print("", separator: password, terminator: "") // $ hasCleartextLogging=67
+ print("", separator: "", terminator: password) // $ hasCleartextLogging=68
+
+ NSLog(password) // $ hasCleartextLogging=70
+ NSLog("%@", password as! CVarArg) // $ MISSING: hasCleartextLogging=71
+ NSLog("%@ %@", "" as! CVarArg, password as! CVarArg) // $ MISSING: hasCleartextLogging=72
+ NSLog("\(password)") // $ hasCleartextLogging=73
+ NSLogv("%@", getVaList([password as! CVarArg])) // $ MISSING: hasCleartextLogging=74
+ NSLogv("%@ %@", getVaList(["" as! CVarArg, password as! CVarArg])) // $ MISSING: hasCleartextLogging=75
+
+ let log = Logger()
+ log.log("\(password)") // $ hasCleartextLogging=78
+ log.log("\(password, privacy: .private)") // Safe
+ log.log(level: .default, "\(password)") // $ hasCleartextLogging=80
+ log.trace("\(password)") // $ hasCleartextLogging=81
+ log.debug("\(password)") // $ hasCleartextLogging=82
+ log.info("\(password)") // $ hasCleartextLogging=83
+ log.notice("\(password)") // $ hasCleartextLogging=84
+ log.warning("\(password)") // $ hasCleartextLogging=85
+ log.error("\(password)") // $ hasCleartextLogging=86
+ log.critical("\(password)") // $ hasCleartextLogging=87
+ log.fault("\(password)") // $ hasCleartextLogging=88
+}
+/*
+class MyClass {
+ var harmless = "abc"
+ var password = "123"
+}
+
+func test3(x: String) {
+ // alternative evidence of sensitivity...
+
+ UserDefaults.standard.set(x, forKey: "myKey") // $ MISSING: hasCleartextLogging
+ doSomething(password: x);
+ UserDefaults.standard.set(x, forKey: "myKey") // $ hasCleartextLogging
+
+ let y = getPassword();
+ UserDefaults.standard.set(y, forKey: "myKey") // $ hasCleartextLogging
+
+ let z = MyClass()
+ UserDefaults.standard.set(z.harmless, forKey: "myKey") // Safe
+ UserDefaults.standard.set(z.password, forKey: "myKey") // $ hasCleartextLogging
+}
+
+func test4(passwd: String) {
+ // sanitizers...
+
+ var x = passwd;
+ var y = passwd;
+ var z = passwd;
+
+ UserDefaults.standard.set(x, forKey: "myKey") // $ hasCleartextLogging
+ UserDefaults.standard.set(y, forKey: "myKey") // $ hasCleartextLogging
+ UserDefaults.standard.set(z, forKey: "myKey") // $ hasCleartextLogging
+
+ x = encrypt(x);
+ hash(data: &y);
+ z = "";
+
+ UserDefaults.standard.set(x, forKey: "myKey") // Safe
+ UserDefaults.standard.set(y, forKey: "myKey") // Safe
+ UserDefaults.standard.set(z, forKey: "myKey") // Safe
+}
+*/
\ No newline at end of file
From b203a9eb6e90344c6955c5a74a7075a53253a056 Mon Sep 17 00:00:00 2001
From: Tony Torralba
Date: Wed, 7 Dec 2022 18:31:32 +0100
Subject: [PATCH 21/41] Add a sanitizer for OSLogPrivacy options
Add test cases to verify how the sanitizer behaves depending on the argument type and the privacy option being used.
---
.../swift/security/CleartextLogging.qll | 50 ++++++++++--
.../CWE-312/cleartextLoggingTest.swift | 77 +++++++++++++------
2 files changed, 98 insertions(+), 29 deletions(-)
diff --git a/swift/ql/lib/codeql/swift/security/CleartextLogging.qll b/swift/ql/lib/codeql/swift/security/CleartextLogging.qll
index 9e5e551effd..e6261543077 100644
--- a/swift/ql/lib/codeql/swift/security/CleartextLogging.qll
+++ b/swift/ql/lib/codeql/swift/security/CleartextLogging.qll
@@ -29,15 +29,51 @@ private class DefaultCleartextLoggingSink extends CleartextLoggingSink {
DefaultCleartextLoggingSink() { sinkNode(this, "logging") }
}
-// TODO: Remove this. It shouldn't be necessary.
-private class EncryptionCleartextLoggingSanitizer extends CleartextLoggingSanitizer {
- EncryptionCleartextLoggingSanitizer() { this.asExpr() instanceof EncryptedExpr }
+/**
+ * A sanitizer for `OSLogMessage`s configured with the appropriate privacy option.
+ * Numeric and boolean arguments aren't redacted unless the `public` option is used.
+ * Arguments of other types are always redacted unless the `private` or `sensitive` options are used.
+ */
+private class OsLogPrivacyCleartextLoggingSanitizer extends CleartextLoggingSanitizer {
+ OsLogPrivacyCleartextLoggingSanitizer() {
+ exists(CallExpr c, AutoClosureExpr e |
+ c.getStaticTarget().getName().matches("appendInterpolation(_:%privacy:%)") and
+ c.getArgument(0).getExpr() = e and
+ this.asExpr() = e
+ |
+ e.getExpr().getType() instanceof OsLogNonRedactedType and
+ c.getArgumentWithLabel("privacy").getExpr().(OsLogPrivacyRef).isSafe()
+ or
+ not e.getExpr().getType() instanceof OsLogNonRedactedType and
+ not c.getArgumentWithLabel("privacy").getExpr().(OsLogPrivacyRef).isPublic()
+ )
+ }
}
-/*
- * TODO: Add a sanitizer for the OsLogMessage interpolation with .private/.sensitive privacy options,
- * or restrict the sinks to require .public interpolation depending on what the default behavior is.
- */
+/** A type that isn't redacted by default in an `OSLogMessage`. */
+private class OsLogNonRedactedType extends Type {
+ OsLogNonRedactedType() {
+ this.getName() = [["", "U"] + "Int" + ["", "16", "32", "64"], "Double", "Float", "Bool"]
+ }
+}
+
+/** A reference to a field of `OsLogPrivacy`. */
+private class OsLogPrivacyRef extends MemberRefExpr {
+ string optionName;
+
+ OsLogPrivacyRef() {
+ exists(FieldDecl f | this.getMember() = f |
+ f.getEnclosingDecl().(NominalTypeDecl).getName() = "OSLogPrivacy" and
+ optionName = f.getName()
+ )
+ }
+
+ /** Holds if this is a safe privacy option (private or sensitive). */
+ predicate isSafe() { optionName = ["private", "sensitive"] }
+
+ /** Holds if this is a public (that is, unsafe) privacy option. */
+ predicate isPublic() { optionName = "public" }
+}
private class LoggingSinks extends SinkModelCsv {
override predicate row(string row) {
diff --git a/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift b/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift
index 659ff02840d..21d1387fb79 100644
--- a/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift
+++ b/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift
@@ -13,10 +13,16 @@ struct OSLogStringAlignment {
static var none = OSLogStringAlignment()
}
+enum OSLogIntegerFormatting { case decimal }
+enum OSLogInt32ExtendedFormat { case none }
+enum OSLogFloatFormatting { case fixed }
+enum OSLogBoolFormat { case truth }
+
struct OSLogPrivacy {
enum Mask { case none }
static var auto = OSLogPrivacy()
static var `private` = OSLogPrivacy()
+ static var `public` = OSLogPrivacy()
static var sensitive = OSLogPrivacy()
static func auto(mask: OSLogPrivacy.Mask) -> OSLogPrivacy { return .auto }
@@ -30,6 +36,23 @@ struct OSLogInterpolation : StringInterpolationProtocol {
func appendLiteral(_: Self.StringLiteralType) {}
mutating func appendInterpolation(_ argumentString: @autoclosure @escaping () -> String, align: OSLogStringAlignment = .none, privacy: OSLogPrivacy = .auto) {}
mutating func appendInterpolation(_ argumentString: @autoclosure @escaping () -> String, align: OSLogStringAlignment = .none, privacy: OSLogPrivacy = .auto, attributes: String) {}
+ mutating func appendInterpolation(_ number: @autoclosure @escaping () -> Int, format: OSLogIntegerFormatting = .decimal, align: OSLogStringAlignment = .none, privacy: OSLogPrivacy = .auto) {}
+ mutating func appendInterpolation(_ number: @autoclosure @escaping () -> Int8, format: OSLogIntegerFormatting = .decimal, align: OSLogStringAlignment = .none, privacy: OSLogPrivacy = .auto) {}
+ mutating func appendInterpolation(_ number: @autoclosure @escaping () -> Int16, format: OSLogIntegerFormatting = .decimal, align: OSLogStringAlignment = .none, privacy: OSLogPrivacy = .auto) {}
+ mutating func appendInterpolation(_ number: @autoclosure @escaping () -> Int32, format: OSLogInt32ExtendedFormat, privacy: OSLogPrivacy = .auto) {}
+ mutating func appendInterpolation(_ number: @autoclosure @escaping () -> Int32, format: OSLogInt32ExtendedFormat, privacy: OSLogPrivacy = .auto, attributes: String) {}
+ mutating func appendInterpolation(_ number: @autoclosure @escaping () -> Int32, format: OSLogIntegerFormatting = .decimal, align: OSLogStringAlignment = .none, privacy: OSLogPrivacy = .auto) {}
+ mutating func appendInterpolation(_ number: @autoclosure @escaping () -> Int64, format: OSLogIntegerFormatting = .decimal, align: OSLogStringAlignment = .none, privacy: OSLogPrivacy = .auto) {}
+ mutating func appendInterpolation(_ number: @autoclosure @escaping () -> UInt, format: OSLogIntegerFormatting = .decimal, align: OSLogStringAlignment = .none, privacy: OSLogPrivacy = .auto) {}
+ mutating func appendInterpolation(_ number: @autoclosure @escaping () -> UInt8, format: OSLogIntegerFormatting = .decimal, align: OSLogStringAlignment = .none, privacy: OSLogPrivacy = .auto) {}
+ mutating func appendInterpolation(_ number: @autoclosure @escaping () -> UInt16, format: OSLogIntegerFormatting = .decimal, align: OSLogStringAlignment = .none, privacy: OSLogPrivacy = .auto) {}
+ mutating func appendInterpolation(_ number: @autoclosure @escaping () -> UInt32, format: OSLogIntegerFormatting = .decimal, align: OSLogStringAlignment = .none, privacy: OSLogPrivacy = .auto) {}
+ mutating func appendInterpolation(_ number: @autoclosure @escaping () -> UInt64, format: OSLogIntegerFormatting = .decimal, align: OSLogStringAlignment = .none, privacy: OSLogPrivacy = .auto) {}
+ mutating func appendInterpolation(_ number: @autoclosure @escaping () -> Double, format: OSLogFloatFormatting = .fixed, align: OSLogStringAlignment = .none, privacy: OSLogPrivacy = .auto) {}
+ mutating func appendInterpolation(_ number: @autoclosure @escaping () -> Double, format: OSLogFloatFormatting = .fixed, align: OSLogStringAlignment = .none, privacy: OSLogPrivacy = .auto, attributes: String) {}
+ mutating func appendInterpolation(_ number: @autoclosure @escaping () -> Float,format: OSLogFloatFormatting = .fixed, align: OSLogStringAlignment = .none, privacy: OSLogPrivacy = .auto) {}
+ mutating func appendInterpolation(_ number: @autoclosure @escaping () -> Float, format: OSLogFloatFormatting = .fixed, align: OSLogStringAlignment = .none, privacy: OSLogPrivacy = .auto, attributes: String) {}
+ mutating func appendInterpolation(_ boolean: @autoclosure @escaping () -> Bool, format: OSLogBoolFormat = .truth, privacy: OSLogPrivacy = .auto) {}
}
struct OSLogMessage : ExpressibleByStringInterpolation {
@@ -60,32 +83,42 @@ struct Logger {
// --- tests ---
func test1(password: String, passwordHash : String) {
- print(password) // $ MISSING: hasCleartextLogging=63
- print(password, separator: "") // $ MISSING: $ hasCleartextLogging=64
- print("", separator: password) // $ hasCleartextLogging=65
- print(password, separator: "", terminator: "") // $ MISSING: hasCleartextLogging=66
- print("", separator: password, terminator: "") // $ hasCleartextLogging=67
- print("", separator: "", terminator: password) // $ hasCleartextLogging=68
+ print(password) // $ MISSING: hasCleartextLogging=86
+ print(password, separator: "") // $ MISSING: $ hasCleartextLogging=97
+ print("", separator: password) // $ hasCleartextLogging=88
+ print(password, separator: "", terminator: "") // $ MISSING: hasCleartextLogging=89
+ print("", separator: password, terminator: "") // $ hasCleartextLogging=90
+ print("", separator: "", terminator: password) // $ hasCleartextLogging=91
- NSLog(password) // $ hasCleartextLogging=70
- NSLog("%@", password as! CVarArg) // $ MISSING: hasCleartextLogging=71
- NSLog("%@ %@", "" as! CVarArg, password as! CVarArg) // $ MISSING: hasCleartextLogging=72
- NSLog("\(password)") // $ hasCleartextLogging=73
- NSLogv("%@", getVaList([password as! CVarArg])) // $ MISSING: hasCleartextLogging=74
- NSLogv("%@ %@", getVaList(["" as! CVarArg, password as! CVarArg])) // $ MISSING: hasCleartextLogging=75
+ NSLog(password) // $ hasCleartextLogging=93
+ NSLog("%@", password as! CVarArg) // $ MISSING: hasCleartextLogging=94
+ NSLog("%@ %@", "" as! CVarArg, password as! CVarArg) // $ MISSING: hasCleartextLogging=95
+ NSLog("\(password)") // $ hasCleartextLogging=96
+ NSLogv("%@", getVaList([password as! CVarArg])) // $ MISSING: hasCleartextLogging=97
+ NSLogv("%@ %@", getVaList(["" as! CVarArg, password as! CVarArg])) // $ MISSING: hasCleartextLogging=98
+ let bankAccount: Int = 0
let log = Logger()
- log.log("\(password)") // $ hasCleartextLogging=78
+ // These MISSING test cases will be fixed when we properly generate the CFG around autoclosures.
+ log.log("\(password)") // Safe
+ log.log("\(password, privacy: .auto)") // Safe
log.log("\(password, privacy: .private)") // Safe
- log.log(level: .default, "\(password)") // $ hasCleartextLogging=80
- log.trace("\(password)") // $ hasCleartextLogging=81
- log.debug("\(password)") // $ hasCleartextLogging=82
- log.info("\(password)") // $ hasCleartextLogging=83
- log.notice("\(password)") // $ hasCleartextLogging=84
- log.warning("\(password)") // $ hasCleartextLogging=85
- log.error("\(password)") // $ hasCleartextLogging=86
- log.critical("\(password)") // $ hasCleartextLogging=87
- log.fault("\(password)") // $ hasCleartextLogging=88
+ log.log("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=106
+ log.log("\(password, privacy: .sensitive)") // Safe
+ log.log("\(bankAccount)") // $ MISSING: hasCleartextLogging=108
+ log.log("\(bankAccount, privacy: .auto)") // $ MISSING: hasCleartextLogging=109
+ log.log("\(bankAccount, privacy: .private)") // Safe
+ log.log("\(bankAccount, privacy: .public)") // $ MISSING: hasCleartextLogging=111
+ log.log("\(bankAccount, privacy: .sensitive)") // Safe
+ log.log(level: .default, "\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=113
+ log.trace("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=114
+ log.debug("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=115
+ log.info("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=116
+ log.notice("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=117
+ log.warning("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=118
+ log.error("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=119
+ log.critical("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=120
+ log.fault("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=121
}
/*
class MyClass {
From c1f19dd145f2991d23d9ddf74d236a12929b6577 Mon Sep 17 00:00:00 2001
From: Tony Torralba
Date: Fri, 9 Dec 2022 11:04:14 +0100
Subject: [PATCH 22/41] Add stub so that tests work on Linux
---
.../CWE-312/cleartextLoggingTest.swift | 51 ++++++++++---------
1 file changed, 26 insertions(+), 25 deletions(-)
diff --git a/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift b/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift
index 21d1387fb79..fc4b5021d9d 100644
--- a/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift
+++ b/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift
@@ -2,6 +2,7 @@
func NSLog(_ format: String, _ args: CVarArg...) {}
func NSLogv(_ format: String, _ args: CVaListPointer) {}
+func getVaList(_ args: [CVarArg]) -> CVaListPointer { return CVaListPointer(_fromUnsafeMutablePointer: UnsafeMutablePointer(bitPattern: 0)!) }
struct OSLogType : RawRepresentable {
static let `default` = OSLogType(rawValue: 0)
@@ -83,19 +84,19 @@ struct Logger {
// --- tests ---
func test1(password: String, passwordHash : String) {
- print(password) // $ MISSING: hasCleartextLogging=86
- print(password, separator: "") // $ MISSING: $ hasCleartextLogging=97
- print("", separator: password) // $ hasCleartextLogging=88
- print(password, separator: "", terminator: "") // $ MISSING: hasCleartextLogging=89
- print("", separator: password, terminator: "") // $ hasCleartextLogging=90
- print("", separator: "", terminator: password) // $ hasCleartextLogging=91
+ print(password) // $ MISSING: hasCleartextLogging=87
+ print(password, separator: "") // $ MISSING: $ hasCleartextLogging=88
+ print("", separator: password) // $ hasCleartextLogging=89
+ print(password, separator: "", terminator: "") // $ MISSING: hasCleartextLogging=90
+ print("", separator: password, terminator: "") // $ hasCleartextLogging=91
+ print("", separator: "", terminator: password) // $ hasCleartextLogging=92
- NSLog(password) // $ hasCleartextLogging=93
- NSLog("%@", password as! CVarArg) // $ MISSING: hasCleartextLogging=94
- NSLog("%@ %@", "" as! CVarArg, password as! CVarArg) // $ MISSING: hasCleartextLogging=95
- NSLog("\(password)") // $ hasCleartextLogging=96
- NSLogv("%@", getVaList([password as! CVarArg])) // $ MISSING: hasCleartextLogging=97
- NSLogv("%@ %@", getVaList(["" as! CVarArg, password as! CVarArg])) // $ MISSING: hasCleartextLogging=98
+ NSLog(password) // $ hasCleartextLogging=94
+ NSLog("%@", password as! CVarArg) // $ MISSING: hasCleartextLogging=95
+ NSLog("%@ %@", "" as! CVarArg, password as! CVarArg) // $ MISSING: hasCleartextLogging=96
+ NSLog("\(password)") // $ hasCleartextLogging=97
+ NSLogv("%@", getVaList([password as! CVarArg])) // $ MISSING: hasCleartextLogging=98
+ NSLogv("%@ %@", getVaList(["" as! CVarArg, password as! CVarArg])) // $ MISSING: hasCleartextLogging=99
let bankAccount: Int = 0
let log = Logger()
@@ -103,22 +104,22 @@ func test1(password: String, passwordHash : String) {
log.log("\(password)") // Safe
log.log("\(password, privacy: .auto)") // Safe
log.log("\(password, privacy: .private)") // Safe
- log.log("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=106
+ log.log("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=107
log.log("\(password, privacy: .sensitive)") // Safe
- log.log("\(bankAccount)") // $ MISSING: hasCleartextLogging=108
- log.log("\(bankAccount, privacy: .auto)") // $ MISSING: hasCleartextLogging=109
+ log.log("\(bankAccount)") // $ MISSING: hasCleartextLogging=109
+ log.log("\(bankAccount, privacy: .auto)") // $ MISSING: hasCleartextLogging=110
log.log("\(bankAccount, privacy: .private)") // Safe
- log.log("\(bankAccount, privacy: .public)") // $ MISSING: hasCleartextLogging=111
+ log.log("\(bankAccount, privacy: .public)") // $ MISSING: hasCleartextLogging=112
log.log("\(bankAccount, privacy: .sensitive)") // Safe
- log.log(level: .default, "\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=113
- log.trace("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=114
- log.debug("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=115
- log.info("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=116
- log.notice("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=117
- log.warning("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=118
- log.error("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=119
- log.critical("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=120
- log.fault("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=121
+ log.log(level: .default, "\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=114
+ log.trace("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=115
+ log.debug("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=116
+ log.info("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=117
+ log.notice("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=118
+ log.warning("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=119
+ log.error("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=120
+ log.critical("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=121
+ log.fault("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=122
}
/*
class MyClass {
From 7e0869965c32bda2b269453a6a849b1daca71787 Mon Sep 17 00:00:00 2001
From: Tony Torralba
Date: Fri, 9 Dec 2022 11:11:07 +0100
Subject: [PATCH 23/41] Uncomment tests
---
.../CWE-312/cleartextLoggingTest.swift | 36 +++++--------------
1 file changed, 9 insertions(+), 27 deletions(-)
diff --git a/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift b/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift
index fc4b5021d9d..6e0fb9038b3 100644
--- a/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift
+++ b/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift
@@ -121,44 +121,26 @@ func test1(password: String, passwordHash : String) {
log.critical("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=121
log.fault("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=122
}
-/*
+
class MyClass {
var harmless = "abc"
var password = "123"
}
+func getPassword() -> String { return "" }
+func doSomething(password: String) { }
+
func test3(x: String) {
// alternative evidence of sensitivity...
- UserDefaults.standard.set(x, forKey: "myKey") // $ MISSING: hasCleartextLogging
+ NSLog(x) // $ MISSING: hasCleartextLogging=137
doSomething(password: x);
- UserDefaults.standard.set(x, forKey: "myKey") // $ hasCleartextLogging
+ NSLog(x) // $ hasCleartextLogging=137
let y = getPassword();
- UserDefaults.standard.set(y, forKey: "myKey") // $ hasCleartextLogging
+ NSLog(y) // $ hasCleartextLogging=140
let z = MyClass()
- UserDefaults.standard.set(z.harmless, forKey: "myKey") // Safe
- UserDefaults.standard.set(z.password, forKey: "myKey") // $ hasCleartextLogging
+ NSLog(z.harmless) // Safe
+ NSLog(z.password) // $ hasCleartextLogging=145
}
-
-func test4(passwd: String) {
- // sanitizers...
-
- var x = passwd;
- var y = passwd;
- var z = passwd;
-
- UserDefaults.standard.set(x, forKey: "myKey") // $ hasCleartextLogging
- UserDefaults.standard.set(y, forKey: "myKey") // $ hasCleartextLogging
- UserDefaults.standard.set(z, forKey: "myKey") // $ hasCleartextLogging
-
- x = encrypt(x);
- hash(data: &y);
- z = "";
-
- UserDefaults.standard.set(x, forKey: "myKey") // Safe
- UserDefaults.standard.set(y, forKey: "myKey") // Safe
- UserDefaults.standard.set(z, forKey: "myKey") // Safe
-}
-*/
\ No newline at end of file
From 33029b0ed8f4d2e4fd0efc61d9a4ce7953274130 Mon Sep 17 00:00:00 2001
From: Tony Torralba
Date: Fri, 9 Dec 2022 11:13:34 +0100
Subject: [PATCH 24/41] Fix sanitizer QLDoc
---
swift/ql/lib/codeql/swift/security/CleartextLogging.qll | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/swift/ql/lib/codeql/swift/security/CleartextLogging.qll b/swift/ql/lib/codeql/swift/security/CleartextLogging.qll
index e6261543077..309edf16f71 100644
--- a/swift/ql/lib/codeql/swift/security/CleartextLogging.qll
+++ b/swift/ql/lib/codeql/swift/security/CleartextLogging.qll
@@ -31,8 +31,8 @@ private class DefaultCleartextLoggingSink extends CleartextLoggingSink {
/**
* A sanitizer for `OSLogMessage`s configured with the appropriate privacy option.
- * Numeric and boolean arguments aren't redacted unless the `public` option is used.
- * Arguments of other types are always redacted unless the `private` or `sensitive` options are used.
+ * Numeric and boolean arguments aren't redacted unless the `private` or `sensitive` options are used.
+ * Arguments of other types are always redacted unless the `public` option is used.
*/
private class OsLogPrivacyCleartextLoggingSanitizer extends CleartextLoggingSanitizer {
OsLogPrivacyCleartextLoggingSanitizer() {
From 160d89fb4ed55ac5a2913a53e4ef25e0b601ca9b Mon Sep 17 00:00:00 2001
From: Tony Torralba
Date: Mon, 12 Dec 2022 09:36:11 +0100
Subject: [PATCH 25/41] Add qhelp examples
---
swift/ql/src/queries/Security/CWE-312/CleartextLoggingBad.swift | 2 ++
.../ql/src/queries/Security/CWE-312/CleartextLoggingGood.swift | 2 ++
2 files changed, 4 insertions(+)
diff --git a/swift/ql/src/queries/Security/CWE-312/CleartextLoggingBad.swift b/swift/ql/src/queries/Security/CWE-312/CleartextLoggingBad.swift
index e69de29bb2d..036001e8717 100644
--- a/swift/ql/src/queries/Security/CWE-312/CleartextLoggingBad.swift
+++ b/swift/ql/src/queries/Security/CWE-312/CleartextLoggingBad.swift
@@ -0,0 +1,2 @@
+let password = "P@ssw0rd"
+NSLog("User password changed to \(password)")
diff --git a/swift/ql/src/queries/Security/CWE-312/CleartextLoggingGood.swift b/swift/ql/src/queries/Security/CWE-312/CleartextLoggingGood.swift
index e69de29bb2d..1d90ac5e565 100644
--- a/swift/ql/src/queries/Security/CWE-312/CleartextLoggingGood.swift
+++ b/swift/ql/src/queries/Security/CWE-312/CleartextLoggingGood.swift
@@ -0,0 +1,2 @@
+let password = "P@ssw0rd"
+NSLog("User password changed")
From 49a41c98ee31f910e9dadce27b68df8c3c831116 Mon Sep 17 00:00:00 2001
From: Tony Torralba
Date: Mon, 9 Jan 2023 18:00:22 +0100
Subject: [PATCH 26/41] Test that hashed passwords are 'safe' to log
This doesn't seem completely right, but the heuristic approach we have regarding sensitive expressions has to draw the line somewhere.
---
.../CWE-312/cleartextLoggingTest.swift | 58 +++++++++++--------
1 file changed, 35 insertions(+), 23 deletions(-)
diff --git a/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift b/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift
index 6e0fb9038b3..9151ab0f44f 100644
--- a/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift
+++ b/swift/ql/test/query-tests/Security/CWE-312/cleartextLoggingTest.swift
@@ -90,13 +90,16 @@ func test1(password: String, passwordHash : String) {
print(password, separator: "", terminator: "") // $ MISSING: hasCleartextLogging=90
print("", separator: password, terminator: "") // $ hasCleartextLogging=91
print("", separator: "", terminator: password) // $ hasCleartextLogging=92
+ print(passwordHash) // Safe
- NSLog(password) // $ hasCleartextLogging=94
- NSLog("%@", password as! CVarArg) // $ MISSING: hasCleartextLogging=95
- NSLog("%@ %@", "" as! CVarArg, password as! CVarArg) // $ MISSING: hasCleartextLogging=96
- NSLog("\(password)") // $ hasCleartextLogging=97
- NSLogv("%@", getVaList([password as! CVarArg])) // $ MISSING: hasCleartextLogging=98
- NSLogv("%@ %@", getVaList(["" as! CVarArg, password as! CVarArg])) // $ MISSING: hasCleartextLogging=99
+ NSLog(password) // $ hasCleartextLogging=95
+ NSLog("%@", password as! CVarArg) // $ MISSING: hasCleartextLogging=96
+ NSLog("%@ %@", "" as! CVarArg, password as! CVarArg) // $ MISSING: hasCleartextLogging=97
+ NSLog("\(password)") // $ hasCleartextLogging=98
+ NSLogv("%@", getVaList([password as! CVarArg])) // $ MISSING: hasCleartextLogging=99
+ NSLogv("%@ %@", getVaList(["" as! CVarArg, password as! CVarArg])) // $ MISSING: hasCleartextLogging=100
+ NSLog(passwordHash) // SAfe
+ NSLogv("%@", getVaList([passwordHash as! CVarArg])) // Safe
let bankAccount: Int = 0
let log = Logger()
@@ -104,22 +107,31 @@ func test1(password: String, passwordHash : String) {
log.log("\(password)") // Safe
log.log("\(password, privacy: .auto)") // Safe
log.log("\(password, privacy: .private)") // Safe
- log.log("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=107
+ log.log("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=110
+ log.log("\(passwordHash, privacy: .public)") // Safe
log.log("\(password, privacy: .sensitive)") // Safe
- log.log("\(bankAccount)") // $ MISSING: hasCleartextLogging=109
- log.log("\(bankAccount, privacy: .auto)") // $ MISSING: hasCleartextLogging=110
+ log.log("\(bankAccount)") // $ MISSING: hasCleartextLogging=113
+ log.log("\(bankAccount, privacy: .auto)") // $ MISSING: hasCleartextLogging=114
log.log("\(bankAccount, privacy: .private)") // Safe
- log.log("\(bankAccount, privacy: .public)") // $ MISSING: hasCleartextLogging=112
+ log.log("\(bankAccount, privacy: .public)") // $ MISSING: hasCleartextLogging=116
log.log("\(bankAccount, privacy: .sensitive)") // Safe
- log.log(level: .default, "\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=114
- log.trace("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=115
- log.debug("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=116
- log.info("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=117
- log.notice("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=118
- log.warning("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=119
- log.error("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=120
- log.critical("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=121
- log.fault("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=122
+ log.log(level: .default, "\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=118
+ log.trace("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=119
+ log.trace("\(passwordHash, privacy: .public)") // Safe
+ log.debug("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=121
+ log.debug("\(passwordHash, privacy: .public)") // Safe
+ log.info("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=123
+ log.info("\(passwordHash, privacy: .public)") // Safe
+ log.notice("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=125
+ log.notice("\(passwordHash, privacy: .public)") // Safe
+ log.warning("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=127
+ log.warning("\(passwordHash, privacy: .public)") // Safe
+ log.error("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=129
+ log.error("\(passwordHash, privacy: .public)") // Safe
+ log.critical("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=131
+ log.critical("\(passwordHash, privacy: .public)") // Safe
+ log.fault("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=133
+ log.fault("\(passwordHash, privacy: .public)") // Safe
}
class MyClass {
@@ -133,14 +145,14 @@ func doSomething(password: String) { }
func test3(x: String) {
// alternative evidence of sensitivity...
- NSLog(x) // $ MISSING: hasCleartextLogging=137
+ NSLog(x) // $ MISSING: hasCleartextLogging=148
doSomething(password: x);
- NSLog(x) // $ hasCleartextLogging=137
+ NSLog(x) // $ hasCleartextLogging=149
let y = getPassword();
- NSLog(y) // $ hasCleartextLogging=140
+ NSLog(y) // $ hasCleartextLogging=152
let z = MyClass()
NSLog(z.harmless) // Safe
- NSLog(z.password) // $ hasCleartextLogging=145
+ NSLog(z.password) // $ hasCleartextLogging=157
}
From 8e0a018673c6968831a9e8612a2ecd45521ca8c8 Mon Sep 17 00:00:00 2001
From: Tony Torralba
Date: Mon, 9 Jan 2023 18:05:09 +0100
Subject: [PATCH 27/41] Consider Int8 and UInt8 as OsLogNonRedactedTypes
---
swift/ql/lib/codeql/swift/security/CleartextLogging.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/swift/ql/lib/codeql/swift/security/CleartextLogging.qll b/swift/ql/lib/codeql/swift/security/CleartextLogging.qll
index 309edf16f71..b3aa3ba414e 100644
--- a/swift/ql/lib/codeql/swift/security/CleartextLogging.qll
+++ b/swift/ql/lib/codeql/swift/security/CleartextLogging.qll
@@ -53,7 +53,7 @@ private class OsLogPrivacyCleartextLoggingSanitizer extends CleartextLoggingSani
/** A type that isn't redacted by default in an `OSLogMessage`. */
private class OsLogNonRedactedType extends Type {
OsLogNonRedactedType() {
- this.getName() = [["", "U"] + "Int" + ["", "16", "32", "64"], "Double", "Float", "Bool"]
+ this.getName() = [["", "U"] + "Int" + ["", "8", "16", "32", "64"], "Double", "Float", "Bool"]
}
}
From f2658a0936912267c9e42fadc19952a5ff3943f1 Mon Sep 17 00:00:00 2001
From: Erik Krogh Kristensen
Date: Tue, 10 Jan 2023 12:56:22 +0100
Subject: [PATCH 28/41] apply suggestions from doc review
Co-authored-by: mc <42146119+mchammer01@users.noreply.github.com>
---
.../src/queries/security/cwe-094/UnsafeCodeConstruction.qhelp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/ruby/ql/src/queries/security/cwe-094/UnsafeCodeConstruction.qhelp b/ruby/ql/src/queries/security/cwe-094/UnsafeCodeConstruction.qhelp
index f6dcb452661..32e8c369ef5 100644
--- a/ruby/ql/src/queries/security/cwe-094/UnsafeCodeConstruction.qhelp
+++ b/ruby/ql/src/queries/security/cwe-094/UnsafeCodeConstruction.qhelp
@@ -5,7 +5,7 @@
-When a library function dynamically constructs code in a potentially unsafe way, then
+When a library function dynamically constructs code in a potentially unsafe way,
it's important to document to clients of the library that the function should only be
used with trusted inputs.
@@ -35,7 +35,7 @@ then an attacker might be able to execute arbitrary code on the system.
To avoid this problem, either properly document that the function is potentially
-unsafe, or use an alternative solution such as JSON.parse or another library, like in the examples below,
+unsafe, or use an alternative solution such as JSON.parse or another library
that does not allow arbitrary code to be executed.
From 23a847b1cfca06e56c3962d8ea06798dfba9f92f Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Tue, 10 Jan 2023 10:10:36 +0100
Subject: [PATCH 29/41] track shell:true more in
js/shell-command-constructed-from-input
---
.../ql/lib/semmle/javascript/ApiGraphs.qll | 7 ++++++-
...eShellCommandConstructionCustomizations.qll | 5 +++++
.../UnsafeShellCommandConstruction.expected | 18 ++++++++++++++++++
.../UnsafeShellCommandConstruction/lib/lib.js | 9 +++++++++
4 files changed, 38 insertions(+), 1 deletion(-)
diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll
index 3e708098635..a9c8bca2345 100644
--- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll
+++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll
@@ -210,7 +210,12 @@ module API {
* This is similar to `asSink()` but additionally includes nodes that transitively reach a sink by data flow.
* See `asSink()` for examples.
*/
- DataFlow::Node getAValueReachingSink() { result = Impl::trackDefNode(this.asSink()) }
+ DataFlow::Node getAValueReachingSink() {
+ result = Impl::trackDefNode(this.asSink())
+ or
+ // in case `asSink()` is not a `SourceNode`.
+ result = asSink()
+ }
/** DEPRECATED. This predicate has been renamed to `asSink`. */
deprecated DataFlow::Node getARhs() { result = this.asSink() }
diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll
index ca6920db466..6a2b4d94f35 100644
--- a/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll
+++ b/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll
@@ -166,6 +166,11 @@ module UnsafeShellCommandConstruction {
.asExpr()
.(BooleanLiteral)
.getValue() = "true"
+ or
+ exists(API::Node node |
+ node.asSink() = sys.getOptionsArg() and
+ node.getMember("shell").getAValueReachingSink().mayHaveBooleanValue(true)
+ )
}
/**
diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction/UnsafeShellCommandConstruction.expected b/javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction/UnsafeShellCommandConstruction.expected
index de11fb884c9..92cca128c59 100644
--- a/javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction/UnsafeShellCommandConstruction.expected
+++ b/javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction/UnsafeShellCommandConstruction.expected
@@ -282,6 +282,14 @@ nodes
| lib/lib.js:543:23:543:26 | name |
| lib/lib.js:545:23:545:26 | name |
| lib/lib.js:545:23:545:26 | name |
+| lib/lib.js:550:39:550:42 | name |
+| lib/lib.js:550:39:550:42 | name |
+| lib/lib.js:551:33:551:36 | args |
+| lib/lib.js:552:23:552:26 | args |
+| lib/lib.js:552:23:552:26 | args |
+| lib/lib.js:555:25:555:37 | ["-rf", name] |
+| lib/lib.js:555:33:555:36 | name |
+| lib/lib.js:555:33:555:36 | name |
| lib/subLib2/compiled-file.ts:3:26:3:29 | name |
| lib/subLib2/compiled-file.ts:3:26:3:29 | name |
| lib/subLib2/compiled-file.ts:4:25:4:28 | name |
@@ -659,6 +667,14 @@ edges
| lib/lib.js:509:39:509:42 | name | lib/lib.js:545:23:545:26 | name |
| lib/lib.js:509:39:509:42 | name | lib/lib.js:545:23:545:26 | name |
| lib/lib.js:509:39:509:42 | name | lib/lib.js:545:23:545:26 | name |
+| lib/lib.js:550:39:550:42 | name | lib/lib.js:555:33:555:36 | name |
+| lib/lib.js:550:39:550:42 | name | lib/lib.js:555:33:555:36 | name |
+| lib/lib.js:550:39:550:42 | name | lib/lib.js:555:33:555:36 | name |
+| lib/lib.js:550:39:550:42 | name | lib/lib.js:555:33:555:36 | name |
+| lib/lib.js:551:33:551:36 | args | lib/lib.js:552:23:552:26 | args |
+| lib/lib.js:551:33:551:36 | args | lib/lib.js:552:23:552:26 | args |
+| lib/lib.js:555:25:555:37 | ["-rf", name] | lib/lib.js:551:33:551:36 | args |
+| lib/lib.js:555:33:555:36 | name | lib/lib.js:555:25:555:37 | ["-rf", name] |
| lib/subLib2/compiled-file.ts:3:26:3:29 | name | lib/subLib2/compiled-file.ts:4:25:4:28 | name |
| lib/subLib2/compiled-file.ts:3:26:3:29 | name | lib/subLib2/compiled-file.ts:4:25:4:28 | name |
| lib/subLib2/compiled-file.ts:3:26:3:29 | name | lib/subLib2/compiled-file.ts:4:25:4:28 | name |
@@ -775,6 +791,8 @@ edges
| lib/lib.js:537:11:537:26 | "rm -rf " + name | lib/lib.js:509:39:509:42 | name | lib/lib.js:537:23:537:26 | name | This string concatenation which depends on $@ is later used in a $@. | lib/lib.js:509:39:509:42 | name | library input | lib/lib.js:537:3:537:27 | cp.exec ... + name) | shell command |
| lib/lib.js:543:11:543:26 | "rm -rf " + name | lib/lib.js:509:39:509:42 | name | lib/lib.js:543:23:543:26 | name | This string concatenation which depends on $@ is later used in a $@. | lib/lib.js:509:39:509:42 | name | library input | lib/lib.js:543:3:543:27 | cp.exec ... + name) | shell command |
| lib/lib.js:545:11:545:26 | "rm -rf " + name | lib/lib.js:509:39:509:42 | name | lib/lib.js:545:23:545:26 | name | This string concatenation which depends on $@ is later used in a $@. | lib/lib.js:509:39:509:42 | name | library input | lib/lib.js:545:3:545:27 | cp.exec ... + name) | shell command |
+| lib/lib.js:552:23:552:26 | args | lib/lib.js:550:39:550:42 | name | lib/lib.js:552:23:552:26 | args | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:550:39:550:42 | name | library input | lib/lib.js:552:9:552:38 | cp.spaw ... wnOpts) | shell command |
+| lib/lib.js:555:33:555:36 | name | lib/lib.js:550:39:550:42 | name | lib/lib.js:555:33:555:36 | name | This shell argument which depends on $@ is later used in a $@. | lib/lib.js:550:39:550:42 | name | library input | lib/lib.js:552:9:552:38 | cp.spaw ... wnOpts) | shell command |
| lib/subLib2/compiled-file.ts:4:13:4:28 | "rm -rf " + name | lib/subLib2/compiled-file.ts:3:26:3:29 | name | lib/subLib2/compiled-file.ts:4:25:4:28 | name | This string concatenation which depends on $@ is later used in a $@. | lib/subLib2/compiled-file.ts:3:26:3:29 | name | library input | lib/subLib2/compiled-file.ts:4:5:4:29 | cp.exec ... + name) | shell command |
| lib/subLib2/special-file.js:4:10:4:25 | "rm -rf " + name | lib/subLib2/special-file.js:3:28:3:31 | name | lib/subLib2/special-file.js:4:22:4:25 | name | This string concatenation which depends on $@ is later used in a $@. | lib/subLib2/special-file.js:3:28:3:31 | name | library input | lib/subLib2/special-file.js:4:2:4:26 | cp.exec ... + name) | shell command |
| lib/subLib3/my-file.ts:4:10:4:25 | "rm -rf " + name | lib/subLib3/my-file.ts:3:28:3:31 | name | lib/subLib3/my-file.ts:4:22:4:25 | name | This string concatenation which depends on $@ is later used in a $@. | lib/subLib3/my-file.ts:3:28:3:31 | name | library input | lib/subLib3/my-file.ts:4:2:4:26 | cp.exec ... + name) | shell command |
diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction/lib/lib.js b/javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction/lib/lib.js
index 64b279d7d05..728ddbfa91e 100644
--- a/javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction/lib/lib.js
+++ b/javascript/ql/test/query-tests/Security/CWE-078/UnsafeShellCommandConstruction/lib/lib.js
@@ -545,3 +545,12 @@ module.exports.sanitizer4 = function (name) {
cp.exec("rm -rf " + name); // NOT OK
}
}
+
+
+module.exports.shellThing = function (name) {
+ function indirectShell(cmd, args, spawnOpts) {
+ cp.spawn(cmd, args, spawnOpts); // NOT OK
+ }
+
+ indirectShell("rm", ["-rf", name], {shell: true});
+}
\ No newline at end of file
From 43696f5e27db5495d35259099c0a3328e7953a56 Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Tue, 10 Jan 2023 10:24:19 +0100
Subject: [PATCH 30/41] add explicit this
---
javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll
index a9c8bca2345..80a5487943a 100644
--- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll
+++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll
@@ -214,7 +214,7 @@ module API {
result = Impl::trackDefNode(this.asSink())
or
// in case `asSink()` is not a `SourceNode`.
- result = asSink()
+ result = this.asSink()
}
/** DEPRECATED. This predicate has been renamed to `asSink`. */
From 71af8ab02281f1021455dfee355c2f5ccf990179 Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Fri, 13 Jan 2023 13:18:52 +0100
Subject: [PATCH 31/41] simplifications inspired by review
---
javascript/ql/lib/semmle/javascript/ApiGraphs.qll | 7 +------
.../UnsafeShellCommandConstructionCustomizations.qll | 2 +-
2 files changed, 2 insertions(+), 7 deletions(-)
diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll
index 80a5487943a..3e708098635 100644
--- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll
+++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll
@@ -210,12 +210,7 @@ module API {
* This is similar to `asSink()` but additionally includes nodes that transitively reach a sink by data flow.
* See `asSink()` for examples.
*/
- DataFlow::Node getAValueReachingSink() {
- result = Impl::trackDefNode(this.asSink())
- or
- // in case `asSink()` is not a `SourceNode`.
- result = this.asSink()
- }
+ DataFlow::Node getAValueReachingSink() { result = Impl::trackDefNode(this.asSink()) }
/** DEPRECATED. This predicate has been renamed to `asSink`. */
deprecated DataFlow::Node getARhs() { result = this.asSink() }
diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll
index 6a2b4d94f35..3b140fa4a9f 100644
--- a/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll
+++ b/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeShellCommandConstructionCustomizations.qll
@@ -169,7 +169,7 @@ module UnsafeShellCommandConstruction {
or
exists(API::Node node |
node.asSink() = sys.getOptionsArg() and
- node.getMember("shell").getAValueReachingSink().mayHaveBooleanValue(true)
+ node.getMember("shell").asSink().mayHaveBooleanValue(true)
)
}
From cf7189bb2896a9cde9cdfad284c4cb5eb1de0cd2 Mon Sep 17 00:00:00 2001
From: jelaiw <14236583+jelaiw@users.noreply.github.com>
Date: Fri, 13 Jan 2023 19:16:11 -0600
Subject: [PATCH 32/41] Fix small typo in good/bad code sample.
---
java/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/java/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.java b/java/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.java
index 7d8299f2369..6a66258747f 100644
--- a/java/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.java
+++ b/java/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.java
@@ -7,6 +7,6 @@ byte[] encrypted = cipher.doFinal(input.getBytes("UTF-8"));
// ...
// GOOD: AES is a strong algorithm
-Cipher des = Cipher.getInstance("AES");
+Cipher aes = Cipher.getInstance("AES");
-// ...
\ No newline at end of file
+// ...
From fdb3b65bcea14b162a59a4a6c6b8e7a1d2a43cdd Mon Sep 17 00:00:00 2001
From: Tony Torralba
Date: Mon, 16 Jan 2023 11:57:37 +0100
Subject: [PATCH 33/41] Apply suggestions from code review
Co-authored-by: Ben Ahmady <32935794+subatoi@users.noreply.github.com>
---
.../Security/CWE-312/CleartextLogging.qhelp | 18 +++++++-----------
.../Security/CWE-312/CleartextLogging.ql | 2 +-
2 files changed, 8 insertions(+), 12 deletions(-)
diff --git a/swift/ql/src/queries/Security/CWE-312/CleartextLogging.qhelp b/swift/ql/src/queries/Security/CWE-312/CleartextLogging.qhelp
index fc5c7ed6f5e..5959fe5ef8d 100644
--- a/swift/ql/src/queries/Security/CWE-312/CleartextLogging.qhelp
+++ b/swift/ql/src/queries/Security/CWE-312/CleartextLogging.qhelp
@@ -5,37 +5,33 @@
-Sensitive information that is logged unencrypted is accessible to an attacker
-who gains access to the logs.
+Attackers could gain access to sensitive information that is logged unencrypted.
-Ensure that sensitive information is always encrypted or obfuscated before being
-logged.
+Always make sure to encrypt or obfuscate sensitive information before you log it.
-In general, decrypt sensitive information only at the point where it is
-necessary for it to be used in cleartext.
+Generally, you should decrypt sensitive information only at the point where it is necessary for it to be used in cleartext.
-Be aware that external processes often store the standard out and
-standard error streams of the application, causing logged sensitive
-information to be stored.
+Be aware that external processes often store the standard output and
+standard error streams of the application. This will include logged sensitive information.
The following example code logs user credentials (in this case, their password)
-in plain text:
+in plaintext:
-Instead, the credentials should be encrypted, obfuscated, or omitted entirely:
+Instead, you should encrypt or obfuscate the credentials, or omit them entirely:
diff --git a/swift/ql/src/queries/Security/CWE-312/CleartextLogging.ql b/swift/ql/src/queries/Security/CWE-312/CleartextLogging.ql
index 2b21b5da1bc..ecff134f0bd 100644
--- a/swift/ql/src/queries/Security/CWE-312/CleartextLogging.ql
+++ b/swift/ql/src/queries/Security/CWE-312/CleartextLogging.ql
@@ -1,6 +1,6 @@
/**
* @name Clear-text logging of sensitive information
- * @description Logging sensitive information without encryption or hashing can
+ * @description Logging sensitive information in plaintext can
* expose it to an attacker.
* @kind path-problem
* @problem.severity error
From 0017461e2dd52e6767897a1a0726a7acdb2d7666 Mon Sep 17 00:00:00 2001
From: Tony Torralba
Date: Mon, 16 Jan 2023 15:35:58 +0100
Subject: [PATCH 34/41] Update
swift/ql/src/queries/Security/CWE-312/CleartextLogging.ql
Co-authored-by: Ben Ahmady <32935794+subatoi@users.noreply.github.com>
---
swift/ql/src/queries/Security/CWE-312/CleartextLogging.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/swift/ql/src/queries/Security/CWE-312/CleartextLogging.ql b/swift/ql/src/queries/Security/CWE-312/CleartextLogging.ql
index ecff134f0bd..df5c128685f 100644
--- a/swift/ql/src/queries/Security/CWE-312/CleartextLogging.ql
+++ b/swift/ql/src/queries/Security/CWE-312/CleartextLogging.ql
@@ -1,5 +1,5 @@
/**
- * @name Clear-text logging of sensitive information
+ * @name Cleartext logging of sensitive information
* @description Logging sensitive information in plaintext can
* expose it to an attacker.
* @kind path-problem
From 1d62751e159d1fa2dcd4a0de6f177a9f8a6fbecf Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Fri, 13 Jan 2023 13:40:24 +0100
Subject: [PATCH 35/41] test QL-for-QL on mac/win
---
.github/workflows/ql-for-ql-tests.yml | 40 ++++++++++++++++++++++++++-
ql/rust-toolchain.toml | 7 +++++
2 files changed, 46 insertions(+), 1 deletion(-)
create mode 100644 ql/rust-toolchain.toml
diff --git a/.github/workflows/ql-for-ql-tests.yml b/.github/workflows/ql-for-ql-tests.yml
index 1663c5d2fd8..ff894bcc6a9 100644
--- a/.github/workflows/ql-for-ql-tests.yml
+++ b/.github/workflows/ql-for-ql-tests.yml
@@ -33,7 +33,7 @@ jobs:
~/.cargo/registry
~/.cargo/git
ql/target
- key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-qltest-cargo-${{ hashFiles('ql/**/Cargo.lock') }}
+ key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-qltest-cargo-${{ hashFiles('ql/rust-toolchain.toml', 'ql/**/Cargo.lock') }}
- name: Build extractor
run: |
cd ql;
@@ -49,3 +49,41 @@ jobs:
find ql/ql/src "(" -name "*.ql" -or -name "*.qll" ")" -print0 | xargs -0 "${CODEQL}" query format --check-only
env:
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
+
+ other-os:
+ strategy:
+ matrix:
+ os: [macos-latest, windows-latest]
+ needs: [qltest]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install GNU tar
+ if: runner.os == 'macOS'
+ run: |
+ brew install gnu-tar
+ echo "/usr/local/opt/gnu-tar/libexec/gnubin" >> $GITHUB_PATH
+ - name: Find codeql
+ id: find-codeql
+ uses: github/codeql-action/init@77a8d2d10c0b403a8b4aadbd223dc489ecd22683
+ with:
+ languages: javascript # does not matter
+ - uses: ./.github/actions/os-version
+ id: os_version
+ - uses: actions/cache@v3
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ ql/target
+ key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-qltest-cargo-${{ hashFiles('ql/rust-toolchain.toml', 'ql/**/Cargo.lock') }}
+ - name: Build extractor
+ run: |
+ cd ql;
+ codeqlpath=$(dirname ${{ steps.find-codeql.outputs.codeql-path }});
+ env "PATH=$PATH:$codeqlpath" ./scripts/create-extractor-pack.sh
+ - name: Run a single QL tests
+ run: |
+ "${CODEQL}" test run --check-databases --search-path "${{ github.workspace }}/ql/extractor-pack" ql/ql/test/queries/style/DeadCode/DeadCode.qlref
+ env:
+ CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
\ No newline at end of file
diff --git a/ql/rust-toolchain.toml b/ql/rust-toolchain.toml
new file mode 100644
index 00000000000..c0ca7a2593a
--- /dev/null
+++ b/ql/rust-toolchain.toml
@@ -0,0 +1,7 @@
+# This file specifies the Rust version used to develop and test the QL
+# extractor. It is set to the lowest version of Rust we want to support.
+
+[toolchain]
+channel = "1.54"
+profile = "minimal"
+components = [ "rustfmt" ]
From 0685732e3f8c4cf98a65176b13adbb8ef1bfbaa2 Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Fri, 13 Jan 2023 13:51:22 +0100
Subject: [PATCH 36/41] delete ql/ specific format step now that we have an
all-languages format check
---
.github/workflows/ql-for-ql-tests.yml | 5 -----
1 file changed, 5 deletions(-)
diff --git a/.github/workflows/ql-for-ql-tests.yml b/.github/workflows/ql-for-ql-tests.yml
index ff894bcc6a9..93bac75c3ad 100644
--- a/.github/workflows/ql-for-ql-tests.yml
+++ b/.github/workflows/ql-for-ql-tests.yml
@@ -44,11 +44,6 @@ jobs:
"${CODEQL}" test run --check-databases --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --search-path "${{ github.workspace }}/ql/extractor-pack" --consistency-queries ql/ql/consistency-queries ql/ql/test
env:
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
- - name: Check QL formatting
- run: |
- find ql/ql/src "(" -name "*.ql" -or -name "*.qll" ")" -print0 | xargs -0 "${CODEQL}" query format --check-only
- env:
- CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
other-os:
strategy:
From 1de65131fe33b4d3d1a11a785c1c0e6a6b6bbbff Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Fri, 13 Jan 2023 13:52:35 +0100
Subject: [PATCH 37/41] add compilation cache to QL-for-QL tests
---
.github/workflows/ql-for-ql-tests.yml | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/ql-for-ql-tests.yml b/.github/workflows/ql-for-ql-tests.yml
index 93bac75c3ad..d4a1baa9bf8 100644
--- a/.github/workflows/ql-for-ql-tests.yml
+++ b/.github/workflows/ql-for-ql-tests.yml
@@ -39,9 +39,14 @@ jobs:
cd ql;
codeqlpath=$(dirname ${{ steps.find-codeql.outputs.codeql-path }});
env "PATH=$PATH:$codeqlpath" ./scripts/create-extractor-pack.sh
+ - name: Cache compilation cache
+ id: query-cache
+ uses: ./.github/actions/cache-query-compilation
+ with:
+ key: ql-for-ql-tests
- name: Run QL tests
run: |
- "${CODEQL}" test run --check-databases --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --search-path "${{ github.workspace }}/ql/extractor-pack" --consistency-queries ql/ql/consistency-queries ql/ql/test
+ "${CODEQL}" test run --check-databases --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --search-path "${{ github.workspace }}/ql/extractor-pack" --consistency-queries ql/ql/consistency-queries --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}" ql/ql/test
env:
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
From 2c1ecb507d429207bd4a5f6e666e12726ae54112 Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Fri, 13 Jan 2023 13:59:09 +0100
Subject: [PATCH 38/41] fix windows
---
.github/workflows/ql-for-ql-tests.yml | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/ql-for-ql-tests.yml b/.github/workflows/ql-for-ql-tests.yml
index d4a1baa9bf8..d0196bcc961 100644
--- a/.github/workflows/ql-for-ql-tests.yml
+++ b/.github/workflows/ql-for-ql-tests.yml
@@ -78,12 +78,28 @@ jobs:
ql/target
key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-qltest-cargo-${{ hashFiles('ql/rust-toolchain.toml', 'ql/**/Cargo.lock') }}
- name: Build extractor
+ if: runner.os != 'Windows'
run: |
cd ql;
codeqlpath=$(dirname ${{ steps.find-codeql.outputs.codeql-path }});
env "PATH=$PATH:$codeqlpath" ./scripts/create-extractor-pack.sh
- - name: Run a single QL tests
+ - name: Build extractor (Windows)
+ if: runner.os == 'Windows'
+ shell: pwsh
+ run: |
+ cd ql;
+ $Env:PATH += ";$(dirname ${{ steps.find-codeql.outputs.codeql-path }})"
+ pwsh ./scripts/create-extractor-pack.ps1
+ - name: Run a single QL tests - Unix
+ if: runner.os != 'Windows'
run: |
"${CODEQL}" test run --check-databases --search-path "${{ github.workspace }}/ql/extractor-pack" ql/ql/test/queries/style/DeadCode/DeadCode.qlref
env:
- CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
\ No newline at end of file
+ CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
+ - name: Run a single QL tests - Windows
+ if: runner.os == 'Windows'
+ shell: pwsh
+ run: |
+ $Env:PATH += ";$(dirname ${{ steps.find-codeql.outputs.codeql-path }})"
+ codeql test run --check-databases --search-path "${{ github.workspace }}/ql/extractor-pack" ql/ql/test/queries/style/DeadCode/DeadCode.qlref
+
\ No newline at end of file
From 587adea80901a9b6f62e8b94b0e56cd44c0491f9 Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Sun, 15 Jan 2023 15:15:23 +0100
Subject: [PATCH 39/41] QL: add --working-dir to qltest.cmd to fix qltest
---
ql/tools/qltest.cmd | 1 +
1 file changed, 1 insertion(+)
diff --git a/ql/tools/qltest.cmd b/ql/tools/qltest.cmd
index 2573d4f929e..15b42c8af35 100644
--- a/ql/tools/qltest.cmd
+++ b/ql/tools/qltest.cmd
@@ -8,6 +8,7 @@ type NUL && "%CODEQL_DIST%\codeql.exe" database index-files ^
--include-extension=.yml ^
--size-limit=5m ^
--language=ql ^
+ --working-dir=. ^
"%CODEQL_EXTRACTOR_QL_WIP_DATABASE%"
exit /b %ERRORLEVEL%
From 9e153cfb0d710ae83ff9fa03389e794cc91775db Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Mon, 16 Jan 2023 14:36:57 +0100
Subject: [PATCH 40/41] change the Ruby-build test such that Windows fails
---
.github/workflows/ruby-build.yml | 22 +++-------------------
1 file changed, 3 insertions(+), 19 deletions(-)
diff --git a/.github/workflows/ruby-build.yml b/.github/workflows/ruby-build.yml
index e371ffcae27..bdb09bc9c7e 100644
--- a/.github/workflows/ruby-build.yml
+++ b/.github/workflows/ruby-build.yml
@@ -205,11 +205,6 @@ jobs:
- name: Fetch CodeQL
uses: ./.github/actions/fetch-codeql
- - uses: actions/checkout@v3
- with:
- repository: Shopify/example-ruby-app
- ref: 67a0decc5eb550f3a9228eda53925c3afd40dfe9
-
- name: Download Ruby bundle
uses: actions/download-artifact@v3
with:
@@ -218,26 +213,15 @@ jobs:
- name: Unzip Ruby bundle
shell: bash
run: unzip -q -d "${{ runner.temp }}/ruby-bundle" "${{ runner.temp }}/codeql-ruby-bundle.zip"
- - name: Prepare test files
- shell: bash
- run: |
- echo "import codeql.ruby.AST select count(File f)" > "test.ql"
- echo "| 4 |" > "test.expected"
- echo 'name: sample-tests
- version: 0.0.0
- dependencies:
- codeql/ruby-all: "*"
- extractor: ruby
- tests: .
- ' > qlpack.yml
+
- name: Run QL test
shell: bash
run: |
- codeql test run --search-path "${{ runner.temp }}/ruby-bundle" --additional-packs "${{ runner.temp }}/ruby-bundle" .
+ codeql test run --search-path "${{ runner.temp }}/ruby-bundle" --additional-packs "${{ runner.temp }}/ruby-bundle" ruby/ql/test/library-tests/ast/constants/
- name: Create database
shell: bash
run: |
- codeql database create --search-path "${{ runner.temp }}/ruby-bundle" --language ruby --source-root . ../database
+ codeql database create --search-path "${{ runner.temp }}/ruby-bundle" --language ruby --source-root ruby/ql/test/library-tests/ast/constants/ ../database
- name: Analyze database
shell: bash
run: |
From 713599963be58ba8be517c434af44d1aa350e8ff Mon Sep 17 00:00:00 2001
From: erik-krogh
Date: Mon, 16 Jan 2023 14:47:49 +0100
Subject: [PATCH 41/41] add --working-dir to Ruby qltest.cmd to fix Windows
---
ruby/tools/qltest.cmd | 1 +
1 file changed, 1 insertion(+)
diff --git a/ruby/tools/qltest.cmd b/ruby/tools/qltest.cmd
index e43fe47e3fa..00a223cae5c 100644
--- a/ruby/tools/qltest.cmd
+++ b/ruby/tools/qltest.cmd
@@ -8,6 +8,7 @@ type NUL && "%CODEQL_DIST%\codeql.exe" database index-files ^
--include=**/Gemfile ^
--size-limit=5m ^
--language=ruby ^
+ --working-dir=. ^
"%CODEQL_EXTRACTOR_RUBY_WIP_DATABASE%"
exit /b %ERRORLEVEL%