diff --git a/unified/ql/lib/ext/legacy-swift.model.yml b/unified/ql/lib/ext/legacy-swift.model.yml index a44a48adada5..d44d441bcea7 100644 --- a/unified/ql/lib/ext/legacy-swift.model.yml +++ b/unified/ql/lib/ext/legacy-swift.model.yml @@ -235,6 +235,7 @@ extensions: - ["", "Connection", true, "scalar(_:_:)", "", "", "Argument[0]", "database-store", "manual"] - ["", "Connection", true, "scalar(_:_:)", "", "", "Argument[1]", "database-store", "manual"] - ["", "Connection", true, "init(_:readonly:)", "", "", "Argument[0]", "path-injection", "manual"] + - ["", "Connection.Location", true, "uri(_:parameters:)", "", "", "Argument[0]", "path-injection", "manual"] - ["", "Database", true, "changePassphrase(_:)", "", "", "Argument[0]", "encryption-key", "manual"] - ["", "Database", true, "usePassphrase(_:)", "", "", "Argument[0]", "encryption-key", "manual"] - ["", "Database", true, "allStatements(arguments:sql:)", "", "", "Argument[arguments:]", "database-store", "manual"] diff --git a/unified/ql/src/queries/security/CWE-022/PathInjection.qhelp b/unified/ql/src/queries/security/CWE-022/PathInjection.qhelp new file mode 100644 index 000000000000..a4ec83290a19 --- /dev/null +++ b/unified/ql/src/queries/security/CWE-022/PathInjection.qhelp @@ -0,0 +1,58 @@ + + + + +

Accessing paths controlled by users can expose resources to attackers.

+ +

Paths that are naively constructed from data controlled by a user may contain unexpected special characters, +such as ... Such a path could point to any directory on the file system.

+
+ + + +

Validate user input before using it to construct a file path. Ideally, follow these rules:

+ + + +
+ + +

+The following code shows two bad examples. +

+ + + +

+In the first, a file name is read from an HTTP request and then used to access a file. In this case, a malicious response could include a file name that is an absolute path, such as +"/Applications/(current_application)/Documents/sensitive.data". +

+ +

+In the second bad example, it appears that the user is restricted to opening a file within the +"/Library/Caches" home directory. In this case, a malicious response could contain a file name containing +special characters. For example, the string "../../Documents/sensitive.data" will result in the code +reading the file located at "/Applications/(current_application)/Library/Caches/../../Documents/sensitive.data", +which contains users' sensitive data. This file may then be made accessible to an attacker, giving them access to all this data. +

+ +

+In the following (good) example, the path used to access the file system is normalized before being checked against a +known prefix. This ensures that regardless of the user input, the resulting path is safe. +

+ + +
+ + +
  • OWASP: Path Traversal.
  • +
    +
    diff --git a/unified/ql/src/queries/security/CWE-022/PathInjection.ql b/unified/ql/src/queries/security/CWE-022/PathInjection.ql new file mode 100644 index 000000000000..3ae09d533f1e --- /dev/null +++ b/unified/ql/src/queries/security/CWE-022/PathInjection.ql @@ -0,0 +1,64 @@ +/** + * @name Uncontrolled data used in path expression + * @description Accessing paths influenced by users can allow an attacker to access unexpected resources. + * @kind path-problem + * @problem.severity error + * @security-severity 7.5 + * @precision high + * @id unified/swift/path-injection + * @tags security + * external/cwe/cwe-022 + * external/cwe/cwe-023 + * external/cwe/cwe-036 + * external/cwe/cwe-073 + * external/cwe/cwe-099 + */ + +import unified + +/** + * A string that might be a label for a path argument. + */ +pragma[inline] +private predicate pathLikeHeuristic(string label) { + label = + [ + "atFile", "atPath", "atDirectory", "toFile", "toPath", "toDirectory", "inFile", "inPath", + "inDirectory", "contentsOfFile", "contentsOfPath", "contentsOfDirectory", "filePath", + "directory", "directoryPath" + ] +} + +predicate heuristicSink(DataFlow::Node node) { + node.isIncomingValue(any(Identifier id | id.getValue() = "sqlite3_temp_directory")) + or + exists(Argument arg | + pathLikeHeuristic(arg.getName()) and + node.asExpr() = arg.getValue() + ) +} + +module PathInjectionConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node node) { Models::isSource(node, _) } + + predicate isSink(DataFlow::Node node) { + Models::isSink(node, "path-injection") or + heuristicSink(node) + } + + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { none() } + + predicate isBarrier(DataFlow::Node node) { + // TODO: add barriers + none() + } +} + +module PathInjectionFlow = DataFlow::Global; + +import PathInjectionFlow::PathGraph + +from PathInjectionFlow::PathNode source, PathInjectionFlow::PathNode sink +where PathInjectionFlow::flowPath(source, sink) +select sink.getNode(), source, sink, "This path depends on a $@.", source.getNode(), + "user-provided value" diff --git a/unified/ql/src/queries/security/CWE-022/PathInjectionBad.swift b/unified/ql/src/queries/security/CWE-022/PathInjectionBad.swift new file mode 100644 index 000000000000..9ce9d0f42e8b --- /dev/null +++ b/unified/ql/src/queries/security/CWE-022/PathInjectionBad.swift @@ -0,0 +1,10 @@ +let fm = FileManager.default +let path = try String(contentsOf: URL(string: "http://example.com/")!) + +// BAD +return fm.contents(atPath: path) + +// BAD +if (path.hasPrefix(NSHomeDirectory() + "/Library/Caches")) { + return fm.contents(atPath: path) +} diff --git a/unified/ql/src/queries/security/CWE-022/PathInjectionGood.swift b/unified/ql/src/queries/security/CWE-022/PathInjectionGood.swift new file mode 100644 index 000000000000..8e0799fc4862 --- /dev/null +++ b/unified/ql/src/queries/security/CWE-022/PathInjectionGood.swift @@ -0,0 +1,8 @@ +let fm = FileManager.default +let path = try String(contentsOf: URL(string: "http://example.com/")!) + +// GOOD +let filePath = FilePath(stringLiteral: path) +if (filePath.lexicallyNormalized().starts(with: FilePath(stringLiteral: NSHomeDirectory() + "/Library/Caches"))) { + return fm.contents(atPath: path) +} diff --git a/unified/ql/test/library-tests/mad/test.expected b/unified/ql/test/library-tests/mad/test.expected index cac0df439216..15c67b01654f 100644 --- a/unified/ql/test/library-tests/mad/test.expected +++ b/unified/ql/test/library-tests/mad/test.expected @@ -15,6 +15,7 @@ isSink | test.swift:100:24:100:36 | encryptionKey | encryption-key | | test.swift:101:18:101:24 | fileURL | path-injection | | test.swift:107:23:107:34 | seedFilePath | path-injection | +| test.swift:119:29:119:34 | string | path-injection | isSource | test.swift:4:5:4:27 | String(...) | remote | | test.swift:5:5:5:44 | String(...) | remote | diff --git a/unified/ql/test/library-tests/mad/test.swift b/unified/ql/test/library-tests/mad/test.swift index bf895e5e9274..fd1af8f38351 100644 --- a/unified/ql/test/library-tests/mad/test.swift +++ b/unified/ql/test/library-tests/mad/test.swift @@ -108,3 +108,13 @@ func testQualifiedConstructors( shouldCompactOnLaunch: nil, syncConfiguration: nil) } + +class Connection { + enum Location { + static func uri(_ path: String, parameters: String) {} + } +} + +func testConnectionLocation(string: String) { + Connection.Location.uri(string, parameters: "") // $ isSink=path-injection +} diff --git a/unified/ql/test/query-tests/security/CWE-022/PathInjection/CONSISTENCY/CfgConsistency.expected b/unified/ql/test/query-tests/security/CWE-022/PathInjection/CONSISTENCY/CfgConsistency.expected new file mode 100644 index 000000000000..b7d75c0421ae --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-022/PathInjection/CONSISTENCY/CfgConsistency.expected @@ -0,0 +1,4 @@ +consistencyOverview +| deadEnd | 1 | +deadEnd +| testPathInjection.swift:330:14:330:62 | Entry | diff --git a/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.expected b/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.expected new file mode 100644 index 000000000000..542a755d4f04 --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.expected @@ -0,0 +1,306 @@ +#select +| testPathInjection.swift:351:34:351:45 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:351:34:351:45 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:352:26:352:37 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:352:26:352:37 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:356:44:356:55 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:356:44:356:55 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:359:35:359:46 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:359:35:359:46 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:360:44:360:55 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:360:44:360:55 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:361:33:361:44 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:361:33:361:44 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:362:28:362:36 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:362:28:362:36 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:363:40:363:51 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:363:40:363:51 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:364:35:364:46 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:364:35:364:46 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:365:23:365:31 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:365:23:365:31 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:366:27:366:38 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:366:27:366:38 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:367:22:367:30 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:367:22:367:30 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:368:30:368:38 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:368:30:368:38 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:369:30:369:36 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:369:30:369:36 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:369:51:369:59 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:369:51:369:59 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:371:13:371:21 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:371:13:371:21 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:371:36:371:42 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:371:36:371:42 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:374:13:374:19 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:374:13:374:19 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:374:34:374:42 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:374:34:374:42 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:376:21:376:29 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:376:21:376:29 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:376:36:376:42 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:376:36:376:42 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:377:21:377:27 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:377:21:377:27 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:377:34:377:42 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:377:34:377:42 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:378:25:378:36 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:378:25:378:36 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:379:37:379:48 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:379:37:379:48 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:380:21:380:29 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:380:21:380:29 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:380:36:380:42 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:380:36:380:42 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:381:21:381:27 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:381:21:381:27 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:381:34:381:42 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:381:34:381:42 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:382:25:382:36 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:382:25:382:36 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:383:37:383:48 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:383:37:383:48 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:384:31:384:39 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:384:31:384:39 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:384:62:384:68 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:384:62:384:68 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:385:31:385:37 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:385:31:385:37 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:385:60:385:68 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:385:60:385:68 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:386:35:386:46 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:386:35:386:46 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:387:60:387:71 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:387:60:387:71 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:388:21:388:29 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:388:21:388:29 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:388:36:388:42 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:388:36:388:42 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:389:21:389:27 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:389:21:389:27 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:389:34:389:42 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:389:34:389:42 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:390:25:390:36 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:390:25:390:36 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:391:37:391:48 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:391:37:391:48 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:392:50:392:61 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:392:50:392:61 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:393:35:393:46 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:393:35:393:46 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:395:17:395:28 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:395:17:395:28 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:396:41:396:52 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:396:41:396:52 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:397:33:397:44 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:397:33:397:44 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:398:38:398:49 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:398:38:398:49 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:399:51:399:62 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:399:51:399:62 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:400:43:400:54 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:400:43:400:54 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:401:34:401:42 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:401:34:401:42 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:403:50:403:61 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:403:50:403:61 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:404:42:404:53 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:404:42:404:53 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:405:40:405:51 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:405:40:405:51 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:406:43:406:54 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:406:43:406:54 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:407:60:407:71 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:407:60:407:71 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:408:50:408:61 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:408:50:408:61 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:412:26:412:34 | safeNsUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:412:26:412:34 | safeNsUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:412:52:412:62 | remoteNsUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:412:52:412:62 | remoteNsUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:415:41:415:52 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:415:41:415:52 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:416:41:416:52 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:416:41:416:52 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:417:41:417:52 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:417:41:417:52 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:419:43:419:54 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:419:43:419:54 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:420:43:420:54 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:420:43:420:54 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:421:26:421:34 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:421:26:421:34 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:422:30:422:41 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:422:30:422:41 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:424:59:424:70 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:424:59:424:70 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:436:25:436:33 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:436:25:436:33 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:437:26:437:37 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:437:26:437:37 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:441:28:441:39 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:441:28:441:39 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:443:32:443:43 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:443:32:443:43 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:445:33:445:44 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:445:33:445:44 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:447:40:447:51 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:447:40:447:51 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:456:15:456:26 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:456:15:456:26 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:482:22:482:33 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:482:22:482:33 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:484:24:484:30 | buffer2 | testPathInjection.swift:477:22:477:87 | Data(...) | testPathInjection.swift:484:24:484:30 | buffer2 | This path depends on a $@. | testPathInjection.swift:477:22:477:87 | Data(...) | user-provided value | +| testPathInjection.swift:486:25:486:36 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:486:25:486:36 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:498:49:498:60 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:498:49:498:60 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:510:37:510:48 | remoteString | testPathInjection.swift:504:24:504:78 | String(...) | testPathInjection.swift:510:37:510:48 | remoteString | This path depends on a $@. | testPathInjection.swift:504:24:504:78 | String(...) | user-provided value | +| testPathInjection.swift:512:33:512:44 | remoteString | testPathInjection.swift:504:24:504:78 | String(...) | testPathInjection.swift:512:33:512:44 | remoteString | This path depends on a $@. | testPathInjection.swift:504:24:504:78 | String(...) | user-provided value | +| testPathInjection.swift:527:28:527:29 | u1 | testPathInjection.swift:519:24:519:78 | String(...) | testPathInjection.swift:527:28:527:29 | u1 | This path depends on a $@. | testPathInjection.swift:519:24:519:78 | String(...) | user-provided value | +| testPathInjection.swift:529:28:529:39 | remoteString | testPathInjection.swift:519:24:519:78 | String(...) | testPathInjection.swift:529:28:529:39 | remoteString | This path depends on a $@. | testPathInjection.swift:519:24:519:78 | String(...) | user-provided value | +| testPathInjection.swift:541:32:541:43 | remoteString | testPathInjection.swift:519:24:519:78 | String(...) | testPathInjection.swift:541:32:541:43 | remoteString | This path depends on a $@. | testPathInjection.swift:519:24:519:78 | String(...) | user-provided value | +| testPathInjection.swift:542:38:542:49 | remoteString | testPathInjection.swift:519:24:519:78 | String(...) | testPathInjection.swift:542:38:542:49 | remoteString | This path depends on a $@. | testPathInjection.swift:519:24:519:78 | String(...) | user-provided value | +| testPathInjection.swift:543:45:543:56 | remoteString | testPathInjection.swift:519:24:519:78 | String(...) | testPathInjection.swift:543:45:543:56 | remoteString | This path depends on a $@. | testPathInjection.swift:519:24:519:78 | String(...) | user-provided value | +| testPathInjection.swift:547:32:547:41 | ... .pointee | testPathInjection.swift:519:24:519:78 | String(...) | testPathInjection.swift:547:32:547:41 | ... .pointee | This path depends on a $@. | testPathInjection.swift:519:24:519:78 | String(...) | user-provided value | +| testPathInjection.swift:559:35:559:46 | remoteString | testPathInjection.swift:519:24:519:78 | String(...) | testPathInjection.swift:559:35:559:46 | remoteString | This path depends on a $@. | testPathInjection.swift:519:24:519:78 | String(...) | user-provided value | +| testPathInjection.swift:560:41:560:52 | remoteString | testPathInjection.swift:519:24:519:78 | String(...) | testPathInjection.swift:560:41:560:52 | remoteString | This path depends on a $@. | testPathInjection.swift:519:24:519:78 | String(...) | user-provided value | +| testPathInjection.swift:582:25:582:36 | remoteString | testPathInjection.swift:580:24:580:78 | String(...) | testPathInjection.swift:582:25:582:36 | remoteString | This path depends on a $@. | testPathInjection.swift:580:24:580:78 | String(...) | user-provided value | +| testPathInjection.swift:584:41:584:52 | remoteString | testPathInjection.swift:580:24:580:78 | String(...) | testPathInjection.swift:584:41:584:52 | remoteString | This path depends on a $@. | testPathInjection.swift:580:24:580:78 | String(...) | user-provided value | +| testPathInjection.swift:586:38:586:49 | remoteString | testPathInjection.swift:580:24:580:78 | String(...) | testPathInjection.swift:586:38:586:49 | remoteString | This path depends on a $@. | testPathInjection.swift:580:24:580:78 | String(...) | user-provided value | +| testPathInjection.swift:588:22:588:33 | remoteString | testPathInjection.swift:580:24:580:78 | String(...) | testPathInjection.swift:588:22:588:33 | remoteString | This path depends on a $@. | testPathInjection.swift:580:24:580:78 | String(...) | user-provided value | +edges +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:351:34:351:45 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:352:26:352:37 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:356:44:356:55 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:359:35:359:46 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:360:44:360:55 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:361:33:361:44 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:362:28:362:36 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:363:40:363:51 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:364:35:364:46 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:365:23:365:31 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:366:27:366:38 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:367:22:367:30 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:368:30:368:38 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:369:30:369:36 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:369:51:369:59 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:371:13:371:21 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:371:36:371:42 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:374:13:374:19 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:374:34:374:42 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:376:21:376:29 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:376:36:376:42 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:377:21:377:27 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:377:34:377:42 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:378:25:378:36 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:379:37:379:48 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:380:21:380:29 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:380:36:380:42 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:381:21:381:27 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:381:34:381:42 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:382:25:382:36 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:383:37:383:48 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:384:31:384:39 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:384:62:384:68 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:385:31:385:37 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:385:60:385:68 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:386:35:386:46 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:387:60:387:71 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:388:21:388:29 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:388:36:388:42 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:389:21:389:27 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:389:34:389:42 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:390:25:390:36 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:391:37:391:48 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:392:50:392:61 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:393:35:393:46 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:395:17:395:28 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:396:41:396:52 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:397:33:397:44 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:398:38:398:49 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:399:51:399:62 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:400:43:400:54 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:401:34:401:42 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:403:50:403:61 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:404:42:404:53 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:405:40:405:51 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:406:43:406:54 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:407:60:407:71 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:408:50:408:61 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:412:26:412:34 | safeNsUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:412:52:412:62 | remoteNsUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:415:41:415:52 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:416:41:416:52 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:417:41:417:52 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:419:43:419:54 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:420:43:420:54 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:421:26:421:34 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:422:30:422:41 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:424:59:424:70 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:436:25:436:33 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:437:26:437:37 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:441:28:441:39 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:443:32:443:43 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:445:33:445:44 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:447:40:447:51 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:456:15:456:26 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:482:22:482:33 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:486:25:486:36 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:498:49:498:60 | remoteString | provenance | | +| testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:340:9:340:20 | remoteString | provenance | | +| testPathInjection.swift:477:9:477:18 | remoteData | testPathInjection.swift:484:24:484:30 | buffer2 | provenance | | +| testPathInjection.swift:477:22:477:87 | Data(...) | testPathInjection.swift:477:9:477:18 | remoteData | provenance | | +| testPathInjection.swift:504:9:504:20 | remoteString | testPathInjection.swift:510:37:510:48 | remoteString | provenance | | +| testPathInjection.swift:504:9:504:20 | remoteString | testPathInjection.swift:512:33:512:44 | remoteString | provenance | | +| testPathInjection.swift:504:24:504:78 | String(...) | testPathInjection.swift:504:9:504:20 | remoteString | provenance | | +| testPathInjection.swift:519:9:519:20 | remoteString | testPathInjection.swift:527:28:527:29 | u1 | provenance | | +| testPathInjection.swift:519:9:519:20 | remoteString | testPathInjection.swift:529:28:529:39 | remoteString | provenance | | +| testPathInjection.swift:519:9:519:20 | remoteString | testPathInjection.swift:541:32:541:43 | remoteString | provenance | | +| testPathInjection.swift:519:9:519:20 | remoteString | testPathInjection.swift:542:38:542:49 | remoteString | provenance | | +| testPathInjection.swift:519:9:519:20 | remoteString | testPathInjection.swift:543:45:543:56 | remoteString | provenance | | +| testPathInjection.swift:519:9:519:20 | remoteString | testPathInjection.swift:546:18:546:29 | remoteString | provenance | | +| testPathInjection.swift:519:9:519:20 | remoteString | testPathInjection.swift:559:35:559:46 | remoteString | provenance | | +| testPathInjection.swift:519:9:519:20 | remoteString | testPathInjection.swift:560:41:560:52 | remoteString | provenance | | +| testPathInjection.swift:519:24:519:78 | String(...) | testPathInjection.swift:519:9:519:20 | remoteString | provenance | | +| testPathInjection.swift:546:5:546:6 | [post] s1 [pointee] | testPathInjection.swift:547:32:547:33 | s1 [pointee] | provenance | | +| testPathInjection.swift:546:5:546:14 | ... .pointee | testPathInjection.swift:546:5:546:6 | [post] s1 [pointee] | provenance | | +| testPathInjection.swift:546:18:546:29 | remoteString | testPathInjection.swift:546:5:546:14 | ... .pointee | provenance | | +| testPathInjection.swift:547:32:547:33 | s1 [pointee] | testPathInjection.swift:547:32:547:41 | ... .pointee | provenance | | +| testPathInjection.swift:580:9:580:20 | remoteString | testPathInjection.swift:582:25:582:36 | remoteString | provenance | | +| testPathInjection.swift:580:9:580:20 | remoteString | testPathInjection.swift:584:41:584:52 | remoteString | provenance | | +| testPathInjection.swift:580:9:580:20 | remoteString | testPathInjection.swift:586:38:586:49 | remoteString | provenance | | +| testPathInjection.swift:580:9:580:20 | remoteString | testPathInjection.swift:588:22:588:33 | remoteString | provenance | | +| testPathInjection.swift:580:24:580:78 | String(...) | testPathInjection.swift:580:9:580:20 | remoteString | provenance | | +nodes +| testPathInjection.swift:340:9:340:20 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:340:24:340:78 | String(...) | semmle.label | String(...) | +| testPathInjection.swift:351:34:351:45 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:352:26:352:37 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:356:44:356:55 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:359:35:359:46 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:360:44:360:55 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:361:33:361:44 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:362:28:362:36 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:363:40:363:51 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:364:35:364:46 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:365:23:365:31 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:366:27:366:38 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:367:22:367:30 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:368:30:368:38 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:369:30:369:36 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:369:51:369:59 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:371:13:371:21 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:371:36:371:42 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:374:13:374:19 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:374:34:374:42 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:376:21:376:29 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:376:36:376:42 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:377:21:377:27 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:377:34:377:42 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:378:25:378:36 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:379:37:379:48 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:380:21:380:29 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:380:36:380:42 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:381:21:381:27 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:381:34:381:42 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:382:25:382:36 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:383:37:383:48 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:384:31:384:39 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:384:62:384:68 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:385:31:385:37 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:385:60:385:68 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:386:35:386:46 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:387:60:387:71 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:388:21:388:29 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:388:36:388:42 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:389:21:389:27 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:389:34:389:42 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:390:25:390:36 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:391:37:391:48 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:392:50:392:61 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:393:35:393:46 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:395:17:395:28 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:396:41:396:52 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:397:33:397:44 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:398:38:398:49 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:399:51:399:62 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:400:43:400:54 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:401:34:401:42 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:403:50:403:61 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:404:42:404:53 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:405:40:405:51 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:406:43:406:54 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:407:60:407:71 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:408:50:408:61 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:412:26:412:34 | safeNsUrl | semmle.label | safeNsUrl | +| testPathInjection.swift:412:52:412:62 | remoteNsUrl | semmle.label | remoteNsUrl | +| testPathInjection.swift:415:41:415:52 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:416:41:416:52 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:417:41:417:52 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:419:43:419:54 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:420:43:420:54 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:421:26:421:34 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:422:30:422:41 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:424:59:424:70 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:436:25:436:33 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:437:26:437:37 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:441:28:441:39 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:443:32:443:43 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:445:33:445:44 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:447:40:447:51 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:456:15:456:26 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:477:9:477:18 | remoteData | semmle.label | remoteData | +| testPathInjection.swift:477:22:477:87 | Data(...) | semmle.label | Data(...) | +| testPathInjection.swift:482:22:482:33 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:484:24:484:30 | buffer2 | semmle.label | buffer2 | +| testPathInjection.swift:486:25:486:36 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:498:49:498:60 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:504:9:504:20 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:504:24:504:78 | String(...) | semmle.label | String(...) | +| testPathInjection.swift:510:37:510:48 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:512:33:512:44 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:519:9:519:20 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:519:24:519:78 | String(...) | semmle.label | String(...) | +| testPathInjection.swift:527:28:527:29 | u1 | semmle.label | u1 | +| testPathInjection.swift:529:28:529:39 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:541:32:541:43 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:542:38:542:49 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:543:45:543:56 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:546:5:546:6 | [post] s1 [pointee] | semmle.label | [post] s1 [pointee] | +| testPathInjection.swift:546:5:546:14 | ... .pointee | semmle.label | ... .pointee | +| testPathInjection.swift:546:18:546:29 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:547:32:547:33 | s1 [pointee] | semmle.label | s1 [pointee] | +| testPathInjection.swift:547:32:547:41 | ... .pointee | semmle.label | ... .pointee | +| testPathInjection.swift:559:35:559:46 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:560:41:560:52 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:580:9:580:20 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:580:24:580:78 | String(...) | semmle.label | String(...) | +| testPathInjection.swift:582:25:582:36 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:584:41:584:52 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:586:38:586:49 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:588:22:588:33 | remoteString | semmle.label | remoteString | +subpaths diff --git a/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.qlref b/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.qlref new file mode 100644 index 000000000000..1fab38d532b7 --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.qlref @@ -0,0 +1,3 @@ +query: queries/security/CWE-022/PathInjection.ql +postprocess: + - utils/test/InlineExpectationsTestQuery.ql diff --git a/unified/ql/test/query-tests/security/CWE-022/PathInjection/testPathInjection.swift b/unified/ql/test/query-tests/security/CWE-022/PathInjection/testPathInjection.swift new file mode 100644 index 000000000000..238646a61565 --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-022/PathInjection/testPathInjection.swift @@ -0,0 +1,595 @@ +// --- stubs --- + +struct URL { + enum DirectoryHint { + case inferFromPath + } + + init?(string: String) {} + init(fileURLWithPath path: String, isDirectory: Bool) {} + init( + filePath path: String, directoryHint: URL.DirectoryHint = .inferFromPath, + relativeTo base: URL? = nil + ) {} + + mutating func appendPathComponent(_ pathComponent: String) {} + func appendingPathComponent(_ pathComponent: String) -> URL { return self } +} + +class NSURL { + init?(string: String) {} + + func appendingPathComponent(_ pathComponent: String) -> URL? { return nil } + + var filePathURL: URL? { URL(string: "") } +} + +extension StringProtocol { + func completePath( + into outputName: UnsafeMutablePointer? = nil, + caseSensitive: Bool, + matchesInto outputArray: UnsafeMutablePointer<[String]>? = nil, + filterTypes: [String]? = nil + ) -> Int { 0 } +} + +extension String { + struct Encoding { + static let utf8 = Encoding() + } + + init(contentsOf: URL) { + let data = "" + self.init(data) + } + + init(contentsOfFile path: String) throws { + self.init("") + } + + init(contentsOfFile path: String, encoding enc: String.Encoding) throws { + self.init("") + } + + init(contentsOfFile path: String, usedEncoding: inout String.Encoding) throws { + self.init("") + } +} + +class NSString { + init() {} + init(string aString: String) {} + convenience init(contentsOfFile path: String, encoding enc: UInt) throws { self.init() } + convenience init(contentsOfFile path: String, usedEncoding enc: UnsafeMutablePointer?) + throws + { self.init() } + + func write(toFile: String, atomically: Bool, encoding: UInt) {} + func write(to: URL, atomically: Bool, encoding: UInt) {} + + var utf8String: UnsafePointer? { return nil } +} +protocol DataProtocol {} +class Data: DataProtocol { + struct ReadingOptions: OptionSet { let rawValue: Int } + struct WritingOptions: OptionSet { let rawValue: Int } + + init(_ elements: S) { count = 0 } + init(contentsOf: URL, options: ReadingOptions) { count = 0 } + + func copyBytes(to: UnsafeMutablePointer, count: Int) {} + + func write(to: URL, options: Data.WritingOptions = []) {} + + var count: Int +} + +class NSData { + init() {} + init?(contentsOfFile path: String) {} + init?(contentsOfMappedFile path: String) {} + init?(contentsOf url: URL) {} + class func dataWithContentsOfMappedFile(_ path: String) -> Any? { return nil } + + struct WritingOptions: OptionSet { let rawValue: Int } + func write(to: URL, atomically: Bool) -> Bool { return false } + func write(to: URL, options: NSData.WritingOptions) {} + func write(toFile: String, atomically: Bool) -> Bool { return false } + func write(toFile: String, options: NSData.WritingOptions) {} +} + +class NSKeyedUnarchiver { + func unarchiveObject(withFile: String) -> Any? { return nil } +} + +struct URLResourceKey {} + +struct FileAttributeKey: Hashable {} + +struct ObjCBool {} + +struct AutoreleasingUnsafeMutablePointer {} + +struct FilePath: ExpressibleByStringLiteral { + typealias StringLiteralType = String + init(stringLiteral: String) {} + func lexicallyNormalized() -> FilePath { return "" } + func starts(with: FilePath) -> Bool { return false } +} + +class FileManager { + class DirectoryEnumerator {} + struct DirectoryEnumerationOptions: OptionSet { let rawValue: Int } + struct ItemReplacementOptions: OptionSet { let rawValue: Int } + struct UnmountOptions: OptionSet { let rawValue: Int } + struct SearchPathDomainMask {} + enum SearchPathDirectory: UInt { case none } + enum URLRelationship: Int { case none } + + func contentsOfDirectory( + at: URL, includingPropertiesForKeys: [URLResourceKey]?, + options: FileManager.DirectoryEnumerationOptions + ) -> [URL] { return [] } + func contentsOfDirectory(atPath: String) -> [String] { return [] } + func enumerator( + at: URL, includingPropertiesForKeys: [URLResourceKey]?, + options: FileManager.DirectoryEnumerationOptions, errorHandler: ((URL, Error) -> Bool)? + ) -> FileManager.DirectoryEnumerator? { return nil } + func enumerator(atPath: String) -> FileManager.DirectoryEnumerator? { return nil } + func subpathsOfDirectory(atPath: String) -> [String] { return [] } + func subpaths(atPath: String) -> [String]? { return nil } + func createDirectory( + at: URL, withIntermediateDirectories: Bool, attributes: [FileAttributeKey: Any]? + ) {} + func createDirectory( + atPath: String, withIntermediateDirectories: Bool, attributes: [FileAttributeKey: Any]? + ) {} + func createFile(atPath: String, contents: Data?, attributes: [FileAttributeKey: Any]?) -> Bool { + return false + } + func removeItem(at: URL) {} + func removeItem(atPath: String) {} + func trashItem(at: URL, resultingItemURL: AutoreleasingUnsafeMutablePointer?) {} + func replaceItemAt( + _: URL, withItemAt: URL, backupItemName: String?, + options: FileManager.ItemReplacementOptions + ) -> URL? { return nil } + func replaceItem( + at: URL, withItemAt: URL, backupItemName: String?, + options: FileManager.ItemReplacementOptions, + resultingItemURL: AutoreleasingUnsafeMutablePointer? + ) {} + func copyItem(at: URL, to: URL) {} + func copyItem(atPath: String, toPath: String) {} + func moveItem(at: URL, to: URL) {} + func moveItem(atPath: String, toPath: String) {} + func createSymbolicLink(at: URL, withDestinationURL: URL) {} + func createSymbolicLink(atPath: String, withDestinationPath: String) {} + func linkItem(at: URL, to: URL) {} + func linkItem(atPath: String, toPath: String) {} + func destinationOfSymbolicLink(atPath: String) -> String { return "" } + func fileExists(atPath: String) -> Bool { return false } + func fileExists(atPath: String, isDirectory: UnsafeMutablePointer?) -> Bool { + return false + } + func setAttributes(_: [FileAttributeKey: Any], ofItemAtPath: String) {} + func contents(atPath: String) -> Data? { return nil } + func contentsEqual(atPath: String, andPath: String) -> Bool { return false } + func changeCurrentDirectoryPath(_: String) -> Bool { return false } + func unmountVolume( + at: URL, options: FileManager.UnmountOptions, completionHandler: (Error?) -> Void + ) {} + // Deprecated methods + func fileAttributes(atPath path: String, traverseLink yorn: Bool) -> [AnyHashable: Any]? { nil } + func changeFileAttributes(_: [AnyHashable: Any], atPath: String) -> Bool { return false } + func attributesOfItem(atPath path: String) throws -> [FileAttributeKey: Any] { return [:] } + func directoryContents(atPath: String) -> [Any]? { return nil } + func createDirectory(atPath: String, attributes: [AnyHashable: Any]) -> Bool { return false } + func createSymbolicLink(atPath: String, pathContent: String) -> Bool { return false } + func pathContentOfSymbolicLink(atPath: String) -> String? { return nil } + func replaceItemAtURL( + originalItemURL: NSURL, withItemAtURL: NSURL, backupItemName: String?, + options: FileManager.ItemReplacementOptions + ) -> NSURL? { return nil } +} + +struct FileDescriptor { + struct AccessMode: RawRepresentable { + static let readOnly = AccessMode(rawValue: 0) + let rawValue: UInt8 + init(rawValue: UInt8) { self.rawValue = rawValue } + } + + struct OpenOptions: RawRepresentable { + static let append = OpenOptions(rawValue: 0) + let rawValue: UInt8 + init(rawValue: UInt8) { self.rawValue = rawValue } + } +} + +struct FilePermissions: RawRepresentable { + static let ownerRead = FilePermissions(rawValue: 0) + let rawValue: UInt8 + init(rawValue: UInt8) { self.rawValue = rawValue } +} + +class ArchiveByteStream { + static func fileStream(fd: FileDescriptor, automaticClose: Bool = true) -> ArchiveByteStream? { + return nil + } + static func withFileStream( + fd: FileDescriptor, automaticClose: Bool = true, _ body: (ArchiveByteStream) -> E + ) -> E { return body(ArchiveByteStream()) } + static func fileStream( + path: FilePath, mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions, + permissions: FilePermissions + ) -> ArchiveByteStream? { return nil } + static func withFileStream( + path: FilePath, mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions, + permissions: FilePermissions, _ body: (ArchiveByteStream) -> E + ) -> E { return body(ArchiveByteStream()) } +} + +class Bundle { + init?(url: URL) {} + init?(path: String) {} +} + +class KeyPath { +} + +class NSSortDescriptor { + init(key: String?, ascending: Bool) {} + convenience init(keyPath: KeyPath, ascending: Bool) { + self.init(key: nil, ascending: ascending) + } +} + +// GRDB + +struct Configuration {} + +class Database { + init(path: String, description: String, configuration: Configuration) {} +} + +class DatabasePool { + init(path: String, configuration: Configuration) {} +} + +class DatabaseQueue { + init(path: String, configuration: Configuration) {} +} + +class DatabaseSnapshotPool { + init(path: String, configuration: Configuration) {} +} + +class SerializedDatabase { + init( + path: String, configuration: Configuration = Configuration(), defaultLabel: String, + purpose: String? = nil + ) {} +} + +// Realm + +class Realm { +} + +extension Realm { + struct Configuration { + init( + fileURL: URL? = URL(fileURLWithPath: "defaultFile", isDirectory: false), + inMemoryIdentifier: String? = nil, + syncConfiguration: Int = 0, + encryptionKey: Data? = nil, + readOnly: Bool = false, + schemaVersion: UInt64 = 0, + migrationBlock: Int = 0, + deleteRealmIfMigrationNeeded: Bool = false, + shouldCompactOnLaunch: Bool = false, + objectTypes: Int = 0, + seedFilePath: URL? = nil + ) {} + + var fileURL: URL? + var seedFilePath: URL? + } +} + +// sqlite3 + +func sqlite3_open( + _ filename: UnsafePointer?, + _ ppDb: UnsafeMutablePointer? +) -> Int32 { return 0 } + +func sqlite3_open16( + _ filename: UnsafeRawPointer?, + _ ppDb: UnsafeMutablePointer? +) -> Int32 { return 0 } + +func sqlite3_open_v2( + _ filename: UnsafePointer?, + _ ppDb: UnsafeMutablePointer?, + _ flags: Int32, + _ zVfs: UnsafePointer? +) -> Int32 { return 0 } + +var sqlite3_temp_directory: UnsafeMutablePointer? + +// SQLite.swift + +enum URIQueryParameter { +} + +class Connection { + enum Location { + case inMemory + case uri(String, parameters: [URIQueryParameter] = []) + } + + init(_ location: Location = .inMemory, readonly: Bool = false) throws {} + convenience init(_ filename: String, readonly: Bool = false) throws { try self.init() } +} + +// --- tests --- + +func test(buffer1: UnsafeMutablePointer, buffer2: UnsafeMutablePointer) { + let remoteString = String(contentsOf: URL(string: "http://example.com/")!) // $ Source + let remoteUrl = URL(string: remoteString)! + let remoteNsUrl = NSURL(string: remoteString)! + let safeUrl = URL(string: "")! + let safeNsUrl = NSURL(string: "")! + + Data("").write(to: remoteUrl, options: []) // $ MISSING: Alert + + let nsData = NSData() + let _ = nsData.write(to: remoteUrl, atomically: false) // $ MISSING: Alert + nsData.write(to: remoteUrl, options: []) // $ MISSING: Alert + let _ = nsData.write(toFile: remoteString, atomically: false) // $ Alert + nsData.write(toFile: remoteString, options: []) // $ Alert + + let fm = FileManager() + let _ = fm.contentsOfDirectory(at: remoteUrl, includingPropertiesForKeys: [], options: []) // $ MISSING: Alert + let _ = fm.contentsOfDirectory(atPath: remoteString) // $ Alert + let _ = fm.enumerator( + at: remoteUrl, includingPropertiesForKeys: [], options: [], errorHandler: nil) // $ MISSING: Alert + let _ = fm.enumerator(atPath: remoteString) // $ Alert + let _ = fm.subpathsOfDirectory(atPath: remoteString) // $ Alert + let _ = fm.subpaths(atPath: remoteString) // $ Alert + fm.createDirectory(at: remoteUrl, withIntermediateDirectories: false, attributes: [:]) // $ Alert + let _ = fm.createDirectory(atPath: remoteString, attributes: [:]) // $ Alert + let _ = fm.createFile(atPath: remoteString, contents: nil, attributes: [:]) // $ Alert + fm.removeItem(at: remoteUrl) // $ Alert + fm.removeItem(atPath: remoteString) // $ Alert + fm.trashItem(at: remoteUrl, resultingItemURL: AutoreleasingUnsafeMutablePointer()) // $ Alert + let _ = fm.replaceItemAt(remoteUrl, withItemAt: safeUrl, backupItemName: nil, options: []) // $ Alert + let _ = fm.replaceItemAt(safeUrl, withItemAt: remoteUrl, backupItemName: nil, options: []) // $ Alert + fm.replaceItem( + at: remoteUrl, withItemAt: safeUrl, backupItemName: nil, options: [], // $ SPURIOUS: Alert + resultingItemURL: AutoreleasingUnsafeMutablePointer()) // $ MISSING: Alert + fm.replaceItem( + at: safeUrl, withItemAt: remoteUrl, backupItemName: nil, options: [], // $ SPURIOUS: Alert + resultingItemURL: AutoreleasingUnsafeMutablePointer()) // $ MISSING: Alert + fm.copyItem(at: remoteUrl, to: safeUrl) // $ Alert + fm.copyItem(at: safeUrl, to: remoteUrl) // $ Alert + fm.copyItem(atPath: remoteString, toPath: "") // $ Alert + fm.copyItem(atPath: "", toPath: remoteString) // $ Alert + fm.moveItem(at: remoteUrl, to: safeUrl) // $ Alert + fm.moveItem(at: safeUrl, to: remoteUrl) // $ Alert + fm.moveItem(atPath: remoteString, toPath: "") // $ Alert + fm.moveItem(atPath: "", toPath: remoteString) // $ Alert + fm.createSymbolicLink(at: remoteUrl, withDestinationURL: safeUrl) // $ Alert + fm.createSymbolicLink(at: safeUrl, withDestinationURL: remoteUrl) // $ Alert + fm.createSymbolicLink(atPath: remoteString, withDestinationPath: "") // $ Alert + fm.createSymbolicLink(atPath: "", withDestinationPath: remoteString) // $ Alert + fm.linkItem(at: remoteUrl, to: safeUrl) // $ Alert + fm.linkItem(at: safeUrl, to: remoteUrl) // $ Alert + fm.linkItem(atPath: remoteString, toPath: "") // $ Alert + fm.linkItem(atPath: "", toPath: remoteString) // $ Alert + let _ = fm.destinationOfSymbolicLink(atPath: remoteString) // $ Alert + let _ = fm.fileExists(atPath: remoteString) // $ Alert + let _ = fm.fileExists( + atPath: remoteString, isDirectory: UnsafeMutablePointer.init(bitPattern: 0)) // $ Alert + fm.setAttributes([:], ofItemAtPath: remoteString) // $ Alert + let _ = fm.contents(atPath: remoteString) // $ Alert + let _ = fm.contentsEqual(atPath: remoteString, andPath: "") // $ Alert + let _ = fm.contentsEqual(atPath: "", andPath: remoteString) // $ Alert + let _ = fm.changeCurrentDirectoryPath(remoteString) // $ Alert + let _ = fm.unmountVolume(at: remoteUrl, options: [], completionHandler: { _ in }) // $ Alert + // Deprecated methods + let _ = fm.changeFileAttributes([:], atPath: remoteString) // $ Alert + let _ = fm.directoryContents(atPath: remoteString) // $ Alert + let _ = fm.createDirectory(atPath: remoteString, attributes: [:]) // $ Alert + let _ = fm.createSymbolicLink(atPath: remoteString, pathContent: "") // $ Alert + let _ = fm.createSymbolicLink(atPath: "", pathContent: remoteString) // $ Alert + let _ = fm.pathContentOfSymbolicLink(atPath: remoteString) // $ Alert + let _ = fm.replaceItemAtURL( + originalItemURL: remoteNsUrl, withItemAtURL: safeNsUrl, backupItemName: nil, options: []) // $ MISSING: Alert + let _ = fm.replaceItemAtURL( + originalItemURL: safeNsUrl, withItemAtURL: remoteNsUrl, backupItemName: nil, options: []) // $ Alert + + var encoding = String.Encoding.utf8 + let _ = try! String(contentsOfFile: remoteString) // $ Alert + let _ = try! String(contentsOfFile: remoteString, encoding: String.Encoding.utf8) // $ Alert + let _ = try! String(contentsOfFile: remoteString, usedEncoding: &encoding) // $ Alert + + let _ = try! NSString(contentsOfFile: remoteString, encoding: 0) // $ Alert + let _ = try! NSString(contentsOfFile: remoteString, usedEncoding: nil) // $ Alert + NSString().write(to: remoteUrl, atomically: true, encoding: 0) // $ Alert + NSString().write(toFile: remoteString, atomically: true, encoding: 0) // $ Alert + + let _ = NSKeyedUnarchiver().unarchiveObject(withFile: remoteString) // $ Alert + let _ = ArchiveByteStream.fileStream(fd: remoteString as! FileDescriptor, automaticClose: true) // $ MISSING: Alert + ArchiveByteStream.withFileStream(fd: remoteString as! FileDescriptor, automaticClose: true) { + _ in + } // $ MISSING: Alert + let _ = ArchiveByteStream.fileStream( + path: FilePath(stringLiteral: remoteString), mode: .readOnly, options: .append, + permissions: .ownerRead) // $ MISSING: Alert + ArchiveByteStream.withFileStream( + path: FilePath(stringLiteral: remoteString), mode: .readOnly, options: .append, + permissions: .ownerRead + ) { _ in } // $ MISSING: Alert + let _ = Bundle(url: remoteUrl) // $ Alert + let _ = Bundle(path: remoteString) // $ Alert + + // GRDB + + let _ = Database(path: remoteString, description: "", configuration: Configuration()) // $ Alert + let _ = Database(path: "", description: "", configuration: Configuration()) // Safe + let _ = DatabasePool(path: remoteString, configuration: Configuration()) // $ Alert + let _ = DatabasePool(path: "", configuration: Configuration()) // Safe + let _ = DatabaseQueue(path: remoteString, configuration: Configuration()) // $ Alert + let _ = DatabaseQueue(path: "", configuration: Configuration()) // Safe + let _ = DatabaseSnapshotPool(path: remoteString, configuration: Configuration()) // $ Alert + let _ = DatabaseSnapshotPool(path: "", configuration: Configuration()) // Safe + let _ = SerializedDatabase(path: remoteString, defaultLabel: "") // $ MISSING: Alert + let _ = SerializedDatabase(path: "", defaultLabel: "") // Safe + let _ = SerializedDatabase(path: remoteString, defaultLabel: "", purpose: nil) // $ MISSING: Alert + let _ = SerializedDatabase(path: "", defaultLabel: "", purpose: nil) // Safe + let _ = SerializedDatabase(path: remoteString, configuration: Configuration(), defaultLabel: "") // $ MISSING: Alert + let _ = SerializedDatabase(path: "", configuration: Configuration(), defaultLabel: "") // Safe + let _ = SerializedDatabase( + path: remoteString, configuration: Configuration(), defaultLabel: "", purpose: nil) // $ Alert + let _ = SerializedDatabase( + path: "", configuration: Configuration(), defaultLabel: "", purpose: nil) // Safe + + // Realm + + _ = Realm.Configuration(fileURL: safeUrl) // GOOD + _ = Realm.Configuration(fileURL: remoteUrl) // $ MISSING: Alert + _ = Realm.Configuration(seedFilePath: safeUrl) // GOOD + _ = Realm.Configuration(seedFilePath: remoteUrl) // $ MISSING: Alert + + var config = Realm.Configuration() // GOOD + config.fileURL = safeUrl // GOOD + config.fileURL = remoteUrl // $ MISSING: Alert + config.seedFilePath = safeUrl // GOOD + config.seedFilePath = remoteUrl // $ MISSING: Alert + + // sqlite3 + + var db: OpaquePointer? + let localData = Data(0) + let remoteData = Data(contentsOf: URL(string: "http://example.com/")!, options: []) // $ Source + localData.copyBytes(to: buffer1, count: localData.count) + remoteData.copyBytes(to: buffer2, count: remoteData.count) + + _ = sqlite3_open("myFile.sqlite3", &db) // GOOD + _ = sqlite3_open(remoteString, &db) // $ Alert + _ = sqlite3_open16(buffer1, &db) // GOOD + _ = sqlite3_open16(buffer2, &db) // $ Alert + _ = sqlite3_open_v2("myFile.sqlite3", &db, 0, nil) // GOOD + _ = sqlite3_open_v2(remoteString, &db, 0, nil) // $ Alert + + sqlite3_temp_directory = UnsafeMutablePointer( + mutating: NSString(string: "myFile.sqlite3").utf8String) // GOOD + sqlite3_temp_directory = UnsafeMutablePointer( + mutating: NSString(string: remoteString).utf8String) // $ MISSING: Alert // Originally missing + + // SQLite.swift + + try! _ = Connection() + try! _ = Connection(Connection.Location.uri("myFile.sqlite3")) // GOOD + try! _ = Connection(Connection.Location.uri(remoteString)) // $ MISSING: Alert + try! _ = Connection(Connection.Location.uri(remoteString, parameters: [])) // $ Alert + try! _ = Connection("myFile.sqlite3") // GOOD + try! _ = Connection(remoteString) // $ MISSING: Alert +} + +func testBarriers() { + let remoteString = String(contentsOf: URL(string: "http://example.com/")!) // $ Source + + let fm = FileManager() + + let filePath = FilePath(stringLiteral: remoteString) + if filePath.lexicallyNormalized().starts(with: "/safe") { + let _ = fm.contents(atPath: remoteString) // $ SPURIOUS: Alert // Safe + } + let _ = fm.contents(atPath: remoteString) // $ Alert +} + +func testPathInjection2( + s1: UnsafeMutablePointer, s2: UnsafeMutablePointer, + s3: UnsafeMutablePointer, fm: FileManager +) throws { + let remoteString = String(contentsOf: URL(string: "http://example.com/")!) // $ Source + + var u1 = URL(filePath: "") + _ = NSData(contentsOf: u1) + _ = NSData(contentsOf: u1.appendingPathComponent("")) + _ = NSData(contentsOf: u1.appendingPathComponent(remoteString)) // $ MISSING: Alert + _ = NSData(contentsOf: u1.appendingPathComponent(remoteString).appendingPathComponent("")) // $ MISSING: Alert + u1.appendPathComponent(remoteString) + _ = NSData(contentsOf: u1) // $ Alert + + let u2 = URL(filePath: remoteString) // $ Alert + _ = NSData(contentsOf: u2) // $ MISSING: Alert + + let u3 = NSURL(string: "")! + Data("").write(to: u3.filePathURL!, options: []) + Data("").write(to: u3.appendingPathComponent("")!, options: []) + Data("").write(to: u3.appendingPathComponent(remoteString)!, options: []) // $ MISSING: Alert + + let u4 = NSURL(string: remoteString)! + Data("").write(to: u4.filePathURL!, options: []) // $ MISSING: Alert + Data("").write(to: u4.appendingPathComponent("")!, options: []) // $ MISSING: Alert + + _ = NSData(contentsOfFile: remoteString)! // $ Alert + _ = NSData(contentsOfMappedFile: remoteString)! // $ Alert + _ = NSData.dataWithContentsOfMappedFile(remoteString)! // $ Alert + + _ = NSData().write(toFile: s1.pointee, atomically: true) + s1.pointee = remoteString + _ = NSData().write(toFile: s1.pointee, atomically: true) // $ Alert + _ = NSData().write(toFile: s1[0], atomically: true) // $ MISSING: Alert // Originally missing + + _ = "".completePath(into: s2, caseSensitive: false, matchesInto: nil, filterTypes: nil) + _ = NSData().write(toFile: s2.pointee, atomically: true) + _ = NSData().write(toFile: s2[0], atomically: true) + + _ = remoteString.completePath( + into: s3, caseSensitive: false, matchesInto: nil, filterTypes: nil) + _ = NSData().write(toFile: s3.pointee, atomically: true) // $ MISSING: Alert // Originally missing + _ = NSData().write(toFile: s3[0], atomically: true) // $ MISSING: Alert + + _ = fm.fileAttributes(atPath: remoteString, traverseLink: true) // $ Alert + _ = try fm.attributesOfItem(atPath: remoteString) // $ Alert +} + +// --- + +func myOpenFile1(atPath path: String) {} +func myOpenFile2(_ filePath: String) {} +func myFindFiles(ofType type: Int, inDirectory dir: String) {} + +class MyClass { + init(contentsOfFile: String) {} + func doSomething(keyPath: String) {} + func write(toFile: String) {} +} + +class MyFile { + init(path: String) {} +} + +func testPathInjectionHeuristics() { + let remoteString = String(contentsOf: URL(string: "http://example.com/")!) // $ Source + + myOpenFile1(atPath: remoteString) // $ Alert + myOpenFile2(remoteString) // $ MISSING: Alert + myFindFiles(ofType: 0, inDirectory: remoteString) // $ Alert + + let mc = MyClass(contentsOfFile: remoteString) // $ Alert + mc.doSomething(keyPath: remoteString) // good - not a path + mc.write(toFile: remoteString) // $ Alert + + let mf1 = MyFile(path: "") + let mf2 = MyFile(path: remoteString) // $ MISSING: Alert // Originally missing + + _ = NSSortDescriptor(key: remoteString, ascending: true) // good - not a path + _ = NSSortDescriptor(keyPath: remoteString as! KeyPath, ascending: true) // good - not a path +}