Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e0bdc77

Browse files
committed
JS: Make ReDoS check string-based regexes
1 parent 97e5da1 commit e0bdc77

4 files changed

Lines changed: 33 additions & 10 deletions

File tree

javascript/ql/src/Performance/ReDoS.ql

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,19 @@ import javascript
8484
* whose root node is not a disjunction.
8585
*/
8686
class RegExpRoot extends @regexpterm {
87+
RegExpParent parent;
88+
8789
// RegExpTerm is abstract, so do not extend it.
8890
RegExpRoot() {
89-
exists(RegExpLiteral literal, RegExpAlt alt | alt.getParent() = literal |
90-
this = alt.getAChild()
91+
exists(RegExpAlt alt |
92+
alt.isRootTerm() and
93+
this = alt.getAChild() and
94+
parent = alt.getParent()
9195
)
9296
or
93-
exists(RegExpLiteral literal |
94-
not exists(RegExpAlt alt | alt.getParent() = literal) and
95-
this.(RegExpTerm).getParent() = literal
96-
)
97+
this.(RegExpTerm).isRootTerm() and
98+
not this instanceof RegExpAlt and
99+
parent = this.(RegExpTerm).getParent()
97100
}
98101

99102
/**
@@ -103,7 +106,13 @@ class RegExpRoot extends @regexpterm {
103106
// there is at least one repetition
104107
exists(RegExpRepetition rep | getRoot(rep) = this) and
105108
// there are no lookbehinds
106-
not exists(RegExpLookbehind lbh | getRoot(lbh) = this)
109+
not exists(RegExpLookbehind lbh | getRoot(lbh) = this) and
110+
// is actually used as a RegExp
111+
(
112+
parent instanceof RegExpLiteral
113+
or
114+
parent.(StringLiteral).flow() instanceof RegExpPatternSource
115+
)
107116
}
108117

109118
string toString() { result = this.(RegExpTerm).toString() }
@@ -352,10 +361,10 @@ predicate delta(State q1, EdgeLabel lbl, State q2) {
352361
)
353362
)
354363
or
355-
exists(RegExpDot dot, RegExpLiteral rel |
356-
q1 = before(dot) and q2 = after(dot) and rel = dot.getLiteral()
364+
exists(RegExpDot dot |
365+
q1 = before(dot) and q2 = after(dot)
357366
|
358-
if rel.isDotAll() then lbl = Any() else lbl = Dot()
367+
if dot.getLiteral().isDotAll() then lbl = Any() else lbl = Dot()
359368
)
360369
or
361370
exists(RegExpCharacterClass cc |

javascript/ql/src/semmle/javascript/Regexp.qll

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,13 @@ abstract class RegExpTerm extends Locatable, @regexpterm {
105105
* it has an enclosing lookbehind assertions.
106106
*/
107107
predicate isInBackwardMatchingContext() { this = any(RegExpLookbehind lbh).getAChild+() }
108+
109+
/**
110+
* Holds if this is the root term of a regular expression.
111+
*/
112+
predicate isRootTerm() {
113+
not getParent() instanceof RegExpTerm
114+
}
108115
}
109116

110117
/**

javascript/ql/test/query-tests/Performance/ReDoS/ReDoS.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,4 @@
4949
| tst.js:74:14:74:21 | (b\|a?b)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'b'. |
5050
| tst.js:77:14:77:21 | (a\|aa?)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. |
5151
| tst.js:83:14:83:20 | (.\|\\n)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\n'. |
52+
| tst.js:89:25:89:32 | (a\|aa?)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. |

javascript/ql/test/query-tests/Performance/ReDoS/tst.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,9 @@ var bad16 = /(.|\n)*!/s;
8484

8585
// GOOD
8686
var good8 = /([\w.]+)*/;
87+
88+
// NOT GOOD
89+
var bad17 = new RegExp('(a|aa?)*b');
90+
91+
// GOOD - not used as regexp
92+
var good9 = '(a|aa?)*b';

0 commit comments

Comments
 (0)