Skip to content

Commit 3d23575

Browse files
authored
Merge pull request #292 from github/regexp_slash_az
Don't parse `\A` and `\Z` as `RegExpConstant`
2 parents 1fd91ab + ebf23d0 commit 3d23575

File tree

4 files changed

+24
-17
lines changed

4 files changed

+24
-17
lines changed

ql/lib/codeql/ruby/regexp/ParseRegExp.qll

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -388,10 +388,17 @@ class RegExp extends AST::RegExpLiteral {
388388

389389
predicate specialCharacter(int start, int end, string char) {
390390
this.character(start, end) and
391-
end = start + 1 and
392-
char = this.getChar(start) and
393-
(char = "$" or char = "^" or char = ".") and
394-
not this.inCharSet(start)
391+
not this.inCharSet(start) and
392+
(
393+
end = start + 1 and
394+
char = this.getChar(start) and
395+
(char = "$" or char = "^" or char = ".")
396+
or
397+
end = start + 2 and
398+
this.escapingChar(start) and
399+
char = this.getText().substring(start, end) and
400+
char = ["\\A", "\\Z", "\\z"]
401+
)
395402
}
396403

397404
/** Whether the text in the range `start,end` is a group */
@@ -808,10 +815,8 @@ class RegExp extends AST::RegExpLiteral {
808815
or
809816
this.qualifiedItem(x, start, true, _)
810817
or
811-
this.specialCharacter(x, start, "^")
812-
or
813-
// \A matches the start of the string
814-
x = start - 2 and this.escapingChar(x) and this.getChar(x + 1) = "A"
818+
// ^ and \A match the start of the string
819+
this.specialCharacter(x, start, ["^", "\\A"])
815820
)
816821
or
817822
exists(int y | this.firstPart(start, y) |
@@ -836,9 +841,8 @@ class RegExp extends AST::RegExpLiteral {
836841
or
837842
this.qualifiedItem(end, y, true, _)
838843
or
839-
this.specialCharacter(end, y, "$")
840-
or
841-
y = end + 2 and this.escapingChar(end) and this.getChar(end + 1) = "Z"
844+
// $, \Z, and \z match the end of the string.
845+
this.specialCharacter(end, y, ["$", "\\Z", "\\z"])
842846
)
843847
or
844848
exists(int x |

ql/lib/codeql/ruby/regexp/RegExpTreeView.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -594,13 +594,13 @@ class RegExpDot extends RegExpSpecialChar {
594594
}
595595

596596
class RegExpDollar extends RegExpSpecialChar {
597-
RegExpDollar() { this.getChar() = "$" }
597+
RegExpDollar() { this.getChar() = ["$", "\\Z", "\\z"] }
598598

599599
override string getAPrimaryQlClass() { result = "RegExpDollar" }
600600
}
601601

602602
class RegExpCaret extends RegExpSpecialChar {
603-
RegExpCaret() { this.getChar() = "^" }
603+
RegExpCaret() { this.getChar() = ["^", "\\A"] }
604604

605605
override string getAPrimaryQlClass() { result = "RegExpCaret" }
606606
}

ql/test/library-tests/regexp/parse.expected

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,10 @@ regexp.rb:
146146

147147
# 19| [RegExpConstant, RegExpNormalChar] _
148148

149-
# 20| [RegExpConstant, RegExpEscape] \A
149+
# 20| [RegExpCaret] \A
150150

151151
# 20| [RegExpSequence] \A[+-]?\d+
152-
#-----| 0 -> [RegExpConstant, RegExpEscape] \A
152+
#-----| 0 -> [RegExpCaret] \A
153153
#-----| 1 -> [RegExpOpt] [+-]?
154154
#-----| 2 -> [RegExpPlus] \d+
155155

ql/test/query-tests/security/cwe-1333-polynomial-redos/PolynomialReDoS.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ def some_request_handler
3333

3434
# GOOD - guarded by a string length check
3535
if name.length < 1024
36-
name.gsub regex, ''
36+
name.gsub regex, ''
3737
end
38+
39+
# GOOD - regex does not suffer from polynomial backtracking (regression test)
40+
params[:foo] =~ /\A[bc].*\Z/
3841
end
39-
end
42+
end

0 commit comments

Comments
 (0)