Skip to content

Commit 6b0df9b

Browse files
committed
refactor the concretize algorithm
1 parent dbeae9a commit 6b0df9b

File tree

12 files changed

+408
-288
lines changed

12 files changed

+408
-288
lines changed

java/ql/lib/semmle/code/java/security/performance/ExponentialBackTracking.qll

Lines changed: 11 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -313,54 +313,30 @@ private StatePair getAForkPair(State fork) {
313313
result = MkStatePair(epsilonPred*(fork), epsilonPred*(fork))
314314
}
315315

316-
private predicate hasSuffix(Trace suffix, Trace t, int i) {
317-
// Declaring `t` to be a `RelevantTrace` currently causes a redundant check in the
318-
// recursive case, so instead we check it explicitly here.
319-
t instanceof RelevantTrace and
320-
i = 0 and
321-
suffix = t
322-
or
323-
hasSuffix(Step(_, _, suffix), t, i - 1)
324-
}
325-
326-
pragma[noinline]
327-
private predicate hasTuple(InputSymbol s1, InputSymbol s2, Trace t, int i) {
328-
hasSuffix(Step(s1, s2, _), t, i)
329-
}
316+
/** An implementation of a chain containing chars for use by `Concretizer`. */
317+
private module CharTreeImpl implements CharTree {
318+
class CharNode = Trace;
330319

331-
private class RelevantTrace extends Trace, Step {
332-
RelevantTrace() {
333-
exists(State fork, StatePair q |
334-
isReachableFromFork(fork, q, this, _) and
335-
q = getAForkPair(fork)
336-
)
337-
}
320+
CharNode getPrev(CharNode t) { t = Step(_, _, result) }
338321

339-
pragma[noinline]
340-
private string intersect(int i) {
341-
exists(InputSymbol s1, InputSymbol s2 |
342-
hasTuple(s1, s2, this, i) and
343-
result = intersect(s1, s2)
344-
)
322+
/** Holds if `n` is a trace that is used by `concretize` in `isPumpable`. */
323+
predicate isARelevantEnd(CharNode n) {
324+
exists(State f | isReachableFromFork(f, getAForkPair(f), n, _))
345325
}
346326

347-
/** Gets a string corresponding to this trace. */
348-
// the pragma is needed for the case where `intersect(s1, s2)` has multiple values,
349-
// not for recursion
350-
language[monotonicAggregates]
351-
string concretise() {
352-
result = strictconcat(int i | hasTuple(_, _, this, i) | this.intersect(i) order by i desc)
327+
string getChar(CharNode t) {
328+
exists(InputSymbol s1, InputSymbol s2 | t = Step(s1, s2, _) | result = intersect(s1, s2))
353329
}
354330
}
355331

356332
/**
357333
* Holds if `fork` is a pumpable fork with word `w`.
358334
*/
359335
private predicate isPumpable(State fork, string w) {
360-
exists(StatePair q, RelevantTrace t |
336+
exists(StatePair q, Trace t |
361337
isReachableFromFork(fork, q, t, _) and
362338
q = getAForkPair(fork) and
363-
w = t.concretise()
339+
w = Concretizer<CharTreeImpl>::concretize(t)
364340
)
365341
}
366342

java/ql/lib/semmle/code/java/security/performance/ReDoSUtil.qll

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,3 +1240,82 @@ module ReDoSPruning<isCandidateSig/2 isCandidate> {
12401240
result = str.suffix(str.length() - i) + str.prefix(str.length() - i)
12411241
}
12421242
}
1243+
1244+
/**
1245+
* A module that describes a tree where each node has one or more accosiated characters.
1246+
* The root node has no accosiated character.
1247+
* This module is a signature used in `Concretizer`.
1248+
*/
1249+
signature module CharTree {
1250+
/** A node in the tree. */
1251+
class CharNode;
1252+
1253+
/** Gets the previous node in the tree from `t`. */
1254+
CharNode getPrev(CharNode t);
1255+
1256+
/**
1257+
* Holds if `n` is at the end of a tree. I.e. a node that should have a result in the `Concretizer` module.
1258+
* A leaf can still have children.
1259+
*/
1260+
predicate isARelevantEnd(CharNode n);
1261+
1262+
/** Gets a char associated with `t`. */
1263+
string getChar(CharNode t);
1264+
}
1265+
1266+
/**
1267+
* Implements an algorithm for computing all possible strings
1268+
* from following a tree of nodes (as described in `CharTree`).
1269+
*
1270+
* The string is build using one big concat, where all the chars are computed first.
1271+
* See `concretize`.
1272+
*/
1273+
module Concretizer<CharTree Impl> {
1274+
private class Node = Impl::CharNode;
1275+
1276+
private predicate getPrev = Impl::getPrev/1;
1277+
1278+
private predicate isARelevantEnd = Impl::isARelevantEnd/1;
1279+
1280+
private predicate getChar = Impl::getChar/1;
1281+
1282+
/** Holds if `n` is on a path from the root to a leaf, and is therefore relevant for the results in `concretize`. */
1283+
private predicate isRelevant(Node n) {
1284+
isARelevantEnd(n)
1285+
or
1286+
exists(Node prev | isRelevant(prev) | n = getPrev(prev))
1287+
}
1288+
1289+
/** Holds if `n` is a root with no predecessors. */
1290+
private predicate isRoot(Node n) { not exists(getPrev(n)) }
1291+
1292+
/** Gets the distance from a root to `n`. */
1293+
private int nodeDist(Node n) {
1294+
result = 0 and isRoot(n)
1295+
or
1296+
isRelevant(n) and
1297+
exists(Node prev | result = nodeDist(prev) + 1 | prev = getPrev(n))
1298+
}
1299+
1300+
/** Holds if `n` is part of a chain that we want to compute a string for. */
1301+
private Node getANodeInLongChain(Node end) {
1302+
isARelevantEnd(end) and result = end
1303+
or
1304+
exists(Node prev | prev = getANodeInLongChain(end) | result = getPrev(prev))
1305+
}
1306+
1307+
pragma[noinline]
1308+
private string getPrefixChar(Node n, int i) {
1309+
exists(Node prev |
1310+
result = getChar(prev) and
1311+
prev = getANodeInLongChain(n) and
1312+
i = nodeDist(n) - nodeDist(prev)
1313+
)
1314+
}
1315+
1316+
/** Gets a string corresponding to `node`. */
1317+
language[monotonicAggregates]
1318+
string concretize(Node n) {
1319+
result = strictconcat(int i | exists(getPrefixChar(n, i)) | getPrefixChar(n, i) order by i desc)
1320+
}
1321+
}

java/ql/lib/semmle/code/java/security/performance/SuperlinearBackTracking.qll

Lines changed: 12 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -312,49 +312,24 @@ StateTuple getAnEndTuple(State pivot, State succ) {
312312
result = MkStateTuple(pivot, succ, succ)
313313
}
314314

315-
private predicate hasSuffix(Trace suffix, Trace t, int i) {
316-
// Declaring `t` to be a `RelevantTrace` currently causes a redundant check in the
317-
// recursive case, so instead we check it explicitly here.
318-
t instanceof RelevantTrace and
319-
i = 0 and
320-
suffix = t
321-
or
322-
hasSuffix(Step(_, _, _, suffix), t, i - 1)
323-
}
315+
/** An implementation of a chain containing chars for use by `Concretizer`. */
316+
private module CharTreeImpl implements CharTree {
317+
class CharNode = Trace;
324318

325-
pragma[noinline]
326-
private predicate hasTuple(InputSymbol s1, InputSymbol s2, InputSymbol s3, Trace t, int i) {
327-
hasSuffix(Step(s1, s2, s3, _), t, i)
328-
}
319+
CharNode getPrev(CharNode t) { t = Step(_, _, _, result) }
329320

330-
private class RelevantTrace extends Trace, Step {
331-
RelevantTrace() {
332-
exists(State pivot, State succ, StateTuple q |
333-
isReachableFromStartTuple(pivot, succ, q, this, _) and
334-
q = getAnEndTuple(pivot, succ)
321+
/** Holds if `n` is used in `isPumpable`. */
322+
predicate isARelevantEnd(CharNode n) {
323+
exists(State pivot, State succ |
324+
isReachableFromStartTuple(pivot, succ, getAnEndTuple(pivot, succ), n, _)
335325
)
336326
}
337327

338-
pragma[noinline]
339-
private string getAThreewayIntersect(int i) {
340-
exists(InputSymbol s1, InputSymbol s2, InputSymbol s3 |
341-
hasTuple(s1, s2, s3, this, i) and
328+
string getChar(CharNode t) {
329+
exists(InputSymbol s1, InputSymbol s2, InputSymbol s3 | t = Step(s1, s2, s3, _) |
342330
result = getAThreewayIntersect(s1, s2, s3)
343331
)
344332
}
345-
346-
/** Gets a string corresponding to this trace. */
347-
// the pragma is needed for the case where `getAThreewayIntersect(s1, s2, s3)` has multiple values,
348-
// not for recursion
349-
language[monotonicAggregates]
350-
string concretise() {
351-
result =
352-
strictconcat(int i |
353-
hasTuple(_, _, _, this, i)
354-
|
355-
this.getAThreewayIntersect(i) order by i desc
356-
)
357-
}
358333
}
359334

360335
/**
@@ -370,10 +345,10 @@ private class RelevantTrace extends Trace, Step {
370345
* available in the `hasReDoSResult` predicate.
371346
*/
372347
predicate isPumpable(State pivot, State succ, string pump) {
373-
exists(StateTuple q, RelevantTrace t |
348+
exists(StateTuple q, Trace t |
374349
isReachableFromStartTuple(pivot, succ, q, t, _) and
375350
q = getAnEndTuple(pivot, succ) and
376-
pump = t.concretise()
351+
pump = Concretizer<CharTreeImpl>::concretize(t)
377352
)
378353
}
379354

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

Lines changed: 11 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -313,54 +313,30 @@ private StatePair getAForkPair(State fork) {
313313
result = MkStatePair(epsilonPred*(fork), epsilonPred*(fork))
314314
}
315315

316-
private predicate hasSuffix(Trace suffix, Trace t, int i) {
317-
// Declaring `t` to be a `RelevantTrace` currently causes a redundant check in the
318-
// recursive case, so instead we check it explicitly here.
319-
t instanceof RelevantTrace and
320-
i = 0 and
321-
suffix = t
322-
or
323-
hasSuffix(Step(_, _, suffix), t, i - 1)
324-
}
325-
326-
pragma[noinline]
327-
private predicate hasTuple(InputSymbol s1, InputSymbol s2, Trace t, int i) {
328-
hasSuffix(Step(s1, s2, _), t, i)
329-
}
316+
/** An implementation of a chain containing chars for use by `Concretizer`. */
317+
private module CharTreeImpl implements CharTree {
318+
class CharNode = Trace;
330319

331-
private class RelevantTrace extends Trace, Step {
332-
RelevantTrace() {
333-
exists(State fork, StatePair q |
334-
isReachableFromFork(fork, q, this, _) and
335-
q = getAForkPair(fork)
336-
)
337-
}
320+
CharNode getPrev(CharNode t) { t = Step(_, _, result) }
338321

339-
pragma[noinline]
340-
private string intersect(int i) {
341-
exists(InputSymbol s1, InputSymbol s2 |
342-
hasTuple(s1, s2, this, i) and
343-
result = intersect(s1, s2)
344-
)
322+
/** Holds if `n` is a trace that is used by `concretize` in `isPumpable`. */
323+
predicate isARelevantEnd(CharNode n) {
324+
exists(State f | isReachableFromFork(f, getAForkPair(f), n, _))
345325
}
346326

347-
/** Gets a string corresponding to this trace. */
348-
// the pragma is needed for the case where `intersect(s1, s2)` has multiple values,
349-
// not for recursion
350-
language[monotonicAggregates]
351-
string concretise() {
352-
result = strictconcat(int i | hasTuple(_, _, this, i) | this.intersect(i) order by i desc)
327+
string getChar(CharNode t) {
328+
exists(InputSymbol s1, InputSymbol s2 | t = Step(s1, s2, _) | result = intersect(s1, s2))
353329
}
354330
}
355331

356332
/**
357333
* Holds if `fork` is a pumpable fork with word `w`.
358334
*/
359335
private predicate isPumpable(State fork, string w) {
360-
exists(StatePair q, RelevantTrace t |
336+
exists(StatePair q, Trace t |
361337
isReachableFromFork(fork, q, t, _) and
362338
q = getAForkPair(fork) and
363-
w = t.concretise()
339+
w = Concretizer<CharTreeImpl>::concretize(t)
364340
)
365341
}
366342

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

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,3 +1240,82 @@ module ReDoSPruning<isCandidateSig/2 isCandidate> {
12401240
result = str.suffix(str.length() - i) + str.prefix(str.length() - i)
12411241
}
12421242
}
1243+
1244+
/**
1245+
* A module that describes a tree where each node has one or more accosiated characters.
1246+
* The root node has no accosiated character.
1247+
* This module is a signature used in `Concretizer`.
1248+
*/
1249+
signature module CharTree {
1250+
/** A node in the tree. */
1251+
class CharNode;
1252+
1253+
/** Gets the previous node in the tree from `t`. */
1254+
CharNode getPrev(CharNode t);
1255+
1256+
/**
1257+
* Holds if `n` is at the end of a tree. I.e. a node that should have a result in the `Concretizer` module.
1258+
* A leaf can still have children.
1259+
*/
1260+
predicate isARelevantEnd(CharNode n);
1261+
1262+
/** Gets a char associated with `t`. */
1263+
string getChar(CharNode t);
1264+
}
1265+
1266+
/**
1267+
* Implements an algorithm for computing all possible strings
1268+
* from following a tree of nodes (as described in `CharTree`).
1269+
*
1270+
* The string is build using one big concat, where all the chars are computed first.
1271+
* See `concretize`.
1272+
*/
1273+
module Concretizer<CharTree Impl> {
1274+
private class Node = Impl::CharNode;
1275+
1276+
private predicate getPrev = Impl::getPrev/1;
1277+
1278+
private predicate isARelevantEnd = Impl::isARelevantEnd/1;
1279+
1280+
private predicate getChar = Impl::getChar/1;
1281+
1282+
/** Holds if `n` is on a path from the root to a leaf, and is therefore relevant for the results in `concretize`. */
1283+
private predicate isRelevant(Node n) {
1284+
isARelevantEnd(n)
1285+
or
1286+
exists(Node prev | isRelevant(prev) | n = getPrev(prev))
1287+
}
1288+
1289+
/** Holds if `n` is a root with no predecessors. */
1290+
private predicate isRoot(Node n) { not exists(getPrev(n)) }
1291+
1292+
/** Gets the distance from a root to `n`. */
1293+
private int nodeDist(Node n) {
1294+
result = 0 and isRoot(n)
1295+
or
1296+
isRelevant(n) and
1297+
exists(Node prev | result = nodeDist(prev) + 1 | prev = getPrev(n))
1298+
}
1299+
1300+
/** Holds if `n` is part of a chain that we want to compute a string for. */
1301+
private Node getANodeInLongChain(Node end) {
1302+
isARelevantEnd(end) and result = end
1303+
or
1304+
exists(Node prev | prev = getANodeInLongChain(end) | result = getPrev(prev))
1305+
}
1306+
1307+
pragma[noinline]
1308+
private string getPrefixChar(Node n, int i) {
1309+
exists(Node prev |
1310+
result = getChar(prev) and
1311+
prev = getANodeInLongChain(n) and
1312+
i = nodeDist(n) - nodeDist(prev)
1313+
)
1314+
}
1315+
1316+
/** Gets a string corresponding to `node`. */
1317+
language[monotonicAggregates]
1318+
string concretize(Node n) {
1319+
result = strictconcat(int i | exists(getPrefixChar(n, i)) | getPrefixChar(n, i) order by i desc)
1320+
}
1321+
}

0 commit comments

Comments
 (0)