Skip to content

Commit 8875487

Browse files
committed
Swift: Expand the URL taint models.
1 parent 45d32c3 commit 8875487

File tree

2 files changed

+163
-73
lines changed
  • swift/ql
    • lib/codeql/swift/frameworks/StandardLibrary
    • test/library-tests/dataflow/taint/libraries

2 files changed

+163
-73
lines changed

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

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,36 @@ private class UrlRequestFieldsInheritTaint extends TaintInheritingContent,
3535
}
3636
}
3737

38+
/**
39+
* A content implying that, if a `URLResource` is tainted, then its fields `name`
40+
* and `subdirectory` are tainted.
41+
*/
42+
private class UrlResourceFieldsInheritTaint extends TaintInheritingContent,
43+
DataFlow::Content::FieldContent
44+
{
45+
UrlResourceFieldsInheritTaint() {
46+
this.getField().getEnclosingDecl().asNominalTypeDecl().getName() = "URLResource" and
47+
this.getField().getName() = ["name", "subdirectory"]
48+
}
49+
}
50+
51+
/**
52+
* A content implying that, if a `URLResourceValues` is tainted, then certain
53+
* fields are tainted.
54+
*/
55+
private class UrlResourceValuesFieldsInheritTaint extends TaintInheritingContent,
56+
DataFlow::Content::FieldContent
57+
{
58+
UrlResourceValuesFieldsInheritTaint() {
59+
this.getField().getEnclosingDecl().asNominalTypeDecl().getName() = "URLResourceValues" and
60+
this.getField().getName() =
61+
[
62+
"name", "path", "canonicalPath", "localizedLabel", "localizedName", "parentDirectory",
63+
"thumbnail"
64+
]
65+
}
66+
}
67+
3868
/**
3969
* A model for `URL` members that are sources of remote flow.
4070
*/
@@ -49,14 +79,74 @@ private class UrlRemoteFlowSource extends SourceModelCsv {
4979
}
5080

5181
/**
52-
* A model for `URL` members that permit taint flow.
82+
* A model for `URL` and related class members that permit taint flow.
5383
*/
5484
private class UrlSummaries extends SummaryModelCsv {
5585
override predicate row(string row) {
5686
row =
5787
[
5888
";URL;true;init(string:);(String);;Argument[0];ReturnValue;taint",
59-
";URL;true;init(string:relativeTo:);(String,URL?);;Argument[0,1];ReturnValue;taint"
89+
";URL;true;init(string:relativeTo:);(String,URL?);;Argument[0..1];ReturnValue;taint",
90+
";URL;true;init(fileURLWithPath:);;;Argument[0];ReturnValue;taint",
91+
";URL;true;init(fileURLWithPath:isDirectory:);;;Argument[0];ReturnValue;taint",
92+
";URL;true;init(fileURLWithPath:relativeTo:);;;Argument[0..1];ReturnValue;taint",
93+
";URL;true;init(fileURLWithPath:isDirectory:relativeTo:);;;Argument[0];ReturnValue;taint",
94+
";URL;true;init(fileURLWithPath:isDirectory:relativeTo:);;;Argument[2];ReturnValue;taint",
95+
";URL;true;init(fileURLWithFileSystemRepresentation:isDirectory:relativeTo:);;;Argument[0];ReturnValue;taint",
96+
";URL;true;init(fileURLWithFileSystemRepresentation:isDirectory:relativeTo:);;;Argument[2];ReturnValue;taint",
97+
";URL;true;init(fileReferenceLiteralResourceName:);;;Argument[0];ReturnValue;taint",
98+
";URL;true;init(_:);;;Argument[0];ReturnValue;taint",
99+
";URL;true;init(_:isDirectory:);;;Argument[0];ReturnValue;taint",
100+
";URL;true;init(resolvingBookmarkData:options:relativeTo:bookmarkDataIsStale:);;;Argument[0];ReturnValue;taint",
101+
";URL;true;init(resolvingBookmarkData:options:relativeTo:bookmarkDataIsStale:);;;Argument[2];ReturnValue;taint",
102+
";URL;true;init(resolvingAliasFileAt:options:);;;Argument[0];ReturnValue;taint",
103+
";URL;true;init(resource:);;;Argument[0];ReturnValue;taint",
104+
";URL;true;init(dataRepresentation:relativeTo:isAbsolute:);;;Argument[0..1];ReturnValue;taint",
105+
";URL;true;init(_:strategy:);;;Argument[0];ReturnValue;taint",
106+
";URL;true;init(filePath:directoryHint:);;;Argument[0];ReturnValue;taint",
107+
";URL;true;init(filePath:directoryHint:relativeTo:);;;Argument[0];ReturnValue;taint",
108+
";URL;true;init(filePath:directoryHint:relativeTo:);;;Argument[2];ReturnValue;taint",
109+
";URL;true;init(for:in:appropriateFor:create:);;;Argument[0..2];ReturnValue;taint",
110+
";URL;true;init(string:encodingInvalidCharacters:);;;Argument[0];ReturnValue;taint",
111+
";URL;true;resourceValues(forKeys:);;;Argument[-1];ReturnValue;taint",
112+
";URL;true;setResourceValues(_:);;;Argument[0];Argument[-1];taint",
113+
";URL;true;setTemporaryResourceValue(_:forKey:);;;Argument[-1..0];Argument[-1];taint",
114+
";URL;true;withUnsafeFileSystemRepresentation(_:);;;Argument[-1],Argument[0].Parameter[0];ReturnValue;taint",
115+
";URL;true;withUnsafeFileSystemRepresentation(_:);;;Argument[0].ReturnValue;ReturnValue;taint",
116+
";URL;true;resolvingSymlinksInPath();;;Argument[-1];ReturnValue;taint",
117+
";URL;true;appendPathComponent(_:);;;Argument[-1..0];Argument[-1];taint",
118+
";URL;true;appendPathComponent(_:isDirectory:);;;Argument[-1..0];Argument[-1];taint",
119+
";URL;true;appendPathComponent(_:conformingTo:);;;Argument[-1..0];Argument[-1];taint",
120+
";URL;true;appendingPathComponent(_:);;;Argument[-1..0];ReturnValue;taint",
121+
";URL;true;appendingPathComponent(_:isDirectory:);;;Argument[-1..0];ReturnValue;taint",
122+
";URL;true;appendingPathComponent(_:conformingTo:);;;Argument[-1..0];ReturnValue;taint",
123+
";URL;true;appendPathExtension(_:);;;Argument[-1..0];Argument[-1];taint",
124+
";URL;true;appendingPathExtension(_:);;;Argument[-1..0];ReturnValue;taint",
125+
";URL;true;deletingLastPathComponent();;;Argument[-1];ReturnValue;taint",
126+
";URL;true;deletingPathExtension();;;Argument[-1];ReturnValue;taint",
127+
";URL;true;bookmarkData(options:includingResourceValuesForKeys:relativeTo:);;;Argument[-1];ReturnValue;taint",
128+
";URL;true;bookmarkData(options:includingResourceValuesForKeys:relativeTo:);;;Argument[1..2];ReturnValue;taint",
129+
";URL;true;bookmarkData(withContentsOf:);;;Argument[0];ReturnValue;taint",
130+
";URL;true;resourceValues(forKeys:fromBookmarkData:);;;Argument[1];ReturnValue;taint",
131+
";URL;true;promisedItemResourceValues(forKeys:);;;Argument[-1];ReturnValue;taint",
132+
";URL;true;append(component:directoryHint:);;;Argument[-1..0];Argument[-1];taint",
133+
";URL;true;append(components:directoryHint:);;;Argument[-1..0];Argument[-1];taint",
134+
";URL;true;append(path:directoryHint:);;;Argument[-1..0];Argument[-1];taint",
135+
";URL;true;append(queryItems:);;;Argument[-1..0];Argument[-1];taint",
136+
";URL;true;appending(component:directoryHint:);;;Argument[-1..0];ReturnValue;taint",
137+
";URL;true;appending(components:directoryHint:);;;Argument[-1..0];ReturnValue;taint",
138+
";URL;true;appending(path:directoryHint:);;;Argument[-1..0];ReturnValue;taint",
139+
";URL;true;appending(queryItems:);;;Argument[-1..0];ReturnValue;taint",
140+
";URL;true;formatted();;;Argument[-1];ReturnValue;taint",
141+
";URL;true;formatted(_:);;;Argument[-1..0];ReturnValue;taint",
142+
";URL;true;fragment(percentEncoded:);;;Argument[-1];ReturnValue;taint",
143+
";URL;true;host(percentEncoded:);;;Argument[-1];ReturnValue;taint",
144+
";URL;true;password(percentEncoded:);;;Argument[-1];ReturnValue;taint",
145+
";URL;true;path(percentEncoded:);;;Argument[-1];ReturnValue;taint",
146+
";URL;true;query(percentEncoded:);;;Argument[-1];ReturnValue;taint",
147+
";URL;true;user(percentEncoded:);;;Argument[-1];ReturnValue;taint",
148+
";URL;true;homeDirectory(forUser:);;;Argument[0];ReturnValue;taint",
149+
";URLResource;true;init(name:subdirectory:locale:bundle:);;;Argument[0..1];ReturnValue;taint",
60150
]
61151
}
62152
}

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

Lines changed: 71 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -277,13 +277,13 @@ func taintThroughURL() {
277277
sink(data: data!) // $ tainted=210
278278
}
279279

280-
sink(arg: URL(fileURLWithPath: tainted)) // $ MISSING: tainted=210
281-
sink(arg: URL(fileURLWithPath: tainted, isDirectory: false)) // $ MISSING: tainted=210
282-
sink(arg: URL(fileURLWithPath: tainted, relativeTo: urlClean)) // $ MISSING: tainted=210
283-
sink(arg: URL(fileURLWithPath: clean, relativeTo: urlTainted)) // $ MISSING: tainted=210
284-
sink(arg: URL(fileURLWithPath: tainted, isDirectory: false, relativeTo: urlClean)) // $ MISSING: tainted=210
285-
sink(arg: URL(fileURLWithPath: clean, isDirectory: false, relativeTo: urlTainted)) // $ MISSING: tainted=210
286-
sink(arg: URL(fileURLWithPath: tainted)) // $ MISSING: tainted=
280+
sink(arg: URL(fileURLWithPath: tainted)) // $ tainted=210
281+
sink(arg: URL(fileURLWithPath: tainted, isDirectory: false)) // $ tainted=210
282+
sink(arg: URL(fileURLWithPath: tainted, relativeTo: urlClean)) // $ tainted=210
283+
sink(arg: URL(fileURLWithPath: clean, relativeTo: urlTainted)) // $ tainted=210
284+
sink(arg: URL(fileURLWithPath: tainted, isDirectory: false, relativeTo: urlClean)) // $ tainted=210
285+
sink(arg: URL(fileURLWithPath: clean, isDirectory: false, relativeTo: urlTainted)) // $ tainted=210
286+
sink(arg: URL(fileURLWithPath: tainted)) // $ tainted=210
287287

288288
let _ = clean.withCString({
289289
ptrClean in
@@ -295,27 +295,27 @@ func taintThroughURL() {
295295
sink(arg: URL(fileURLWithFileSystemRepresentation: ptrTainted, isDirectory: false, relativeTo: nil)) // $ MISSING: tainted=210
296296
})
297297

298-
sink(arg: URL(fileReferenceLiteralResourceName: tainted)) // $ MISSING: tainted=210
299-
sink(arg: URL(FilePath(tainted))!) // $ MISSING: tainted=210
300-
sink(arg: URL(FilePath(tainted), isDirectory: false)!) // $ MISSING: tainted=210
298+
sink(arg: URL(fileReferenceLiteralResourceName: tainted)) // $ tainted=210
299+
sink(arg: URL(FilePath(tainted))!) // $ tainted=210
300+
sink(arg: URL(FilePath(tainted), isDirectory: false)!) // $ tainted=210
301301

302302
if let values = try? urlTainted.resourceValues(forKeys: []) {
303-
sink(any: values)
304-
sink(string: values.name!) // $ MISSING: tainted=210
305-
sink(string: values.path!) // $ MISSING: tainted=210
306-
sink(string: values.canonicalPath!) // $ MISSING: tainted=210
307-
sink(string: values.localizedLabel!) // $ MISSING: tainted=210
308-
sink(string: values.localizedName!) // $ MISSING: tainted=210
309-
sink(any: values.parentDirectory!) // $ MISSING: tainted=210
303+
sink(any: values) // $ tainted=210
304+
sink(string: values.name!) // $ tainted=210
305+
sink(string: values.path!) // $ tainted=210
306+
sink(string: values.canonicalPath!) // $ tainted=210
307+
sink(string: values.localizedLabel!) // $ tainted=210
308+
sink(string: values.localizedName!) // $ tainted=210
309+
sink(any: values.parentDirectory!) // $ tainted=210
310310
}
311311
if let values = try? urlTainted.promisedItemResourceValues(forKeys: []) {
312-
sink(any: values)
313-
sink(string: values.name!) // $ MISSING: tainted=210
314-
sink(string: values.path!) // $ MISSING: tainted=210
315-
sink(string: values.canonicalPath!) // $ MISSING: tainted=210
316-
sink(string: values.localizedLabel!) // $ MISSING: tainted=210
317-
sink(string: values.localizedName!) // $ MISSING: tainted=210
318-
sink(any: values.parentDirectory!) // $ MISSING: tainted=210
312+
sink(any: values) // $ tainted=210
313+
sink(string: values.name!) // $ tainted=210
314+
sink(string: values.path!) // $ tainted=210
315+
sink(string: values.canonicalPath!) // $ tainted=210
316+
sink(string: values.localizedLabel!) // $ tainted=210
317+
sink(string: values.localizedName!) // $ tainted=210
318+
sink(any: values.parentDirectory!) // $ tainted=210
319319
}
320320

321321
urlClean.withUnsafeFileSystemRepresentation({
@@ -327,47 +327,47 @@ func taintThroughURL() {
327327
sink(any: ptr!) // $ MISSING: tainted=210
328328
})
329329

330-
sink(arg: urlTainted.resolvingSymlinksInPath()) // $ MISSING: tainted=210
331-
sink(arg: urlTainted.appendingPathComponent(clean)) // $ MISSING: tainted=210
332-
sink(arg: urlClean.appendingPathComponent(tainted)) // $ MISSING: tainted=210
333-
sink(arg: urlTainted.appendingPathComponent(clean, isDirectory: false)) // $ MISSING: tainted=210
334-
sink(arg: urlClean.appendingPathComponent(tainted, isDirectory: false)) // $ MISSING: tainted=210
335-
sink(arg: urlTainted.appendingPathExtension(clean)) // $ MISSING: tainted=210
336-
sink(arg: urlClean.appendingPathExtension(tainted)) // $ MISSING: tainted=210
337-
sink(arg: urlTainted.deletingLastPathComponent()) // $ MISSING: tainted=210
338-
sink(arg: urlTainted.deletingPathExtension()) // $ MISSING: tainted=210
339-
sink(arg: urlTainted.appending(component: clean)) // $ MISSING: tainted=210
340-
sink(arg: urlClean.appending(component: tainted)) // $ MISSING: tainted=210
341-
sink(arg: urlTainted.appending(components: clean)) // $ MISSING: tainted=210
330+
sink(arg: urlTainted.resolvingSymlinksInPath()) // $ tainted=210
331+
sink(arg: urlTainted.appendingPathComponent(clean)) // $ tainted=210
332+
sink(arg: urlClean.appendingPathComponent(tainted)) // $ tainted=210
333+
sink(arg: urlTainted.appendingPathComponent(clean, isDirectory: false)) // $ tainted=210
334+
sink(arg: urlClean.appendingPathComponent(tainted, isDirectory: false)) // $ tainted=210
335+
sink(arg: urlTainted.appendingPathExtension(clean)) // $ tainted=210
336+
sink(arg: urlClean.appendingPathExtension(tainted)) // $ tainted=210
337+
sink(arg: urlTainted.deletingLastPathComponent()) // $ tainted=210
338+
sink(arg: urlTainted.deletingPathExtension()) // $ tainted=210
339+
sink(arg: urlTainted.appending(component: clean)) // $ tainted=210
340+
sink(arg: urlClean.appending(component: tainted)) // $ tainted=210
341+
sink(arg: urlTainted.appending(components: clean)) // $ tainted=210
342342
sink(arg: urlClean.appending(components: tainted)) // $ MISSING: tainted=210
343343
sink(arg: urlClean.appending(components: clean, tainted)) // $ MISSING: tainted=210
344-
sink(arg: urlTainted.appending(path: clean)) // $ MISSING: tainted=210
345-
sink(arg: urlClean.appending(path: tainted)) // $ MISSING: tainted=210
346-
sink(arg: urlTainted.appending(queryItems: [])) // $ MISSING: tainted=210
344+
sink(arg: urlTainted.appending(path: clean)) // $ tainted=210
345+
sink(arg: urlClean.appending(path: tainted)) // $ tainted=210
346+
sink(arg: urlTainted.appending(queryItems: [])) // $ tainted=210
347347
sink(arg: urlClean.appending(queryItems: [source() as! URLQueryItem])) // $ MISSING: tainted=210
348348

349-
sink(arg: URL(filePath: tainted)) // $ MISSING: tainted=210
350-
sink(arg: URL(filePath: tainted, relativeTo: nil)) // $ MISSING: tainted=210
351-
sink(arg: URL(filePath: clean, relativeTo: urlTainted)) // $ MISSING: tainted=210
352-
sink(arg: try! URL(resolvingAliasFileAt: urlTainted)) // $ MISSING: tainted=210
353-
sink(arg: URL(resource: URLResource(name: tainted))!) // $ MISSING: tainted=210
354-
sink(arg: URL(resource: URLResource(name: clean, subdirectory: tainted))!) // $ MISSING: tainted=210
349+
sink(arg: URL(filePath: tainted)) // $ tainted=210
350+
sink(arg: URL(filePath: tainted, relativeTo: nil)) // $ tainted=210
351+
sink(arg: URL(filePath: clean, relativeTo: urlTainted)) // $ tainted=210
352+
sink(arg: try! URL(resolvingAliasFileAt: urlTainted)) // $ tainted=210
353+
sink(arg: URL(resource: URLResource(name: tainted))!) // $ tainted=210
354+
sink(arg: URL(resource: URLResource(name: clean, subdirectory: tainted))!) // $ tainted=210
355355

356356
let dataClean = Data(clean)
357357
let dataTainted = Data(tainted)
358358
var stale = true
359-
sink(arg: URL(dataRepresentation: dataTainted, relativeTo: urlClean)!) // $ MISSING: tainted=210
360-
sink(arg: URL(dataRepresentation: dataClean, relativeTo: urlTainted)!) // $ MISSING: tainted=210
361-
sink(arg: try! URL(resolvingBookmarkData: dataTainted, bookmarkDataIsStale: &stale)) // $ MISSING: tainted=210
362-
sink(arg: try! URL(resolvingBookmarkData: dataClean, relativeTo: urlTainted, bookmarkDataIsStale: &stale)) // $ MISSING: tainted=210
363-
364-
sink(string: urlTainted.formatted()) // $ MISSING: tainted=210
365-
sink(string: urlTainted.fragment()!) // $ MISSING: tainted=210
366-
sink(string: urlTainted.host()!) // $ MISSING: tainted=210
367-
sink(string: urlTainted.password()!) // $ MISSING: tainted=210
368-
sink(string: urlTainted.path()) // $ MISSING: tainted=210
369-
sink(string: urlTainted.query()!) // $ MISSING: tainted=210
370-
sink(string: urlTainted.user()!) // $ MISSING: tainted=210
359+
sink(arg: URL(dataRepresentation: dataTainted, relativeTo: urlClean)!) // $ tainted=210
360+
sink(arg: URL(dataRepresentation: dataClean, relativeTo: urlTainted)!) // $ tainted=210
361+
sink(arg: try! URL(resolvingBookmarkData: dataTainted, bookmarkDataIsStale: &stale)) // $ tainted=210
362+
sink(arg: try! URL(resolvingBookmarkData: dataClean, relativeTo: urlTainted, bookmarkDataIsStale: &stale)) // $ tainted=210
363+
364+
sink(string: urlTainted.formatted()) // $ tainted=210
365+
sink(string: urlTainted.fragment()!) // $ tainted=210
366+
sink(string: urlTainted.host()!) // $ tainted=210
367+
sink(string: urlTainted.password()!) // $ tainted=210
368+
sink(string: urlTainted.path()) // $ tainted=210
369+
sink(string: urlTainted.query()!) // $ tainted=210
370+
sink(string: urlTainted.user()!) // $ tainted=210
371371

372372
var url1 = URL(string: clean)!
373373
if let values = try? urlClean.resourceValues(forKeys: []) {
@@ -377,29 +377,29 @@ func taintThroughURL() {
377377
if let values = try? urlTainted.resourceValues(forKeys: []) {
378378
try! url1.setResourceValues(values)
379379
}
380-
sink(arg: url1) // $ MISSING: tainted=210
380+
sink(arg: url1) // $ tainted=210
381381

382382
var url2 = URL(string: clean)!
383-
url2.setTemporaryResourceValue(source() as Sendable, forKey: URL.URLResourceKey(""))
384-
sink(arg: url2) // $ MISSING: tainted=210
383+
url2.setTemporaryResourceValue(source(), forKey: URL.URLResourceKey(""))
384+
sink(arg: url2) // $ tainted=383
385385

386386
var url3 = URL(string: clean)!
387387
url3.appendPathComponent(clean)
388388
sink(arg: url3)
389389
url3.appendPathComponent(tainted)
390-
sink(arg: url3) // $ MISSING: tainted=210
390+
sink(arg: url3) // $ tainted=210
391391

392392
var url4 = URL(string: clean)!
393393
url4.appendPathComponent(tainted, isDirectory: false)
394-
sink(arg: url4) // $ MISSING: tainted=210
394+
sink(arg: url4) // $ tainted=210
395395

396396
var url5 = URL(string: clean)!
397397
url5.appendPathExtension(tainted)
398-
sink(arg: url5) // $ MISSING: tainted=210
398+
sink(arg: url5) // $ tainted=210
399399

400400
var url6 = URL(string: clean)!
401401
url6.append(component: tainted)
402-
sink(arg: url6) // $ MISSING: tainted=210
402+
sink(arg: url6) // $ tainted=210
403403

404404
var url7 = URL(string: clean)!
405405
url7.append(components: tainted)
@@ -411,19 +411,19 @@ func taintThroughURL() {
411411

412412
var url9 = URL(string: clean)!
413413
url9.append(path: tainted)
414-
sink(arg: url9) // $ MISSING: tainted=210
414+
sink(arg: url9) // $ tainted=210
415415

416416
var url10 = URL(string: clean)!
417417
url10.append(queryItems: [source() as! URLQueryItem])
418418
sink(arg: url10) // $ MISSING: tainted=210
419419

420-
sink(data: try! urlTainted.bookmarkData()) // $ MISSING: tainted=210
421-
sink(data: try! URL.bookmarkData(withContentsOf: urlTainted)) // $ MISSING: tainted=210
422-
sink(any: URL.resourceValues(forKeys: [], fromBookmarkData: dataTainted)!) // $ MISSING: tainted=210
420+
sink(data: try! urlTainted.bookmarkData()) // $ tainted=210
421+
sink(data: try! URL.bookmarkData(withContentsOf: urlTainted)) // $ tainted=210
422+
sink(any: URL.resourceValues(forKeys: [], fromBookmarkData: dataTainted)!) // $ tainted=210
423423

424424
sink(arg: URL.homeDirectory) // (static var, not tainted)
425425
sink(arg: URL.homeDirectory(forUser: clean)!)
426-
sink(arg: URL.homeDirectory(forUser: tainted)!) // $ MISSING: tainted=210
426+
sink(arg: URL.homeDirectory(forUser: tainted)!) // $ tainted=210
427427
}
428428

429429
func taintThroughUrlRequest() {
@@ -481,9 +481,9 @@ func taintThroughUrlResource() {
481481
let tainted = source() as! URLResource
482482

483483
sink(string: clean.name)
484-
sink(string: tainted.name) // $ MISSING: tainted=481
484+
sink(string: tainted.name) // $ tainted=481
485485
sink(string: clean.subdirectory!)
486-
sink(string: tainted.subdirectory!) // $ MISSING: tainted=481
486+
sink(string: tainted.subdirectory!) // $ tainted=481
487487
}
488488

489489
func taintUrlAsync() async throws {

0 commit comments

Comments
 (0)