Skip to content

Commit d8f53e5

Browse files
authored
Merge pull request #14925 from geoffw0/flows
Swift: Imprecise Taint Flows
2 parents 17cd22f + e60dc9a commit d8f53e5

File tree

5 files changed

+251
-6
lines changed

5 files changed

+251
-6
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Added imprecise flow models for `append` and `insert` methods, and initializer calls with a `data` argument.

swift/ql/lib/codeql/swift/frameworks/Heuristic.qll

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import swift
77
private import codeql.swift.dataflow.DataFlow
88
private import codeql.swift.dataflow.FlowSources
9+
private import codeql.swift.dataflow.FlowSteps
910

1011
/**
1112
* An initializer call `ce` that has a "contentsOf" argument, along with a
@@ -51,3 +52,60 @@ private class InitializerContentsOfLocalSource extends LocalFlowSource {
5152

5253
override string getSourceType() { result = "contentsOf initializer" }
5354
}
55+
56+
/**
57+
* An imprecise flow step for an initializer call with a "data" argument. For
58+
* example:
59+
* ```
60+
* let mc = MyClass(data: taintedData)
61+
* ```
62+
*/
63+
private class InitializerFromDataStep extends AdditionalTaintStep {
64+
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
65+
exists(InitializerCallExpr ce, Argument arg |
66+
ce.getAnArgument() = arg and
67+
arg.getLabel() = "data" and
68+
node1.asExpr() = arg.getExpr() and
69+
node2.asExpr() = ce
70+
)
71+
}
72+
}
73+
74+
/**
75+
* An imprecise flow step for an `append`, `insert` or similar function. For
76+
* example:
77+
* ```
78+
* mc.append(taintedObj)
79+
* mc.insert(taintedObj, at: 0)
80+
* ```
81+
*/
82+
private class AppendCallStep extends AdditionalTaintStep {
83+
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
84+
exists(CallExpr ce, Argument arg |
85+
ce.getAnArgument() = arg and
86+
ce.getStaticTarget().(Function).getShortName() = ["append", "insert"] and
87+
arg.getLabel() = ["", "contentsOf"] and
88+
node1.asExpr() = arg.getExpr() and
89+
node2.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr() = ce.getQualifier()
90+
)
91+
}
92+
}
93+
94+
/**
95+
* An imprecise flow step for an `appending` or similar function. For
96+
* example:
97+
* ```
98+
* let mc2 = mc.appending(taintedObj)
99+
* ```
100+
*/
101+
private class AppendingCallStep extends AdditionalTaintStep {
102+
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
103+
exists(CallExpr ce, Argument arg |
104+
ce.getAnArgument() = arg and
105+
ce.getStaticTarget().(Function).getShortName() = ["appending", "inserting"] and
106+
arg.getLabel() = ["", "contentsOf"] and
107+
node1.asExpr() = arg.getExpr() and
108+
node2.asExpr() = ce
109+
)
110+
}
111+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
2+
// --- stubs ---
3+
4+
// --- tests ---
5+
6+
func source(_ label: String) -> Int { return 0 }
7+
func sink(arg: Any) {}
8+
9+
// ---
10+
11+
func testArray() {
12+
let arrClean = Array<Int>()
13+
14+
var arr1 = Array<Int>()
15+
arr1.append(1)
16+
sink(arg: arr1[arr1.endIndex - 1])
17+
arr1.append(source("int1"))
18+
sink(arg: arr1[arr1.endIndex - 1]) // $ tainted=int1
19+
20+
var arr2 = Array<Int>()
21+
arr2.append(contentsOf: arrClean)
22+
sink(arg: arr2[arr2.endIndex - 1])
23+
arr2.append(contentsOf: arr1)
24+
sink(arg: arr2[arr2.endIndex - 1]) // $ tainted=int1
25+
26+
var arr3 = Array<Int>()
27+
arr3.insert(1, at: 0)
28+
sink(arg: arr3[0])
29+
arr3.insert(source("int3"), at: 0)
30+
sink(arg: arr3[0]) // $ tainted=int3
31+
32+
var arr4 = Array<Int>()
33+
arr4.insert(contentsOf: arrClean, at: 0)
34+
sink(arg: arr4[0])
35+
arr4.insert(contentsOf: arr3, at: 0)
36+
sink(arg: arr4[0]) // $ tainted=int3
37+
38+
var arr5 = Array<Int>()
39+
arr5.insert(contentsOf: 1...10, at: 0)
40+
sink(arg: arr5[arr5.endIndex - 1])
41+
arr5.insert(contentsOf: 1...source("int5"), at: 0)
42+
sink(arg: arr5[arr5.endIndex - 1]) // $ MISSING: tainted=int5
43+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// --- stubs ---
2+
3+
class Data {
4+
init<S>(_ elements: S) {}
5+
}
6+
7+
struct URL {
8+
init?(string: String) {}
9+
}
10+
11+
// A `MyContainer` contains `Data` in something rather like a `Sequence`.
12+
struct MyContainer {
13+
init() { }
14+
init(data: Data) { }
15+
init(data: Data, flags: Int) { }
16+
17+
mutating func append(_ data: Data) { }
18+
mutating func insert(_ data: Data, at: Int) { }
19+
func appending(_ data: Data) -> MyContainer { return self }
20+
func inserting(_ data: Data, at: Int) -> MyContainer { return self }
21+
22+
mutating func append(contentsOf other: MyContainer) { }
23+
mutating func insert(contentsOf other: MyContainer, at: Int) { }
24+
func appending(contentsOf other: MyContainer) -> MyContainer { return self }
25+
func inserting(contentsOf other: MyContainer, at: Int) -> MyContainer { return self }
26+
27+
mutating func append(_ string: String) { }
28+
mutating func append(contentsOf array: Array<Data>) { }
29+
30+
subscript(index: Int) -> Data { return Data(0) }
31+
}
32+
33+
// --- tests ---
34+
35+
func source(_ label: String) -> Data { return Data(0) }
36+
func sourceString(_ label: String) -> String { return "" }
37+
func sink(arg: Any) {}
38+
39+
// ---
40+
41+
func testCustom() {
42+
let clean = MyContainer(data: Data(0))
43+
let tainted = MyContainer(data: source("data1"))
44+
let tainted2 = MyContainer(data: source("data2"), flags: 123)
45+
sink(arg: clean)
46+
sink(arg: clean[0])
47+
sink(arg: tainted) // $ tainted=data1
48+
sink(arg: tainted[0]) // $ tainted=data1
49+
sink(arg: tainted2) // $ tainted=data2
50+
sink(arg: tainted2[0]) // $ tainted=data2
51+
52+
var mc1 = MyContainer()
53+
mc1.append(Data(0))
54+
sink(arg: mc1)
55+
sink(arg: mc1[0])
56+
mc1.append(source("data3"))
57+
sink(arg: mc1) // $ tainted=data3
58+
sink(arg: mc1[0]) // $ tainted=data3
59+
60+
var mc2 = MyContainer()
61+
mc2.insert(Data(0), at: 0)
62+
sink(arg: mc2)
63+
sink(arg: mc2[0])
64+
mc2.insert(source("data4"), at: 0)
65+
sink(arg: mc2) // $ tainted=data4
66+
sink(arg: mc2[0]) // $ tainted=data4
67+
68+
var mc3 = MyContainer()
69+
mc3.append(contentsOf: clean)
70+
sink(arg: mc3)
71+
sink(arg: mc3[0])
72+
mc3.append(contentsOf: tainted)
73+
sink(arg: mc3) // $ tainted=data1
74+
sink(arg: mc3[0]) // $ tainted=data1
75+
76+
var mc4 = MyContainer()
77+
mc4.insert(contentsOf: clean, at: 0)
78+
sink(arg: mc4)
79+
sink(arg: mc4[0])
80+
mc4.insert(contentsOf: tainted, at: 0)
81+
sink(arg: mc4) // $ tainted=data1
82+
sink(arg: mc4[0]) // $ tainted=data1
83+
84+
let mc5 = MyContainer()
85+
sink(arg: mc5.appending(Data(0)))
86+
sink(arg: mc5.appending(Data(0))[0])
87+
sink(arg: mc5.appending(source("data5"))) // $ tainted=data5
88+
sink(arg: mc5.appending(source("data6"))[0]) // $ tainted=data6
89+
sink(arg: mc5.inserting(Data(0), at: 0))
90+
sink(arg: mc5.inserting(Data(0), at: 0)[0])
91+
sink(arg: mc5.inserting(source("data7"), at: 0)) // $ tainted=data7
92+
sink(arg: mc5.inserting(source("data8"), at: 0)[0]) // $ tainted=data8
93+
sink(arg: mc5.appending(contentsOf: clean))
94+
sink(arg: mc5.appending(contentsOf: clean)[0])
95+
sink(arg: mc5.appending(contentsOf: tainted)) // $ tainted=data1
96+
sink(arg: mc5.appending(contentsOf: tainted)[0]) // $ tainted=data1
97+
sink(arg: mc5.inserting(contentsOf: clean, at: 0))
98+
sink(arg: mc5.inserting(contentsOf: clean, at: 0)[0])
99+
sink(arg: mc5.inserting(contentsOf: tainted, at: 0)) // $ tainted=data1
100+
sink(arg: mc5.inserting(contentsOf: tainted, at: 0)[0]) // $ tainted=data1
101+
sink(arg: mc5)
102+
103+
let taintedString = sourceString("data9")
104+
var mc6 = MyContainer()
105+
mc6.append("")
106+
sink(arg: mc6)
107+
sink(arg: mc6[0])
108+
mc6.append(taintedString)
109+
sink(arg: mc6) // $ tainted=data9
110+
sink(arg: mc6[0]) // $ tainted=data9
111+
112+
let taintedArray = [source("data10")]
113+
var mc7 = MyContainer()
114+
mc7.append(contentsOf: [])
115+
sink(arg: mc7)
116+
sink(arg: mc7[0])
117+
mc7.append(contentsOf: taintedArray)
118+
sink(arg: mc7) // $ MISSING: tainted=data10
119+
sink(arg: mc7[0]) // $ MISSING: tainted=data10
120+
}

swift/ql/test/library-tests/dataflow/taint/libraries/ui.swift

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,31 +41,42 @@ class UIScene {
4141
class OpenURLOptions {}
4242
}
4343

44+
struct CGFloat { }
45+
46+
class Data {
47+
init<S>(_ elements: S) {}
48+
}
49+
50+
class UIImage {
51+
init?(data: Data) { }
52+
init?(data: Data, scale: CGFloat) { }
53+
}
54+
4455
// --- tests ---
4556

46-
func source() -> Any { return "" }
57+
func source(_ label: String) -> Any { return "" }
4758
func sink(_: Any) {}
4859

4960
func testUIOpenURLContext() {
5061
let safe = UIOpenURLContext()
51-
let tainted = source() as! UIOpenURLContext
62+
let tainted = source("OpenURLContext") as! UIOpenURLContext
5263

5364
sink(safe.url)
5465
sink(safe.options)
55-
sink(tainted.url) // $ tainted=51
66+
sink(tainted.url) // $ tainted=OpenURLContext
5667
sink(tainted.options)
5768
}
5869

5970
func testConnectionOptions() {
6071
let safe = UIScene.ConnectionOptions()
61-
let tainted = source() as! UIScene.ConnectionOptions
72+
let tainted = source("ConnectionOptions") as! UIScene.ConnectionOptions
6273

6374
sink(safe.userActivities)
64-
sink(tainted.userActivities) // $ tainted=61
75+
sink(tainted.userActivities) // $ tainted=ConnectionOptions
6576
sink(safe.shortcutItem)
6677
sink(tainted.shortcutItem)
6778
sink(safe.urlContexts)
68-
sink(tainted.urlContexts) // $ tainted=61
79+
sink(tainted.urlContexts) // $ tainted=ConnectionOptions
6980
sink(safe.handoffUserActivityType)
7081
sink(tainted.handoffUserActivityType)
7182
sink(safe.cloudKitShareMetadata)
@@ -75,3 +86,12 @@ func testConnectionOptions() {
7586
sink(safe.sourceApplication)
7687
sink(tainted.sourceApplication)
7788
}
89+
90+
func testUIImage(scale: CGFloat) {
91+
let taintedData = source("UIImage") as! Data
92+
93+
sink(UIImage(data: Data(0))!)
94+
sink(UIImage(data: Data(taintedData))!) // $ tainted=UIImage
95+
sink(UIImage(data: Data(0), scale: scale)!)
96+
sink(UIImage(data: Data(taintedData), scale: scale)!) // $ tainted=UIImage
97+
}

0 commit comments

Comments
 (0)