Skip to content

Commit 50dc382

Browse files
authored
Merge pull request #8589 from hvitved/regex/speedup-concretise
2 parents 3119885 + 46d69cf commit 50dc382

File tree

9 files changed

+270
-81
lines changed

9 files changed

+270
-81
lines changed

javascript/ql/lib/semmle/javascript/security/performance/ExponentialBackTracking.qll

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -279,17 +279,6 @@ private class Trace extends TTrace {
279279
}
280280
}
281281

282-
/**
283-
* Gets a string corresponding to the trace `t`.
284-
*/
285-
private string concretise(Trace t) {
286-
t = Nil() and result = ""
287-
or
288-
exists(InputSymbol s1, InputSymbol s2, Trace rest | t = Step(s1, s2, rest) |
289-
result = concretise(rest) + intersect(s1, s2)
290-
)
291-
}
292-
293282
/**
294283
* Holds if `r` is reachable from `(fork, fork)` under input `w`, and there is
295284
* a path from `r` back to `(fork, fork)` with `rem` steps.
@@ -321,14 +310,54 @@ private StatePair getAForkPair(State fork) {
321310
result = MkStatePair(epsilonPred*(fork), epsilonPred*(fork))
322311
}
323312

313+
private predicate hasSuffix(Trace suffix, Trace t, int i) {
314+
// Declaring `t` to be a `RelevantTrace` currently causes a redundant check in the
315+
// recursive case, so instead we check it explicitly here.
316+
t instanceof RelevantTrace and
317+
i = 0 and
318+
suffix = t
319+
or
320+
hasSuffix(Step(_, _, suffix), t, i - 1)
321+
}
322+
323+
pragma[noinline]
324+
private predicate hasTuple(InputSymbol s1, InputSymbol s2, Trace t, int i) {
325+
hasSuffix(Step(s1, s2, _), t, i)
326+
}
327+
328+
private class RelevantTrace extends Trace, Step {
329+
RelevantTrace() {
330+
exists(State fork, StatePair q |
331+
isReachableFromFork(fork, q, this, _) and
332+
q = getAForkPair(fork)
333+
)
334+
}
335+
336+
pragma[noinline]
337+
private string intersect(int i) {
338+
exists(InputSymbol s1, InputSymbol s2 |
339+
hasTuple(s1, s2, this, i) and
340+
result = intersect(s1, s2)
341+
)
342+
}
343+
344+
/** Gets a string corresponding to this trace. */
345+
// the pragma is needed for the case where `intersect(s1, s2)` has multiple values,
346+
// not for recursion
347+
language[monotonicAggregates]
348+
string concretise() {
349+
result = strictconcat(int i | hasTuple(_, _, this, i) | this.intersect(i) order by i desc)
350+
}
351+
}
352+
324353
/**
325354
* Holds if `fork` is a pumpable fork with word `w`.
326355
*/
327356
private predicate isPumpable(State fork, string w) {
328-
exists(StatePair q, Trace t |
357+
exists(StatePair q, RelevantTrace t |
329358
isReachableFromFork(fork, q, t, _) and
330359
q = getAForkPair(fork) and
331-
w = concretise(t)
360+
w = t.concretise()
332361
)
333362
}
334363

javascript/ql/lib/semmle/javascript/security/performance/ReDoSUtil.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ abstract class ReDoSConfiguration extends string {
3232
}
3333

3434
/**
35-
* Holds if repeating `pump' starting at `state` is a candidate for causing backtracking.
35+
* Holds if repeating `pump` starting at `state` is a candidate for causing backtracking.
3636
* No check whether a rejected suffix exists has been made.
3737
*/
3838
private predicate isReDoSCandidate(State state, string pump) {

javascript/ql/lib/semmle/javascript/security/performance/SuperlinearBackTracking.qll

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -254,17 +254,6 @@ class Trace extends TTrace {
254254
}
255255
}
256256

257-
/**
258-
* Gets a string corresponding to the trace `t`.
259-
*/
260-
string concretise(Trace t) {
261-
t = Nil() and result = ""
262-
or
263-
exists(InputSymbol s1, InputSymbol s2, InputSymbol s3, Trace rest | t = Step(s1, s2, s3, rest) |
264-
result = concretise(rest) + getAThreewayIntersect(s1, s2, s3)
265-
)
266-
}
267-
268257
/**
269258
* Holds if there exists a transition from `r` to `q` in the product automaton.
270259
* Notice that the arguments are flipped, and thus the direction is backwards.
@@ -332,6 +321,51 @@ StateTuple getAnEndTuple(State pivot, State succ) {
332321
result = MkStateTuple(pivot, succ, succ)
333322
}
334323

324+
private predicate hasSuffix(Trace suffix, Trace t, int i) {
325+
// Declaring `t` to be a `RelevantTrace` currently causes a redundant check in the
326+
// recursive case, so instead we check it explicitly here.
327+
t instanceof RelevantTrace and
328+
i = 0 and
329+
suffix = t
330+
or
331+
hasSuffix(Step(_, _, _, suffix), t, i - 1)
332+
}
333+
334+
pragma[noinline]
335+
private predicate hasTuple(InputSymbol s1, InputSymbol s2, InputSymbol s3, Trace t, int i) {
336+
hasSuffix(Step(s1, s2, s3, _), t, i)
337+
}
338+
339+
private class RelevantTrace extends Trace, Step {
340+
RelevantTrace() {
341+
exists(State pivot, State succ, StateTuple q |
342+
isReachableFromStartTuple(pivot, succ, q, this, _) and
343+
q = getAnEndTuple(pivot, succ)
344+
)
345+
}
346+
347+
pragma[noinline]
348+
private string getAThreewayIntersect(int i) {
349+
exists(InputSymbol s1, InputSymbol s2, InputSymbol s3 |
350+
hasTuple(s1, s2, s3, this, i) and
351+
result = getAThreewayIntersect(s1, s2, s3)
352+
)
353+
}
354+
355+
/** Gets a string corresponding to this trace. */
356+
// the pragma is needed for the case where `getAThreewayIntersect(s1, s2, s3)` has multiple values,
357+
// not for recursion
358+
language[monotonicAggregates]
359+
string concretise() {
360+
result =
361+
strictconcat(int i |
362+
hasTuple(_, _, _, this, i)
363+
|
364+
this.getAThreewayIntersect(i) order by i desc
365+
)
366+
}
367+
}
368+
335369
/**
336370
* Holds if matching repetitions of `pump` can:
337371
* 1) Transition from `pivot` back to `pivot`.
@@ -345,10 +379,10 @@ StateTuple getAnEndTuple(State pivot, State succ) {
345379
* available in the `hasReDoSResult` predicate.
346380
*/
347381
predicate isPumpable(State pivot, State succ, string pump) {
348-
exists(StateTuple q, Trace t |
382+
exists(StateTuple q, RelevantTrace t |
349383
isReachableFromStartTuple(pivot, succ, q, t, _) and
350384
q = getAnEndTuple(pivot, succ) and
351-
pump = concretise(t)
385+
pump = t.concretise()
352386
)
353387
}
354388

python/ql/lib/semmle/python/security/performance/ExponentialBackTracking.qll

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -279,17 +279,6 @@ private class Trace extends TTrace {
279279
}
280280
}
281281

282-
/**
283-
* Gets a string corresponding to the trace `t`.
284-
*/
285-
private string concretise(Trace t) {
286-
t = Nil() and result = ""
287-
or
288-
exists(InputSymbol s1, InputSymbol s2, Trace rest | t = Step(s1, s2, rest) |
289-
result = concretise(rest) + intersect(s1, s2)
290-
)
291-
}
292-
293282
/**
294283
* Holds if `r` is reachable from `(fork, fork)` under input `w`, and there is
295284
* a path from `r` back to `(fork, fork)` with `rem` steps.
@@ -321,14 +310,54 @@ private StatePair getAForkPair(State fork) {
321310
result = MkStatePair(epsilonPred*(fork), epsilonPred*(fork))
322311
}
323312

313+
private predicate hasSuffix(Trace suffix, Trace t, int i) {
314+
// Declaring `t` to be a `RelevantTrace` currently causes a redundant check in the
315+
// recursive case, so instead we check it explicitly here.
316+
t instanceof RelevantTrace and
317+
i = 0 and
318+
suffix = t
319+
or
320+
hasSuffix(Step(_, _, suffix), t, i - 1)
321+
}
322+
323+
pragma[noinline]
324+
private predicate hasTuple(InputSymbol s1, InputSymbol s2, Trace t, int i) {
325+
hasSuffix(Step(s1, s2, _), t, i)
326+
}
327+
328+
private class RelevantTrace extends Trace, Step {
329+
RelevantTrace() {
330+
exists(State fork, StatePair q |
331+
isReachableFromFork(fork, q, this, _) and
332+
q = getAForkPair(fork)
333+
)
334+
}
335+
336+
pragma[noinline]
337+
private string intersect(int i) {
338+
exists(InputSymbol s1, InputSymbol s2 |
339+
hasTuple(s1, s2, this, i) and
340+
result = intersect(s1, s2)
341+
)
342+
}
343+
344+
/** Gets a string corresponding to this trace. */
345+
// the pragma is needed for the case where `intersect(s1, s2)` has multiple values,
346+
// not for recursion
347+
language[monotonicAggregates]
348+
string concretise() {
349+
result = strictconcat(int i | hasTuple(_, _, this, i) | this.intersect(i) order by i desc)
350+
}
351+
}
352+
324353
/**
325354
* Holds if `fork` is a pumpable fork with word `w`.
326355
*/
327356
private predicate isPumpable(State fork, string w) {
328-
exists(StatePair q, Trace t |
357+
exists(StatePair q, RelevantTrace t |
329358
isReachableFromFork(fork, q, t, _) and
330359
q = getAForkPair(fork) and
331-
w = concretise(t)
360+
w = t.concretise()
332361
)
333362
}
334363

python/ql/lib/semmle/python/security/performance/ReDoSUtil.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ abstract class ReDoSConfiguration extends string {
3232
}
3333

3434
/**
35-
* Holds if repeating `pump' starting at `state` is a candidate for causing backtracking.
35+
* Holds if repeating `pump` starting at `state` is a candidate for causing backtracking.
3636
* No check whether a rejected suffix exists has been made.
3737
*/
3838
private predicate isReDoSCandidate(State state, string pump) {

python/ql/lib/semmle/python/security/performance/SuperlinearBackTracking.qll

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -254,17 +254,6 @@ class Trace extends TTrace {
254254
}
255255
}
256256

257-
/**
258-
* Gets a string corresponding to the trace `t`.
259-
*/
260-
string concretise(Trace t) {
261-
t = Nil() and result = ""
262-
or
263-
exists(InputSymbol s1, InputSymbol s2, InputSymbol s3, Trace rest | t = Step(s1, s2, s3, rest) |
264-
result = concretise(rest) + getAThreewayIntersect(s1, s2, s3)
265-
)
266-
}
267-
268257
/**
269258
* Holds if there exists a transition from `r` to `q` in the product automaton.
270259
* Notice that the arguments are flipped, and thus the direction is backwards.
@@ -332,6 +321,51 @@ StateTuple getAnEndTuple(State pivot, State succ) {
332321
result = MkStateTuple(pivot, succ, succ)
333322
}
334323

324+
private predicate hasSuffix(Trace suffix, Trace t, int i) {
325+
// Declaring `t` to be a `RelevantTrace` currently causes a redundant check in the
326+
// recursive case, so instead we check it explicitly here.
327+
t instanceof RelevantTrace and
328+
i = 0 and
329+
suffix = t
330+
or
331+
hasSuffix(Step(_, _, _, suffix), t, i - 1)
332+
}
333+
334+
pragma[noinline]
335+
private predicate hasTuple(InputSymbol s1, InputSymbol s2, InputSymbol s3, Trace t, int i) {
336+
hasSuffix(Step(s1, s2, s3, _), t, i)
337+
}
338+
339+
private class RelevantTrace extends Trace, Step {
340+
RelevantTrace() {
341+
exists(State pivot, State succ, StateTuple q |
342+
isReachableFromStartTuple(pivot, succ, q, this, _) and
343+
q = getAnEndTuple(pivot, succ)
344+
)
345+
}
346+
347+
pragma[noinline]
348+
private string getAThreewayIntersect(int i) {
349+
exists(InputSymbol s1, InputSymbol s2, InputSymbol s3 |
350+
hasTuple(s1, s2, s3, this, i) and
351+
result = getAThreewayIntersect(s1, s2, s3)
352+
)
353+
}
354+
355+
/** Gets a string corresponding to this trace. */
356+
// the pragma is needed for the case where `getAThreewayIntersect(s1, s2, s3)` has multiple values,
357+
// not for recursion
358+
language[monotonicAggregates]
359+
string concretise() {
360+
result =
361+
strictconcat(int i |
362+
hasTuple(_, _, _, this, i)
363+
|
364+
this.getAThreewayIntersect(i) order by i desc
365+
)
366+
}
367+
}
368+
335369
/**
336370
* Holds if matching repetitions of `pump` can:
337371
* 1) Transition from `pivot` back to `pivot`.
@@ -345,10 +379,10 @@ StateTuple getAnEndTuple(State pivot, State succ) {
345379
* available in the `hasReDoSResult` predicate.
346380
*/
347381
predicate isPumpable(State pivot, State succ, string pump) {
348-
exists(StateTuple q, Trace t |
382+
exists(StateTuple q, RelevantTrace t |
349383
isReachableFromStartTuple(pivot, succ, q, t, _) and
350384
q = getAnEndTuple(pivot, succ) and
351-
pump = concretise(t)
385+
pump = t.concretise()
352386
)
353387
}
354388

0 commit comments

Comments
 (0)