Skip to content

Commit cbfa7e7

Browse files
committed
Swift: Move query logic into .qlls.
1 parent 522c9d6 commit cbfa7e7

File tree

6 files changed

+344
-319
lines changed

6 files changed

+344
-319
lines changed
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/**
2+
* Provides a taint-tracking configuration for reasoning about database
3+
* queries built from user-controlled sources (that is, SQL injection
4+
* vulnerabilities).
5+
*/
6+
7+
import swift
8+
import codeql.swift.dataflow.DataFlow
9+
import codeql.swift.dataflow.TaintTracking
10+
import codeql.swift.dataflow.FlowSources
11+
12+
/**
13+
* A `DataFlow::Node` that is a sink for a SQL string to be executed.
14+
*/
15+
abstract class SqlSink extends DataFlow::Node { }
16+
17+
/**
18+
* A sink for the sqlite3 C API.
19+
*/
20+
class CApiSqlSink extends SqlSink {
21+
CApiSqlSink() {
22+
// `sqlite3_exec` and variants of `sqlite3_prepare`.
23+
exists(CallExpr call |
24+
call.getStaticTarget()
25+
.(FreeFunctionDecl)
26+
.hasName([
27+
"sqlite3_exec(_:_:_:_:_:)", "sqlite3_prepare(_:_:_:_:_:)",
28+
"sqlite3_prepare_v2(_:_:_:_:_:)", "sqlite3_prepare_v3(_:_:_:_:_:_:)",
29+
"sqlite3_prepare16(_:_:_:_:_:)", "sqlite3_prepare16_v2(_:_:_:_:_:)",
30+
"sqlite3_prepare16_v3(_:_:_:_:_:_:)"
31+
]) and
32+
call.getArgument(1).getExpr() = this.asExpr()
33+
)
34+
}
35+
}
36+
37+
/**
38+
* A sink for the SQLite.swift library.
39+
*/
40+
class SQLiteSwiftSqlSink extends SqlSink {
41+
SQLiteSwiftSqlSink() {
42+
// Variants of `Connection.execute`, `connection.prepare` and `connection.scalar`.
43+
exists(CallExpr call |
44+
call.getStaticTarget()
45+
.(MethodDecl)
46+
.hasQualifiedName("Connection",
47+
["execute(_:)", "prepare(_:_:)", "run(_:_:)", "scalar(_:_:)"]) and
48+
call.getArgument(0).getExpr() = this.asExpr()
49+
)
50+
or
51+
// String argument to the `Statement` constructor.
52+
exists(CallExpr call |
53+
call.getStaticTarget().(MethodDecl).hasQualifiedName("Statement", "init(_:_:)") and
54+
call.getArgument(1).getExpr() = this.asExpr()
55+
)
56+
}
57+
}
58+
59+
/** A sink for the GRDB library. */
60+
class GrdbSqlSink extends SqlSink {
61+
GrdbSqlSink() {
62+
exists(CallExpr call, MethodDecl method |
63+
call.getStaticTarget() = method and
64+
call.getArgument(0).getExpr() = this.asExpr()
65+
|
66+
method
67+
.hasQualifiedName("Database",
68+
[
69+
"allStatements(sql:arguments:)", "cachedStatement(sql:)",
70+
"internalCachedStatement(sql:)", "execute(sql:arguments:)", "makeStatement(sql:)",
71+
"makeStatement(sql:prepFlags:)"
72+
])
73+
or
74+
method
75+
.hasQualifiedName("SQLRequest",
76+
[
77+
"init(stringLiteral:)", "init(unicodeScalarLiteral:)",
78+
"init(extendedGraphemeClusterLiteral:)", "init(stringInterpolation:)",
79+
"init(sql:arguments:adapter:cached:)"
80+
])
81+
or
82+
method
83+
.hasQualifiedName("SQL",
84+
[
85+
"init(stringLiteral:)", "init(unicodeScalarLiteral:)",
86+
"init(extendedGraphemeClusterLiteral:)", "init(stringInterpolation:)",
87+
"init(sql:arguments:)", "append(sql:arguments:)"
88+
])
89+
or
90+
method
91+
.hasQualifiedName("TableDefinition", ["column(sql:)", "check(sql:)", "constraint(sql:)"])
92+
or
93+
method.hasQualifiedName("TableAlteration", "addColumn(sql:)")
94+
or
95+
method
96+
.hasQualifiedName("ColumnDefinition",
97+
["check(sql:)", "defaults(sql:)", "generatedAs(sql:_:)"])
98+
or
99+
method
100+
.hasQualifiedName("TableRecord",
101+
[
102+
"select(sql:arguments:)", "select(sql:arguments:as:)", "filter(sql:arguments:)",
103+
"order(sql:arguments:)"
104+
])
105+
or
106+
method.hasQualifiedName("StatementCache", "statement(_:)")
107+
)
108+
or
109+
exists(CallExpr call, MethodDecl method |
110+
call.getStaticTarget() = method and
111+
call.getArgument(1).getExpr() = this.asExpr()
112+
|
113+
method
114+
.hasQualifiedName(["Row", "DatabaseValueConvertible"],
115+
[
116+
"fetchCursor(_:sql:arguments:adapter:)", "fetchAll(_:sql:arguments:adapter:)",
117+
"fetchSet(_:sql:arguments:adapter:)", "fetchOne(_:sql:arguments:adapter:)"
118+
])
119+
or
120+
method.hasQualifiedName("SQLStatementCursor", "init(database:sql:arguments:prepFlags:)")
121+
)
122+
or
123+
exists(CallExpr call, MethodDecl method |
124+
call.getStaticTarget() = method and
125+
call.getArgument(3).getExpr() = this.asExpr()
126+
|
127+
method
128+
.hasQualifiedName("CommonTableExpression", "init(recursive:named:columns:sql:arguments:)")
129+
)
130+
}
131+
}
132+
133+
/**
134+
* A taint configuration for tainted data that reaches a SQL sink.
135+
*/
136+
class SqlInjectionConfig extends TaintTracking::Configuration {
137+
SqlInjectionConfig() { this = "SqlInjectionConfig" }
138+
139+
override predicate isSource(DataFlow::Node node) { node instanceof FlowSource }
140+
141+
override predicate isSink(DataFlow::Node node) { node instanceof SqlSink }
142+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/**
2+
* Provides a taint-tracking configuration for reasoning about javascript
3+
* evaluation vulnerabilities.
4+
*/
5+
6+
import swift
7+
import codeql.swift.dataflow.DataFlow
8+
import codeql.swift.dataflow.TaintTracking
9+
import codeql.swift.dataflow.FlowSources
10+
11+
/**
12+
* A source of untrusted, user-controlled data.
13+
*/
14+
class Source = FlowSource;
15+
16+
/**
17+
* A sink that evaluates a string of JavaScript code.
18+
*/
19+
abstract class Sink extends DataFlow::Node { }
20+
21+
class WKWebView extends Sink {
22+
WKWebView() {
23+
any(CallExpr ce |
24+
ce.getStaticTarget()
25+
.(MethodDecl)
26+
.hasQualifiedName("WKWebView",
27+
[
28+
"evaluateJavaScript(_:)", "evaluateJavaScript(_:completionHandler:)",
29+
"evaluateJavaScript(_:in:in:completionHandler:)",
30+
"evaluateJavaScript(_:in:contentWorld:)",
31+
"callAsyncJavaScript(_:arguments:in:in:completionHandler:)",
32+
"callAsyncJavaScript(_:arguments:in:contentWorld:)"
33+
])
34+
).getArgument(0).getExpr() = this.asExpr()
35+
}
36+
}
37+
38+
class WKUserContentController extends Sink {
39+
WKUserContentController() {
40+
any(CallExpr ce |
41+
ce.getStaticTarget()
42+
.(MethodDecl)
43+
.hasQualifiedName("WKUserContentController", "addUserScript(_:)")
44+
).getArgument(0).getExpr() = this.asExpr()
45+
}
46+
}
47+
48+
class UIWebView extends Sink {
49+
UIWebView() {
50+
any(CallExpr ce |
51+
ce.getStaticTarget()
52+
.(MethodDecl)
53+
.hasQualifiedName(["UIWebView", "WebView"], "stringByEvaluatingJavaScript(from:)")
54+
).getArgument(0).getExpr() = this.asExpr()
55+
}
56+
}
57+
58+
class JSContext extends Sink {
59+
JSContext() {
60+
any(CallExpr ce |
61+
ce.getStaticTarget()
62+
.(MethodDecl)
63+
.hasQualifiedName("JSContext", ["evaluateScript(_:)", "evaluateScript(_:withSourceURL:)"])
64+
).getArgument(0).getExpr() = this.asExpr()
65+
}
66+
}
67+
68+
class JSEvaluateScript extends Sink {
69+
JSEvaluateScript() {
70+
any(CallExpr ce |
71+
ce.getStaticTarget().(FreeFunctionDecl).hasName("JSEvaluateScript(_:_:_:_:_:_:)")
72+
).getArgument(1).getExpr() = this.asExpr()
73+
}
74+
}
75+
76+
/**
77+
* A taint configuration from taint sources to sinks for this query.
78+
*/
79+
class UnsafeJsEvalConfig extends TaintTracking::Configuration {
80+
UnsafeJsEvalConfig() { this = "UnsafeJsEvalConfig" }
81+
82+
override predicate isSource(DataFlow::Node node) { node instanceof Source }
83+
84+
override predicate isSink(DataFlow::Node node) { node instanceof Sink }
85+
86+
// TODO: convert to new taint flow models
87+
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
88+
exists(Argument arg |
89+
arg =
90+
any(CallExpr ce |
91+
ce.getStaticTarget().(MethodDecl).hasQualifiedName("String", "init(decoding:as:)")
92+
).getArgument(0)
93+
or
94+
arg =
95+
any(CallExpr ce |
96+
ce.getStaticTarget()
97+
.(FreeFunctionDecl)
98+
.hasName([
99+
"JSStringCreateWithUTF8CString(_:)", "JSStringCreateWithCharacters(_:_:)",
100+
"JSStringRetain(_:)"
101+
])
102+
).getArgument(0)
103+
|
104+
nodeFrom.asExpr() = arg.getExpr() and
105+
nodeTo.asExpr() = arg.getApplyExpr()
106+
)
107+
or
108+
exists(CallExpr ce, Expr self, AbstractClosureExpr closure |
109+
ce.getStaticTarget()
110+
.getName()
111+
.matches(["withContiguousStorageIfAvailable(%)", "withUnsafeBufferPointer(%)"]) and
112+
self = ce.getQualifier() and
113+
ce.getArgument(0).getExpr() = closure
114+
|
115+
nodeFrom.asExpr() = self and
116+
nodeTo.(DataFlow::ParameterNode).getParameter() = closure.getParam(0)
117+
)
118+
or
119+
exists(MemberRefExpr e, Expr self, VarDecl member |
120+
self.getType().getName().matches(["Unsafe%Buffer%", "Unsafe%Pointer%"]) and
121+
member.getName() = "baseAddress"
122+
|
123+
e.getBase() = self and
124+
e.getMember() = member and
125+
nodeFrom.asExpr() = self and
126+
nodeTo.asExpr() = e
127+
)
128+
}
129+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Provides a taint-tracking configuration for reasoning about unsafe
3+
* webview fetch vulnerabilities.
4+
*/
5+
6+
import swift
7+
import codeql.swift.dataflow.DataFlow
8+
import codeql.swift.dataflow.TaintTracking
9+
import codeql.swift.dataflow.FlowSources
10+
11+
/**
12+
* A sink that is a candidate result for this query, such as certain arguments
13+
* to `UIWebView.loadHTMLString`.
14+
*/
15+
class Sink extends DataFlow::Node {
16+
Expr baseUrl;
17+
18+
Sink() {
19+
exists(
20+
MethodDecl funcDecl, CallExpr call, string className, string funcName, int arg, int baseArg
21+
|
22+
// arguments to method calls...
23+
(
24+
// `loadHTMLString`
25+
className = ["UIWebView", "WKWebView"] and
26+
funcName = "loadHTMLString(_:baseURL:)" and
27+
arg = 0 and
28+
baseArg = 1
29+
or
30+
// `UIWebView.load`
31+
className = "UIWebView" and
32+
funcName = "load(_:mimeType:textEncodingName:baseURL:)" and
33+
arg = 0 and
34+
baseArg = 3
35+
or
36+
// `WKWebView.load`
37+
className = "WKWebView" and
38+
funcName = "load(_:mimeType:characterEncodingName:baseURL:)" and
39+
arg = 0 and
40+
baseArg = 3
41+
) and
42+
call.getStaticTarget() = funcDecl and
43+
// match up `funcName`, `paramName`, `arg`, `node`.
44+
funcDecl.hasQualifiedName(className, funcName) and
45+
call.getArgument(arg).getExpr() = this.asExpr() and
46+
// match up `baseURLArg`
47+
call.getArgument(baseArg).getExpr() = baseUrl
48+
)
49+
}
50+
51+
/**
52+
* Gets the `baseURL` argument associated with this sink.
53+
*/
54+
Expr getBaseUrl() { result = baseUrl }
55+
}
56+
57+
/**
58+
* A taint configuration from taint sources to sinks (and `baseURL` arguments)
59+
* for this query.
60+
*/
61+
class UnsafeWebViewFetchConfig extends TaintTracking::Configuration {
62+
UnsafeWebViewFetchConfig() { this = "UnsafeWebViewFetchConfig" }
63+
64+
override predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource }
65+
66+
override predicate isSink(DataFlow::Node node) {
67+
node instanceof Sink or
68+
node.asExpr() = any(Sink s).getBaseUrl()
69+
}
70+
}

0 commit comments

Comments
 (0)