Skip to content

Commit a052a4e

Browse files
committed
Swift: Update to a proper data flow config so we can add implicit reads from arrays at the sink.
1 parent cdc0d1f commit a052a4e

File tree

4 files changed

+84
-15
lines changed

4 files changed

+84
-15
lines changed

swift/ql/lib/codeql/swift/regex/Regex.qll

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,12 @@ abstract class RegexEval extends CallExpr {
280280
*/
281281
abstract DataFlow::Node getStringInput();
282282

283+
/**
284+
* Gets a dataflow node for the options input that might contain parse mode
285+
* flags (if any).
286+
*/
287+
DataFlow::Node getAnOptionsInput() { none() }
288+
283289
/**
284290
* Gets a regular expression value that is evaluated here (if any can be identified).
285291
*/
@@ -367,14 +373,14 @@ private class AlwaysRegexEval extends RegexEval {
367373

368374
/**
369375
* A call to a function that sometimes evaluates a regular expression, if
370-
* `options: .regularExpression` is set as an argument.
376+
* `NSString.CompareOptions.regularExpression` is set as an `options` argument.
371377
*/
372-
private class SometimesRegexEval extends RegexEval {
378+
private class NSStringCompareOptionsMaybeRegexEval extends RegexEval {
373379
DataFlow::Node regexInput;
374380
DataFlow::Node stringInput;
375381
DataFlow::Node optionsInput;
376382

377-
SometimesRegexEval() {
383+
NSStringCompareOptionsMaybeRegexEval() {
378384
(
379385
this.getStaticTarget()
380386
.(Method)
@@ -391,18 +397,23 @@ private class SometimesRegexEval extends RegexEval {
391397
) and
392398
regexInput.asExpr() = this.getArgument(0).getExpr() and
393399
stringInput.asExpr() = this.getQualifier() and
394-
optionsInput.asExpr() = this.getArgumentWithLabel("options").getExpr() and
395-
// flow from `.regularExpression` to `options` argument
400+
optionsInput.asExpr() = this.getArgumentWithLabel("options").getExpr()
401+
}
402+
403+
override DataFlow::Node getRegexInput() {
404+
// check there is flow from a `NSString.CompareOptions.regularExpression` value to an `options` argument;
405+
// if it isn't, the input won't be interpretted as a regular expression and we should discard it.
396406
exists(MemberRefExpr sourceValue |
397407
sourceValue
398408
.getMember()
399409
.(FieldDecl)
400410
.hasQualifiedName("NSString.CompareOptions", "regularExpression") and
401-
DataFlow::localFlow(DataFlow::exprNode(sourceValue), optionsInput)
402-
)
411+
RegexEnableFlagFlow::flow(DataFlow::exprNode(sourceValue), optionsInput)
412+
) and
413+
result = regexInput
403414
}
404415

405-
override DataFlow::Node getRegexInput() { result = regexInput }
406-
407416
override DataFlow::Node getStringInput() { result = stringInput }
417+
418+
override DataFlow::Node getAnOptionsInput() { result = optionsInput }
408419
}

swift/ql/lib/codeql/swift/regex/internal/RegexTracking.qll

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,31 @@ private module RegexParseModeConfig implements DataFlow::StateConfigSig {
102102
}
103103

104104
module RegexParseModeFlow = DataFlow::GlobalWithState<RegexParseModeConfig>;
105+
106+
/**
107+
* A data flow configuration for tracking `NSString.CompareOptions.regularExpression`
108+
* values from where they are created to the point of use.
109+
*/
110+
private module RegexEnableFlagConfig implements DataFlow::ConfigSig {
111+
predicate isSource(DataFlow::Node node) {
112+
// creation of a `NSString.CompareOptions.regularExpression` value
113+
node.asExpr()
114+
.(MemberRefExpr)
115+
.getMember()
116+
.(FieldDecl)
117+
.hasQualifiedName("NSString.CompareOptions", "regularExpression")
118+
}
119+
120+
predicate isSink(DataFlow::Node node) {
121+
// use in a regex eval `options` argument
122+
any(RegexEval eval).getAnOptionsInput() = node
123+
}
124+
125+
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
126+
// flow out from collection content at the sink.
127+
isSink(node) and
128+
c.getAReadContent() instanceof DataFlow::Content::CollectionContent
129+
}
130+
}
131+
132+
module RegexEnableFlagFlow = DataFlow::Global<RegexEnableFlagConfig>;

swift/ql/test/library-tests/regex/parse.expected

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6349,11 +6349,21 @@ regex.swift:
63496349
# 150| [RegExpStar] .*
63506350
#-----| 0 -> [RegExpDot] .
63516351

6352+
# 151| [RegExpDot] .
6353+
6354+
# 151| [RegExpStar] .*
6355+
#-----| 0 -> [RegExpDot] .
6356+
63526357
# 152| [RegExpDot] .
63536358

63546359
# 152| [RegExpStar] .*
63556360
#-----| 0 -> [RegExpDot] .
63566361

6362+
# 153| [RegExpDot] .
6363+
6364+
# 153| [RegExpStar] .*
6365+
#-----| 0 -> [RegExpDot] .
6366+
63576367
# 155| [RegExpDot] .
63586368

63596369
# 155| [RegExpStar] .*
@@ -6613,3 +6623,23 @@ regex.swift:
66136623

66146624
# 246| [RegExpStar] .*
66156625
#-----| 0 -> [RegExpDot] .
6626+
6627+
# 251| [RegExpDot] .
6628+
6629+
# 251| [RegExpStar] .*
6630+
#-----| 0 -> [RegExpDot] .
6631+
6632+
# 252| [RegExpDot] .
6633+
6634+
# 252| [RegExpStar] .*
6635+
#-----| 0 -> [RegExpDot] .
6636+
6637+
# 253| [RegExpDot] .
6638+
6639+
# 253| [RegExpStar] .*
6640+
#-----| 0 -> [RegExpDot] .
6641+
6642+
# 254| [RegExpDot] .
6643+
6644+
# 254| [RegExpStar] .*
6645+
#-----| 0 -> [RegExpDot] .

swift/ql/test/library-tests/regex/regex.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,9 @@ func myRegexpMethodsTests(b: Bool, str_unknown: String) throws {
148148
let regexOptions = NSString.CompareOptions.regularExpression
149149
let regexOptions2 : NSString.CompareOptions = [.regularExpression, .caseInsensitive]
150150
_ = inputNS.range(of: ".*", options: .regularExpression) // $ regex=.* input=inputNS
151-
_ = inputNS.range(of: ".*", options: [.regularExpression]) // $ MISSING: regex=.* input=inputNS
151+
_ = inputNS.range(of: ".*", options: [.regularExpression]) // $ regex=.* input=inputNS
152152
_ = inputNS.range(of: ".*", options: regexOptions) // $ regex=.* input=inputNS
153-
_ = inputNS.range(of: ".*", options: regexOptions2) // $ MISSING: regex=.* input=inputNS
153+
_ = inputNS.range(of: ".*", options: regexOptions2) // $ regex=.* input=inputNS
154154
_ = inputNS.range(of: ".*", options: .literal) // (not a regular expression)
155155
_ = inputNS.replacingOccurrences(of: ".*", with: "", options: .regularExpression, range: NSMakeRange(0, inputNS.length)) // $ regex=.* input=inputNS
156156
_ = inputNS.replacingOccurrences(of: ".*", with: "", options: .literal, range: NSMakeRange(0, inputNS.length)) // (not a regular expression)
@@ -248,8 +248,8 @@ func myRegexpMethodsTests(b: Bool, str_unknown: String) throws {
248248
// parse modes set through other methods
249249

250250
let myOptions2 : NSString.CompareOptions = [.regularExpression, .caseInsensitive]
251-
_ = input.replacingOccurrences(of: ".*", with: "", options: [.regularExpression, .caseInsensitive]) // $ MISSING: regex=.* input=input modes=IGNORECASE
252-
_ = input.replacingOccurrences(of: ".*", with: "", options: myOptions2) // $ MISSING: regex=.* input=input modes=IGNORECASE
253-
_ = NSString(string: "abc").replacingOccurrences(of: ".*", with: "", options: [.regularExpression, .caseInsensitive], range: NSMakeRange(0, inputNS.length)) // $ MISSING: regex=.* input="call to NSString.init(string:)" modes=IGNORECASE
254-
_ = NSString(string: "abc").replacingOccurrences(of: ".*", with: "", options: myOptions2, range: NSMakeRange(0, inputNS.length)) // $ MISSING: regex=.* input="call to NSString.init(string:)" modes=IGNORECASE
251+
_ = input.replacingOccurrences(of: ".*", with: "", options: [.regularExpression, .caseInsensitive]) // $ regex=.* input=input MISSING: modes=IGNORECASE
252+
_ = input.replacingOccurrences(of: ".*", with: "", options: myOptions2) // $ regex=.* input=input MISSING: modes=IGNORECASE
253+
_ = NSString(string: "abc").replacingOccurrences(of: ".*", with: "", options: [.regularExpression, .caseInsensitive], range: NSMakeRange(0, inputNS.length)) // $ regex=.* input="call to NSString.init(string:)" MISSING: modes=IGNORECASE
254+
_ = NSString(string: "abc").replacingOccurrences(of: ".*", with: "", options: myOptions2, range: NSMakeRange(0, inputNS.length)) // $ regex=.* input="call to NSString.init(string:)" MISSING: modes=IGNORECASE
255255
}

0 commit comments

Comments
 (0)