Skip to content

Commit 4245a38

Browse files
committed
Swift: Add SQLite.swift and sqlite3 C API test cases for swift/cleartext-storage-database.
1 parent 4183fbe commit 4245a38

File tree

3 files changed

+278
-0
lines changed

3 files changed

+278
-0
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
2+
// --- stubs ---
3+
4+
public protocol Binding {}
5+
6+
public protocol Number: Binding {}
7+
8+
extension String: Binding {}
9+
10+
extension Int: Number {}
11+
12+
class Statement {
13+
fileprivate let connection: Connection
14+
15+
init(_ connection: Connection, _ SQL: String) throws { self.connection = connection}
16+
17+
public func bind(_ values: Binding?...) -> Statement { return self }
18+
public func bind(_ values: [Binding?]) -> Statement { return self }
19+
public func bind(_ values: [String: Binding?]) -> Statement { return self }
20+
21+
@discardableResult public func run(_ bindings: Binding?...) throws -> Statement { return self }
22+
@discardableResult public func run(_ bindings: [Binding?]) throws -> Statement { return self }
23+
@discardableResult public func run(_ bindings: [String: Binding?]) throws -> Statement { return self }
24+
25+
public func scalar(_ bindings: Binding?...) throws -> Binding? { return nil }
26+
public func scalar(_ bindings: [Binding?]) throws -> Binding? { return nil }
27+
public func scalar(_ bindings: [String: Binding?]) throws -> Binding? { return nil }
28+
}
29+
30+
class Connection {
31+
public func execute(_ SQL: String) throws { }
32+
33+
public func prepare(_ statement: String, _ bindings: Binding?...) throws -> Statement { return try Statement(self, "") }
34+
public func prepare(_ statement: String, _ bindings: [Binding?]) throws -> Statement { return try Statement(self, "") }
35+
public func prepare(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement { return try Statement(self, "") }
36+
37+
@discardableResult public func run(_ statement: String, _ bindings: Binding?...) throws -> Statement { return try Statement(self, "") }
38+
@discardableResult public func run(_ statement: String, _ bindings: [Binding?]) throws -> Statement { return try Statement(self, "") }
39+
@discardableResult public func run(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement { return try Statement(self, "") }
40+
41+
public func scalar(_ statement: String, _ bindings: Binding?...) throws -> Binding? { return nil }
42+
public func scalar(_ statement: String, _ bindings: [Binding?]) throws -> Binding? { return nil }
43+
public func scalar(_ statement: String, _ bindings: [String: Binding?]) throws -> Binding? { return nil }
44+
}
45+
46+
protocol QueryType { }
47+
48+
protocol SchemaType: QueryType { }
49+
50+
struct Table: SchemaType {
51+
init(_ name: String, database: String? = nil) { }
52+
}
53+
54+
protocol ExpressionType : CustomStringConvertible {
55+
init(_ template: String, _ bindings: [Binding?])
56+
}
57+
58+
extension ExpressionType {
59+
init(_ identifier: String) {
60+
self.init(identifier, [])
61+
}
62+
63+
var description: String { get { "" } }
64+
}
65+
66+
extension ExpressionType { // where UnderlyingType == String
67+
public func replace(_ pattern: String, with replacement: String) -> Expression<String> {
68+
return Expression<String>("")
69+
}
70+
}
71+
72+
struct Expression<Datatype> : ExpressionType {
73+
typealias UnderlyingType = Datatype
74+
75+
init(_ template: String, _ bindings: [Binding?]) { }
76+
}
77+
78+
struct Insert: ExpressionType {
79+
init(_ template: String, _ bindings: [Binding?]) { }
80+
}
81+
82+
struct Update: ExpressionType {
83+
init(_ template: String, _ bindings: [Binding?]) { }
84+
}
85+
86+
extension Connection {
87+
@discardableResult public func run(_ query: Insert) throws -> Int64 { return 0 }
88+
@discardableResult public func run(_ query: Update) throws -> Int { return 0 }
89+
}
90+
91+
struct Setter { }
92+
93+
infix operator <-
94+
95+
func <-<V>(column: Expression<V>, value: Expression<V>) -> Setter { return Setter() }
96+
func <-<V>(column: Expression<V>, value: V) -> Setter { return Setter() }
97+
98+
extension QueryType {
99+
func filter(_ predicate: Expression<Bool>) -> Self { return self }
100+
101+
func insert(_ value: Setter, _ more: Setter...) -> Insert { return Insert("") }
102+
func update(_ values: Setter...) -> Update { return Update("") }
103+
}
104+
105+
func ==<V>(lhs: Expression<V>, rhs: V) -> Expression<Bool> { return Expression<Bool>("") }
106+
107+
// --- tests ---
108+
109+
func test_sqlite_swift_api(db: Connection, id: Int, mobilePhoneNumber: String) throws {
110+
// --- sensitive data in SQL (in practice these cases may also be SQL injection) ---
111+
112+
let insertQuery = "INSERT INTO CONTACTS(ID, NUMBER) VALUES(\(id), \(mobilePhoneNumber));"
113+
let updateQuery = "UPDATE CONTACTS SET NUMBER=\(mobilePhoneNumber) WHERE ID=\(id);"
114+
let deleteQuery = "DELETE FROM CONTACTS WHERE ID=\(id);"
115+
116+
try db.execute(insertQuery) // BAD (sensitive data) [NOT DETECTED]
117+
try db.execute(updateQuery) // BAD (sensitive data) [NOT DETECTED]
118+
try db.execute(deleteQuery) // GOOD
119+
120+
_ = try db.prepare(insertQuery).run() // BAD (sensitive data) [NOT DETECTED]
121+
_ = try db.prepare(updateQuery).run() // BAD (sensitive data) [NOT DETECTED]
122+
_ = try db.prepare(deleteQuery).run() // GOOD
123+
124+
_ = try db.run(insertQuery) // BAD (sensitive data) [NOT DETECTED]
125+
_ = try db.run(updateQuery) // BAD (sensitive data) [NOT DETECTED]
126+
_ = try db.run(deleteQuery) // GOOD
127+
128+
_ = try db.scalar(insertQuery) // BAD (sensitive data) [NOT DETECTED]
129+
_ = try db.scalar(updateQuery) // BAD (sensitive data) [NOT DETECTED]
130+
_ = try db.scalar(deleteQuery) // GOOD
131+
132+
_ = try Statement(db, insertQuery).run() // BAD (sensitive data) [NOT DETECTED]
133+
_ = try Statement(db, updateQuery).run() // BAD (sensitive data) [NOT DETECTED]
134+
_ = try Statement(db, deleteQuery).run() // GOOD
135+
136+
// --- sensitive data in bindings ---
137+
138+
let varQuery1 = "UPDATE CONTACTS SET NUMBER=?;"
139+
140+
_ = try db.prepare(varQuery1, mobilePhoneNumber).run() // BAD (sensitive data) [NOT DETECTED]
141+
_ = try db.run(varQuery1, mobilePhoneNumber) // BAD (sensitive data) [NOT DETECTED]
142+
_ = try db.scalar(varQuery1, mobilePhoneNumber) // BAD (sensitive data) [NOT DETECTED]
143+
144+
let stmt1 = try db.prepare(varQuery1) // GOOD
145+
_ = try stmt1.bind(mobilePhoneNumber).run() // BAD (sensitive data) [NOT DETECTED]
146+
_ = try stmt1.run(mobilePhoneNumber) // BAD (sensitive data) [NOT DETECTED]
147+
_ = try stmt1.scalar(mobilePhoneNumber) // BAD (sensitive data) [NOT DETECTED]
148+
149+
let varQuery2 = "UPDATE CONTACTS SET NUMBER=? WHERE ID=?;"
150+
151+
_ = try db.prepare(varQuery2, [mobilePhoneNumber, id]).run() // BAD (sensitive data) [NOT DETECTED]
152+
_ = try db.run(varQuery2, [mobilePhoneNumber, id]) // BAD (sensitive data) [NOT DETECTED]
153+
_ = try db.scalar(varQuery2, [mobilePhoneNumber, id]) // BAD (sensitive data) [NOT DETECTED]
154+
155+
let stmt2 = try db.prepare(varQuery2) // GOOD
156+
_ = try stmt2.bind([mobilePhoneNumber, id]).run() // BAD (sensitive data) [NOT DETECTED]
157+
_ = try stmt2.run([mobilePhoneNumber, id]) // BAD (sensitive data) [NOT DETECTED]
158+
_ = try stmt2.scalar([mobilePhoneNumber, id]) // BAD (sensitive data) [NOT DETECTED]
159+
160+
let varQuery3 = "UPDATE CONTACTS SET NUMBER=$number WHERE ID=$id;"
161+
162+
_ = try db.prepare(varQuery3, ["id": id, "number": mobilePhoneNumber]).run() // BAD (sensitive data) [NOT DETECTED]
163+
_ = try db.run(varQuery3, ["id": id, "number": mobilePhoneNumber]) // BAD (sensitive data) [NOT DETECTED]
164+
_ = try db.scalar(varQuery3, ["id": id, "number": mobilePhoneNumber]) // BAD (sensitive data) [NOT DETECTED]
165+
166+
let stmt3 = try db.prepare(varQuery3) // GOOD
167+
_ = try stmt3.bind(["id": id, "number": mobilePhoneNumber]).run() // BAD (sensitive data) [NOT DETECTED]
168+
_ = try stmt3.run(["id": id, "number": mobilePhoneNumber]) // BAD (sensitive data) [NOT DETECTED]
169+
_ = try stmt3.scalar(["id": id, "number": mobilePhoneNumber]) // BAD (sensitive data) [NOT DETECTED]
170+
171+
// --- higher level insert / update ---
172+
173+
let table = Table("TABLE")
174+
let idExpr = Expression<Int>("ID")
175+
let numberExpr = Expression<String>("NUMBER")
176+
let filter = table.filter(idExpr == id) // GOOD
177+
178+
try db.run(table.insert(idExpr <- id, numberExpr <- "123")) // GOOD
179+
try db.run(table.insert(idExpr <- id, numberExpr <- mobilePhoneNumber)) // BAD (sensitive data) [NOT DETECTED]
180+
181+
try db.run(table.update(numberExpr <- "123")) // GOOD
182+
try db.run(table.update(numberExpr <- mobilePhoneNumber)) // BAD (sensitive data) [NOT DETECTED]
183+
try db.run(filter.update(numberExpr <- "123")) // GOOD
184+
try db.run(filter.update(numberExpr <- mobilePhoneNumber)) // BAD (sensitive data) [NOT DETECTED]
185+
try db.run(table.update(numberExpr <- numberExpr.replace("123", with: "456"))) // GOOD
186+
try db.run(table.update(numberExpr <- numberExpr.replace("123", with: mobilePhoneNumber))) // BAD (sensitive data) [NOT DETECTED]
187+
// (much more complex query construction is possible in SQLite.swift)
188+
}

swift/ql/test/query-tests/Security/CWE-311/SensitiveExprs.expected

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,30 @@
1+
| SQLite.swift:112:70:112:70 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
2+
| SQLite.swift:113:50:113:50 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
3+
| SQLite.swift:140:32:140:32 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
4+
| SQLite.swift:141:28:141:28 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
5+
| SQLite.swift:142:31:142:31 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
6+
| SQLite.swift:145:21:145:21 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
7+
| SQLite.swift:146:20:146:20 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
8+
| SQLite.swift:147:23:147:23 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
9+
| SQLite.swift:151:33:151:33 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
10+
| SQLite.swift:152:29:152:29 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
11+
| SQLite.swift:153:32:153:32 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
12+
| SQLite.swift:156:22:156:22 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
13+
| SQLite.swift:157:21:157:21 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
14+
| SQLite.swift:158:24:158:24 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
15+
| SQLite.swift:162:53:162:53 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
16+
| SQLite.swift:163:49:163:49 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
17+
| SQLite.swift:164:52:164:52 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
18+
| SQLite.swift:167:42:167:42 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
19+
| SQLite.swift:168:41:168:41 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
20+
| SQLite.swift:169:44:169:44 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
21+
| SQLite.swift:179:54:179:54 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
22+
| SQLite.swift:182:40:182:40 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
23+
| SQLite.swift:184:41:184:41 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
24+
| SQLite.swift:186:72:186:72 | mobilePhoneNumber | label:mobilePhoneNumber, type:private information |
25+
| sqlite3_c_api.swift:42:69:42:69 | medicalNotes | label:medicalNotes, type:private information |
26+
| sqlite3_c_api.swift:43:49:43:49 | medicalNotes | label:medicalNotes, type:private information |
27+
| sqlite3_c_api.swift:58:36:58:36 | medicalNotes | label:medicalNotes, type:private information |
128
| testAlamofire.swift:150:45:150:45 | password | label:password, type:credential |
229
| testAlamofire.swift:152:51:152:51 | password | label:password, type:credential |
330
| testAlamofire.swift:154:38:154:38 | email | label:email, type:private information |
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
2+
// --- stubs ---
3+
4+
var SQLITE_OK : Int32 = 0
5+
var SQLITE_TRANSIENT : (@convention(c) (UnsafeMutableRawPointer?) -> Void)?
6+
7+
func sqlite3_exec(
8+
_ _: OpaquePointer?,
9+
_ sql: UnsafePointer<CChar>?,
10+
_ callback: (@convention(c) (UnsafeMutableRawPointer?, Int32, UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?, UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?) -> Int32)?,
11+
_ _: UnsafeMutableRawPointer?,
12+
_ errmsg: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
13+
) -> Int32 { return SQLITE_OK }
14+
15+
func sqlite3_prepare(
16+
_ db: OpaquePointer?,
17+
_ zSql: UnsafePointer<CChar>?,
18+
_ nByte: Int32,
19+
_ ppStmt: UnsafeMutablePointer<OpaquePointer?>?,
20+
_ pzTail: UnsafeMutablePointer<UnsafePointer<CChar>?>?
21+
) -> Int32 { return SQLITE_OK }
22+
23+
func sqlite3_bind_int(
24+
_ _: OpaquePointer?,
25+
_ _: Int32,
26+
_ _: Int32
27+
) -> Int32 { return SQLITE_OK }
28+
29+
func sqlite3_bind_text(
30+
_ _: OpaquePointer?,
31+
_ _: Int32,
32+
_ _: UnsafePointer<CChar>?,
33+
_ _: Int32,
34+
_ callback: (@convention(c) (UnsafeMutableRawPointer?) -> Void)?
35+
) -> Int32 { return SQLITE_OK }
36+
37+
// --- tests ---
38+
39+
func test_sqlite3_c_api(db: OpaquePointer?, id: Int32, medicalNotes: String) {
40+
// --- sensitive data in SQL (in practice these cases may also be SQL injection) ---
41+
42+
let insertQuery = "INSERT INTO PATIENTS(ID, NOTES) VALUES(\(id), \(medicalNotes));"
43+
let updateQuery = "UPDATE PATIENTS SET NOTES=\(medicalNotes) WHERE ID=\(id);"
44+
let deleteQuery = "DELETE FROM PATIENTS WHERE ID=\(id);"
45+
46+
let _ = sqlite3_exec(db, insertQuery, nil, nil, nil) // BAD (sensitive data) [NOT DETECTED]
47+
let _ = sqlite3_exec(db, updateQuery, nil, nil, nil) // BAD (sensitive data) [NOT DETECTED]
48+
let _ = sqlite3_exec(db, deleteQuery, nil, nil, nil) // GOOD
49+
50+
// --- sensitive data in bindings ---
51+
52+
let varQuery = "UPDATE PATIENTS SET NOTES=? WHERE ID=?;"
53+
54+
var stmt1: OpaquePointer?
55+
56+
if (sqlite3_prepare(db, varQuery, -1, &stmt1, nil) == SQLITE_OK) { // GOOD
57+
if (sqlite3_bind_int(stmt1, 1, id) == SQLITE_OK) { // GOOD
58+
if (sqlite3_bind_text(stmt1, 2, medicalNotes, -1, SQLITE_TRANSIENT) == SQLITE_OK) { // BAD (sensitive data) [NOT DETECTED]
59+
// ...
60+
}
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)