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

Skip to content

Commit 59945cd

Browse files
Add dataflow logic to PolynomialRedDoS
1 parent 37240f0 commit 59945cd

5 files changed

Lines changed: 223 additions & 35 deletions

File tree

java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ private module Frameworks {
140140
private import semmle.code.java.frameworks.jOOQ
141141
private import semmle.code.java.frameworks.JMS
142142
private import semmle.code.java.frameworks.RabbitMQ
143-
private import semmle.code.java.regex.RegexFlow
143+
private import semmle.code.java.regex.RegexFlowModels
144144
}
145145

146146
private predicate sourceModelCsv(string row) {
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/**
2+
* Defines configurations and steps for handling regexes
3+
*/
4+
5+
import java
6+
private import semmle.code.java.dataflow.DataFlow
7+
private import semmle.code.java.dataflow.DataFlow2
8+
private import semmle.code.java.dataflow.DataFlow3
9+
private import RegexFlowModels
10+
11+
private class RegexCompileFlowConf extends DataFlow2::Configuration {
12+
RegexCompileFlowConf() { this = "RegexCompileFlowConfig" }
13+
14+
override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof StringLiteral }
15+
16+
override predicate isSink(DataFlow::Node node) { sinkNode(node, "regex-compile") }
17+
}
18+
19+
/**
20+
* Holds if `s` is used as a regex, with the mode `mode` (if known).
21+
* If regex mode is not known, `mode` will be `"None"`.
22+
*/
23+
predicate used_as_regex(Expr s, string mode) {
24+
any(RegexCompileFlowConf c).hasFlow(DataFlow2::exprNode(s), _) and
25+
mode = "None" // TODO: proper mode detection
26+
}
27+
28+
/**
29+
* A method access that can match a regex against a string
30+
*/
31+
abstract class RegexMatchMethodAccess extends MethodAccess {
32+
string package;
33+
string type;
34+
string name;
35+
int regexArg;
36+
int stringArg;
37+
Method m;
38+
39+
RegexMatchMethodAccess() {
40+
this.getMethod().overrides*(m) and
41+
m.hasQualifiedName(package, type, name) and
42+
regexArg in [-1 .. m.getNumberOfParameters() - 1] and
43+
stringArg in [-1 .. m.getNumberOfParameters() - 1]
44+
}
45+
46+
/** Gets the argument of this call that the regex to be matched against flows into */
47+
Expr getRegexArg() { result = argOf(this, regexArg) }
48+
49+
/** Gets the argument of this call that the */
50+
Expr getStringArg() { result = argOf(this, stringArg) }
51+
}
52+
53+
private Expr argOf(MethodAccess ma, int arg) {
54+
arg = -1 and result = ma.getQualifier()
55+
or
56+
result = ma.getArgument(arg)
57+
}
58+
59+
/**
60+
* A unit class for adding additional regex flow steps.
61+
*
62+
* Extend this class to add additional flow steps that should apply to regex flow configurations.
63+
*/
64+
class RegexAdditionalFlowStep extends Unit {
65+
/**
66+
* Holds if the step from `node1` to `node2` should be considered a flow
67+
* step for regex flow configurations.
68+
*/
69+
abstract predicate step(DataFlow::Node node1, DataFlow::Node node2);
70+
}
71+
72+
// TODO: can this be done with the models-as-data framework?
73+
private class JdkRegexMatchMethodAccess extends RegexMatchMethodAccess {
74+
JdkRegexMatchMethodAccess() {
75+
package = "java.util.regex" and
76+
type = "Pattern" and
77+
(
78+
name = "matcher" and regexArg = -1 and stringArg = 0
79+
or
80+
name = "matches" and regexArg = 0 and stringArg = 1
81+
or
82+
name = "split" and regexArg = 0 and stringArg = 1
83+
or
84+
name = "splitAsStream" and regexArg = 0 and stringArg = 1
85+
)
86+
or
87+
package = "java.lang" and
88+
type = "String" and
89+
name = ["matches", "split"] and
90+
regexArg = 0 and
91+
stringArg = -1
92+
or
93+
package = "java.util" and
94+
type = "Predicate" and
95+
name = "test" and
96+
regexArg = -1 and
97+
stringArg = 0
98+
}
99+
}
100+
101+
private class JdkRegexFlowStep extends RegexAdditionalFlowStep {
102+
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
103+
exists(MethodAccess ma, Method m, string package, string type, string name, int arg |
104+
ma.getMethod().overrides*(m) and
105+
m.hasQualifiedName(package, type, name) and
106+
node1.asExpr() = argOf(ma, arg) and
107+
node2.asExpr() = ma
108+
|
109+
package = "java.util.regex" and
110+
type = "Pattern" and
111+
(
112+
name = ["asMatchPredicate", "asPredicate"] and
113+
arg = -1
114+
or
115+
name = "compile" and
116+
arg = 0
117+
)
118+
or
119+
package = "java.util" and
120+
type = "Predicate" and
121+
name = ["and", "or", "not", "negate"] and
122+
arg = [-1, 0]
123+
)
124+
}
125+
}
126+
127+
private class GuavaRegexMatchMethodAccess extends RegexMatchMethodAccess {
128+
GuavaRegexMatchMethodAccess() {
129+
package = "com.google.common.collect" and
130+
regexArg = -1 and
131+
stringArg = 0 and
132+
type = ["Splitter", "Splitter$MapSplitter"] and
133+
name = ["split", "splitToList"]
134+
}
135+
}
136+
137+
private class GuavaRegexFlowStep extends RegexAdditionalFlowStep {
138+
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
139+
exists(MethodAccess ma, Method m, string package, string type, string name, int arg |
140+
ma.getMethod().overrides*(m) and
141+
m.hasQualifiedName(package, type, name) and
142+
node1.asExpr() = argOf(ma, arg) and
143+
node2.asExpr() = ma
144+
|
145+
package = "com.google.common.base" and
146+
type = "Splitter" and
147+
(
148+
name = "on" and
149+
m.getParameterType(0).(RefType).hasQualifiedName("java.util.regex", "Pattern") and
150+
arg = 0
151+
or
152+
name = "withKeyValueSeparator" and
153+
m.getParameterType(0).(RefType).hasQualifiedName("com.google.common.base", "Splitter") and
154+
arg = 0
155+
or
156+
name = "onPattern" and
157+
arg = 0
158+
or
159+
name = ["limit", "omitEmptyStrings", "trimResults", "withKeyValueSeparator"] and
160+
arg = -1
161+
)
162+
)
163+
}
164+
}
165+
166+
private class RegexMatchFlowConf extends DataFlow2::Configuration {
167+
RegexMatchFlowConf() { this = "RegexMatchFlowConf" }
168+
169+
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof StringLiteral }
170+
171+
override predicate isSink(DataFlow::Node sink) {
172+
exists(RegexMatchMethodAccess ma | sink.asExpr() = ma.getRegexArg())
173+
}
174+
175+
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
176+
any(RegexAdditionalFlowStep s).step(node1, node2)
177+
}
178+
}
179+
180+
/**
181+
* Holds if the string literal `regex` is matched against the expression `str`.
182+
*/
183+
predicate regex_match(StringLiteral regex, Expr str) {
184+
exists(
185+
DataFlow::Node src, DataFlow::Node sink, RegexMatchMethodAccess ma, RegexMatchFlowConf conf
186+
|
187+
src.asExpr() = regex and
188+
sink.asExpr() = ma.getRegexArg() and
189+
conf.hasFlow(src, sink) and
190+
str = ma.getStringArg()
191+
)
192+
}

java/ql/lib/semmle/code/java/regex/RegexFlow.qll renamed to java/ql/lib/semmle/code/java/regex/RegexFlowModels.qll

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ private class RegexSinkCsv extends SinkModelCsv {
66
row =
77
[
88
//"namespace;type;subtypes;name;signature;ext;input;kind"
9-
"java.util.regex;Pattern;false;compile;(String);;Argument[0];regex-use",
10-
"java.util.regex;Pattern;false;compile;(String,int);;Argument[0];regex-use",
11-
"java.util.regex;Pattern;false;matches;(String,CharSequence);;Argument[0];regex-use",
12-
"java.util;String;false;matches;(String);;Argument[0];regex-use",
13-
"java.util;String;false;split;(String);;Argument[0];regex-use",
14-
"java.util;String;false;split;(String,int);;Argument[0];regex-use",
15-
"com.google.common.base;Splitter;false;onPattern;(String);;Argument[0];regex-use"
9+
"java.util.regex;Pattern;false;compile;(String);;Argument[0];regex-compile",
10+
"java.util.regex;Pattern;false;compile;(String,int);;Argument[0];regex-compile",
11+
"java.util.regex;Pattern;false;matches;(String,CharSequence);;Argument[0];regex-compile",
12+
"java.util;String;false;matches;(String);;Argument[0];regex-compile",
13+
"java.util;String;false;split;(String);;Argument[0];regex-compile",
14+
"java.util;String;false;split;(String,int);;Argument[0];regex-compile",
15+
"com.google.common.base;Splitter;false;onPattern;(String);;Argument[0];regex-compile"
1616
]
1717
}
1818
}

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

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,5 @@
11
import java
2-
import semmle.code.java.dataflow.DataFlow2
3-
import semmle.code.java.dataflow.ExternalFlow
4-
5-
class RegexFlowConf extends DataFlow2::Configuration {
6-
RegexFlowConf() { this = "RegexFlowConf" }
7-
8-
override predicate isSource(DataFlow2::Node node) { node.asExpr() instanceof StringLiteral }
9-
10-
override predicate isSink(DataFlow2::Node node) { sinkNode(node, "regex-use") }
11-
}
12-
13-
/**
14-
* Holds if `s` is used as a regex, with the mode `mode` (if known).
15-
* If regex mode is not known, `mode` will be `"None"`.
16-
*/
17-
predicate used_as_regex(Expr s, string mode) {
18-
any(RegexFlowConf c).hasFlow(DataFlow2::exprNode(s), _) and
19-
mode = "None" // TODO: proper mode detection
20-
}
2+
private import RegexFlowConfigs
213

224
/**
235
* A string literal that is used as a regular exprssion.

java/ql/src/Security/CWE/CWE-730/PolynomialReDoS.ql

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,35 @@
1414
import java
1515
import semmle.code.java.security.performance.SuperlinearBackTracking
1616
import semmle.code.java.dataflow.DataFlow
17-
// import semmle.python.security.dataflow.PolynomialReDoS
17+
import semmle.code.java.regex.RegexTreeView
18+
import semmle.code.java.regex.RegexFlowConfigs
19+
import semmle.code.java.dataflow.FlowSources
1820
import DataFlow::PathGraph
1921

22+
class PolynomialRedosSink extends DataFlow::Node {
23+
RegExpLiteral reg;
24+
25+
PolynomialRedosSink() { regex_match(reg.getRegex(), this.asExpr()) }
26+
27+
RegExpTerm getRegExp() { result = reg }
28+
}
29+
30+
class PolynomialRedosConfig extends DataFlow::Configuration {
31+
PolynomialRedosConfig() { this = "PolynomialRodisConfig" }
32+
33+
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
34+
35+
override predicate isSink(DataFlow::Node sink) { sink instanceof PolynomialRedosSink }
36+
}
37+
2038
from
21-
PolynomialReDoS::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink,
22-
PolynomialReDoS::Sink sinkNode, PolynomialBackTrackingTerm regexp
39+
PolynomialRedosConfig config, DataFlow::PathNode source, DataFlow::PathNode sink,
40+
PolynomialRedosSink sinkNode, PolynomialBackTrackingTerm regexp
2341
where
2442
config.hasFlowPath(source, sink) and
2543
sinkNode = sink.getNode() and
2644
regexp.getRootTerm() = sinkNode.getRegExp()
27-
// not (
28-
// source.getNode().(Source).getKind() = "url" and
29-
// regexp.isAtEndLine()
30-
// )
31-
select sinkNode.getHighlight(), source, sink,
45+
select sinkNode, source, sink,
3246
"This $@ that depends on $@ may run slow on strings " + regexp.getPrefixMessage() +
3347
"with many repetitions of '" + regexp.getPumpString() + "'.", regexp, "regular expression",
3448
source.getNode(), "a user-provided value"

0 commit comments

Comments
 (0)