Skip to content

Commit 48b0cc0

Browse files
committed
Add models for JSContext and JSValue
1 parent 8170154 commit 48b0cc0

File tree

6 files changed

+482
-11
lines changed

6 files changed

+482
-11
lines changed

swift/ql/lib/codeql/swift/frameworks/StandardLibrary/WebView.qll

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ private import codeql.swift.dataflow.ExternalFlow
44
private import codeql.swift.dataflow.FlowSources
55
private import codeql.swift.dataflow.FlowSteps
66

7-
private class WKScriptMessageSource extends SourceModelCsv {
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 {
813
override predicate row(string row) {
914
row = ";WKScriptMessageHandler;true;userContentController(_:didReceive:);;;Parameter[1];remote"
1015
}
@@ -27,3 +32,98 @@ private class WKScriptMessageBodyInheritsTaint extends TaintInheritingContent,
2732
)
2833
}
2934
}
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 class `JSContext`. */
95+
private class JsContextDecl extends ClassDecl {
96+
JsContextDecl() { this.getName() = "JSContext" }
97+
}
98+
99+
/** The type of an object exposed to JavaScript through `JSContext.setObject`. */
100+
private class TypeExposedThroughJsContext extends Type {
101+
TypeExposedThroughJsContext() {
102+
exists(ApplyExpr c, FuncDecl f |
103+
c.getStaticTarget() = f and
104+
f.getEnclosingDecl() instanceof JsContextDecl and
105+
f.getName() = "setObject(_:forKeyedSubscript)"
106+
|
107+
c.getArgument(0).getExpr().getType() = this
108+
)
109+
}
110+
}
111+
112+
/**
113+
* The members (fields and parameters of functions) of a class or struct
114+
* an instance of which is exposed to JavaScript through `JSContext.setObject`.
115+
*/
116+
private class ExposedThroughJsContextSource extends RemoteFlowSource {
117+
ExposedThroughJsContextSource() {
118+
exists(ParamDecl p | this.(DataFlow::ParameterNode).getParameter() = p |
119+
p.getDeclaringFunction().getEnclosingDecl().(ClassOrStructDecl).getType() instanceof
120+
TypeExposedThroughJsContext
121+
)
122+
or
123+
exists(FieldDecl f | this.asDefinition().getSourceVariable() = f |
124+
f.getEnclosingDecl().(ClassOrStructDecl).getType() instanceof TypeExposedThroughJsContext
125+
)
126+
}
127+
128+
override string getSourceType() { result = "Member of a type exposed through JSContext" }
129+
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@
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:11:82:11:102 | message | external |
11+
| webview.swift:19:82:19:102 | message | external |
12+
| webview.swift:24:5:24:13 | .globalObject | external |
13+
| webview.swift:25:5:25:39 | call to objectForKeyedSubscript(_:) | external |

swift/ql/test/library-tests/dataflow/flowsources/webview.swift

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,31 @@ class WKScriptMessage {}
55
protocol WKScriptMessageHandler {
66
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)
77
}
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+
}
816

917
// --- tests ---
1018
class TestMessageHandler: WKScriptMessageHandler {
1119
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { // SOURCE
1220
}
13-
}
21+
}
22+
23+
func testJsContext(context: JSContext) {
24+
context.globalObject // SOURCE
25+
context.objectForKeyedSubscript("") // SOURCE
26+
context.setObject(Exposed.self, forKeyedSubscript: "exposed" as! NSCopying & NSObjectProtocol)
27+
}
28+
29+
class Exposed {
30+
var tainted: Any { get { return "" } } // SOURCE
31+
32+
func tainted(arg1: Any, arg2: Any) { // SOURCES
33+
34+
}
35+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,4 +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:12:13:12:20 | call to source() | webview.swift:12:13:12:22 | .body |
164+
| webview.swift:52:11:52:18 | call to source() | webview.swift:52:10:52:41 | .body |

0 commit comments

Comments
 (0)