Skip to content

Commit 6928e62

Browse files
committed
Swift: Split the three sensitive exprs queries into separate QL and QLL files.
1 parent 0f4df0d commit 6928e62

10 files changed

+348
-300
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
* Provides classes and predicates for reasoning about cleartext database
3+
* storage vulnerabilities.
4+
*/
5+
6+
import swift
7+
import codeql.swift.dataflow.DataFlow
8+
9+
/**
10+
* A `DataFlow::Node` that is something stored in a local database.
11+
*/
12+
abstract class Stored extends DataFlow::Node { }
13+
14+
/**
15+
* A `DataFlow::Node` that is an expression stored with the Core Data library.
16+
*/
17+
class CoreDataStore extends Stored {
18+
CoreDataStore() {
19+
// values written into Core Data objects through `set*Value` methods are a sink.
20+
exists(CallExpr call |
21+
call.getStaticTarget()
22+
.(MethodDecl)
23+
.hasQualifiedName("NSManagedObject",
24+
["setValue(_:forKey:)", "setPrimitiveValue(_:forKey:)"]) and
25+
call.getArgument(0).getExpr() = this.asExpr()
26+
)
27+
or
28+
// any write into a class derived from `NSManagedObject` is a sink. For
29+
// example in `coreDataObj.data = sensitive` the post-update node corresponding
30+
// with `coreDataObj.data` is a sink.
31+
// (ideally this would be only members with the `@NSManaged` attribute)
32+
exists(ClassOrStructDecl cd, Expr e |
33+
cd.getABaseTypeDecl*().getName() = "NSManagedObject" and
34+
this.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr() = e and
35+
e.getFullyConverted().getType() = cd.getType() and
36+
not e.(DeclRefExpr).getDecl() instanceof SelfParamDecl
37+
)
38+
}
39+
}
40+
41+
/**
42+
* A `DataFlow::Node` that is an expression stored with the Realm database
43+
* library.
44+
*/
45+
class RealmStore extends Stored instanceof DataFlow::PostUpdateNode {
46+
RealmStore() {
47+
// any write into a class derived from `RealmSwiftObject` is a sink. For
48+
// example in `realmObj.data = sensitive` the post-update node corresponding
49+
// with `realmObj.data` is a sink.
50+
exists(ClassOrStructDecl cd, Expr e |
51+
cd.getABaseTypeDecl*().getName() = "RealmSwiftObject" and
52+
this.getPreUpdateNode().asExpr() = e and
53+
e.getFullyConverted().getType() = cd.getType() and
54+
not e.(DeclRefExpr).getDecl() instanceof SelfParamDecl
55+
)
56+
}
57+
}
58+
59+
/**
60+
* A `DataFlow::Node` that is an expression stored with the GRDB library.
61+
*/
62+
class GrdbStore extends Stored {
63+
GrdbStore() {
64+
exists(CallExpr call, MethodDecl method |
65+
call.getStaticTarget() = method and
66+
call.getArgumentWithLabel("arguments").getExpr() = this.asExpr()
67+
|
68+
method
69+
.hasQualifiedName("Database",
70+
["allStatements(sql:arguments:)", "execute(sql:arguments:)",])
71+
or
72+
method.hasQualifiedName("SQLRequest", "init(sql:arguments:adapter:cached:)")
73+
or
74+
method.hasQualifiedName("SQL", ["init(sql:arguments:)", "append(sql:arguments:)"])
75+
or
76+
method.hasQualifiedName("SQLStatementCursor", "init(database:sql:arguments:prepFlags:)")
77+
or
78+
method
79+
.hasQualifiedName("TableRecord",
80+
[
81+
"select(sql:arguments:)", "select(sql:arguments:as:)", "filter(sql:arguments:)",
82+
"order(sql:arguments:)"
83+
])
84+
or
85+
method
86+
.hasQualifiedName(["Row", "DatabaseValueConvertible", "FetchableRecord"],
87+
[
88+
"fetchCursor(_:sql:arguments:adapter:)", "fetchAll(_:sql:arguments:adapter:)",
89+
"fetchSet(_:sql:arguments:adapter:)", "fetchOne(_:sql:arguments:adapter:)"
90+
])
91+
or
92+
method
93+
.hasQualifiedName("FetchableRecord",
94+
[
95+
"fetchCursor(_:arguments:adapter:)", "fetchAll(_:arguments:adapter:)",
96+
"fetchSet(_:arguments:adapter:)", "fetchOne(_:arguments:adapter:)",
97+
])
98+
or
99+
method.hasQualifiedName("Statement", ["execute(arguments:)"])
100+
or
101+
method
102+
.hasQualifiedName("CommonTableExpression", "init(recursive:named:columns:sql:arguments:)")
103+
)
104+
or
105+
exists(CallExpr call, MethodDecl method |
106+
call.getStaticTarget() = method and
107+
call.getArgument(0).getExpr() = this.asExpr()
108+
|
109+
method.hasQualifiedName("Statement", "setArguments(_:)")
110+
)
111+
}
112+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Provides a taint-tracking configuration for reasoning about cleartext
3+
* database storage vulnerabilities.
4+
*/
5+
6+
import swift
7+
import codeql.swift.security.SensitiveExprs
8+
import codeql.swift.dataflow.DataFlow
9+
import codeql.swift.dataflow.TaintTracking
10+
import codeql.swift.security.CleartextStorageDatabaseExtensions
11+
12+
/**
13+
* A taint configuration from sensitive information to expressions that are
14+
* transmitted over a network.
15+
*/
16+
class CleartextStorageConfig extends TaintTracking::Configuration {
17+
CleartextStorageConfig() { this = "CleartextStorageConfig" }
18+
19+
override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof SensitiveExpr }
20+
21+
override predicate isSink(DataFlow::Node node) { node instanceof Stored }
22+
23+
override predicate isSanitizerIn(DataFlow::Node node) {
24+
// make sources barriers so that we only report the closest instance
25+
isSource(node)
26+
}
27+
28+
override predicate isSanitizer(DataFlow::Node node) {
29+
// encryption barrier
30+
node.asExpr() instanceof EncryptedExpr
31+
}
32+
33+
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
34+
// Needed until we have proper content flow through arrays
35+
exists(ArrayExpr arr |
36+
node1.asExpr() = arr.getAnElement() and
37+
node2.asExpr() = arr
38+
)
39+
}
40+
41+
override predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
42+
// flow out from fields of an `NSManagedObject` or `RealmSwiftObject` at the sink,
43+
// for example in `realmObj.data = sensitive`.
44+
isSink(node) and
45+
exists(ClassOrStructDecl cd, Decl cx |
46+
cd.getABaseTypeDecl*().getName() = ["NSManagedObject", "RealmSwiftObject"] and
47+
cx.asNominalTypeDecl() = cd and
48+
c.getAReadContent().(DataFlow::Content::FieldContent).getField() = cx.getAMember()
49+
)
50+
or
51+
// any default implicit reads
52+
super.allowImplicitRead(node, c)
53+
}
54+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Provides classes and predicates for reasoning about cleartext preferences
3+
* storage vulnerabilities.
4+
*/
5+
6+
import swift
7+
import codeql.swift.dataflow.DataFlow
8+
9+
/**
10+
* A `DataFlow::Node` of something that gets stored in an application preference store.
11+
*/
12+
abstract class Stored extends DataFlow::Node {
13+
abstract string getStoreName();
14+
}
15+
16+
/** The `DataFlow::Node` of an expression that gets written to the user defaults database */
17+
class UserDefaultsStore extends Stored {
18+
UserDefaultsStore() {
19+
exists(CallExpr call |
20+
call.getStaticTarget().(MethodDecl).hasQualifiedName("UserDefaults", "set(_:forKey:)") and
21+
call.getArgument(0).getExpr() = this.asExpr()
22+
)
23+
}
24+
25+
override string getStoreName() { result = "the user defaults database" }
26+
}
27+
28+
/** The `DataFlow::Node` of an expression that gets written to the iCloud-backed NSUbiquitousKeyValueStore */
29+
class NSUbiquitousKeyValueStore extends Stored {
30+
NSUbiquitousKeyValueStore() {
31+
exists(CallExpr call |
32+
call.getStaticTarget()
33+
.(MethodDecl)
34+
.hasQualifiedName("NSUbiquitousKeyValueStore", "set(_:forKey:)") and
35+
call.getArgument(0).getExpr() = this.asExpr()
36+
)
37+
}
38+
39+
override string getStoreName() { result = "iCloud" }
40+
}
41+
42+
/**
43+
* A more complicated case, this is a macOS-only way of writing to
44+
* NSUserDefaults by modifying the `NSUserDefaultsController.values: Any`
45+
* object via reflection (`perform(Selector)`) or the `NSKeyValueCoding`,
46+
* `NSKeyValueBindingCreation` APIs. (TODO)
47+
*/
48+
class NSUserDefaultsControllerStore extends Stored {
49+
NSUserDefaultsControllerStore() { none() }
50+
51+
override string getStoreName() { result = "the user defaults database" }
52+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Provides a taint-tracking configuration for reasoning about cleartext
3+
* preferences storage vulnerabilities.
4+
*/
5+
6+
import swift
7+
import codeql.swift.security.SensitiveExprs
8+
import codeql.swift.dataflow.DataFlow
9+
import codeql.swift.dataflow.TaintTracking
10+
import codeql.swift.security.CleartextStoragePreferencesExtensions
11+
12+
/**
13+
* A taint configuration from sensitive information to expressions that are
14+
* stored as preferences.
15+
*/
16+
class CleartextStorageConfig extends TaintTracking::Configuration {
17+
CleartextStorageConfig() { this = "CleartextStorageConfig" }
18+
19+
override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof SensitiveExpr }
20+
21+
override predicate isSink(DataFlow::Node node) { node instanceof Stored }
22+
23+
override predicate isSanitizerIn(DataFlow::Node node) {
24+
// make sources barriers so that we only report the closest instance
25+
this.isSource(node)
26+
}
27+
28+
override predicate isSanitizer(DataFlow::Node node) {
29+
// encryption barrier
30+
node.asExpr() instanceof EncryptedExpr
31+
}
32+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* Provides classes and predicates for reasoning about cleartext transmission
3+
* vulnerabilities.
4+
*/
5+
6+
import swift
7+
import codeql.swift.dataflow.DataFlow
8+
9+
/**
10+
* An `Expr` that is transmitted over a network.
11+
*/
12+
abstract class Transmitted extends Expr { }
13+
14+
/**
15+
* An `Expr` that is transmitted with `NWConnection.send`.
16+
*/
17+
class NWConnectionSend extends Transmitted {
18+
NWConnectionSend() {
19+
// `content` arg to `NWConnection.send` is a sink
20+
exists(CallExpr call |
21+
call.getStaticTarget()
22+
.(MethodDecl)
23+
.hasQualifiedName("NWConnection", "send(content:contentContext:isComplete:completion:)") and
24+
call.getArgument(0).getExpr() = this
25+
)
26+
}
27+
}
28+
29+
/**
30+
* An `Expr` that is used to form a `URL`. Such expressions are very likely to
31+
* be transmitted over a network, because that's what URLs are for.
32+
*/
33+
class Url extends Transmitted {
34+
Url() {
35+
// `string` arg in `URL.init` is a sink
36+
// (we assume here that the URL goes on to be used in a network operation)
37+
exists(CallExpr call |
38+
call.getStaticTarget()
39+
.(MethodDecl)
40+
.hasQualifiedName("URL", ["init(string:)", "init(string:relativeTo:)"]) and
41+
call.getArgument(0).getExpr() = this
42+
)
43+
}
44+
}
45+
46+
/**
47+
* An `Expr` that transmitted through the Alamofire library.
48+
*/
49+
class AlamofireTransmitted extends Transmitted {
50+
AlamofireTransmitted() {
51+
// sinks are the first argument containing the URL, and the `parameters`
52+
// and `headers` arguments to appropriate methods of `Session`.
53+
exists(CallExpr call, string fName |
54+
call.getStaticTarget().(MethodDecl).hasQualifiedName("Session", fName) and
55+
fName.regexpMatch("(request|streamRequest|download)\\(.*") and
56+
(
57+
call.getArgument(0).getExpr() = this or
58+
call.getArgumentWithLabel(["headers", "parameters"]).getExpr() = this
59+
)
60+
)
61+
}
62+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Provides a taint-tracking configuration for reasoning about cleartext
3+
* transmission vulnerabilities.
4+
*/
5+
6+
import swift
7+
import codeql.swift.security.SensitiveExprs
8+
import codeql.swift.dataflow.DataFlow
9+
import codeql.swift.dataflow.TaintTracking
10+
import codeql.swift.security.CleartextTransmissionExtensions
11+
12+
/**
13+
* A taint configuration from sensitive information to expressions that are
14+
* transmitted over a network.
15+
*/
16+
class CleartextTransmissionConfig extends TaintTracking::Configuration {
17+
CleartextTransmissionConfig() { this = "CleartextTransmissionConfig" }
18+
19+
override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof SensitiveExpr }
20+
21+
override predicate isSink(DataFlow::Node node) { node.asExpr() instanceof Transmitted }
22+
23+
override predicate isSanitizerIn(DataFlow::Node node) {
24+
// make sources barriers so that we only report the closest instance
25+
isSource(node)
26+
}
27+
28+
override predicate isSanitizer(DataFlow::Node node) {
29+
// encryption barrier
30+
node.asExpr() instanceof EncryptedExpr
31+
}
32+
}

swift/ql/lib/codeql/swift/security/SqlInjectionExtensions.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import swift
88
import codeql.swift.dataflow.DataFlow
9-
private import codeql.swift.dataflow.ExternalFlow
9+
import codeql.swift.dataflow.ExternalFlow
1010

1111
/**
1212
* A dataflow sink for SQL injection vulnerabilities.

0 commit comments

Comments
 (0)