+
+
+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..4376f0f4c0f3
--- /dev/null
+++ b/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.ql
@@ -0,0 +1,21 @@
+/**
+ * @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 swift/uncontrolled-format-string
+ * @tags security
+ * external/cwe/cwe-134
+ */
+
+import swift
+import codeql.swift.dataflow.DataFlow
+import codeql.swift.security.UncontrolledFormatStringQuery
+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..94dd27a82c2f
--- /dev/null
+++ b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.expected
@@ -0,0 +1,98 @@
+edges
+| UncontrolledFormatString.swift:77:12:77:22 | format | UncontrolledFormatString.swift:78:22:80:5 | format | provenance | |
+| UncontrolledFormatString.swift:78:22:80:5 | format | UncontrolledFormatString.swift:78:22:80:5 | { ... } [format] | provenance | |
+| UncontrolledFormatString.swift:78:22:80:5 | { ... } [format] | UncontrolledFormatString.swift:79:16:79:16 | this [format] | provenance | |
+| UncontrolledFormatString.swift:79:16:79:16 | this [format] | UncontrolledFormatString.swift:79:16:79:16 | format | provenance | |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:97:24:97:24 | tainted | provenance | |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:100:24:100:24 | tainted | provenance | |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:101:24:101:24 | tainted | provenance | |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:103:24:103:24 | tainted | provenance | |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:104:24:104:24 | tainted | provenance | |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:105:24:105:24 | tainted | provenance | |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:106:42:106:42 | tainted | provenance | |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:108:43:108:43 | tainted | provenance | |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:109:57:109:57 | tainted | provenance | |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:111:50:111:50 | tainted | provenance | |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:112:64:112:64 | tainted | provenance | |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:115:11:115:11 | tainted | provenance | |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:116:11:116:11 | tainted | provenance | |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:116:11:116:11 | tainted | provenance | |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:118:61:118:61 | tainted | provenance | |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:130:39:130:39 | tainted | provenance | |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:135:37:135:37 | tainted | provenance | |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:139:5:139:5 | tainted | provenance | |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:154:26:154:26 | tainted | provenance | |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:156:32:156:32 | tainted | provenance | |
+| UncontrolledFormatString.swift:108:43:108:43 | tainted | UncontrolledFormatString.swift:108:26:108:50 | call to NSString.init(string:) | provenance | |
+| UncontrolledFormatString.swift:109:57:109:57 | tainted | UncontrolledFormatString.swift:109:40:109:64 | call to NSString.init(string:) | provenance | |
+| UncontrolledFormatString.swift:111:50:111:50 | tainted | UncontrolledFormatString.swift:111:33:111:57 | call to NSString.init(string:) | provenance | |
+| UncontrolledFormatString.swift:112:64:112:64 | tainted | UncontrolledFormatString.swift:112:47:112:71 | call to NSString.init(string:) | provenance | |
+| UncontrolledFormatString.swift:116:11:116:11 | tainted | UncontrolledFormatString.swift:77:12:77:22 | format | provenance | |
+| UncontrolledFormatString.swift:135:37:135:37 | tainted | UncontrolledFormatString.swift:135:20:135:44 | call to NSString.init(string:) | provenance | |
+| UncontrolledFormatString.swift:139:5:139:5 | tainted | UncontrolledFormatString.swift:140:9:140:9 | cstr [Collection element] | provenance | |
+| UncontrolledFormatString.swift:140:9:140:9 | cstr [Collection element] | UncontrolledFormatString.swift:141:24:141:24 | cstr | provenance | |
+| UncontrolledFormatString.swift:140:9:140:9 | cstr [Collection element] | UncontrolledFormatString.swift:143:21:143:21 | cstr | provenance | |
+| UncontrolledFormatString.swift:140:9:140:9 | cstr [Collection element] | UncontrolledFormatString.swift:145:27:145:27 | cstr | provenance | |
+| UncontrolledFormatString.swift:140:9:140:9 | cstr [Collection element] | UncontrolledFormatString.swift:147:35:147:35 | cstr | provenance | |
+nodes
+| UncontrolledFormatString.swift:77:12:77:22 | format | semmle.label | format |
+| UncontrolledFormatString.swift:78:22:80:5 | format | semmle.label | format |
+| UncontrolledFormatString.swift:78:22:80:5 | { ... } [format] | semmle.label | { ... } [format] |
+| UncontrolledFormatString.swift:79:16:79:16 | format | semmle.label | format |
+| UncontrolledFormatString.swift:79:16:79:16 | this [format] | semmle.label | this [format] |
+| UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | semmle.label | call to String.init(contentsOf:) |
+| UncontrolledFormatString.swift:97:24:97:24 | tainted | semmle.label | tainted |
+| UncontrolledFormatString.swift:100:24:100:24 | tainted | semmle.label | tainted |
+| UncontrolledFormatString.swift:101:24:101:24 | tainted | semmle.label | tainted |
+| UncontrolledFormatString.swift:103:24:103:24 | tainted | semmle.label | tainted |
+| UncontrolledFormatString.swift:104:24:104:24 | tainted | semmle.label | tainted |
+| UncontrolledFormatString.swift:105:24:105:24 | tainted | semmle.label | tainted |
+| UncontrolledFormatString.swift:106:42:106:42 | tainted | semmle.label | tainted |
+| UncontrolledFormatString.swift:108:26:108:50 | call to NSString.init(string:) | semmle.label | call to NSString.init(string:) |
+| UncontrolledFormatString.swift:108:43:108:43 | tainted | semmle.label | tainted |
+| UncontrolledFormatString.swift:109:40:109:64 | call to NSString.init(string:) | semmle.label | call to NSString.init(string:) |
+| UncontrolledFormatString.swift:109:57:109:57 | tainted | semmle.label | tainted |
+| UncontrolledFormatString.swift:111:33:111:57 | call to NSString.init(string:) | semmle.label | call to NSString.init(string:) |
+| UncontrolledFormatString.swift:111:50:111:50 | tainted | semmle.label | tainted |
+| UncontrolledFormatString.swift:112:47:112:71 | call to NSString.init(string:) | semmle.label | call to NSString.init(string:) |
+| UncontrolledFormatString.swift:112:64:112:64 | tainted | semmle.label | tainted |
+| UncontrolledFormatString.swift:115:11:115:11 | tainted | semmle.label | tainted |
+| UncontrolledFormatString.swift:116:11:116:11 | tainted | semmle.label | tainted |
+| UncontrolledFormatString.swift:116:11:116:11 | tainted | semmle.label | tainted |
+| UncontrolledFormatString.swift:118:61:118:61 | tainted | semmle.label | tainted |
+| UncontrolledFormatString.swift:130:39:130:39 | tainted | semmle.label | tainted |
+| UncontrolledFormatString.swift:135:20:135:44 | call to NSString.init(string:) | semmle.label | call to NSString.init(string:) |
+| UncontrolledFormatString.swift:135:37:135:37 | tainted | semmle.label | tainted |
+| UncontrolledFormatString.swift:139:5:139:5 | tainted | semmle.label | tainted |
+| UncontrolledFormatString.swift:140:9:140:9 | cstr [Collection element] | semmle.label | cstr [Collection element] |
+| UncontrolledFormatString.swift:141:24:141:24 | cstr | semmle.label | cstr |
+| UncontrolledFormatString.swift:143:21:143:21 | cstr | semmle.label | cstr |
+| UncontrolledFormatString.swift:145:27:145:27 | cstr | semmle.label | cstr |
+| UncontrolledFormatString.swift:147:35:147:35 | cstr | semmle.label | cstr |
+| UncontrolledFormatString.swift:154:26:154:26 | tainted | semmle.label | tainted |
+| UncontrolledFormatString.swift:156:32:156:32 | tainted | semmle.label | tainted |
+subpaths
+#select
+| UncontrolledFormatString.swift:79:16:79:16 | format | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:79:16:79:16 | format | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:97:24:97:24 | tainted | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:97:24:97:24 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:100:24:100:24 | tainted | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:100:24:100:24 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:101:24:101:24 | tainted | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:101:24:101:24 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:103:24:103:24 | tainted | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:103:24:103:24 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:104:24:104:24 | tainted | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:104:24:104:24 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:105:24:105:24 | tainted | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:105:24:105:24 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:106:42:106:42 | tainted | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:106:42:106:42 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:108:26:108:50 | call to NSString.init(string:) | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:108:26:108:50 | call to NSString.init(string:) | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:109:40:109:64 | call to NSString.init(string:) | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:109:40:109:64 | call to NSString.init(string:) | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:111:33:111:57 | call to NSString.init(string:) | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:111:33:111:57 | call to NSString.init(string:) | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:112:47:112:71 | call to NSString.init(string:) | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:112:47:112:71 | call to NSString.init(string:) | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:115:11:115:11 | tainted | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:115:11:115:11 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:116:11:116:11 | tainted | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:116:11:116:11 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:118:61:118:61 | tainted | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:118:61:118:61 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:130:39:130:39 | tainted | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:130:39:130:39 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:135:20:135:44 | call to NSString.init(string:) | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:135:20:135:44 | call to NSString.init(string:) | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:141:24:141:24 | cstr | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:141:24:141:24 | cstr | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:143:21:143:21 | cstr | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:143:21:143:21 | cstr | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:145:27:145:27 | cstr | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:145:27:145:27 | cstr | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:147:35:147:35 | cstr | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:147:35:147:35 | cstr | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:154:26:154:26 | tainted | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:154:26:154:26 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
+| UncontrolledFormatString.swift:156:32:156:32 | tainted | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | UncontrolledFormatString.swift:156:32:156:32 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | call to String.init(contentsOf:) | this user-provided value |
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..62b791e5d6f7
--- /dev/null
+++ b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.qlref
@@ -0,0 +1,2 @@
+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..37c9b2bbca5d
--- /dev/null
+++ b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.swift
@@ -0,0 +1,158 @@
+
+// --- 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