Skip to content

Commit 759ffc4

Browse files
authored
Merge pull request github#11027 from atorralba/atorralba/swift/webview-js-native-bridge-sources
Swift: WebView JS-native bridge sources
2 parents 79aba19 + b62ede1 commit 759ffc4

File tree

8 files changed

+598
-0
lines changed

8 files changed

+598
-0
lines changed

swift/ql/lib/codeql/swift/dataflow/ExternalFlow.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ private module Frameworks {
8282
private import codeql.swift.frameworks.StandardLibrary.String
8383
private import codeql.swift.frameworks.StandardLibrary.Url
8484
private import codeql.swift.frameworks.StandardLibrary.UrlSession
85+
private import codeql.swift.frameworks.StandardLibrary.WebView
8586
}
8687

8788
/**

swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPrivate.qll

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,14 @@ private module PostUpdateNodes {
574574

575575
override DataFlowCallable getEnclosingCallable() { result = TDataFlowFunc(n.getScope()) }
576576
}
577+
578+
class SummaryPostUpdateNode extends SummaryNode, PostUpdateNodeImpl {
579+
SummaryPostUpdateNode() { FlowSummaryImpl::Private::summaryPostUpdateNode(this, _) }
580+
581+
override Node getPreUpdateNode() {
582+
FlowSummaryImpl::Private::summaryPostUpdateNode(this, result)
583+
}
584+
}
577585
}
578586

579587
private import PostUpdateNodes
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import swift
2+
private import codeql.swift.dataflow.DataFlow
3+
private import codeql.swift.dataflow.ExternalFlow
4+
private import codeql.swift.dataflow.FlowSources
5+
private import codeql.swift.dataflow.FlowSteps
6+
7+
/**
8+
* A model for WKScriptMessage sources. Classes implementing the `WKScriptMessageHandler` protocol
9+
* act as a bridge between JavaScript and native code. The messages sent from JavaScript code are
10+
* stored in the `message` parameter of `userContentController`.
11+
*/
12+
private class WKScriptMessageSources extends SourceModelCsv {
13+
override predicate row(string row) {
14+
row = ";WKScriptMessageHandler;true;userContentController(_:didReceive:);;;Parameter[1];remote"
15+
}
16+
}
17+
18+
/** The class `WKScriptMessage`. */
19+
private class WKScriptMessageDecl extends ClassDecl {
20+
WKScriptMessageDecl() { this.getName() = "WKScriptMessage" }
21+
}
22+
23+
/**
24+
* A content implying that, if a `WKScriptMessage` is tainted, its `body` field is tainted.
25+
*/
26+
private class WKScriptMessageBodyInheritsTaint extends TaintInheritingContent,
27+
DataFlow::Content::FieldContent {
28+
WKScriptMessageBodyInheritsTaint() {
29+
exists(FieldDecl f | this.getField() = f |
30+
f.getEnclosingDecl() instanceof WKScriptMessageDecl and
31+
f.getName() = "body"
32+
)
33+
}
34+
}
35+
36+
/**
37+
* A model for `JSContext` sources. `JSContext` acts as a bridge between JavaScript and
38+
* native code, so any object obtained from it has the potential of being tainted by a malicious
39+
* website visited in the WebView.
40+
*/
41+
private class JsContextSources extends SourceModelCsv {
42+
override predicate row(string row) {
43+
row =
44+
[
45+
";JSContext;true;globalObject;;;;remote",
46+
";JSContext;true;objectAtIndexedSubscript(_:);;;ReturnValue;remote",
47+
";JSContext;true;objectForKeyedSubscript(_:);;;ReturnValue;remote"
48+
]
49+
}
50+
}
51+
52+
/**
53+
* A model for `JSValue` summaries. If a `JSValue` is tainted, any object it is converted into
54+
* is also tainted.
55+
*/
56+
private class JsValueSummaries extends SummaryModelCsv {
57+
override predicate row(string row) {
58+
row =
59+
[
60+
";JSValue;true;init(object:in:);;;Argument[0];ReturnValue;taint",
61+
";JSValue;true;init(bool:in:);;;Argument[0];ReturnValue;taint",
62+
";JSValue;true;init(double:in:);;;Argument[0];ReturnValue;taint",
63+
";JSValue;true;init(int32:in:);;;Argument[0];ReturnValue;taint",
64+
";JSValue;true;init(uInt32:in:);;;Argument[0];ReturnValue;taint",
65+
";JSValue;true;init(point:in:);;;Argument[0];ReturnValue;taint",
66+
";JSValue;true;init(range:in:);;;Argument[0];ReturnValue;taint",
67+
";JSValue;true;init(rect:in:);;;Argument[0];ReturnValue;taint",
68+
";JSValue;true;init(size:in:);;;Argument[0];ReturnValue;taint",
69+
";JSValue;true;toObject();;;Argument[-1];ReturnValue;taint",
70+
";JSValue;true;toObjectOf(_:);;;Argument[-1];ReturnValue;taint",
71+
";JSValue;true;toBool();;;Argument[-1];ReturnValue;taint",
72+
";JSValue;true;toDouble();;;Argument[-1];ReturnValue;taint",
73+
";JSValue;true;toInt32();;;Argument[-1];ReturnValue;taint",
74+
";JSValue;true;toUInt32();;;Argument[-1];ReturnValue;taint",
75+
";JSValue;true;toNumber();;;Argument[-1];ReturnValue;taint",
76+
";JSValue;true;toString();;;Argument[-1];ReturnValue;taint",
77+
";JSValue;true;toDate();;;Argument[-1];ReturnValue;taint",
78+
";JSValue;true;toArray();;;Argument[-1];ReturnValue;taint",
79+
";JSValue;true;toDictionary();;;Argument[-1];ReturnValue;taint",
80+
";JSValue;true;toPoint();;;Argument[-1];ReturnValue;taint",
81+
";JSValue;true;toRange();;;Argument[-1];ReturnValue;taint",
82+
";JSValue;true;toRect();;;Argument[-1];ReturnValue;taint",
83+
";JSValue;true;toSize();;;Argument[-1];ReturnValue;taint",
84+
// TODO: These models could use content flow to be more precise
85+
";JSValue;true;atIndex(_:);;;Argument[-1];ReturnValue;taint",
86+
";JSValue;true;defineProperty(_:descriptor:);;;Argument[1];Argument[-1];taint",
87+
";JSValue;true;forProperty(_:);;;Argument[-1];ReturnValue;taint",
88+
";JSValue;true;setValue(_:at:);;;Argument[0];Argument[-1];taint",
89+
";JSValue;true;setValue(_:forProperty:);;;Argument[0];Argument[-1];taint"
90+
]
91+
}
92+
}
93+
94+
/** The `JSExport` protocol. */
95+
private class JsExport extends ProtocolDecl {
96+
JsExport() { this.getName() = "JSExport" }
97+
}
98+
99+
/** A protocol inheriting `JSExport`. */
100+
private class JsExportedProto extends ProtocolDecl {
101+
JsExportedProto() { this.getABaseTypeDecl+() instanceof JsExport }
102+
}
103+
104+
/** A type that adopts a `JSExport`-inherited protocol. */
105+
private class JsExportedType extends ClassOrStructDecl {
106+
JsExportedType() { this.getABaseTypeDecl*() instanceof JsExportedProto }
107+
}
108+
109+
/**
110+
* A flow source that models properties and methods defined in a `JSExport`-inherited protocol
111+
* and implemented in a type adopting that protcol. These members are accessible from JavaScript
112+
* when the object is assigned to a `JSContext`.
113+
*/
114+
private class JsExportedSource extends RemoteFlowSource {
115+
JsExportedSource() {
116+
exists(MethodDecl adopter, MethodDecl base |
117+
base.getEnclosingDecl() instanceof JsExportedProto and
118+
adopter.getEnclosingDecl() instanceof JsExportedType
119+
|
120+
this.(DataFlow::ParameterNode).getParameter().getDeclaringFunction() = adopter and
121+
adopter.getName() = base.getName()
122+
)
123+
or
124+
exists(FieldDecl adopter, FieldDecl base |
125+
base.getEnclosingDecl() instanceof JsExportedProto and
126+
adopter.getEnclosingDecl() instanceof JsExportedType
127+
|
128+
this.asExpr().(MemberRefExpr).getMember() = adopter and adopter.getName() = base.getName()
129+
)
130+
}
131+
132+
override string getSourceType() { result = "Member of a type exposed through JSExport" }
133+
}

swift/ql/test/library-tests/dataflow/flowsources/FlowSources.expected

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,10 @@
88
| url.swift:53:15:53:19 | .resourceBytes | external |
99
| url.swift:60:15:60:19 | .lines | external |
1010
| url.swift:67:16:67:22 | .lines | external |
11+
| webview.swift:20:82:20:102 | message | external |
12+
| webview.swift:25:5:25:13 | .globalObject | external |
13+
| webview.swift:26:5:26:39 | call to objectForKeyedSubscript(_:) | external |
14+
| webview.swift:39:9:39:9 | .tainted | Member of a type exposed through JSExport |
15+
| webview.swift:43:10:43:10 | self | Member of a type exposed through JSExport |
16+
| webview.swift:43:18:43:24 | arg1 | Member of a type exposed through JSExport |
17+
| webview.swift:43:29:43:35 | arg2 | Member of a type exposed through JSExport |
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
2+
// --- stubs ---
3+
class WKUserContentController {}
4+
class WKScriptMessage {}
5+
protocol WKScriptMessageHandler {
6+
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)
7+
}
8+
protocol NSCopying {}
9+
protocol NSObjectProtocol {}
10+
class JSValue {}
11+
class JSContext {
12+
var globalObject: JSValue { get { return JSValue() } }
13+
func objectForKeyedSubscript(_: Any!) -> JSValue! { return JSValue() }
14+
func setObject(_: Any, forKeyedSubscript: (NSCopying & NSObjectProtocol) ) {}
15+
}
16+
protocol JSExport {}
17+
18+
// --- tests ---
19+
class TestMessageHandler: WKScriptMessageHandler {
20+
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { // SOURCE
21+
}
22+
}
23+
24+
func testJsContext(context: JSContext) {
25+
context.globalObject // SOURCE
26+
context.objectForKeyedSubscript("") // SOURCE
27+
}
28+
29+
protocol Exported : JSExport {
30+
var tainted: Any { get }
31+
func tainted(arg1: Any, arg2: Any)
32+
}
33+
class ExportedImpl : Exported {
34+
var tainted: Any { get { return "" } }
35+
36+
var notTainted: Any { get { return ""} }
37+
38+
func readFields() {
39+
tainted // SOURCE
40+
notTainted
41+
}
42+
43+
func tainted(arg1: Any, arg2: Any) { // SOURCES
44+
}
45+
46+
func notTainted(arg1: Any, arg2: Any) {
47+
}
48+
}

swift/ql/test/library-tests/dataflow/taint/LocalTaint.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,4 @@
161161
| url.swift:100:12:100:54 | ...! | url.swift:100:12:100:56 | .standardizedFileURL |
162162
| url.swift:101:15:101:57 | ...! | url.swift:101:15:101:59 | .user |
163163
| url.swift:102:15:102:57 | ...! | url.swift:102:15:102:59 | .password |
164+
| webview.swift:52:11:52:18 | call to source() | webview.swift:52:10:52:41 | .body |

0 commit comments

Comments
 (0)