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

Skip to content

Commit 5555985

Browse files
Distingush between whether or not a regex is matched against a full string
Also some fixes and additional tests
1 parent 0a5268a commit 5555985

6 files changed

Lines changed: 118 additions & 15 deletions

File tree

java/ql/lib/semmle/code/java/regex/RegexFlowConfigs.qll

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,48 @@ private class RegexCompileFlowConf extends DataFlow2::Configuration {
1313

1414
override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof StringLiteral }
1515

16-
override predicate isSink(DataFlow::Node node) { sinkNode(node, "regex-compile") }
16+
override predicate isSink(DataFlow::Node node) {
17+
sinkNode(node, ["regex-compile", "regex-compile-match", "regex-compile-find"])
18+
}
1719
}
1820

1921
/**
2022
* Holds if `s` is used as a regex, with the mode `mode` (if known).
2123
* If regex mode is not known, `mode` will be `"None"`.
2224
*/
23-
predicate usedAsRegex(StringLiteral s, string mode) {
24-
any(RegexCompileFlowConf c).hasFlow(DataFlow2::exprNode(s), _) and
25-
mode = "None" // TODO: proper mode detection
25+
predicate usedAsRegex(StringLiteral s, string mode, boolean match_full_string) {
26+
exists(DataFlow::Node sink |
27+
any(RegexCompileFlowConf c).hasFlow(DataFlow2::exprNode(s), sink) and
28+
mode = "None" and // TODO: proper mode detection
29+
(if matchesFullString(sink) then match_full_string = true else match_full_string = false)
30+
)
31+
}
32+
33+
/**
34+
* Holds if the regex that flows to `sink` is used to match against a full string,
35+
* as though it was implicitly surrounded by ^ and $.
36+
*/
37+
private predicate matchesFullString(DataFlow::Node sink) {
38+
sinkNode(sink, "regex-compile-match")
39+
or
40+
exists(DataFlow::Node matchSource, RegexCompileToMatchConf conf |
41+
matchSource.asExpr().(MethodAccess).getAnArgument() = sink.asExpr() and
42+
conf.hasFlow(matchSource, _)
43+
)
44+
}
45+
46+
private class RegexCompileToMatchConf extends DataFlow2::Configuration {
47+
RegexCompileToMatchConf() { this = "RegexCompileToMatchConfig" }
48+
49+
override predicate isSource(DataFlow::Node node) { sourceNode(node, "regex-compile") }
50+
51+
override predicate isSink(DataFlow::Node node) { sinkNode(node, "regex-match") }
52+
53+
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
54+
exists(MethodAccess ma | node2.asExpr() = ma and node1.asExpr() = ma.getQualifier() |
55+
ma.getMethod().hasQualifiedName("java.util.regex", "Pattern", "matcher")
56+
)
57+
}
2658
}
2759

2860
/**

java/ql/lib/semmle/code/java/regex/RegexFlowModels.qll

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,33 @@
33
import java
44
import semmle.code.java.dataflow.ExternalFlow
55

6+
private class RegexSourceCsv extends SourceModelCsv {
7+
override predicate row(string row) {
8+
row =
9+
[
10+
//"namespace;type;subtypes;name;signature;ext;output;kind"
11+
"java.util.regex;Pattern;false;compile;(String);;ReturnValue;regex-compile",
12+
]
13+
}
14+
}
15+
616
private class RegexSinkCsv extends SinkModelCsv {
717
override predicate row(string row) {
818
row =
919
[
1020
//"namespace;type;subtypes;name;signature;ext;input;kind"
1121
"java.util.regex;Pattern;false;compile;(String);;Argument[0];regex-compile",
1222
"java.util.regex;Pattern;false;compile;(String,int);;Argument[0];regex-compile",
13-
"java.util.regex;Pattern;false;matches;(String,CharSequence);;Argument[0];regex-compile",
14-
"java.util;String;false;matches;(String);;Argument[0];regex-compile",
15-
"java.util;String;false;split;(String);;Argument[0];regex-compile",
16-
"java.util;String;false;split;(String,int);;Argument[0];regex-compile",
17-
"java.util;String;false;replaceAll;(String,String);;Argument[0];regex-compile",
18-
"java.util;String;false;replaceFirst;(String,String);;Argument[0];regex-compile",
19-
"com.google.common.base;Splitter;false;onPattern;(String);;Argument[0];regex-compile"
23+
"java.util.regex;Pattern;false;matches;(String,CharSequence);;Argument[0];regex-compile-match",
24+
"java.lang;String;false;matches;(String);;Argument[0];regex-compile-match",
25+
"java.lang;String;false;split;(String);;Argument[0];regex-compile-find",
26+
"java.lang;String;false;split;(String,int);;Argument[0];regex-compile-find",
27+
"java.lang;String;false;replaceAll;(String,String);;Argument[0];regex-compile-find",
28+
"java.lang;String;false;replaceFirst;(String,String);;Argument[0];regex-compile-find",
29+
"com.google.common.base;Splitter;false;onPattern;(String);;Argument[0];regex-compile-find",
30+
// regex-match sinks
31+
"java.util.regex;Pattern;false;asMatchPredicate;();;Argument[-1];regex-match",
32+
"java.util.regex;Matcher;false;matches;();;Argument[-1];regex-match",
2033
]
2134
}
2235
}

java/ql/lib/semmle/code/java/regex/regex.qll

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -892,7 +892,9 @@ abstract class RegexString extends StringLiteral {
892892

893893
/** A string literal used as a regular expression */
894894
class Regex extends RegexString {
895-
Regex() { usedAsRegex(this, _) }
895+
boolean matches_full_string;
896+
897+
Regex() { usedAsRegex(this, _, matches_full_string) }
896898

897899
/**
898900
* Gets a mode (if any) of this regular expression. Can be any of:
@@ -906,8 +908,14 @@ class Regex extends RegexString {
906908
*/
907909
string getAMode() {
908910
result != "None" and
909-
usedAsRegex(this, result)
911+
usedAsRegex(this, result, _)
910912
or
911913
result = this.getModeFromPrefix()
912914
}
915+
916+
/**
917+
* Holds if this regex is used to match against a full string,
918+
* as though it was implicitly surrounded by ^ and $.
919+
*/
920+
predicate matchesFullString() { matches_full_string = true }
913921
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,9 @@ State after(RegExpTerm t) {
622622
or
623623
exists(EffectivelyQuestion opt | t = opt.getAChild() | result = after(opt))
624624
or
625-
exists(RegExpRoot root | t = root | result = AcceptAnySuffix(root))
625+
exists(RegExpRoot root | t = root |
626+
if matchesAnySuffix(root) then result = AcceptAnySuffix(root) else result = Accept(root)
627+
)
626628
}
627629

628630
/**
@@ -693,7 +695,7 @@ predicate delta(State q1, EdgeLabel lbl, State q2) {
693695
lbl = Epsilon() and q2 = Accept(root)
694696
)
695697
or
696-
exists(RegExpRoot root | q1 = Match(root, 0) | lbl = Any() and q2 = q1)
698+
exists(RegExpRoot root | q1 = Match(root, 0) | matchesAnyPrefix(root) and lbl = Any() and q2 = q1)
697699
or
698700
exists(RegExpDollar dollar | q1 = before(dollar) |
699701
lbl = Epsilon() and q2 = Accept(getRoot(dollar))

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ predicate isEscapeClass(RegExpTerm term, string clazz) {
2121
*/
2222
predicate isPossessive(RegExpQuantifier term) { term.isPossessive() }
2323

24+
/**
25+
* Holds if the regex that `term` is part of is used in a way that ignores any leading prefix of the input it's matched against.
26+
*/
27+
predicate matchesAnyPrefix(RegExpTerm term) { not term.getRegex().matchesFullString() }
28+
29+
/**
30+
* Holds if the regex that `term` is part of is used in a way that ignores any trailing suffix of the input it's matched against.
31+
*/
32+
predicate matchesAnySuffix(RegExpTerm term) { not term.getRegex().matchesFullString() }
33+
2434
/**
2535
* Holds if the regular expression should not be considered.
2636
*

java/ql/test/query-tests/security/CWE-730/PolyRedosTest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,42 @@ void test(HttpServletRequest request) {
3434
Splitter.on(";").withKeyValueSeparator(Splitter.onPattern(reg)).split(tainted); // $ hasPolyRedos
3535

3636
}
37+
38+
void test2(HttpServletRequest request) {
39+
String tainted = request.getParameter("inp");
40+
41+
Pattern p1 = Pattern.compile(".*a");
42+
Pattern p2 = Pattern.compile(".*b");
43+
44+
p1.matcher(tainted).matches();
45+
p2.matcher(tainted).find(); // $ hasPolyRedos
46+
}
47+
48+
void test3(HttpServletRequest request) {
49+
String tainted = request.getParameter("inp");
50+
51+
Pattern p1 = Pattern.compile("ab*b*");
52+
Pattern p2 = Pattern.compile("cd*d*");
53+
54+
p1.matcher(tainted).matches(); // $ hasPolyRedos
55+
p2.matcher(tainted).find();
56+
}
57+
58+
void test4(HttpServletRequest request) {
59+
String tainted = request.getParameter("inp");
60+
61+
tainted.matches(".*a");
62+
tainted.replaceAll(".*b", "c"); // $ hasPolyRedos
63+
}
64+
65+
static Pattern p3 = Pattern.compile(".*a");
66+
static Pattern p4 = Pattern.compile(".*b");
67+
68+
69+
void test5(HttpServletRequest request) {
70+
String tainted = request.getParameter("inp");
71+
72+
p3.asMatchPredicate().test(tainted);
73+
p4.asPredicate().test(tainted); // $ hasPolyRedos
74+
}
3775
}

0 commit comments

Comments
 (0)