Skip to content

Commit 7748a45

Browse files
committed
Swift: Initial implementation of regex mode flags.
1 parent d88f557 commit 7748a45

File tree

3 files changed

+69
-5
lines changed

3 files changed

+69
-5
lines changed

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

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@ abstract class RegExp extends Expr {
1616
/**
1717
* Holds if this `RegExp` has the `s` flag for multi-line matching.
1818
*/
19-
predicate isDotAll() { none() }
19+
predicate isDotAll() { this.getAMode() = "DOTALL" }
2020

2121
/**
2222
* Holds if this `RegExp` has the `i` flag for case-insensitive matching.
2323
*/
24-
predicate isIgnoreCase() { none() }
24+
predicate isIgnoreCase() { this.getAMode() = "IGNORECASE" }
2525

2626
/**
2727
* Gets the flags for this `RegExp`, or the empty string if it has no flags.
2828
*/
29-
string getFlags() { result = "" }
29+
string getFlags() { result = concat(string mode | mode = this.getAMode() | mode, " | ")}
3030

3131
/**
3232
* Helper predicate for `charSetStart(int start, int end)`.
@@ -274,6 +274,61 @@ abstract class RegExp extends Expr {
274274

275275
private predicate isGroupStart(int i) { this.nonEscapedCharAt(i) = "(" and not this.inCharSet(i) }
276276

277+
/**
278+
* Holds if `start` and `end` are the range of the mode prefix substring (if any) of this
279+
* regular expression, and `c` is a mode prefix character specified in it. For example
280+
* in the following regular expression, `start` is `0`, `end` is `3` and `c` is `i`.
281+
* ```
282+
* (?i)one|two
283+
* ```
284+
*/
285+
private predicate flagGroupStart(int start, int end, string c) {
286+
// TODO: I believe this fails with multiple mode specifiers such as (?is) at the moment.
287+
this.isGroupStart(start) and
288+
this.getChar(start + 1) = "?" and
289+
end = start + 3 and
290+
c = this.getChar(start + 2) and
291+
c in ["i", "m", "s", "u", "x", "U"]
292+
}
293+
294+
/**
295+
* Gets a mode of this regular expression string if it is defined by a mode prefix.
296+
*/
297+
string getModeFromPrefix() {
298+
exists(string c | this.flagGroupStart(_, _, c) |
299+
// TODO: are these correct in Swift?
300+
c = "i" and result = "IGNORECASE"
301+
or
302+
c = "m" and result = "MULTILINE"
303+
or
304+
c = "s" and result = "DOTALL"
305+
or
306+
c = "u" and result = "UNICODE"
307+
or
308+
c = "x" and result = "VERBOSE"
309+
or
310+
c = "U" and result = "UNICODECLASS"
311+
)
312+
}
313+
314+
/**
315+
* Gets a mode (if any) of this regular expression. Can be any of:
316+
* DEBUG
317+
* IGNORECASE
318+
* MULTILINE
319+
* DOTALL
320+
* UNICODE
321+
* VERBOSE
322+
* UNICODECLASS
323+
*/
324+
string getAMode() {
325+
/* TODO
326+
result != "None" and
327+
usedAsRegex(this, result, _)
328+
or*/
329+
result = this.getModeFromPrefix()
330+
}
331+
277332
/**
278333
* Holds if the `i`th character could not be parsed.
279334
*/

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ func myRegexpVariantsTests(myUrl: URL) throws {
143143

144144
// BAD
145145
// attack string: "\n".repeat(100) + "."
146-
_ = try Regex(#"(?s)(.|\n)*!"#).firstMatch(in: tainted) // $ hasParseFailure MISSING: redos-vulnerable=
146+
_ = try Regex(#"(?s)(.|\n)*!"#).firstMatch(in: tainted) // $ hasParseFailure modes=DOTALL MISSING: redos-vulnerable=
147147

148148
// GOOD
149149
_ = try Regex(#"([\w.]+)*"#).firstMatch(in: tainted)

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ bindingset[s]
99
string quote(string s) { if s.matches("% %") then result = "\"" + s + "\"" else result = s }
1010

1111
module RegexTest implements TestSig {
12-
string getARelevantTag() { result = ["regex", "input", "redos-vulnerable", "hasParseFailure"] }
12+
string getARelevantTag() { result = ["regex", "input", "redos-vulnerable", "hasParseFailure", "modes"] }
1313

1414
predicate hasActualResult(Location location, string element, string tag, string value) {
1515
exists(TreeView::RegExpTerm t |
@@ -28,6 +28,15 @@ module RegexTest implements TestSig {
2828
tag = "hasParseFailure" and
2929
value = ""
3030
)
31+
or
32+
exists(RegexEval eval, RegExp regex |
33+
eval.getARegex() = regex and
34+
location = eval.getLocation() and
35+
element = eval.toString() and
36+
tag = "modes" and
37+
value = regex.getFlags() and
38+
value != ""
39+
)
3140
}
3241

3342
predicate hasOptionalResult(Location location, string element, string tag, string value) {

0 commit comments

Comments
 (0)