diff --git a/unified/ql/lib/ext/format-string.model.yml b/unified/ql/lib/ext/format-string.model.yml new file mode 100644 index 000000000000..5c9d683b8e98 --- /dev/null +++ b/unified/ql/lib/ext/format-string.model.yml @@ -0,0 +1,39 @@ +extensions: + - addsTo: + pack: codeql/unified-all + extensible: legacySinkModel + data: # namespace, type, subtypes, name, signature, ext, input, kind, provenance + - ["", "String", true, "init(_:format:)", "", "", "Argument[format:]", "format-string", "manual"] + - ["", "String", true, "init(arguments:format:)", "", "", "Argument[format:]", "format-string", "manual"] + - ["", "String", true, "init(_:format:locale:)", "", "", "Argument[format:]", "format-string", "manual"] + - ["", "String", true, "init(arguments:format:locale:)", "", "", "Argument[format:]", "format-string", "manual"] + - ["", "String", true, "localizedStringWithFormat(_:_:)", "", "", "Argument[0]", "format-string", "manual"] + - ["", "NSString", true, "init(_:format:)", "", "", "Argument[format:]", "format-string", "manual"] + - ["", "NSString", true, "init(arguments:format:)", "", "", "Argument[format:]", "format-string", "manual"] + - ["", "NSString", true, "init(_:format:locale:)", "", "", "Argument[format:]", "format-string", "manual"] + - ["", "NSString", true, "init(arguments:format:locale:)", "", "", "Argument[format:]", "format-string", "manual"] + - ["", "NSString", true, "localizedStringWithFormat(_:_:)", "", "", "Argument[0]", "format-string", "manual"] + - ["", "NSMutableString", true, "init(_:format:)", "", "", "Argument[format:]", "format-string", "manual"] + - ["", "NSMutableString", true, "init(arguments:format:)", "", "", "Argument[format:]", "format-string", "manual"] + - ["", "NSMutableString", true, "init(_:format:locale:)", "", "", "Argument[format:]", "format-string", "manual"] + - ["", "NSMutableString", true, "init(arguments:format:locale:)", "", "", "Argument[format:]", "format-string", "manual"] + - ["", "NSMutableString", true, "localizedStringWithFormat(_:_:)", "", "", "Argument[0]", "format-string", "manual"] + - ["", "NSMutableString", true, "appendFormat(_:_:)", "", "", "Argument[0]", "format-string", "manual"] + - ["", "StringProtocol", true, "appendingFormat(_:_:)", "", "", "Argument[0]", "format-string", "manual"] + - ["", "NSException", true, "init(name:reason:userInfo:)", "", "", "Argument[reason:]", "format-string", "manual"] + - ["", "NSException", true, "raise(_:arguments:format:)", "", "", "Argument[format:]", "format-string", "manual"] + - ["", "", false, "NSLog(_:_:)", "", "", "Argument[0]", "format-string", "manual"] + - ["", "", false, "NSLogv(_:_:)", "", "", "Argument[0]", "format-string", "manual"] + - ["", "", false, "printf(_:_:)", "", "", "Argument[0]", "format-string", "manual"] + - ["", "", false, "fprintf(_:_:_:)", "", "", "Argument[1]", "format-string", "manual"] + - ["", "", false, "sprintf(_:_:_:)", "", "", "Argument[1]", "format-string", "manual"] + - ["", "", false, "snprintf(_:_:_:_:)", "", "", "Argument[2]", "format-string", "manual"] + - ["", "", false, "asprintf(_:_:_:)", "", "", "Argument[1]", "format-string", "manual"] + - ["", "", false, "dprintf(_:_:_:)", "", "", "Argument[1]", "format-string", "manual"] + - ["", "", false, "vprintf(_:_:)", "", "", "Argument[0]", "format-string", "manual"] + - ["", "", false, "vfprintf(_:_:_:)", "", "", "Argument[1]", "format-string", "manual"] + - ["", "", false, "vsprintf(_:_:_:)", "", "", "Argument[1]", "format-string", "manual"] + - ["", "", false, "vsnprintf(_:_:_:_:)", "", "", "Argument[2]", "format-string", "manual"] + - ["", "", false, "vasprintf(_:_:_:)", "", "", "Argument[1]", "format-string", "manual"] + - ["", "", false, "vdprintf(_:_:_:)", "", "", "Argument[1]", "format-string", "manual"] + - ["", "", false, "vasprintf_l(_:_:_:_:)", "", "", "Argument[2]", "format-string", "manual"] diff --git a/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.qhelp b/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.qhelp new file mode 100644 index 000000000000..634d4e77266b --- /dev/null +++ b/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.qhelp @@ -0,0 +1,38 @@ + + + +

+Passing untrusted format strings to functions that use printf style formatting can lead to buffer overflows and data representation problems. An attacker may be able to exploit this weakness to crash the program or obtain sensitive information from its internal state.

+ +
+ + +

Use a constant string literal for the format string to prevent the possibility of data flow from +an untrusted source. This also helps to prevent errors where the format arguments do not match the format string.

+ +

If the format string cannot be constant, ensure that it comes from a secure data source or is compiled into the source code. If you need to include a string value from the user, use an appropriate specifier (such as %@) in the format string and include the user provided value as a format argument. +

+ +
+ + +

In this example, the format string includes a user-controlled inputString:

+ + + +

To fix it, make inputString a format argument rather than part of the format string, as in the following code:

+ + + +
+ + +
  • +OWASP: +Format string attack. +
  • + +
    +
    diff --git a/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.ql b/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.ql new file mode 100644 index 000000000000..ffc109e08e7a --- /dev/null +++ b/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.ql @@ -0,0 +1,49 @@ +/** + * @name Uncontrolled format string + * @description Using external input in format strings can lead to exceptions or information leaks. + * @kind path-problem + * @problem.severity error + * @security-severity 7.3 + * @precision high + * @id unified/swift/uncontrolled-format-string + * @tags security + * external/cwe/cwe-134 + */ + +import unified + +/** + * A taint configuration for tainted data that reaches a format string. + */ +module TaintedFormatConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node node) { Models::isSource(node, _) } + + predicate isSink(DataFlow::Node node) { + // TODO: the MaD format currently has the varargs argument first in the + // signature, rather than in its normal position. + // TODO: we currently model `printf(_:_:)` etc explicitly, rather than with a + // more flexible pattern as in the original Swift's `StringFormat.qll`. + // TODO: Port the variadic-call heuristic from original Swift's + // `UncontrolledFormatStringExtensions.qll` once variadic parameters are extracted. + Models::isSink(node, "format-string") + } + + predicate isBarrier(DataFlow::Node node) { + // TODO: Port the numeric-type barrier from original Swift's UncontrolledFormatStringExtensions.qll. + none() + } + + predicate observeDiffInformedIncrementalMode() { any() } +} + +/** + * Detect taint flow of tainted data that reaches a format string. + */ +module TaintedFormatFlow = TaintTracking::Global; + +import TaintedFormatFlow::PathGraph + +from TaintedFormatFlow::PathNode sourceNode, TaintedFormatFlow::PathNode sinkNode +where TaintedFormatFlow::flowPath(sourceNode, sinkNode) +select sinkNode.getNode(), sourceNode, sinkNode, "This format string depends on $@.", + sourceNode.getNode(), "this user-provided value" diff --git a/unified/ql/src/queries/security/CWE-134/UncontrolledFormatStringBad.swift b/unified/ql/src/queries/security/CWE-134/UncontrolledFormatStringBad.swift new file mode 100644 index 000000000000..8bce9ef81281 --- /dev/null +++ b/unified/ql/src/queries/security/CWE-134/UncontrolledFormatStringBad.swift @@ -0,0 +1,2 @@ + +print(String(format: "User input: " + inputString)) // vulnerable diff --git a/unified/ql/src/queries/security/CWE-134/UncontrolledFormatStringGood.swift b/unified/ql/src/queries/security/CWE-134/UncontrolledFormatStringGood.swift new file mode 100644 index 000000000000..d0d85952956b --- /dev/null +++ b/unified/ql/src/queries/security/CWE-134/UncontrolledFormatStringGood.swift @@ -0,0 +1,2 @@ + +print(String(format: "User input: %@", inputString)) // fixed diff --git a/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.expected b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.expected new file mode 100644 index 000000000000..b0c01b7833df --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.expected @@ -0,0 +1,83 @@ +#select +| UncontrolledFormatString.swift:81:16:81:21 | format | UncontrolledFormatString.swift:93:24:93:77 | String(...) | UncontrolledFormatString.swift:81:16:81:21 | format | This format string depends on $@. | UncontrolledFormatString.swift:93:24:93:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:102:24:102:30 | tainted | UncontrolledFormatString.swift:93:24:93:77 | String(...) | UncontrolledFormatString.swift:102:24:102:30 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:93:24:93:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:103:24:103:30 | tainted | UncontrolledFormatString.swift:93:24:93:77 | String(...) | UncontrolledFormatString.swift:103:24:103:30 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:93:24:93:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:105:24:105:30 | tainted | UncontrolledFormatString.swift:93:24:93:77 | String(...) | UncontrolledFormatString.swift:105:24:105:30 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:93:24:93:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:107:24:107:30 | tainted | UncontrolledFormatString.swift:93:24:93:77 | String(...) | UncontrolledFormatString.swift:107:24:107:30 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:93:24:93:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:110:26:110:50 | NSString(...) | UncontrolledFormatString.swift:93:24:93:77 | String(...) | UncontrolledFormatString.swift:110:26:110:50 | NSString(...) | This format string depends on $@. | UncontrolledFormatString.swift:93:24:93:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:113:33:113:57 | NSString(...) | UncontrolledFormatString.swift:93:24:93:77 | String(...) | UncontrolledFormatString.swift:113:33:113:57 | NSString(...) | This format string depends on $@. | UncontrolledFormatString.swift:93:24:93:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:120:65:120:71 | tainted | UncontrolledFormatString.swift:93:24:93:77 | String(...) | UncontrolledFormatString.swift:120:65:120:71 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:93:24:93:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:121:61:121:67 | tainted | UncontrolledFormatString.swift:93:24:93:77 | String(...) | UncontrolledFormatString.swift:121:61:121:67 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:93:24:93:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:133:39:133:45 | tainted | UncontrolledFormatString.swift:93:24:93:77 | String(...) | UncontrolledFormatString.swift:133:39:133:45 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:93:24:93:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:138:20:138:44 | NSString(...) | UncontrolledFormatString.swift:93:24:93:77 | String(...) | UncontrolledFormatString.swift:138:20:138:44 | NSString(...) | This format string depends on $@. | UncontrolledFormatString.swift:93:24:93:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:166:42:166:48 | tainted | UncontrolledFormatString.swift:164:24:164:77 | String(...) | UncontrolledFormatString.swift:166:42:166:48 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:164:24:164:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:168:40:168:64 | NSString(...) | UncontrolledFormatString.swift:164:24:164:77 | String(...) | UncontrolledFormatString.swift:168:40:168:64 | NSString(...) | This format string depends on $@. | UncontrolledFormatString.swift:164:24:164:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:170:47:170:71 | NSString(...) | UncontrolledFormatString.swift:164:24:164:77 | String(...) | UncontrolledFormatString.swift:170:47:170:71 | NSString(...) | This format string depends on $@. | UncontrolledFormatString.swift:164:24:164:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:173:11:173:17 | tainted | UncontrolledFormatString.swift:164:24:164:77 | String(...) | UncontrolledFormatString.swift:173:11:173:17 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:164:24:164:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:176:20:176:26 | tainted | UncontrolledFormatString.swift:164:24:164:77 | String(...) | UncontrolledFormatString.swift:176:20:176:26 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:164:24:164:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:178:17:178:23 | tainted | UncontrolledFormatString.swift:164:24:164:77 | String(...) | UncontrolledFormatString.swift:178:17:178:23 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:164:24:164:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:180:23:180:29 | tainted | UncontrolledFormatString.swift:164:24:164:77 | String(...) | UncontrolledFormatString.swift:180:23:180:29 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:164:24:164:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:182:31:182:37 | tainted | UncontrolledFormatString.swift:164:24:164:77 | String(...) | UncontrolledFormatString.swift:182:31:182:37 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:164:24:164:77 | String(...) | this user-provided value | +edges +| UncontrolledFormatString.swift:79:14:79:19 | format | UncontrolledFormatString.swift:80:22:82:5 | FunctionExpr [format] | provenance | | +| UncontrolledFormatString.swift:80:22:82:5 | FunctionExpr [format] | UncontrolledFormatString.swift:81:16:81:21 | format | provenance | | +| UncontrolledFormatString.swift:93:9:93:15 | tainted | UncontrolledFormatString.swift:102:24:102:30 | tainted | provenance | | +| UncontrolledFormatString.swift:93:9:93:15 | tainted | UncontrolledFormatString.swift:103:24:103:30 | tainted | provenance | | +| UncontrolledFormatString.swift:93:9:93:15 | tainted | UncontrolledFormatString.swift:105:24:105:30 | tainted | provenance | | +| UncontrolledFormatString.swift:93:9:93:15 | tainted | UncontrolledFormatString.swift:107:24:107:30 | tainted | provenance | | +| UncontrolledFormatString.swift:93:9:93:15 | tainted | UncontrolledFormatString.swift:110:43:110:49 | tainted | provenance | | +| UncontrolledFormatString.swift:93:9:93:15 | tainted | UncontrolledFormatString.swift:113:50:113:56 | tainted | provenance | | +| UncontrolledFormatString.swift:93:9:93:15 | tainted | UncontrolledFormatString.swift:118:11:118:17 | tainted | provenance | | +| UncontrolledFormatString.swift:93:9:93:15 | tainted | UncontrolledFormatString.swift:120:65:120:71 | tainted | provenance | | +| UncontrolledFormatString.swift:93:9:93:15 | tainted | UncontrolledFormatString.swift:121:61:121:67 | tainted | provenance | | +| UncontrolledFormatString.swift:93:9:93:15 | tainted | UncontrolledFormatString.swift:133:39:133:45 | tainted | provenance | | +| UncontrolledFormatString.swift:93:9:93:15 | tainted | UncontrolledFormatString.swift:138:37:138:43 | tainted | provenance | | +| UncontrolledFormatString.swift:93:24:93:77 | String(...) | UncontrolledFormatString.swift:93:9:93:15 | tainted | provenance | | +| UncontrolledFormatString.swift:110:43:110:49 | tainted | UncontrolledFormatString.swift:110:26:110:50 | NSString(...) | provenance | | +| UncontrolledFormatString.swift:113:50:113:56 | tainted | UncontrolledFormatString.swift:113:33:113:57 | NSString(...) | provenance | | +| UncontrolledFormatString.swift:118:11:118:17 | tainted | UncontrolledFormatString.swift:79:14:79:19 | format | provenance | | +| UncontrolledFormatString.swift:138:37:138:43 | tainted | UncontrolledFormatString.swift:138:20:138:44 | NSString(...) | provenance | | +| UncontrolledFormatString.swift:164:9:164:15 | tainted | UncontrolledFormatString.swift:166:42:166:48 | tainted | provenance | | +| UncontrolledFormatString.swift:164:9:164:15 | tainted | UncontrolledFormatString.swift:168:57:168:63 | tainted | provenance | | +| UncontrolledFormatString.swift:164:9:164:15 | tainted | UncontrolledFormatString.swift:170:64:170:70 | tainted | provenance | | +| UncontrolledFormatString.swift:164:9:164:15 | tainted | UncontrolledFormatString.swift:173:11:173:17 | tainted | provenance | | +| UncontrolledFormatString.swift:164:9:164:15 | tainted | UncontrolledFormatString.swift:176:20:176:26 | tainted | provenance | | +| UncontrolledFormatString.swift:164:9:164:15 | tainted | UncontrolledFormatString.swift:178:17:178:23 | tainted | provenance | | +| UncontrolledFormatString.swift:164:9:164:15 | tainted | UncontrolledFormatString.swift:180:23:180:29 | tainted | provenance | | +| UncontrolledFormatString.swift:164:9:164:15 | tainted | UncontrolledFormatString.swift:182:31:182:37 | tainted | provenance | | +| UncontrolledFormatString.swift:164:24:164:77 | String(...) | UncontrolledFormatString.swift:164:9:164:15 | tainted | provenance | | +| UncontrolledFormatString.swift:168:57:168:63 | tainted | UncontrolledFormatString.swift:168:40:168:64 | NSString(...) | provenance | | +| UncontrolledFormatString.swift:170:64:170:70 | tainted | UncontrolledFormatString.swift:170:47:170:71 | NSString(...) | provenance | | +nodes +| UncontrolledFormatString.swift:79:14:79:19 | format | semmle.label | format | +| UncontrolledFormatString.swift:80:22:82:5 | FunctionExpr [format] | semmle.label | FunctionExpr [format] | +| UncontrolledFormatString.swift:81:16:81:21 | format | semmle.label | format | +| UncontrolledFormatString.swift:93:9:93:15 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:93:24:93:77 | String(...) | semmle.label | String(...) | +| UncontrolledFormatString.swift:102:24:102:30 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:103:24:103:30 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:105:24:105:30 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:107:24:107:30 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:110:26:110:50 | NSString(...) | semmle.label | NSString(...) | +| UncontrolledFormatString.swift:110:43:110:49 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:113:33:113:57 | NSString(...) | semmle.label | NSString(...) | +| UncontrolledFormatString.swift:113:50:113:56 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:118:11:118:17 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:120:65:120:71 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:121:61:121:67 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:133:39:133:45 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:138:20:138:44 | NSString(...) | semmle.label | NSString(...) | +| UncontrolledFormatString.swift:138:37:138:43 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:164:9:164:15 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:164:24:164:77 | String(...) | semmle.label | String(...) | +| UncontrolledFormatString.swift:166:42:166:48 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:168:40:168:64 | NSString(...) | semmle.label | NSString(...) | +| UncontrolledFormatString.swift:168:57:168:63 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:170:47:170:71 | NSString(...) | semmle.label | NSString(...) | +| UncontrolledFormatString.swift:170:64:170:70 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:173:11:173:17 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:176:20:176:26 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:178:17:178:23 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:180:23:180:29 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:182:31:182:37 | tainted | semmle.label | tainted | +subpaths diff --git a/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.qlref b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.qlref new file mode 100644 index 000000000000..c4c2f8c13ff5 --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.qlref @@ -0,0 +1,3 @@ +query: queries/security/CWE-134/UncontrolledFormatString.ql +postprocess: + - utils/test/InlineExpectationsTestQuery.ql diff --git a/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.swift b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.swift new file mode 100644 index 000000000000..ff2e09f3d51e --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.swift @@ -0,0 +1,184 @@ + +// --- stubs --- + +struct URL +{ + init?(string: String) {} +} + +struct Locale { +} + +extension String : CVarArg { + public var _cVarArgEncoding: [Int] { get { return [] } } + + init(contentsOf: URL) throws { self.init() } + init(format: String, _ arguments: CVarArg...) { self.init() } + init(format: String, arguments: [CVarArg]) { self.init() } + init(format: String, locale: Locale?, _ args: CVarArg...) { self.init() } + init(format: String, locale: Locale?, arguments: [CVarArg]) { self.init() } + + static func localizedStringWithFormat(_ format: String, _ arguments: CVarArg...) -> String { return "" } +} + +extension StringProtocol { + func appendingFormat(_ format: T, _ arguments: CVarArg...) -> String where T : StringProtocol { return "" } +} + +class NSObject +{ +} + +class NSString : NSObject +{ + init(string aString: String) {} + init(format: NSString, _ args: CVarArg...) {} + + class func localizedStringWithFormat(_ format: NSString, _ args: CVarArg...) {} +} + +class NSMutableString : NSString +{ + func appendFormat(_ format: NSString, _ args: CVarArg...) {} +} + +struct NSExceptionName { + init(_ rawValue: String) {} +} + +class NSException: NSObject +{ + init(name aName: NSExceptionName, reason aReason: String?, userInfo aUserInfo: [AnyHashable: Any]? = nil) {} + + class func raise(_ name: NSExceptionName, format: String, arguments argList: CVaListPointer) {} +} + +func NSLog(_ format: String, _ args: CVarArg...) {} + +func NSLogv(_ format: String, _ args: CVaListPointer) {} + +func getVaList(_ args: [CVarArg]) -> CVaListPointer { return (nil as CVaListPointer?)! } + +// not a (normal) formatting function +class NSPredicate { + init(format: String, _: CVarArg...) {} +} + +// imported from C +typealias FILE = Int32 // this is a simplification +typealias wchar_t = Int32 +typealias locale_t = OpaquePointer +func dprintf(_ fd: Int, _ format: UnsafePointer, _ args: CVarArg...) -> Int32 { return 0 } +func vprintf(_ format: UnsafePointer, _ arg: CVaListPointer) -> Int32 { return 0 } +func vfprintf(_ file: UnsafeMutablePointer?, _ format: UnsafePointer?, _ arg: CVaListPointer) -> Int32 { return 0 } +func vasprintf_l(_ ret: UnsafeMutablePointer?>?, _ loc: locale_t?, _ format: UnsafePointer?, _ ap: CVaListPointer) -> Int32 { return 0 } + + +// --- tests --- + +func MyLog(_ format: String, _ args: CVarArg...) { + withVaList(args) { arglist in + NSLogv(format, arglist) // $ Alert + } +} + +func myFormatMessage(string: String, _ args: CVarArg...) { } + +class MyString { + init(format: String, _ args: CVarArg...) { } + init(formatString: String, _ args: CVarArg...) { } +} + +func tests() throws { + let tainted = try! String(contentsOf: URL(string: "http://example.com")!) // $ Source + + _ = String("abc") // GOOD: not a format string + _ = String(tainted) // GOOD: not a format string + + _ = String(format: "abc") // GOOD: not tainted + _ = String(format: tainted) // $ MISSING: Alert + _ = String(format: "%s", "abc") // GOOD: not tainted + _ = String(format: "%s", tainted) // GOOD: format string itself is not tainted + _ = String(format: tainted, "abc") // $ Alert + _ = String(format: tainted, tainted) // $ Alert + + _ = String(format: tainted, arguments: []) // $ Alert + _ = String(format: tainted, locale: nil) // $ MISSING: Alert + _ = String(format: tainted, locale: nil, arguments: []) // $ Alert + _ = String.localizedStringWithFormat(tainted) // $ MISSING: Alert + + _ = NSString(format: NSString(string: tainted), "abc") // $ Alert + NSString.localizedStringWithFormat(NSString(string: tainted)) // $ MISSING: Alert + + _ = NSMutableString(format: NSString(string: tainted), "abc") // $ Alert + NSMutableString.localizedStringWithFormat(NSString(string: tainted)) // $ MISSING: Alert + + NSLog("abc") // GOOD: not tainted + NSLog(tainted) // $ MISSING: Alert + MyLog(tainted) // $ MISSING: Alert + + _ = NSException(name: NSExceptionName("exception"), reason: tainted, userInfo: nil) // $ Alert + NSException.raise(NSExceptionName("exception"), format: tainted, arguments: getVaList([])) // $ Alert + + let taintedVal = Int(tainted)! + let taintedSan = "\(taintedVal)" + _ = String(format: taintedSan) // GOOD: sufficiently sanitized + + let taintedVal2 = Int(tainted) ?? 0 + let taintedSan2 = String(taintedVal2) + _ = String(format: taintedSan2) // GOOD: sufficiently sanitized + + _ = String("abc").appendingFormat("%s", "abc") // GOOD: not tainted + _ = String("abc").appendingFormat("%s", tainted) // GOOD: format not tainted + _ = String("abc").appendingFormat(tainted, "abc") // $ Alert + _ = String(tainted).appendingFormat("%s", "abc") // GOOD: format not tainted + + let s = NSMutableString(string: "foo") + s.appendFormat(NSString(string: "%s"), "abc") // GOOD: not tainted + s.appendFormat(NSString(string: tainted), "abc") // $ Alert + + _ = NSPredicate(format: tainted) // GOOD: predicate injection, not printf-style formatting + + tainted.withCString({ + cstr in + _ = dprintf(0, cstr, "abc") // $ MISSING: Alert + _ = dprintf(0, "%s", cstr) // GOOD: format not tainted + _ = vprintf(cstr, getVaList(["abc"])) // $ MISSING: Alert + _ = vprintf("%s", getVaList([cstr])) // GOOD: format not tainted + _ = vfprintf(nil, cstr, getVaList(["abc"])) // $ MISSING: Alert + _ = vfprintf(nil, "%s", getVaList([cstr])) // GOOD: format not tainted + _ = vasprintf_l(nil, nil, cstr, getVaList(["abc"])) // $ MISSING: Alert + _ = vasprintf_l(nil, nil, "%s", getVaList([cstr])) // GOOD: format not tainted + }) + + myFormatMessage(string: tainted, "abc") // $ MISSING: Alert // BAD [NOT DETECTED] + myFormatMessage(string: "%s", tainted) // GOOD: format not tainted + + _ = MyString(format: tainted, "abc") // $ MISSING: Alert + _ = MyString(format: "%s", tainted) // GOOD: format not tainted + _ = MyString(formatString: tainted, "abc") // $ MISSING: Alert + _ = MyString(formatString: "%s", tainted) // GOOD: format not tainted +} + +func testModeledFormatStrings() throws { + let tainted = try! String(contentsOf: URL(string: "http://example.com")!) // $ Source + + _ = String.localizedStringWithFormat(tainted, "abc") // $ Alert + _ = String.localizedStringWithFormat("%@", tainted) // GOOD: format not tainted + NSString.localizedStringWithFormat(NSString(string: tainted), "abc") // $ Alert + NSString.localizedStringWithFormat(NSString(string: "%@"), tainted) // GOOD: format not tainted + NSMutableString.localizedStringWithFormat(NSString(string: tainted), "abc") // $ Alert + NSMutableString.localizedStringWithFormat(NSString(string: "%@"), tainted) // GOOD: format not tainted + + NSLog(tainted, "abc") // $ Alert + NSLog("%@", tainted) // GOOD: format not tainted + + _ = dprintf(0, tainted, "abc") // $ Alert + _ = dprintf(0, "%s", tainted) // GOOD: format not tainted + _ = vprintf(tainted, getVaList([])) // $ Alert + _ = vprintf("%s", getVaList([tainted])) // GOOD: format not tainted + _ = vfprintf(nil, tainted, getVaList([])) // $ Alert + _ = vfprintf(nil, "%s", getVaList([tainted])) // GOOD: format not tainted + _ = vasprintf_l(nil, nil, tainted, getVaList([])) // $ Alert + _ = vasprintf_l(nil, nil, "%s", getVaList([tainted])) // GOOD: format not tainted +}