Merge pull request #10892 from atorralba/atorralba/swift/customurlschemes

Swift: Add a new Custom URL Scheme source
This commit is contained in:
Tony Torralba
2022-10-24 15:33:27 +02:00
committed by GitHub
8 changed files with 113 additions and 9 deletions

View File

@@ -148,6 +148,11 @@ private module Cached {
// flow through `!`
nodeFrom.asExpr() = nodeTo.asExpr().(ForceValueExpr).getSubExpr()
or
// flow through `?`
nodeFrom.asExpr() = nodeTo.asExpr().(BindOptionalExpr).getSubExpr()
or
nodeFrom.asExpr() = nodeTo.asExpr().(OptionalEvaluationExpr).getSubExpr()
or
// flow through a flow summary (extension of `SummaryModelCsv`)
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true)
}

View File

@@ -1,5 +1,7 @@
import swift
private import codeql.swift.dataflow.DataFlow
private import codeql.swift.dataflow.ExternalFlow
private import codeql.swift.dataflow.FlowSources
private class UrlRemoteFlowSource extends SourceModelCsv {
override predicate row(string row) {
@@ -7,7 +9,46 @@ private class UrlRemoteFlowSource extends SourceModelCsv {
[
";UIApplicationDelegate;true;application(_:open:options:);;;Parameter[1];remote",
";UIApplicationDelegate;true;application(_:handleOpen:);;;Parameter[1];remote",
";UIApplicationDelegate;true;application(_:open:sourceApplication:annotation:);;;Parameter[1];remote"
";UIApplicationDelegate;true;application(_:open:sourceApplication:annotation:);;;Parameter[1];remote",
// TODO: The actual source is the value of `UIApplication.LaunchOptionsKey.url` in the launchOptions dictionary.
// Use dictionary value contents when available.
// ";UIApplicationDelegate;true;application(_:didFinishLaunchingWithOptions:);;;Parameter[1].MapValue;remote",
// ";UIApplicationDelegate;true;application(_:willFinishLaunchingWithOptions:);;;Parameter[1].MapValue;remote"
]
}
}
/**
* A read of `UIApplication.LaunchOptionsKey.url` on a dictionary received in
* `UIApplicationDelegate.application(_:didFinishLaunchingWithOptions:)` or
* `UIApplicationDelegate.application(_:willFinishLaunchingWithOptions:)`.
*/
// This is a temporary workaround until the TODO above is addressed.
private class UrlLaunchOptionsRemoteFlowSource extends RemoteFlowSource {
UrlLaunchOptionsRemoteFlowSource() {
exists(ApplicationWithLaunchOptionsFunc f, SubscriptExpr e |
DataFlow::localExprFlow(f.getParam(1).getAnAccess(), e.getBase()) and
e.getAnArgument().getExpr().(MemberRefExpr).getMember() instanceof LaunchOptionsUrlVarDecl and
this.asExpr() = e
)
}
override string getSourceType() {
result = "Remote URL in UIApplicationDelegate.application.launchOptions"
}
}
private class ApplicationWithLaunchOptionsFunc extends FuncDecl {
ApplicationWithLaunchOptionsFunc() {
this.getName() = "application(_:" + ["did", "will"] + "FinishLaunchingWithOptions:)" and
this.getEnclosingDecl().(ClassOrStructDecl).getABaseTypeDecl*().(ProtocolDecl).getName() =
"UIApplicationDelegate"
}
}
private class LaunchOptionsUrlVarDecl extends VarDecl {
LaunchOptionsUrlVarDecl() {
this.getEnclosingDecl().(StructDecl).getFullName() = "UIApplication.LaunchOptionsKey" and
this.getName() = "url"
}
}

View File

@@ -98,6 +98,8 @@ edges
| test.swift:219:13:219:15 | .a [x] : | test.swift:219:13:219:17 | .x |
| test.swift:225:14:225:21 | call to source() : | test.swift:235:13:235:15 | .source_value |
| test.swift:225:14:225:21 | call to source() : | test.swift:238:13:238:15 | .source_value |
| test.swift:259:12:259:19 | call to source() : | test.swift:263:13:263:28 | call to optionalSource() : |
| test.swift:263:13:263:28 | call to optionalSource() : | test.swift:264:15:264:16 | ...! |
nodes
| file://:0:0:0:0 | .a [x] : | semmle.label | .a [x] : |
| file://:0:0:0:0 | .x : | semmle.label | .x : |
@@ -208,6 +210,9 @@ nodes
| test.swift:225:14:225:21 | call to source() : | semmle.label | call to source() : |
| test.swift:235:13:235:15 | .source_value | semmle.label | .source_value |
| test.swift:238:13:238:15 | .source_value | semmle.label | .source_value |
| test.swift:259:12:259:19 | call to source() : | semmle.label | call to source() : |
| test.swift:263:13:263:28 | call to optionalSource() : | semmle.label | call to optionalSource() : |
| test.swift:264:15:264:16 | ...! | semmle.label | ...! |
subpaths
| test.swift:75:21:75:22 | &... : | test.swift:65:16:65:28 | arg1 : | test.swift:65:1:70:1 | arg2[return] : | test.swift:75:31:75:32 | [post] &... : |
| test.swift:114:19:114:19 | arg : | test.swift:109:9:109:14 | arg : | test.swift:110:12:110:12 | arg : | test.swift:114:12:114:22 | call to ... : |
@@ -263,3 +268,4 @@ subpaths
| test.swift:219:13:219:17 | .x | test.swift:218:11:218:18 | call to source() : | test.swift:219:13:219:17 | .x | result |
| test.swift:235:13:235:15 | .source_value | test.swift:225:14:225:21 | call to source() : | test.swift:235:13:235:15 | .source_value | result |
| test.swift:238:13:238:15 | .source_value | test.swift:225:14:225:21 | call to source() : | test.swift:238:13:238:15 | .source_value | result |
| test.swift:264:15:264:16 | ...! | test.swift:259:12:259:19 | call to source() : | test.swift:264:15:264:16 | ...! | result |

View File

@@ -4,6 +4,7 @@
import swift
import codeql.swift.dataflow.DataFlow
import codeql.swift.dataflow.ExternalFlow
class TestConfiguration extends DataFlow::Configuration {
TestConfiguration() { this = "TestConfiguration" }
@@ -14,10 +15,17 @@ class TestConfiguration extends DataFlow::Configuration {
override predicate isSink(DataFlow::Node sink) {
exists(CallExpr sinkCall |
sinkCall.getStaticTarget().getName() = "sink(arg:)" and
sinkCall.getStaticTarget().getName() = ["sink(arg:)", "sink(opt:)"] and
sinkCall.getAnArgument().getExpr() = sink.asExpr()
)
}
override int explorationLimit() { result = 100 }
}
private class TestSummaries extends SummaryModelCsv {
override predicate row(string row) {
// model to allow data flow through `signum()` as though it were an identity function, for the benefit of testing flow through optional chaining (`x?.`).
row = ";Int;true;signum();;;Argument[-1];ReturnValue;value"
}
}

View File

@@ -184,3 +184,11 @@
| test.swift:247:9:247:9 | [post] self | test.swift:246:5:248:5 | self[return] |
| test.swift:247:9:247:9 | self | test.swift:246:5:248:5 | self[return] |
| test.swift:252:23:252:23 | value | test.swift:252:23:252:23 | WriteDef |
| test.swift:263:9:263:9 | WriteDef | test.swift:264:15:264:15 | x |
| test.swift:263:13:263:28 | call to optionalSource() | test.swift:263:9:263:9 | WriteDef |
| test.swift:264:15:264:15 | x | test.swift:264:15:264:16 | ...! |
| test.swift:264:15:264:15 | x | test.swift:266:15:266:15 | x |
| test.swift:266:15:266:15 | x | test.swift:266:15:266:16 | ...? |
| test.swift:266:15:266:15 | x | test.swift:267:15:267:15 | x |
| test.swift:266:15:266:25 | call to signum() | test.swift:266:15:266:25 | OptionalEvaluationExpr |
| test.swift:267:15:267:15 | x | test.swift:268:16:268:16 | x |

View File

@@ -251,4 +251,21 @@ func test_computed_property() {
func test_property_wrapper() {
@DidSetSource var x = 42
sink(arg: x) // $ MISSING: flow=243
}
}
func sink(opt: Int?) {}
func optionalSource() -> Int? {
return source()
}
func test_optionals() {
let x = optionalSource()
sink(arg: x!) // $ flow=259
sink(arg: source().signum()) // $ MISSING: flow=259
sink(opt: x?.signum()) // $ MISSING: flow=259
sink(arg: x ?? 0) // $ MISSING: flow=259
if let y = x {
sink(arg: y) // $ MISSING: flow=259
}
}

View File

@@ -1,6 +1,8 @@
| customurlschemes.swift:23:44:23:54 | url | external |
| customurlschemes.swift:27:52:27:68 | url | external |
| customurlschemes.swift:31:52:31:62 | url | external |
| customurlschemes.swift:30:44:30:54 | url | external |
| customurlschemes.swift:34:52:34:68 | url | external |
| customurlschemes.swift:38:52:38:62 | url | external |
| customurlschemes.swift:43:9:43:28 | ...[...] | Remote URL in UIApplicationDelegate.application.launchOptions |
| customurlschemes.swift:48:9:48:28 | ...[...] | Remote URL in UIApplicationDelegate.application.launchOptions |
| string.swift:27:21:27:21 | call to init(contentsOf:) | external |
| string.swift:27:21:27:44 | call to init(contentsOf:) | external |
| url.swift:53:15:53:19 | .resourceBytes | external |

View File

@@ -7,6 +7,11 @@ class UIApplication {
func hash(into hasher: inout Hasher) {}
}
struct LaunchOptionsKey: Hashable {
init(rawValue: String) {}
public static let url: UIApplication.LaunchOptionsKey = UIApplication.LaunchOptionsKey(rawValue: "")
func hash(into hasher: inout Hasher) {}
}
}
struct URL {}
@@ -15,21 +20,33 @@ protocol UIApplicationDelegate {
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool
func application(_ application: UIApplication, handleOpen url: URL) -> Bool
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]?) -> Bool
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]?) -> Bool
}
// --- tests ---
class AppDelegate: UIApplicationDelegate {
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool { // SOURCE
return true;
return true
}
func application(_ application: UIApplication, handleOpen url: URL) -> Bool { // SOURCE
return true;
return true
}
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool { // SOURCE
return true;
return true
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]?) -> Bool {
launchOptions?[.url] // SOURCE
return true
}
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]?) -> Bool {
launchOptions?[.url] // SOURCE
return true
}
}