From 0d519c9b169be96f49302333763e0ab099a04490 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 2 Nov 2026 10:43:07 +0000 Subject: [PATCH 01/10] unified: Copy uncontrolled format string query and tests unchanged Establish a byte-for-byte original Swift baseline for the unified Swift port, including query help and samples. Adaptations follow separately. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../CWE-134/UncontrolledFormatString.qhelp | 38 +++++ .../CWE-134/UncontrolledFormatString.ql | 21 +++ .../CWE-134/UncontrolledFormatStringBad.swift | 2 + .../UncontrolledFormatStringGood.swift | 2 + .../UncontrolledFormatString.expected | 98 +++++++++++ .../UncontrolledFormatString.qlref | 2 + .../UncontrolledFormatString.swift | 158 ++++++++++++++++++ 7 files changed, 321 insertions(+) create mode 100644 unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.qhelp create mode 100644 unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.ql create mode 100644 unified/ql/src/queries/security/CWE-134/UncontrolledFormatStringBad.swift create mode 100644 unified/ql/src/queries/security/CWE-134/UncontrolledFormatStringGood.swift create mode 100644 unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.expected create mode 100644 unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.qlref create mode 100644 unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.swift 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..efc5eb9bff9d --- /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..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(_ 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 +{ + 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) // $ 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) // $ Alert + _ = String(format: tainted, locale: nil, arguments: []) // $ Alert + _ = String.localizedStringWithFormat(tainted) // $ Alert + + _ = NSString(format: NSString(string: tainted), "abc") // $ Alert + NSString.localizedStringWithFormat(NSString(string: tainted)) // $ Alert + + _ = NSMutableString(format: NSString(string: tainted), "abc") // $ Alert + NSMutableString.localizedStringWithFormat(NSString(string: tainted)) // $ Alert + + NSLog("abc") // GOOD: not tainted + NSLog(tainted) // $ Alert + MyLog(tainted) // $ 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: this should be flagged by `swift/predicate-injection`, not `swift/uncontrolled-format-string` + + tainted.withCString({ + cstr in + _ = dprintf(0, cstr, "abc") // $ Alert + _ = dprintf(0, "%s", cstr) // GOOD: format not tainted + _ = vprintf(cstr, getVaList(["abc"])) // $ Alert + _ = vprintf("%s", getVaList([cstr])) // GOOD: format not tainted + _ = vfprintf(nil, cstr, getVaList(["abc"])) // $ Alert + _ = vfprintf(nil, "%s", getVaList([cstr])) // GOOD: format not tainted + _ = vasprintf_l(nil, nil, cstr, getVaList(["abc"])) // $ 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") // $ Alert + _ = MyString(format: "%s", tainted) // GOOD: format not tainted + _ = MyString(formatString: tainted, "abc") // $ Alert + _ = MyString(formatString: "%s", tainted) // GOOD: format not tainted +} From 15a49fa13ec5b094a55fdbe846f4693bfd5f7a1f Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 2 Nov 2026 10:43:37 +0000 Subject: [PATCH 02/10] unified: Collect original Swift format string configuration and models Inline the original query configuration and extension declarations unchanged, recording their provenance before adapting them to unified APIs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../CWE-134/UncontrolledFormatString.ql | 122 +++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.ql b/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.ql index 4376f0f4c0f3..a03228ec15eb 100644 --- a/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.ql +++ b/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.ql @@ -11,8 +11,128 @@ */ import swift +import codeql.swift.StringFormat import codeql.swift.dataflow.DataFlow -import codeql.swift.security.UncontrolledFormatStringQuery +import codeql.swift.dataflow.TaintTracking +import codeql.swift.dataflow.FlowSources +import codeql.swift.dataflow.ExternalFlow +import codeql.swift.frameworks.StandardLibrary.PointerTypes +import codeql.swift.security.PredicateInjectionExtensions + +// Copied from swift/ql/lib/codeql/swift/security/UncontrolledFormatStringExtensions.qll. + +/** + * A dataflow sink for uncontrolled format string vulnerabilities. + */ +abstract class UncontrolledFormatStringSink extends DataFlow::Node { } + +/** + * A barrier for uncontrolled format string vulnerabilities. + */ +abstract class UncontrolledFormatStringBarrier extends DataFlow::Node { } + +/** + * A unit class for adding additional flow steps. + */ +class UncontrolledFormatStringAdditionalFlowStep extends Unit { + /** + * Holds if the step from `node1` to `node2` should be considered a flow + * step for paths related to uncontrolled format string vulnerabilities. + */ + abstract predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo); +} + +/** + * A default uncontrolled format string sink. + */ +private class DefaultUncontrolledFormatStringSink extends UncontrolledFormatStringSink { + DefaultUncontrolledFormatStringSink() { + // the format argument to a `FormattingFunctionCall`. + this.asExpr() = any(FormattingFunctionCall fc).getFormat() + or + // a sink defined in a CSV model. + sinkNode(this, "format-string") + } +} + +/** + * Holds if `f`, `ix` describe `pd` and `pd` is a parameter that might be a format + * string. + */ +pragma[noinline] +predicate formatLikeHeuristic(Callable f, int ix, ParamDecl pd) { + pd.getName() = ["format", "formatString", "fmt"] and + pd = f.getParam(ix) +} + +/** + * An uncontrolled format string sink that is determined by imprecise methods. + */ +class HeuristicUncontrolledFormatStringSink extends UncontrolledFormatStringSink { + HeuristicUncontrolledFormatStringSink() { + exists(Callable f, Type argsType | + ( + // by parameter name + exists(CallExpr ce, int ix | + formatLikeHeuristic(f, ix, _) and + f = ce.getStaticTarget() and + this.asExpr() = ce.getArgument(ix).getExpr() + ) + or + // by argument name + exists(Argument a | + a.getLabel() = ["format", "formatString", "fmt"] and + a.getApplyExpr().getStaticTarget() = f and + this.asExpr() = a.getExpr() + ) + ) and + // last parameter is vararg + argsType = f.getParam(f.getNumberOfParams() - 1).getType().getUnderlyingType() and + ( + argsType instanceof CVaListPointerType or + argsType instanceof VariadicSequenceType + ) + ) and + // prevent overlap with `swift/predicate-injection` + not this instanceof PredicateInjectionSink + } +} + +/** + * A barrier for uncontrolled format string vulnerabilities. + */ +private class UncontrolledFormatStringDefaultBarrier extends UncontrolledFormatStringBarrier { + UncontrolledFormatStringDefaultBarrier() { + // any numeric type + this.asExpr().getType().getUnderlyingType().getABaseType*().getName() = + ["Numeric", "SignedInteger", "UnsignedInteger"] + } +} + +// Copied from swift/ql/lib/codeql/swift/security/UncontrolledFormatStringQuery.qll. + +/** + * A taint configuration for tainted data that reaches a format string. + */ +module TaintedFormatConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node node) { node instanceof FlowSource } + + predicate isSink(DataFlow::Node node) { node instanceof UncontrolledFormatStringSink } + + predicate isBarrier(DataFlow::Node barrier) { barrier instanceof UncontrolledFormatStringBarrier } + + predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { + any(UncontrolledFormatStringAdditionalFlowStep s).step(nodeFrom, nodeTo) + } + + 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 From 8b9814fd909510c02e5b948e89feb7f7d5061d6c Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 2 Nov 2026 10:53:14 +0000 Subject: [PATCH 03/10] unified: Port format string query to unified data flow APIs Use models-as-data sources and format-string sinks, update the query ID and test reference, and record the compiling baseline with missing expectations. Formatting sink models, the variadic-call heuristic and numeric barriers remain to be ported. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../CWE-134/UncontrolledFormatString.ql | 118 ++---------------- .../UncontrolledFormatString.expected | 96 +------------- .../UncontrolledFormatString.qlref | 5 +- .../UncontrolledFormatString.swift | 48 +++---- 4 files changed, 39 insertions(+), 228 deletions(-) diff --git a/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.ql b/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.ql index a03228ec15eb..cf741901a6e0 100644 --- a/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.ql +++ b/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.ql @@ -5,124 +5,28 @@ * @problem.severity error * @security-severity 7.3 * @precision high - * @id swift/uncontrolled-format-string + * @id unified/swift/uncontrolled-format-string * @tags security * external/cwe/cwe-134 */ -import swift -import codeql.swift.StringFormat -import codeql.swift.dataflow.DataFlow -import codeql.swift.dataflow.TaintTracking -import codeql.swift.dataflow.FlowSources -import codeql.swift.dataflow.ExternalFlow -import codeql.swift.frameworks.StandardLibrary.PointerTypes -import codeql.swift.security.PredicateInjectionExtensions - -// Copied from swift/ql/lib/codeql/swift/security/UncontrolledFormatStringExtensions.qll. - -/** - * A dataflow sink for uncontrolled format string vulnerabilities. - */ -abstract class UncontrolledFormatStringSink extends DataFlow::Node { } - -/** - * A barrier for uncontrolled format string vulnerabilities. - */ -abstract class UncontrolledFormatStringBarrier extends DataFlow::Node { } - -/** - * A unit class for adding additional flow steps. - */ -class UncontrolledFormatStringAdditionalFlowStep extends Unit { - /** - * Holds if the step from `node1` to `node2` should be considered a flow - * step for paths related to uncontrolled format string vulnerabilities. - */ - abstract predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo); -} - -/** - * A default uncontrolled format string sink. - */ -private class DefaultUncontrolledFormatStringSink extends UncontrolledFormatStringSink { - DefaultUncontrolledFormatStringSink() { - // the format argument to a `FormattingFunctionCall`. - this.asExpr() = any(FormattingFunctionCall fc).getFormat() - or - // a sink defined in a CSV model. - sinkNode(this, "format-string") - } -} - -/** - * Holds if `f`, `ix` describe `pd` and `pd` is a parameter that might be a format - * string. - */ -pragma[noinline] -predicate formatLikeHeuristic(Callable f, int ix, ParamDecl pd) { - pd.getName() = ["format", "formatString", "fmt"] and - pd = f.getParam(ix) -} - -/** - * An uncontrolled format string sink that is determined by imprecise methods. - */ -class HeuristicUncontrolledFormatStringSink extends UncontrolledFormatStringSink { - HeuristicUncontrolledFormatStringSink() { - exists(Callable f, Type argsType | - ( - // by parameter name - exists(CallExpr ce, int ix | - formatLikeHeuristic(f, ix, _) and - f = ce.getStaticTarget() and - this.asExpr() = ce.getArgument(ix).getExpr() - ) - or - // by argument name - exists(Argument a | - a.getLabel() = ["format", "formatString", "fmt"] and - a.getApplyExpr().getStaticTarget() = f and - this.asExpr() = a.getExpr() - ) - ) and - // last parameter is vararg - argsType = f.getParam(f.getNumberOfParams() - 1).getType().getUnderlyingType() and - ( - argsType instanceof CVaListPointerType or - argsType instanceof VariadicSequenceType - ) - ) and - // prevent overlap with `swift/predicate-injection` - not this instanceof PredicateInjectionSink - } -} - -/** - * A barrier for uncontrolled format string vulnerabilities. - */ -private class UncontrolledFormatStringDefaultBarrier extends UncontrolledFormatStringBarrier { - UncontrolledFormatStringDefaultBarrier() { - // any numeric type - this.asExpr().getType().getUnderlyingType().getABaseType*().getName() = - ["Numeric", "SignedInteger", "UnsignedInteger"] - } -} - -// Copied from swift/ql/lib/codeql/swift/security/UncontrolledFormatStringQuery.qll. +import unified /** * A taint configuration for tainted data that reaches a format string. */ module TaintedFormatConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node node) { node instanceof FlowSource } + predicate isSource(DataFlow::Node node) { Models::isSource(node, _) } - predicate isSink(DataFlow::Node node) { node instanceof UncontrolledFormatStringSink } - - predicate isBarrier(DataFlow::Node barrier) { barrier instanceof UncontrolledFormatStringBarrier } + predicate isSink(DataFlow::Node node) { + // TODO: Port the variadic-call heuristic from original Swift's + // UncontrolledFormatStringExtensions.qll, excluding predicate-injection sinks. + Models::isSink(node, "format-string") + } - predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { - any(UncontrolledFormatStringAdditionalFlowStep s).step(nodeFrom, nodeTo) + predicate isBarrier(DataFlow::Node node) { + // TODO: Port the numeric-type barrier from original Swift's UncontrolledFormatStringExtensions.qll. + none() } predicate observeDiffInformedIncrementalMode() { any() } 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 index 94dd27a82c2f..58f42bec0c84 100644 --- a/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.expected +++ b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.expected @@ -1,98 +1,4 @@ +#select 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 index 62b791e5d6f7..c4c2f8c13ff5 100644 --- a/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.qlref +++ b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.qlref @@ -1,2 +1,3 @@ -query: queries/Security/CWE-134/UncontrolledFormatString.ql -postprocess: utils/test/InlineExpectationsTestQuery.ql +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 index 37c9b2bbca5d..40864b55a5f7 100644 --- a/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.swift +++ b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.swift @@ -76,7 +76,7 @@ func vasprintf_l(_ ret: UnsafeMutablePointer?>?, _ l func MyLog(_ format: String, _ args: CVarArg...) { withVaList(args) { arglist in - NSLogv(format, arglist) // $ Alert + NSLogv(format, arglist) // $ MISSING: Alert } } @@ -88,34 +88,34 @@ class MyString { } func tests() throws { - let tainted = try! String(contentsOf: URL(string: "http://example.com")!) // $ Source + let tainted = try! String(contentsOf: URL(string: "http://example.com")!) // $ MISSING: Source _ = String("abc") // GOOD: not a format string _ = String(tainted) // GOOD: not a format string _ = String(format: "abc") // GOOD: not tainted - _ = String(format: tainted) // $ Alert + _ = 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, "abc") // $ MISSING: Alert + _ = String(format: tainted, tainted) // $ MISSING: Alert - _ = String(format: tainted, arguments: []) // $ Alert - _ = String(format: tainted, locale: nil) // $ Alert - _ = String(format: tainted, locale: nil, arguments: []) // $ Alert - _ = String.localizedStringWithFormat(tainted) // $ Alert + _ = String(format: tainted, arguments: []) // $ MISSING: Alert + _ = String(format: tainted, locale: nil) // $ MISSING: Alert + _ = String(format: tainted, locale: nil, arguments: []) // $ MISSING: Alert + _ = String.localizedStringWithFormat(tainted) // $ MISSING: Alert - _ = NSString(format: NSString(string: tainted), "abc") // $ Alert - NSString.localizedStringWithFormat(NSString(string: tainted)) // $ Alert + _ = NSString(format: NSString(string: tainted), "abc") // $ MISSING: Alert + NSString.localizedStringWithFormat(NSString(string: tainted)) // $ MISSING: Alert - _ = NSMutableString(format: NSString(string: tainted), "abc") // $ Alert - NSMutableString.localizedStringWithFormat(NSString(string: tainted)) // $ Alert + _ = NSMutableString(format: NSString(string: tainted), "abc") // $ MISSING: Alert + NSMutableString.localizedStringWithFormat(NSString(string: tainted)) // $ MISSING: Alert NSLog("abc") // GOOD: not tainted - NSLog(tainted) // $ Alert - MyLog(tainted) // $ Alert + NSLog(tainted) // $ MISSING: Alert + MyLog(tainted) // $ MISSING: Alert - NSException.raise(NSExceptionName("exception"), format: tainted, arguments: getVaList([])) // $ Alert + NSException.raise(NSExceptionName("exception"), format: tainted, arguments: getVaList([])) // $ MISSING: Alert let taintedVal = Int(tainted)! let taintedSan = "\(taintedVal)" @@ -127,32 +127,32 @@ func tests() throws { _ = String("abc").appendingFormat("%s", "abc") // GOOD: not tainted _ = String("abc").appendingFormat("%s", tainted) // GOOD: format not tainted - _ = String("abc").appendingFormat(tainted, "abc") // $ Alert + _ = String("abc").appendingFormat(tainted, "abc") // $ MISSING: 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 + s.appendFormat(NSString(string: tainted), "abc") // $ MISSING: Alert _ = NSPredicate(format: tainted) // GOOD: this should be flagged by `swift/predicate-injection`, not `swift/uncontrolled-format-string` tainted.withCString({ cstr in - _ = dprintf(0, cstr, "abc") // $ Alert + _ = dprintf(0, cstr, "abc") // $ MISSING: Alert _ = dprintf(0, "%s", cstr) // GOOD: format not tainted - _ = vprintf(cstr, getVaList(["abc"])) // $ Alert + _ = vprintf(cstr, getVaList(["abc"])) // $ MISSING: Alert _ = vprintf("%s", getVaList([cstr])) // GOOD: format not tainted - _ = vfprintf(nil, cstr, getVaList(["abc"])) // $ Alert + _ = vfprintf(nil, cstr, getVaList(["abc"])) // $ MISSING: Alert _ = vfprintf(nil, "%s", getVaList([cstr])) // GOOD: format not tainted - _ = vasprintf_l(nil, nil, cstr, getVaList(["abc"])) // $ Alert + _ = 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") // $ Alert + _ = MyString(format: tainted, "abc") // $ MISSING: Alert _ = MyString(format: "%s", tainted) // GOOD: format not tainted - _ = MyString(formatString: tainted, "abc") // $ Alert + _ = MyString(formatString: tainted, "abc") // $ MISSING: Alert _ = MyString(formatString: "%s", tainted) // GOOD: format not tainted } From 912efca4931a5ab0869e87c62e2b9a301a3a7015 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 2 Nov 2026 10:55:11 +0000 Subject: [PATCH 04/10] unified: Model Swift formatting API arguments as format-string sinks Translate the standard formatting sinks into models-as-data for String and Foundation formatting APIs and common printf variants. The original cases now produce ten results; omitted variadic arguments, closure-based C string flow and custom formatting heuristics remain missing. All unified security query tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- unified/ql/lib/ext/format-string.model.yml | 38 ++++++++++++++++ .../UncontrolledFormatString.expected | 45 +++++++++++++++++++ .../UncontrolledFormatString.swift | 22 ++++----- 3 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 unified/ql/lib/ext/format-string.model.yml 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..ae00fdbab145 --- /dev/null +++ b/unified/ql/lib/ext/format-string.model.yml @@ -0,0 +1,38 @@ +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"] + - ["", "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"] + - ["", "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"] + - ["", "String", true, "localizedStringWithFormat(_:_:)", "", "", "Argument[0]", "format-string", "manual"] + - ["", "NSString", true, "localizedStringWithFormat(_:_:)", "", "", "Argument[0]", "format-string", "manual"] + - ["", "NSMutableString", true, "localizedStringWithFormat(_:_:)", "", "", "Argument[0]", "format-string", "manual"] + - ["", "StringProtocol", true, "appendingFormat(_:_:)", "", "", "Argument[0]", "format-string", "manual"] + - ["", "NSMutableString", true, "appendFormat(_:_:)", "", "", "Argument[0]", "format-string", "manual"] + - ["", "", false, "NSLog(_:_:)", "", "", "Argument[0]", "format-string", "manual"] + - ["", "", false, "NSLogv(_:_:)", "", "", "Argument[0]", "format-string", "manual"] + - ["", "NSException", true, "raise(_:arguments:format:)", "", "", "Argument[format:]", "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/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.expected b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.expected index 58f42bec0c84..b000dbd39d73 100644 --- a/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.expected +++ b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.expected @@ -1,4 +1,49 @@ #select +| UncontrolledFormatString.swift:79:16:79:21 | format | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:79:16:79:21 | format | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:100:24:100:30 | tainted | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:100:24:100:30 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:101:24:101:30 | tainted | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:101:24:101:30 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:103:24:103:30 | tainted | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:103:24:103:30 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:105:24:105:30 | tainted | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:105:24:105:30 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:108:26:108:50 | NSString(...) | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:108:26:108:50 | NSString(...) | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:111:33:111:57 | NSString(...) | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:111:33:111:57 | NSString(...) | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:118:61:118:67 | tainted | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:118:61:118:67 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:130:39:130:45 | tainted | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:130:39:130:45 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:135:20:135:44 | NSString(...) | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:135:20:135:44 | NSString(...) | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | edges +| UncontrolledFormatString.swift:77:14:77:19 | format | UncontrolledFormatString.swift:78:22:80:5 | FunctionExpr [format] | provenance | | +| UncontrolledFormatString.swift:78:22:80:5 | FunctionExpr [format] | UncontrolledFormatString.swift:79:16:79:21 | format | provenance | | +| UncontrolledFormatString.swift:91:9:91:15 | tainted | UncontrolledFormatString.swift:100:24:100:30 | tainted | provenance | | +| UncontrolledFormatString.swift:91:9:91:15 | tainted | UncontrolledFormatString.swift:101:24:101:30 | tainted | provenance | | +| UncontrolledFormatString.swift:91:9:91:15 | tainted | UncontrolledFormatString.swift:103:24:103:30 | tainted | provenance | | +| UncontrolledFormatString.swift:91:9:91:15 | tainted | UncontrolledFormatString.swift:105:24:105:30 | tainted | provenance | | +| UncontrolledFormatString.swift:91:9:91:15 | tainted | UncontrolledFormatString.swift:108:43:108:49 | tainted | provenance | | +| UncontrolledFormatString.swift:91:9:91:15 | tainted | UncontrolledFormatString.swift:111:50:111:56 | tainted | provenance | | +| UncontrolledFormatString.swift:91:9:91:15 | tainted | UncontrolledFormatString.swift:116:11:116:17 | tainted | provenance | | +| UncontrolledFormatString.swift:91:9:91:15 | tainted | UncontrolledFormatString.swift:118:61:118:67 | tainted | provenance | | +| UncontrolledFormatString.swift:91:9:91:15 | tainted | UncontrolledFormatString.swift:130:39:130:45 | tainted | provenance | | +| UncontrolledFormatString.swift:91:9:91:15 | tainted | UncontrolledFormatString.swift:135:37:135:43 | tainted | provenance | | +| UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:91:9:91:15 | tainted | provenance | | +| UncontrolledFormatString.swift:108:43:108:49 | tainted | UncontrolledFormatString.swift:108:26:108:50 | NSString(...) | provenance | | +| UncontrolledFormatString.swift:111:50:111:56 | tainted | UncontrolledFormatString.swift:111:33:111:57 | NSString(...) | provenance | | +| UncontrolledFormatString.swift:116:11:116:17 | tainted | UncontrolledFormatString.swift:77:14:77:19 | format | provenance | | +| UncontrolledFormatString.swift:135:37:135:43 | tainted | UncontrolledFormatString.swift:135:20:135:44 | NSString(...) | provenance | | nodes +| UncontrolledFormatString.swift:77:14:77:19 | format | semmle.label | format | +| UncontrolledFormatString.swift:78:22:80:5 | FunctionExpr [format] | semmle.label | FunctionExpr [format] | +| UncontrolledFormatString.swift:79:16:79:21 | format | semmle.label | format | +| UncontrolledFormatString.swift:91:9:91:15 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:91:24:91:77 | String(...) | semmle.label | String(...) | +| UncontrolledFormatString.swift:100:24:100:30 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:101:24:101: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:108:26:108:50 | NSString(...) | semmle.label | NSString(...) | +| UncontrolledFormatString.swift:108:43:108:49 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:111:33:111:57 | NSString(...) | semmle.label | NSString(...) | +| UncontrolledFormatString.swift:111:50:111:56 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:116:11:116:17 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:118:61:118:67 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:130:39:130:45 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:135:20:135:44 | NSString(...) | semmle.label | NSString(...) | +| UncontrolledFormatString.swift:135:37:135:43 | tainted | semmle.label | tainted | subpaths 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 index 40864b55a5f7..fc778017f9a4 100644 --- a/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.swift +++ b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.swift @@ -76,7 +76,7 @@ func vasprintf_l(_ ret: UnsafeMutablePointer?>?, _ l func MyLog(_ format: String, _ args: CVarArg...) { withVaList(args) { arglist in - NSLogv(format, arglist) // $ MISSING: Alert + NSLogv(format, arglist) // $ Alert } } @@ -88,7 +88,7 @@ class MyString { } func tests() throws { - let tainted = try! String(contentsOf: URL(string: "http://example.com")!) // $ MISSING: Source + 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 @@ -97,25 +97,25 @@ func tests() throws { _ = 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") // $ MISSING: Alert - _ = String(format: tainted, tainted) // $ MISSING: Alert + _ = String(format: tainted, "abc") // $ Alert + _ = String(format: tainted, tainted) // $ Alert - _ = String(format: tainted, arguments: []) // $ MISSING: Alert + _ = String(format: tainted, arguments: []) // $ Alert _ = String(format: tainted, locale: nil) // $ MISSING: Alert - _ = String(format: tainted, locale: nil, arguments: []) // $ MISSING: Alert + _ = String(format: tainted, locale: nil, arguments: []) // $ Alert _ = String.localizedStringWithFormat(tainted) // $ MISSING: Alert - _ = NSString(format: NSString(string: tainted), "abc") // $ MISSING: Alert + _ = NSString(format: NSString(string: tainted), "abc") // $ Alert NSString.localizedStringWithFormat(NSString(string: tainted)) // $ MISSING: Alert - _ = NSMutableString(format: NSString(string: tainted), "abc") // $ 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.raise(NSExceptionName("exception"), format: tainted, arguments: getVaList([])) // $ MISSING: Alert + NSException.raise(NSExceptionName("exception"), format: tainted, arguments: getVaList([])) // $ Alert let taintedVal = Int(tainted)! let taintedSan = "\(taintedVal)" @@ -127,12 +127,12 @@ func tests() throws { _ = String("abc").appendingFormat("%s", "abc") // GOOD: not tainted _ = String("abc").appendingFormat("%s", tainted) // GOOD: format not tainted - _ = String("abc").appendingFormat(tainted, "abc") // $ MISSING: Alert + _ = 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") // $ MISSING: Alert + s.appendFormat(NSString(string: tainted), "abc") // $ Alert _ = NSPredicate(format: tainted) // GOOD: this should be flagged by `swift/predicate-injection`, not `swift/uncontrolled-format-string` From 842f2c4a091c3d949b9b370df7f39a04f43779c7 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 2 Nov 2026 10:57:29 +0000 Subject: [PATCH 05/10] unified: Cover modeled variadic formatting and direct C string arguments Add positive and safe comparisons for localized formatting, NSLog and imported C format functions without closure-based conversion. All ten prior results are preserved and eight additional sink locations are covered. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../UncontrolledFormatString.expected | 31 +++++++++++++++++++ .../UncontrolledFormatString.swift | 23 ++++++++++++++ 2 files changed, 54 insertions(+) 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 index b000dbd39d73..0c2d26f94447 100644 --- a/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.expected +++ b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.expected @@ -9,6 +9,14 @@ | UncontrolledFormatString.swift:118:61:118:67 | tainted | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:118:61:118:67 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | | UncontrolledFormatString.swift:130:39:130:45 | tainted | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:130:39:130:45 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | | UncontrolledFormatString.swift:135:20:135:44 | NSString(...) | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:135:20:135:44 | NSString(...) | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:163:42:163:48 | tainted | UncontrolledFormatString.swift:161:24:161:77 | String(...) | UncontrolledFormatString.swift:163:42:163:48 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:161:24:161:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:165:40:165:64 | NSString(...) | UncontrolledFormatString.swift:161:24:161:77 | String(...) | UncontrolledFormatString.swift:165:40:165:64 | NSString(...) | This format string depends on $@. | UncontrolledFormatString.swift:161:24:161:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:167:47:167:71 | NSString(...) | UncontrolledFormatString.swift:161:24:161:77 | String(...) | UncontrolledFormatString.swift:167:47:167:71 | NSString(...) | This format string depends on $@. | UncontrolledFormatString.swift:161:24:161:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:170:11:170:17 | tainted | UncontrolledFormatString.swift:161:24:161:77 | String(...) | UncontrolledFormatString.swift:170:11:170:17 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:161:24:161:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:173:20:173:26 | tainted | UncontrolledFormatString.swift:161:24:161:77 | String(...) | UncontrolledFormatString.swift:173:20:173:26 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:161:24:161:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:175:17:175:23 | tainted | UncontrolledFormatString.swift:161:24:161:77 | String(...) | UncontrolledFormatString.swift:175:17:175:23 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:161:24:161:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:177:23:177:29 | tainted | UncontrolledFormatString.swift:161:24:161:77 | String(...) | UncontrolledFormatString.swift:177:23:177:29 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:161:24:161:77 | String(...) | this user-provided value | +| UncontrolledFormatString.swift:179:31:179:37 | tainted | UncontrolledFormatString.swift:161:24:161:77 | String(...) | UncontrolledFormatString.swift:179:31:179:37 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:161:24:161:77 | String(...) | this user-provided value | edges | UncontrolledFormatString.swift:77:14:77:19 | format | UncontrolledFormatString.swift:78:22:80:5 | FunctionExpr [format] | provenance | | | UncontrolledFormatString.swift:78:22:80:5 | FunctionExpr [format] | UncontrolledFormatString.swift:79:16:79:21 | format | provenance | | @@ -27,6 +35,17 @@ edges | UncontrolledFormatString.swift:111:50:111:56 | tainted | UncontrolledFormatString.swift:111:33:111:57 | NSString(...) | provenance | | | UncontrolledFormatString.swift:116:11:116:17 | tainted | UncontrolledFormatString.swift:77:14:77:19 | format | provenance | | | UncontrolledFormatString.swift:135:37:135:43 | tainted | UncontrolledFormatString.swift:135:20:135:44 | NSString(...) | provenance | | +| UncontrolledFormatString.swift:161:9:161:15 | tainted | UncontrolledFormatString.swift:163:42:163:48 | tainted | provenance | | +| UncontrolledFormatString.swift:161:9:161:15 | tainted | UncontrolledFormatString.swift:165:57:165:63 | tainted | provenance | | +| UncontrolledFormatString.swift:161:9:161:15 | tainted | UncontrolledFormatString.swift:167:64:167:70 | tainted | provenance | | +| UncontrolledFormatString.swift:161:9:161:15 | tainted | UncontrolledFormatString.swift:170:11:170:17 | tainted | provenance | | +| UncontrolledFormatString.swift:161:9:161:15 | tainted | UncontrolledFormatString.swift:173:20:173:26 | tainted | provenance | | +| UncontrolledFormatString.swift:161:9:161:15 | tainted | UncontrolledFormatString.swift:175:17:175:23 | tainted | provenance | | +| UncontrolledFormatString.swift:161:9:161:15 | tainted | UncontrolledFormatString.swift:177:23:177:29 | tainted | provenance | | +| UncontrolledFormatString.swift:161:9:161:15 | tainted | UncontrolledFormatString.swift:179:31:179:37 | tainted | provenance | | +| UncontrolledFormatString.swift:161:24:161:77 | String(...) | UncontrolledFormatString.swift:161:9:161:15 | tainted | provenance | | +| UncontrolledFormatString.swift:165:57:165:63 | tainted | UncontrolledFormatString.swift:165:40:165:64 | NSString(...) | provenance | | +| UncontrolledFormatString.swift:167:64:167:70 | tainted | UncontrolledFormatString.swift:167:47:167:71 | NSString(...) | provenance | | nodes | UncontrolledFormatString.swift:77:14:77:19 | format | semmle.label | format | | UncontrolledFormatString.swift:78:22:80:5 | FunctionExpr [format] | semmle.label | FunctionExpr [format] | @@ -46,4 +65,16 @@ nodes | UncontrolledFormatString.swift:130:39:130:45 | tainted | semmle.label | tainted | | UncontrolledFormatString.swift:135:20:135:44 | NSString(...) | semmle.label | NSString(...) | | UncontrolledFormatString.swift:135:37:135:43 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:161:9:161:15 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:161:24:161:77 | String(...) | semmle.label | String(...) | +| UncontrolledFormatString.swift:163:42:163:48 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:165:40:165:64 | NSString(...) | semmle.label | NSString(...) | +| UncontrolledFormatString.swift:165:57:165:63 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:167:47:167:71 | NSString(...) | semmle.label | NSString(...) | +| UncontrolledFormatString.swift:167:64:167:70 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:170:11:170:17 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:173:20:173:26 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:175:17:175:23 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:177:23:177:29 | tainted | semmle.label | tainted | +| UncontrolledFormatString.swift:179:31:179:37 | tainted | semmle.label | tainted | subpaths 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 index fc778017f9a4..0734a124dca2 100644 --- a/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.swift +++ b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.swift @@ -156,3 +156,26 @@ func tests() throws { _ = 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 +} From c1d2fe9aa59c0f9af60e1cc0e0dd6e5ae813afcb Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 2 Nov 2026 10:58:26 +0000 Subject: [PATCH 06/10] unified: Clarify format string port limitations and refresh help reference Record the remaining printf models and extraction prerequisite for custom variadic-call heuristics. Use the verified canonical OWASP reference and clarify the predicate-injection negative case. Help XML and samples validate, and all five affected query and model tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../queries/security/CWE-134/UncontrolledFormatString.qhelp | 2 +- .../src/queries/security/CWE-134/UncontrolledFormatString.ql | 4 +++- .../UncontrolledFormatString/UncontrolledFormatString.swift | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.qhelp b/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.qhelp index efc5eb9bff9d..634d4e77266b 100644 --- a/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.qhelp +++ b/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.qhelp @@ -31,7 +31,7 @@ an untrusted source. This also helps to prevent errors where the format argument
  • OWASP: -Format string attack. +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 index cf741901a6e0..13f760d9efea 100644 --- a/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.ql +++ b/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.ql @@ -19,8 +19,10 @@ module TaintedFormatConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node node) { Models::isSource(node, _) } predicate isSink(DataFlow::Node node) { + // TODO: Model the remaining printf variants from original Swift's StringFormat.qll. // TODO: Port the variadic-call heuristic from original Swift's - // UncontrolledFormatStringExtensions.qll, excluding predicate-injection sinks. + // UncontrolledFormatStringExtensions.qll once variadic parameters are extracted, + // excluding predicate-injection sinks. Models::isSink(node, "format-string") } 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 index 0734a124dca2..35fd2fb11405 100644 --- a/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.swift +++ b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.swift @@ -134,7 +134,7 @@ func tests() throws { s.appendFormat(NSString(string: "%s"), "abc") // GOOD: not tainted s.appendFormat(NSString(string: tainted), "abc") // $ Alert - _ = NSPredicate(format: tainted) // GOOD: this should be flagged by `swift/predicate-injection`, not `swift/uncontrolled-format-string` + _ = NSPredicate(format: tainted) // GOOD: predicate injection, not printf-style formatting tainted.withCString({ cstr in From f925f3554a42b353466345320b45d589582b1748 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 2 Nov 2026 16:17:32 +0000 Subject: [PATCH 07/10] unified: Update / clarify the TODO list. --- .../queries/security/CWE-134/UncontrolledFormatString.ql | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.ql b/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.ql index 13f760d9efea..ffc109e08e7a 100644 --- a/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.ql +++ b/unified/ql/src/queries/security/CWE-134/UncontrolledFormatString.ql @@ -19,10 +19,12 @@ module TaintedFormatConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node node) { Models::isSource(node, _) } predicate isSink(DataFlow::Node node) { - // TODO: Model the remaining printf variants from original Swift's StringFormat.qll. + // 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, - // excluding predicate-injection sinks. + // `UncontrolledFormatStringExtensions.qll` once variadic parameters are extracted. Models::isSink(node, "format-string") } From cf635e8c9c758e6065867f8b4852aca645c48f2e Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 2 Nov 2026 16:18:45 +0000 Subject: [PATCH 08/10] unified: Re-order the MaD models for readability. --- unified/ql/lib/ext/format-string.model.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/unified/ql/lib/ext/format-string.model.yml b/unified/ql/lib/ext/format-string.model.yml index ae00fdbab145..c480536daa13 100644 --- a/unified/ql/lib/ext/format-string.model.yml +++ b/unified/ql/lib/ext/format-string.model.yml @@ -7,22 +7,22 @@ extensions: - ["", "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"] - - ["", "String", true, "localizedStringWithFormat(_:_:)", "", "", "Argument[0]", "format-string", "manual"] - - ["", "NSString", true, "localizedStringWithFormat(_:_:)", "", "", "Argument[0]", "format-string", "manual"] - ["", "NSMutableString", true, "localizedStringWithFormat(_:_:)", "", "", "Argument[0]", "format-string", "manual"] - - ["", "StringProtocol", true, "appendingFormat(_:_:)", "", "", "Argument[0]", "format-string", "manual"] - ["", "NSMutableString", true, "appendFormat(_:_:)", "", "", "Argument[0]", "format-string", "manual"] + - ["", "StringProtocol", true, "appendingFormat(_:_:)", "", "", "Argument[0]", "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"] - - ["", "NSException", true, "raise(_:arguments:format:)", "", "", "Argument[format:]", "format-string", "manual"] - ["", "", false, "printf(_:_:)", "", "", "Argument[0]", "format-string", "manual"] - ["", "", false, "fprintf(_:_:_:)", "", "", "Argument[1]", "format-string", "manual"] - ["", "", false, "sprintf(_:_:_:)", "", "", "Argument[1]", "format-string", "manual"] From 33559c229da67774d0c645ace1a92903ea3a99b7 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 2 Nov 2026 16:30:17 +0000 Subject: [PATCH 09/10] unified: Add test case for NSException.init. --- .../UncontrolledFormatString.expected | 148 +++++++++--------- .../UncontrolledFormatString.swift | 3 + 2 files changed, 77 insertions(+), 74 deletions(-) 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 index 0c2d26f94447..c6be8d94cdf8 100644 --- a/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.expected +++ b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.expected @@ -1,80 +1,80 @@ #select -| UncontrolledFormatString.swift:79:16:79:21 | format | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:79:16:79:21 | format | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | -| UncontrolledFormatString.swift:100:24:100:30 | tainted | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:100:24:100:30 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | -| UncontrolledFormatString.swift:101:24:101:30 | tainted | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:101:24:101:30 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | -| UncontrolledFormatString.swift:103:24:103:30 | tainted | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:103:24:103:30 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | -| UncontrolledFormatString.swift:105:24:105:30 | tainted | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:105:24:105:30 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | -| UncontrolledFormatString.swift:108:26:108:50 | NSString(...) | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:108:26:108:50 | NSString(...) | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | -| UncontrolledFormatString.swift:111:33:111:57 | NSString(...) | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:111:33:111:57 | NSString(...) | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | -| UncontrolledFormatString.swift:118:61:118:67 | tainted | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:118:61:118:67 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | -| UncontrolledFormatString.swift:130:39:130:45 | tainted | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:130:39:130:45 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | -| UncontrolledFormatString.swift:135:20:135:44 | NSString(...) | UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:135:20:135:44 | NSString(...) | This format string depends on $@. | UncontrolledFormatString.swift:91:24:91:77 | String(...) | this user-provided value | -| UncontrolledFormatString.swift:163:42:163:48 | tainted | UncontrolledFormatString.swift:161:24:161:77 | String(...) | UncontrolledFormatString.swift:163:42:163:48 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:161:24:161:77 | String(...) | this user-provided value | -| UncontrolledFormatString.swift:165:40:165:64 | NSString(...) | UncontrolledFormatString.swift:161:24:161:77 | String(...) | UncontrolledFormatString.swift:165:40:165:64 | NSString(...) | This format string depends on $@. | UncontrolledFormatString.swift:161:24:161:77 | String(...) | this user-provided value | -| UncontrolledFormatString.swift:167:47:167:71 | NSString(...) | UncontrolledFormatString.swift:161:24:161:77 | String(...) | UncontrolledFormatString.swift:167:47:167:71 | NSString(...) | This format string depends on $@. | UncontrolledFormatString.swift:161:24:161:77 | String(...) | this user-provided value | -| UncontrolledFormatString.swift:170:11:170:17 | tainted | UncontrolledFormatString.swift:161:24:161:77 | String(...) | UncontrolledFormatString.swift:170:11:170:17 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:161:24:161:77 | String(...) | this user-provided value | -| UncontrolledFormatString.swift:173:20:173:26 | tainted | UncontrolledFormatString.swift:161:24:161:77 | String(...) | UncontrolledFormatString.swift:173:20:173:26 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:161:24:161:77 | String(...) | this user-provided value | -| UncontrolledFormatString.swift:175:17:175:23 | tainted | UncontrolledFormatString.swift:161:24:161:77 | String(...) | UncontrolledFormatString.swift:175:17:175:23 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:161:24:161:77 | String(...) | this user-provided value | -| UncontrolledFormatString.swift:177:23:177:29 | tainted | UncontrolledFormatString.swift:161:24:161:77 | String(...) | UncontrolledFormatString.swift:177:23:177:29 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:161:24:161:77 | String(...) | this user-provided value | -| UncontrolledFormatString.swift:179:31:179:37 | tainted | UncontrolledFormatString.swift:161:24:161:77 | String(...) | UncontrolledFormatString.swift:179:31:179:37 | tainted | This format string depends on $@. | UncontrolledFormatString.swift:161:24:161:77 | String(...) | this user-provided value | +| 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: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:77:14:77:19 | format | UncontrolledFormatString.swift:78:22:80:5 | FunctionExpr [format] | provenance | | -| UncontrolledFormatString.swift:78:22:80:5 | FunctionExpr [format] | UncontrolledFormatString.swift:79:16:79:21 | format | provenance | | -| UncontrolledFormatString.swift:91:9:91:15 | tainted | UncontrolledFormatString.swift:100:24:100:30 | tainted | provenance | | -| UncontrolledFormatString.swift:91:9:91:15 | tainted | UncontrolledFormatString.swift:101:24:101:30 | tainted | provenance | | -| UncontrolledFormatString.swift:91:9:91:15 | tainted | UncontrolledFormatString.swift:103:24:103:30 | tainted | provenance | | -| UncontrolledFormatString.swift:91:9:91:15 | tainted | UncontrolledFormatString.swift:105:24:105:30 | tainted | provenance | | -| UncontrolledFormatString.swift:91:9:91:15 | tainted | UncontrolledFormatString.swift:108:43:108:49 | tainted | provenance | | -| UncontrolledFormatString.swift:91:9:91:15 | tainted | UncontrolledFormatString.swift:111:50:111:56 | tainted | provenance | | -| UncontrolledFormatString.swift:91:9:91:15 | tainted | UncontrolledFormatString.swift:116:11:116:17 | tainted | provenance | | -| UncontrolledFormatString.swift:91:9:91:15 | tainted | UncontrolledFormatString.swift:118:61:118:67 | tainted | provenance | | -| UncontrolledFormatString.swift:91:9:91:15 | tainted | UncontrolledFormatString.swift:130:39:130:45 | tainted | provenance | | -| UncontrolledFormatString.swift:91:9:91:15 | tainted | UncontrolledFormatString.swift:135:37:135:43 | tainted | provenance | | -| UncontrolledFormatString.swift:91:24:91:77 | String(...) | UncontrolledFormatString.swift:91:9:91:15 | tainted | provenance | | -| UncontrolledFormatString.swift:108:43:108:49 | tainted | UncontrolledFormatString.swift:108:26:108:50 | NSString(...) | provenance | | -| UncontrolledFormatString.swift:111:50:111:56 | tainted | UncontrolledFormatString.swift:111:33:111:57 | NSString(...) | provenance | | -| UncontrolledFormatString.swift:116:11:116:17 | tainted | UncontrolledFormatString.swift:77:14:77:19 | format | provenance | | -| UncontrolledFormatString.swift:135:37:135:43 | tainted | UncontrolledFormatString.swift:135:20:135:44 | NSString(...) | provenance | | -| UncontrolledFormatString.swift:161:9:161:15 | tainted | UncontrolledFormatString.swift:163:42:163:48 | tainted | provenance | | -| UncontrolledFormatString.swift:161:9:161:15 | tainted | UncontrolledFormatString.swift:165:57:165:63 | tainted | provenance | | -| UncontrolledFormatString.swift:161:9:161:15 | tainted | UncontrolledFormatString.swift:167:64:167:70 | tainted | provenance | | -| UncontrolledFormatString.swift:161:9:161:15 | tainted | UncontrolledFormatString.swift:170:11:170:17 | tainted | provenance | | -| UncontrolledFormatString.swift:161:9:161:15 | tainted | UncontrolledFormatString.swift:173:20:173:26 | tainted | provenance | | -| UncontrolledFormatString.swift:161:9:161:15 | tainted | UncontrolledFormatString.swift:175:17:175:23 | tainted | provenance | | -| UncontrolledFormatString.swift:161:9:161:15 | tainted | UncontrolledFormatString.swift:177:23:177:29 | tainted | provenance | | -| UncontrolledFormatString.swift:161:9:161:15 | tainted | UncontrolledFormatString.swift:179:31:179:37 | tainted | provenance | | -| UncontrolledFormatString.swift:161:24:161:77 | String(...) | UncontrolledFormatString.swift:161:9:161:15 | tainted | provenance | | -| UncontrolledFormatString.swift:165:57:165:63 | tainted | UncontrolledFormatString.swift:165:40:165:64 | NSString(...) | provenance | | -| UncontrolledFormatString.swift:167:64:167:70 | tainted | UncontrolledFormatString.swift:167:47:167:71 | NSString(...) | provenance | | +| 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: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:77:14:77:19 | format | semmle.label | format | -| UncontrolledFormatString.swift:78:22:80:5 | FunctionExpr [format] | semmle.label | FunctionExpr [format] | -| UncontrolledFormatString.swift:79:16:79:21 | format | semmle.label | format | -| UncontrolledFormatString.swift:91:9:91:15 | tainted | semmle.label | tainted | -| UncontrolledFormatString.swift:91:24:91:77 | String(...) | semmle.label | String(...) | -| UncontrolledFormatString.swift:100:24:100:30 | tainted | semmle.label | tainted | -| UncontrolledFormatString.swift:101:24:101:30 | tainted | semmle.label | tainted | +| 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:108:26:108:50 | NSString(...) | semmle.label | NSString(...) | -| UncontrolledFormatString.swift:108:43:108:49 | tainted | semmle.label | tainted | -| UncontrolledFormatString.swift:111:33:111:57 | NSString(...) | semmle.label | NSString(...) | -| UncontrolledFormatString.swift:111:50:111:56 | tainted | semmle.label | tainted | -| UncontrolledFormatString.swift:116:11:116:17 | tainted | semmle.label | tainted | -| UncontrolledFormatString.swift:118:61:118:67 | tainted | semmle.label | tainted | -| UncontrolledFormatString.swift:130:39:130:45 | tainted | semmle.label | tainted | -| UncontrolledFormatString.swift:135:20:135:44 | NSString(...) | semmle.label | NSString(...) | -| UncontrolledFormatString.swift:135:37:135:43 | tainted | semmle.label | tainted | -| UncontrolledFormatString.swift:161:9:161:15 | tainted | semmle.label | tainted | -| UncontrolledFormatString.swift:161:24:161:77 | String(...) | semmle.label | String(...) | -| UncontrolledFormatString.swift:163:42:163:48 | tainted | semmle.label | tainted | -| UncontrolledFormatString.swift:165:40:165:64 | NSString(...) | semmle.label | NSString(...) | -| UncontrolledFormatString.swift:165:57:165:63 | tainted | semmle.label | tainted | -| UncontrolledFormatString.swift:167:47:167:71 | NSString(...) | semmle.label | NSString(...) | -| UncontrolledFormatString.swift:167:64:167:70 | tainted | semmle.label | tainted | -| UncontrolledFormatString.swift:170:11:170:17 | tainted | semmle.label | tainted | -| UncontrolledFormatString.swift:173:20:173:26 | tainted | semmle.label | tainted | -| UncontrolledFormatString.swift:175:17:175:23 | tainted | semmle.label | tainted | -| UncontrolledFormatString.swift:177:23:177:29 | tainted | semmle.label | tainted | -| UncontrolledFormatString.swift:179:31:179:37 | 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: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.swift b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.swift index 35fd2fb11405..84ddb16946b3 100644 --- a/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.swift +++ b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.swift @@ -48,6 +48,8 @@ struct NSExceptionName { 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) {} } @@ -115,6 +117,7 @@ func tests() throws { NSLog(tainted) // $ MISSING: Alert MyLog(tainted) // $ MISSING: Alert + _ = NSException(name: NSExceptionName("exception"), reason: tainted, userInfo: nil) // $ MISSING: Alert NSException.raise(NSExceptionName("exception"), format: tainted, arguments: getVaList([])) // $ Alert let taintedVal = Int(tainted)! From c04cf4651e6a978a2a415a38b021221fd396cda2 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Mon, 2 Nov 2026 16:59:23 +0000 Subject: [PATCH 10/10] unified: Model NSException.init. --- unified/ql/lib/ext/format-string.model.yml | 1 + .../UncontrolledFormatString/UncontrolledFormatString.expected | 3 +++ .../UncontrolledFormatString/UncontrolledFormatString.swift | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/unified/ql/lib/ext/format-string.model.yml b/unified/ql/lib/ext/format-string.model.yml index c480536daa13..5c9d683b8e98 100644 --- a/unified/ql/lib/ext/format-string.model.yml +++ b/unified/ql/lib/ext/format-string.model.yml @@ -20,6 +20,7 @@ extensions: - ["", "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"] 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 index c6be8d94cdf8..b0c01b7833df 100644 --- a/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.expected +++ b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.expected @@ -6,6 +6,7 @@ | 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 | @@ -27,6 +28,7 @@ edges | 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 | | @@ -61,6 +63,7 @@ nodes | 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(...) | 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 index 84ddb16946b3..ff2e09f3d51e 100644 --- a/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.swift +++ b/unified/ql/test/query-tests/security/CWE-134/UncontrolledFormatString/UncontrolledFormatString.swift @@ -117,7 +117,7 @@ func tests() throws { NSLog(tainted) // $ MISSING: Alert MyLog(tainted) // $ MISSING: Alert - _ = NSException(name: NSExceptionName("exception"), reason: tainted, userInfo: nil) // $ MISSING: Alert + _ = NSException(name: NSExceptionName("exception"), reason: tainted, userInfo: nil) // $ Alert NSException.raise(NSExceptionName("exception"), format: tainted, arguments: getVaList([])) // $ Alert let taintedVal = Int(tainted)!