Skip to content

Commit 8f115bf

Browse files
committed
Swift: Implement 'isUsedAsReplace'.
1 parent 435638a commit 8f115bf

File tree

5 files changed

+23
-31
lines changed

5 files changed

+23
-31
lines changed

shared/regex/codeql/regex/MissingRegExpAnchor.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ import HostnameRegexp as HostnameShared
1515
signature module MissingRegExpAnchorSig<
1616
RegexTreeViewSig TreeImpl, HostnameShared::HostnameRegexpSig<TreeImpl> Specific>
1717
{
18+
/**
19+
* Holds if this regular expression is used in a 'replacement' operation, such
20+
* as replacing all matches of the regular expression in the input string
21+
* with another string.
22+
*/
1823
predicate isUsedAsReplace(Specific::RegExpPatternSource pattern);
1924

2025
/** Gets a string representation of an end anchor from a regular expression. */

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,13 @@ abstract class RegexEval extends CallExpr {
330330
*/
331331
DataFlow::Node getAnOptionsInput() { none() }
332332

333+
/**
334+
* Holds if this regular expression evaluation is a 'replacement' operation,
335+
* such as replacing all matches of the regular expression in the input
336+
* string with another string.
337+
*/
338+
abstract predicate isUsedAsReplace();
339+
333340
/**
334341
* Gets a regular expression value that is evaluated here (if any can be identified).
335342
*/
@@ -416,6 +423,10 @@ private class AlwaysRegexEval extends RegexEval {
416423
override DataFlow::Node getRegexInputNode() { result = regexInput }
417424

418425
override DataFlow::Node getStringInputNode() { result = stringInput }
426+
427+
override predicate isUsedAsReplace() {
428+
this.getStaticTarget().getName().matches(["replac%", "stringByReplac%", "trim%"])
429+
}
419430
}
420431

421432
/**
@@ -508,4 +519,6 @@ private class NSStringCompareOptionsRegexEval extends RegexEval instanceof NSStr
508519
override DataFlow::Node getAnOptionsInput() {
509520
result = this.(NSStringCompareOptionsPotentialRegexEval).getAnOptionsInput()
510521
}
522+
523+
override predicate isUsedAsReplace() { this.getStaticTarget().getName().matches(["replac%"]) }
511524
}

swift/ql/src/queries/Security/CWE-020/MissingRegexAnchor.ql

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,35 +22,10 @@ private module Impl implements
2222
MissingRegExpAnchor::MissingRegExpAnchorSig<TreeImpl, HostnameRegex::Impl>
2323
{
2424
predicate isUsedAsReplace(RegexPatternSource pattern) {
25-
none()
26-
/* java // is used for capture or replace
27-
exists(DataFlow::MethodCallNode mcn, string name | name = mcn.getMethodName() |
28-
name = "exec" and
29-
mcn = pattern.getARegExpObject().getAMethodCall() and
30-
exists(mcn.getAPropertyRead())
31-
or
32-
exists(DataFlow::Node arg |
33-
arg = mcn.getArgument(0) and
34-
(
35-
pattern.getARegExpObject().flowsTo(arg) or
36-
pattern.getAParse() = arg
37-
)
38-
|
39-
name = "replace"
40-
or
41-
name = "match" and exists(mcn.getAPropertyRead())
42-
)
43-
)*/
44-
/* rb exists(DataFlow::CallNode mcn, DataFlow::Node arg, string name |
45-
name = mcn.getMethodName() and
46-
arg = mcn.getArgument(0)
47-
|
48-
(
49-
pattern.getAParse().(DataFlow::LocalSourceNode).flowsTo(arg) or
50-
pattern.getAParse() = arg
51-
) and
52-
name = ["sub", "sub!", "gsub", "gsub!"]
53-
)*/
25+
exists(RegexEval eval |
26+
eval.getARegex() = pattern.asExpr() and
27+
eval.isUsedAsReplace()
28+
)
5429
}
5530

5631
string getEndAnchorText() { result = "$" }

swift/ql/test/query-tests/Security/CWE-020/MissingRegexAnchor.expected

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
| SemiAnchoredRegex.swift:104:16:104:16 | ^mouse\|touch\|click\|contextmenu\|drop\|dragover\|dragend | Misleading operator precedence. The subexpression '^mouse' is anchored at the beginning, but the other parts of this regular expression are not |
3636
| SemiAnchoredRegex.swift:105:16:105:16 | ^xxx:\|yyy: | Misleading operator precedence. The subexpression '^xxx:' is anchored at the beginning, but the other parts of this regular expression are not |
3737
| SemiAnchoredRegex.swift:106:16:106:16 | _xxx\|_yyy\|_zzz$ | Misleading operator precedence. The subexpression '_zzz$' is anchored at the end, but the other parts of this regular expression are not |
38-
| SemiAnchoredRegex.swift:137:36:137:36 | ^a\|b/ | Misleading operator precedence. The subexpression '^a' is anchored at the beginning, but the other parts of this regular expression are not |
3938
| UnanchoredUrlRegex.swift:62:39:62:39 | https?://good.com | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
4039
| UnanchoredUrlRegex.swift:63:39:63:39 | https?://good.com | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. |
4140
| UnanchoredUrlRegex.swift:64:39:64:39 | ^https?://good.com | This hostname pattern may match any domain name, as it is missing a '$' or '/' at the end. |

swift/ql/test/query-tests/Security/CWE-020/SemiAnchoredRegex.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,5 +134,5 @@ func realWorld(input: String) throws {
134134

135135
func replaceTest(x: String) -> String {
136136
// OK - possibly replacing too much, but not obviously a problem
137-
return x.replacingOccurrences(of: #"^a|b/"#, with: "", options: .regularExpression) // [FALSE POSITIVE]
137+
return x.replacingOccurrences(of: #"^a|b/"#, with: "", options: .regularExpression)
138138
}

0 commit comments

Comments
 (0)