Skip to content

Commit fddda03

Browse files
committed
Swift: Add PotentialRegexEval / doesEvaluate mechanism.
1 parent fa10dbe commit fddda03

File tree

2 files changed

+56
-36
lines changed

2 files changed

+56
-36
lines changed

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

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -299,35 +299,21 @@ class NSStringRegexAdditionalFlowStep extends RegexAdditionalFlowStep {
299299
* Regex("(a|b).*").firstMatch(in: myString)
300300
* ```
301301
*/
302-
abstract class RegexEval extends CallExpr {
303-
/**
304-
* Gets the input to this call that is the regular expression being evaluated.
305-
* This may be a regular expression object or a string literal.
306-
*/
307-
abstract DataFlow::Node getRegexInput();
308-
309-
/**
310-
* Gets the input to this call that is the string the regular expression is evaluated on.
311-
*/
312-
abstract DataFlow::Node getStringInput();
313-
314-
/**
315-
* Gets a dataflow node for the options input that might contain parse mode
316-
* flags (if any).
317-
*/
318-
DataFlow::Node getAnOptionsInput() { none() }
302+
class RegexEval extends CallExpr instanceof PotentialRegexEval {
303+
RegexEval() { this.(PotentialRegexEval).doesEvaluate() }
319304

320305
/**
321306
* Gets a regular expression value that is evaluated here (if any can be identified).
322307
*/
323308
RegExp getARegex() {
324309
// string literal used directly as a regex
325-
DataFlow::exprNode(result).(ParsedStringRegex).getAParse() = this.getRegexInput()
310+
DataFlow::exprNode(result).(ParsedStringRegex).getAParse() =
311+
this.(PotentialRegexEval).getRegexInput()
326312
or
327313
// string literal -> regex object -> use
328314
exists(RegexCreation regexCreation |
329315
DataFlow::exprNode(result).(ParsedStringRegex).getAParse() = regexCreation.getStringInput() and
330-
RegexUseFlow::flow(regexCreation, this.getRegexInput())
316+
RegexUseFlow::flow(regexCreation, this.(PotentialRegexEval).getRegexInput())
331317
)
332318
}
333319

@@ -341,17 +327,50 @@ abstract class RegexEval extends CallExpr {
341327
any(RegexAdditionalFlowStep s).setsParseMode(setNode, result, true) and
342328
// reaches this eval
343329
(
344-
RegexParseModeFlow::flow(setNode, this.getRegexInput()) or
345-
RegexParseModeFlow::flow(setNode, this.getAnOptionsInput())
330+
RegexParseModeFlow::flow(setNode, this.(PotentialRegexEval).getRegexInput()) or
331+
RegexParseModeFlow::flow(setNode, this.(PotentialRegexEval).getAnOptionsInput())
346332
)
347333
)
348334
}
349335
}
350336

337+
/**
338+
* A call that may evaluate a regular expression. Extend this abstract class to
339+
* add new regular expression evaluation models.
340+
*/
341+
abstract class PotentialRegexEval extends CallExpr {
342+
/**
343+
* Gets the input to this call that is the regular expression being evaluated.
344+
* This may be a regular expression object or a string literal.
345+
*/
346+
abstract DataFlow::Node getRegexInput();
347+
348+
/**
349+
* Gets the input to this call that is the string the regular expression is evaluated on.
350+
*/
351+
abstract DataFlow::Node getStringInput();
352+
353+
/**
354+
* Gets a dataflow node for the options input that might contain parse mode
355+
* flags (if any).
356+
*/
357+
DataFlow::Node getAnOptionsInput() { none() }
358+
359+
/**
360+
* Holds if this is an actual regular expression evalaution. If this does not
361+
* hold, the potential regular expression evaluation should be discarded.
362+
*
363+
* This mechanism exists so that we have something to track flow of options
364+
* into to (for example an `NSString.CompareOptions.regularExpression` option)
365+
* before deciding whether a regular expression is actually evaluated.
366+
*/
367+
predicate doesEvaluate() { any() }
368+
}
369+
351370
/**
352371
* A call to a function that always evaluates a regular expression.
353372
*/
354-
private class AlwaysRegexEval extends RegexEval {
373+
private class AlwaysRegexEval extends PotentialRegexEval {
355374
DataFlow::Node regexInput;
356375
DataFlow::Node stringInput;
357376

@@ -409,7 +428,7 @@ private class AlwaysRegexEval extends RegexEval {
409428
* A call to a function that sometimes evaluates a regular expression, if
410429
* `NSString.CompareOptions.regularExpression` is set as an `options` argument.
411430
*/
412-
private class NSStringCompareOptionsMaybeRegexEval extends RegexEval {
431+
private class NSStringCompareOptionsMaybeRegexEval extends PotentialRegexEval {
413432
DataFlow::Node regexInput;
414433
DataFlow::Node stringInput;
415434
DataFlow::Node optionsInput;
@@ -434,20 +453,21 @@ private class NSStringCompareOptionsMaybeRegexEval extends RegexEval {
434453
optionsInput.asExpr() = this.getArgumentWithLabel("options").getExpr()
435454
}
436455

437-
override DataFlow::Node getRegexInput() {
456+
override DataFlow::Node getRegexInput() { result = regexInput }
457+
458+
override DataFlow::Node getStringInput() { result = stringInput }
459+
460+
override DataFlow::Node getAnOptionsInput() { result = optionsInput }
461+
462+
override predicate doesEvaluate() {
438463
// check there is flow from a `NSString.CompareOptions.regularExpression` value to an `options` argument;
439-
// if it isn't, the input won't be interpretted as a regular expression and we should discard it.
464+
// if it isn't, the input won't be interpretted as a regular expression.
440465
exists(MemberRefExpr sourceValue |
441466
sourceValue
442467
.getMember()
443468
.(FieldDecl)
444469
.hasQualifiedName("NSString.CompareOptions", "regularExpression") and
445470
RegexEnableFlagFlow::flow(DataFlow::exprNode(sourceValue), optionsInput)
446-
) and
447-
result = regexInput
471+
)
448472
}
449-
450-
override DataFlow::Node getStringInput() { result = stringInput }
451-
452-
override DataFlow::Node getAnOptionsInput() { result = optionsInput }
453473
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ private module StringLiteralUseConfig implements DataFlow::ConfigSig {
2020

2121
predicate isSink(DataFlow::Node node) {
2222
// evaluated directly as a regular expression
23-
node = any(RegexEval eval).getRegexInput()
23+
node = any(PotentialRegexEval eval).getRegexInput()
2424
or
2525
// used to create a regular expression object
2626
node = any(RegexCreation regexCreation).getStringInput()
@@ -41,7 +41,7 @@ private module RegexUseConfig implements DataFlow::ConfigSig {
4141

4242
predicate isSink(DataFlow::Node node) {
4343
// evaluation of the regex
44-
node = any(RegexEval eval).getRegexInput()
44+
node = any(PotentialRegexEval eval).getRegexInput()
4545
}
4646

4747
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
@@ -67,8 +67,8 @@ private module RegexParseModeConfig implements DataFlow::StateConfigSig {
6767
predicate isSink(DataFlow::Node node, FlowState flowstate) {
6868
// evaluation of a regex
6969
(
70-
node = any(RegexEval eval).getRegexInput() or
71-
node = any(RegexEval eval).getAnOptionsInput()
70+
node = any(PotentialRegexEval eval).getRegexInput() or
71+
node = any(PotentialRegexEval eval).getAnOptionsInput()
7272
) and
7373
exists(flowstate)
7474
}
@@ -122,7 +122,7 @@ private module RegexEnableFlagConfig implements DataFlow::ConfigSig {
122122

123123
predicate isSink(DataFlow::Node node) {
124124
// use in a regex eval `options` argument
125-
any(RegexEval eval).getAnOptionsInput() = node
125+
any(PotentialRegexEval eval).getAnOptionsInput() = node
126126
}
127127

128128
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {

0 commit comments

Comments
 (0)