Merge pull request #6717 from luchua-bc/java/thread-resource-abuse

Java: CWE-400 - Query to detect uncontrolled thread resource consumption
This commit is contained in:
Chris Smowton
2021-11-24 18:59:41 +00:00
committed by GitHub
13 changed files with 638 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
/**
* @name Uncontrolled thread resource consumption from local input source
* @description Using user input directly to control a thread's sleep time could lead to
* performance problems or even resource exhaustion.
* @kind path-problem
* @id java/thread-resource-abuse
* @problem.severity recommendation
* @tags security
* external/cwe/cwe-400
*/
import java
import ThreadResourceAbuse
import semmle.code.java.dataflow.FlowSources
import DataFlow::PathGraph
/** The `getInitParameter` method of servlet or JSF. */
class GetInitParameter extends Method {
GetInitParameter() {
(
this.getDeclaringType()
.getASupertype*()
.hasQualifiedName(["javax.servlet", "jakarta.servlet"],
["FilterConfig", "Registration", "ServletConfig", "ServletContext"]) or
this.getDeclaringType()
.getASupertype*()
.hasQualifiedName(["javax.faces.context", "jakarta.faces.context"], "ExternalContext")
) and
this.getName() = "getInitParameter"
}
}
/** An access to the `getInitParameter` method. */
class GetInitParameterAccess extends MethodAccess {
GetInitParameterAccess() { this.getMethod() instanceof GetInitParameter }
}
/* Init parameter input of a Java EE web application. */
class InitParameterInput extends LocalUserInput {
InitParameterInput() { this.asExpr() instanceof GetInitParameterAccess }
}
/** Taint configuration of uncontrolled thread resource consumption from local user input. */
class ThreadResourceAbuse extends TaintTracking::Configuration {
ThreadResourceAbuse() { this = "ThreadResourceAbuse" }
override predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput }
override predicate isSink(DataFlow::Node sink) { sink instanceof PauseThreadSink }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
any(AdditionalValueStep r).step(pred, succ)
}
override predicate isSanitizer(DataFlow::Node node) {
exists(
MethodAccess ma // Math.min(sleepTime, MAX_INTERVAL)
|
ma.getMethod().hasQualifiedName("java.lang", "Math", "min") and
node.asExpr() = ma.getAnArgument()
)
}
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
guard instanceof LessThanSanitizer // if (sleepTime > 0 && sleepTime < 5000) { ... }
}
}
from DataFlow::PathNode source, DataFlow::PathNode sink, ThreadResourceAbuse conf
where conf.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Possible uncontrolled resource consumption due to $@.",
source.getNode(), "local user-provided value"

View File

@@ -0,0 +1,11 @@
class SleepTest {
public void test(int userSuppliedWaitTime) throws Exception {
// BAD: no boundary check on wait time
Thread.sleep(userSuppliedWaitTime);
// GOOD: enforce an upper limit on wait time
if (userSuppliedWaitTime > 0 && userSuppliedWaitTime < 5000) {
Thread.sleep(userSuppliedWaitTime);
}
}
}

View File

@@ -0,0 +1,48 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>The <code>Thread.sleep</code> method is used to pause the execution of current thread for
specified time. When the sleep time is user-controlled, especially in the web application context,
it can be abused to cause all of a server's threads to sleep, leading to denial of service.</p>
</overview>
<recommendation>
<p>To guard against this attack, consider specifying an upper range of allowed sleep time or adopting
the producer/consumer design pattern with <code>Object.wait</code> method to avoid performance
problems or even resource exhaustion. For more information, refer to the concurrency tutorial of Oracle
listed below or <code>java/ql/src/Likely Bugs/Concurrency</code> queries of CodeQL.</p>
</recommendation>
<example>
<p>The following example shows a bad situation and a good situation respectively. In the bad situation,
a thread sleep time comes directly from user input. In the good situation, an upper
range check on the maximum sleep time allowed is enforced.</p>
<sample src="ThreadResourceAbuse.java" />
</example>
<references>
<li>
Snyk:
<a href="https://snyk.io/vuln/SNYK-JAVA-COMGOOGLECODEGWTUPLOAD-569506">Denial of Service (DoS)
in com.googlecode.gwtupload:gwtupload</a>.
</li>
<li>
gwtupload:
<a href="https://github.com/manolo/gwtupload/issues/33">[Fix DOS issue] Updating the
AbstractUploadListener.java file</a>.
</li>
<li>
The blog of a gypsy engineer:
<a href="https://blog.gypsyengineer.com/en/security/cve-2019-17555-dos-via-retry-after-header-in-apache-olingo.html">
CVE-2019-17555: DoS via Retry-After header in Apache Olingo</a>.
</li>
<li>
Oracle:
<a href="https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html">The Java Concurrency Tutorials</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,47 @@
/**
* @name Uncontrolled thread resource consumption
* @description Using user input directly to control a thread's sleep time could lead to
* performance problems or even resource exhaustion.
* @kind path-problem
* @id java/thread-resource-abuse
* @problem.severity warning
* @tags security
* external/cwe/cwe-400
*/
import java
import ThreadResourceAbuse
import semmle.code.java.dataflow.FlowSources
import DataFlow::PathGraph
/** Taint configuration of uncontrolled thread resource consumption. */
class ThreadResourceAbuse extends TaintTracking::Configuration {
ThreadResourceAbuse() { this = "ThreadResourceAbuse" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof PauseThreadSink }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
any(AdditionalValueStep r).step(pred, succ)
}
override predicate isSanitizer(DataFlow::Node node) {
exists(
MethodAccess ma // Math.min(sleepTime, MAX_INTERVAL)
|
ma.getMethod().hasQualifiedName("java.lang", "Math", "min") and
node.asExpr() = ma.getAnArgument()
)
}
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
guard instanceof LessThanSanitizer // if (sleepTime > 0 && sleepTime < 5000) { ... }
}
}
from DataFlow::PathNode source, DataFlow::PathNode sink, ThreadResourceAbuse conf
where conf.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"Vulnerability of uncontrolled resource consumption due to $@.", source.getNode(),
"user-provided value"

View File

@@ -0,0 +1,78 @@
/** Provides sink models and classes related to pausing thread operations. */
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.ExternalFlow
import semmle.code.java.dataflow.FlowSteps
/** `java.lang.Math` data model for value comparison in the new CSV format. */
private class MathCompDataModel extends SummaryModelCsv {
override predicate row(string row) {
row =
[
"java.lang;Math;false;min;;;Argument[0..1];ReturnValue;value",
"java.lang;Math;false;max;;;Argument[0..1];ReturnValue;value"
]
}
}
/** Thread pause data model in the new CSV format. */
private class PauseThreadDataModel extends SinkModelCsv {
override predicate row(string row) {
row =
[
"java.lang;Thread;true;sleep;;;Argument[0];thread-pause",
"java.util.concurrent;TimeUnit;true;sleep;;;Argument[0];thread-pause"
]
}
}
/** A sink representing methods pausing a thread. */
class PauseThreadSink extends DataFlow::Node {
PauseThreadSink() { sinkNode(this, "thread-pause") }
}
/** A sanitizer for lessThan check. */
class LessThanSanitizer extends DataFlow::BarrierGuard instanceof ComparisonExpr {
override predicate checks(Expr e, boolean branch) {
e = super.getLesserOperand() and
branch = true
or
e = super.getGreaterOperand() and
branch = false
}
}
/** Value step from the constructor call of a `Runnable` to the instance parameter (this) of `run`. */
private class RunnableStartToRunStep extends AdditionalValueStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(ConstructorCall cc, Method m |
m.getDeclaringType() = cc.getConstructedType().getSourceDeclaration() and
cc.getConstructedType().getASupertype*().hasQualifiedName("java.lang", "Runnable") and
m.hasName("run")
|
pred.asExpr() = cc and
succ.(DataFlow::InstanceParameterNode).getEnclosingCallable() = m
)
}
}
/**
* Value step from the constructor call of a `ProgressListener` of Apache File Upload to the
* instance parameter (this) of `update`.
*/
private class ApacheFileUploadProgressUpdateStep extends AdditionalValueStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(ConstructorCall cc, Method m |
m.getDeclaringType() = cc.getConstructedType().getSourceDeclaration() and
cc.getConstructedType()
.getASupertype*()
.hasQualifiedName(["org.apache.commons.fileupload", "org.apache.commons.fileupload2"],
"ProgressListener") and
m.hasName("update")
|
pred.asExpr() = cc and
succ.(DataFlow::InstanceParameterNode).getEnclosingCallable() = m
)
}
}

View File

@@ -0,0 +1,23 @@
edges
| ThreadResourceAbuse.java:37:25:37:73 | getInitParameter(...) : String | ThreadResourceAbuse.java:40:28:40:36 | delayTime : Number |
| ThreadResourceAbuse.java:40:4:40:37 | new UncheckedSyncAction(...) [waitTime] : Number | ThreadResourceAbuse.java:71:15:71:17 | parameter this [waitTime] : Number |
| ThreadResourceAbuse.java:40:28:40:36 | delayTime : Number | ThreadResourceAbuse.java:40:4:40:37 | new UncheckedSyncAction(...) [waitTime] : Number |
| ThreadResourceAbuse.java:40:28:40:36 | delayTime : Number | ThreadResourceAbuse.java:66:30:66:41 | waitTime : Number |
| ThreadResourceAbuse.java:66:30:66:41 | waitTime : Number | ThreadResourceAbuse.java:67:20:67:27 | waitTime : Number |
| ThreadResourceAbuse.java:67:20:67:27 | waitTime : Number | ThreadResourceAbuse.java:67:4:67:7 | this [post update] [waitTime] : Number |
| ThreadResourceAbuse.java:71:15:71:17 | parameter this [waitTime] : Number | ThreadResourceAbuse.java:74:18:74:25 | this <.field> [waitTime] : Number |
| ThreadResourceAbuse.java:74:18:74:25 | this <.field> [waitTime] : Number | ThreadResourceAbuse.java:74:18:74:25 | waitTime |
nodes
| ThreadResourceAbuse.java:37:25:37:73 | getInitParameter(...) : String | semmle.label | getInitParameter(...) : String |
| ThreadResourceAbuse.java:40:4:40:37 | new UncheckedSyncAction(...) [waitTime] : Number | semmle.label | new UncheckedSyncAction(...) [waitTime] : Number |
| ThreadResourceAbuse.java:40:28:40:36 | delayTime : Number | semmle.label | delayTime : Number |
| ThreadResourceAbuse.java:66:30:66:41 | waitTime : Number | semmle.label | waitTime : Number |
| ThreadResourceAbuse.java:67:4:67:7 | this [post update] [waitTime] : Number | semmle.label | this [post update] [waitTime] : Number |
| ThreadResourceAbuse.java:67:20:67:27 | waitTime : Number | semmle.label | waitTime : Number |
| ThreadResourceAbuse.java:71:15:71:17 | parameter this [waitTime] : Number | semmle.label | parameter this [waitTime] : Number |
| ThreadResourceAbuse.java:74:18:74:25 | this <.field> [waitTime] : Number | semmle.label | this <.field> [waitTime] : Number |
| ThreadResourceAbuse.java:74:18:74:25 | waitTime | semmle.label | waitTime |
subpaths
| ThreadResourceAbuse.java:40:28:40:36 | delayTime : Number | ThreadResourceAbuse.java:66:30:66:41 | waitTime : Number | ThreadResourceAbuse.java:67:4:67:7 | this [post update] [waitTime] : Number | ThreadResourceAbuse.java:40:4:40:37 | new UncheckedSyncAction(...) [waitTime] : Number |
#select
| ThreadResourceAbuse.java:74:18:74:25 | waitTime | ThreadResourceAbuse.java:37:25:37:73 | getInitParameter(...) : String | ThreadResourceAbuse.java:74:18:74:25 | waitTime | Possible uncontrolled resource consumption due to $@. | ThreadResourceAbuse.java:37:25:37:73 | getInitParameter(...) | local user-provided value |

View File

@@ -0,0 +1 @@
experimental/Security/CWE/CWE-400/LocalThreadResourceAbuse.ql

View File

@@ -0,0 +1,68 @@
edges
| ThreadResourceAbuse.java:18:25:18:57 | getParameter(...) : String | ThreadResourceAbuse.java:21:28:21:36 | delayTime : Number |
| ThreadResourceAbuse.java:21:4:21:37 | new UncheckedSyncAction(...) [waitTime] : Number | ThreadResourceAbuse.java:71:15:71:17 | parameter this [waitTime] : Number |
| ThreadResourceAbuse.java:21:28:21:36 | delayTime : Number | ThreadResourceAbuse.java:21:4:21:37 | new UncheckedSyncAction(...) [waitTime] : Number |
| ThreadResourceAbuse.java:21:28:21:36 | delayTime : Number | ThreadResourceAbuse.java:66:30:66:41 | waitTime : Number |
| ThreadResourceAbuse.java:29:82:29:114 | getParameter(...) : String | ThreadResourceAbuse.java:30:28:30:36 | delayTime : Number |
| ThreadResourceAbuse.java:30:4:30:37 | new UncheckedSyncAction(...) [waitTime] : Number | ThreadResourceAbuse.java:71:15:71:17 | parameter this [waitTime] : Number |
| ThreadResourceAbuse.java:30:28:30:36 | delayTime : Number | ThreadResourceAbuse.java:30:4:30:37 | new UncheckedSyncAction(...) [waitTime] : Number |
| ThreadResourceAbuse.java:30:28:30:36 | delayTime : Number | ThreadResourceAbuse.java:66:30:66:41 | waitTime : Number |
| ThreadResourceAbuse.java:66:30:66:41 | waitTime : Number | ThreadResourceAbuse.java:67:20:67:27 | waitTime : Number |
| ThreadResourceAbuse.java:67:20:67:27 | waitTime : Number | ThreadResourceAbuse.java:67:4:67:7 | this [post update] [waitTime] : Number |
| ThreadResourceAbuse.java:71:15:71:17 | parameter this [waitTime] : Number | ThreadResourceAbuse.java:74:18:74:25 | this <.field> [waitTime] : Number |
| ThreadResourceAbuse.java:74:18:74:25 | this <.field> [waitTime] : Number | ThreadResourceAbuse.java:74:18:74:25 | waitTime |
| ThreadResourceAbuse.java:141:27:141:43 | getValue(...) : String | ThreadResourceAbuse.java:144:34:144:42 | delayTime |
| ThreadResourceAbuse.java:172:19:172:50 | getHeader(...) : String | ThreadResourceAbuse.java:176:17:176:26 | retryAfter |
| ThreadResourceAbuse.java:206:28:206:56 | getParameter(...) : String | ThreadResourceAbuse.java:209:49:209:59 | uploadDelay : Number |
| ThreadResourceAbuse.java:209:30:209:87 | new UploadListener(...) [slowUploads] : Number | UploadListener.java:28:14:28:19 | parameter this [slowUploads] : Number |
| ThreadResourceAbuse.java:209:49:209:59 | uploadDelay : Number | ThreadResourceAbuse.java:209:30:209:87 | new UploadListener(...) [slowUploads] : Number |
| ThreadResourceAbuse.java:209:49:209:59 | uploadDelay : Number | UploadListener.java:15:24:15:44 | sleepMilliseconds : Number |
| UploadListener.java:15:24:15:44 | sleepMilliseconds : Number | UploadListener.java:16:17:16:33 | sleepMilliseconds : Number |
| UploadListener.java:16:17:16:33 | sleepMilliseconds : Number | UploadListener.java:16:3:16:13 | this <.field> [post update] [slowUploads] : Number |
| UploadListener.java:28:14:28:19 | parameter this [slowUploads] : Number | UploadListener.java:29:3:29:11 | this <.field> [slowUploads] : Number |
| UploadListener.java:29:3:29:11 | this <.field> [slowUploads] : Number | UploadListener.java:30:3:30:15 | this <.field> [slowUploads] : Number |
| UploadListener.java:30:3:30:15 | this <.field> [slowUploads] : Number | UploadListener.java:33:7:33:17 | this <.field> [slowUploads] : Number |
| UploadListener.java:30:3:30:15 | this <.field> [slowUploads] : Number | UploadListener.java:35:18:35:28 | this <.field> [slowUploads] : Number |
| UploadListener.java:33:7:33:17 | slowUploads : Number | UploadListener.java:35:18:35:28 | slowUploads |
| UploadListener.java:33:7:33:17 | this <.field> [slowUploads] : Number | UploadListener.java:33:7:33:17 | slowUploads : Number |
| UploadListener.java:35:18:35:28 | this <.field> [slowUploads] : Number | UploadListener.java:35:18:35:28 | slowUploads |
nodes
| ThreadResourceAbuse.java:18:25:18:57 | getParameter(...) : String | semmle.label | getParameter(...) : String |
| ThreadResourceAbuse.java:21:4:21:37 | new UncheckedSyncAction(...) [waitTime] : Number | semmle.label | new UncheckedSyncAction(...) [waitTime] : Number |
| ThreadResourceAbuse.java:21:28:21:36 | delayTime : Number | semmle.label | delayTime : Number |
| ThreadResourceAbuse.java:29:82:29:114 | getParameter(...) : String | semmle.label | getParameter(...) : String |
| ThreadResourceAbuse.java:30:4:30:37 | new UncheckedSyncAction(...) [waitTime] : Number | semmle.label | new UncheckedSyncAction(...) [waitTime] : Number |
| ThreadResourceAbuse.java:30:28:30:36 | delayTime : Number | semmle.label | delayTime : Number |
| ThreadResourceAbuse.java:66:30:66:41 | waitTime : Number | semmle.label | waitTime : Number |
| ThreadResourceAbuse.java:67:4:67:7 | this [post update] [waitTime] : Number | semmle.label | this [post update] [waitTime] : Number |
| ThreadResourceAbuse.java:67:20:67:27 | waitTime : Number | semmle.label | waitTime : Number |
| ThreadResourceAbuse.java:71:15:71:17 | parameter this [waitTime] : Number | semmle.label | parameter this [waitTime] : Number |
| ThreadResourceAbuse.java:74:18:74:25 | this <.field> [waitTime] : Number | semmle.label | this <.field> [waitTime] : Number |
| ThreadResourceAbuse.java:74:18:74:25 | waitTime | semmle.label | waitTime |
| ThreadResourceAbuse.java:141:27:141:43 | getValue(...) : String | semmle.label | getValue(...) : String |
| ThreadResourceAbuse.java:144:34:144:42 | delayTime | semmle.label | delayTime |
| ThreadResourceAbuse.java:172:19:172:50 | getHeader(...) : String | semmle.label | getHeader(...) : String |
| ThreadResourceAbuse.java:176:17:176:26 | retryAfter | semmle.label | retryAfter |
| ThreadResourceAbuse.java:206:28:206:56 | getParameter(...) : String | semmle.label | getParameter(...) : String |
| ThreadResourceAbuse.java:209:30:209:87 | new UploadListener(...) [slowUploads] : Number | semmle.label | new UploadListener(...) [slowUploads] : Number |
| ThreadResourceAbuse.java:209:49:209:59 | uploadDelay : Number | semmle.label | uploadDelay : Number |
| UploadListener.java:15:24:15:44 | sleepMilliseconds : Number | semmle.label | sleepMilliseconds : Number |
| UploadListener.java:16:3:16:13 | this <.field> [post update] [slowUploads] : Number | semmle.label | this <.field> [post update] [slowUploads] : Number |
| UploadListener.java:16:17:16:33 | sleepMilliseconds : Number | semmle.label | sleepMilliseconds : Number |
| UploadListener.java:28:14:28:19 | parameter this [slowUploads] : Number | semmle.label | parameter this [slowUploads] : Number |
| UploadListener.java:29:3:29:11 | this <.field> [slowUploads] : Number | semmle.label | this <.field> [slowUploads] : Number |
| UploadListener.java:30:3:30:15 | this <.field> [slowUploads] : Number | semmle.label | this <.field> [slowUploads] : Number |
| UploadListener.java:33:7:33:17 | slowUploads : Number | semmle.label | slowUploads : Number |
| UploadListener.java:33:7:33:17 | this <.field> [slowUploads] : Number | semmle.label | this <.field> [slowUploads] : Number |
| UploadListener.java:35:18:35:28 | slowUploads | semmle.label | slowUploads |
| UploadListener.java:35:18:35:28 | this <.field> [slowUploads] : Number | semmle.label | this <.field> [slowUploads] : Number |
subpaths
| ThreadResourceAbuse.java:21:28:21:36 | delayTime : Number | ThreadResourceAbuse.java:66:30:66:41 | waitTime : Number | ThreadResourceAbuse.java:67:4:67:7 | this [post update] [waitTime] : Number | ThreadResourceAbuse.java:21:4:21:37 | new UncheckedSyncAction(...) [waitTime] : Number |
| ThreadResourceAbuse.java:30:28:30:36 | delayTime : Number | ThreadResourceAbuse.java:66:30:66:41 | waitTime : Number | ThreadResourceAbuse.java:67:4:67:7 | this [post update] [waitTime] : Number | ThreadResourceAbuse.java:30:4:30:37 | new UncheckedSyncAction(...) [waitTime] : Number |
| ThreadResourceAbuse.java:209:49:209:59 | uploadDelay : Number | UploadListener.java:15:24:15:44 | sleepMilliseconds : Number | UploadListener.java:16:3:16:13 | this <.field> [post update] [slowUploads] : Number | ThreadResourceAbuse.java:209:30:209:87 | new UploadListener(...) [slowUploads] : Number |
#select
| ThreadResourceAbuse.java:74:18:74:25 | waitTime | ThreadResourceAbuse.java:18:25:18:57 | getParameter(...) : String | ThreadResourceAbuse.java:74:18:74:25 | waitTime | Vulnerability of uncontrolled resource consumption due to $@. | ThreadResourceAbuse.java:18:25:18:57 | getParameter(...) | user-provided value |
| ThreadResourceAbuse.java:74:18:74:25 | waitTime | ThreadResourceAbuse.java:29:82:29:114 | getParameter(...) : String | ThreadResourceAbuse.java:74:18:74:25 | waitTime | Vulnerability of uncontrolled resource consumption due to $@. | ThreadResourceAbuse.java:29:82:29:114 | getParameter(...) | user-provided value |
| ThreadResourceAbuse.java:144:34:144:42 | delayTime | ThreadResourceAbuse.java:141:27:141:43 | getValue(...) : String | ThreadResourceAbuse.java:144:34:144:42 | delayTime | Vulnerability of uncontrolled resource consumption due to $@. | ThreadResourceAbuse.java:141:27:141:43 | getValue(...) | user-provided value |
| ThreadResourceAbuse.java:176:17:176:26 | retryAfter | ThreadResourceAbuse.java:172:19:172:50 | getHeader(...) : String | ThreadResourceAbuse.java:176:17:176:26 | retryAfter | Vulnerability of uncontrolled resource consumption due to $@. | ThreadResourceAbuse.java:172:19:172:50 | getHeader(...) | user-provided value |
| UploadListener.java:35:18:35:28 | slowUploads | ThreadResourceAbuse.java:206:28:206:56 | getParameter(...) : String | UploadListener.java:35:18:35:28 | slowUploads | Vulnerability of uncontrolled resource consumption due to $@. | ThreadResourceAbuse.java:206:28:206:56 | getParameter(...) | user-provided value |

View File

@@ -0,0 +1,212 @@
package test.cwe400.cwe.examples;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ThreadResourceAbuse extends HttpServlet {
static final int DEFAULT_RETRY_AFTER = 5*1000;
static final int MAX_RETRY_AFTER = 10*1000;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// BAD: Get thread pause time from request parameter without validation
String delayTimeStr = request.getParameter("DelayTime");
try {
int delayTime = Integer.valueOf(delayTimeStr);
new UncheckedSyncAction(delayTime).start();
} catch (NumberFormatException e) {
}
}
protected void doGet2(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// BAD: Get thread pause time from request parameter without validation
try {
int delayTime = request.getParameter("nodelay") != null ? 0 : Integer.valueOf(request.getParameter("DelayTime"));
new UncheckedSyncAction(delayTime).start();
} catch (NumberFormatException e) {
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// BAD: Get thread pause time from context init parameter without validation
String delayTimeStr = getServletContext().getInitParameter("DelayTime");
try {
int delayTime = Integer.valueOf(delayTimeStr);
new UncheckedSyncAction(delayTime).start();
} catch (NumberFormatException e) {
}
}
protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// GOOD: Get thread pause time from request cookie with validation
Cookie[] cookies = request.getCookies();
for ( int i=0; i<cookies.length; i++) {
Cookie cookie = cookies[i];
if (cookie.getName().equals("DelayTime")) {
String delayTimeStr = cookie.getValue();
try {
int delayTime = Integer.valueOf(delayTimeStr);
new CheckedSyncAction(delayTime).start();
} catch (NumberFormatException e) {
}
}
}
}
class UncheckedSyncAction extends Thread {
int waitTime;
public UncheckedSyncAction(int waitTime) {
this.waitTime = waitTime;
}
@Override
public void run() {
// BAD: no boundary check on wait time
try {
Thread.sleep(waitTime);
// Do other updates
} catch (InterruptedException e) {
}
}
}
class CheckedSyncAction extends Thread {
int waitTime;
public CheckedSyncAction(int waitTime) {
this.waitTime = waitTime;
}
@Override
public void run() {
// GOOD: enforce an upper limit on wait time
try {
if (waitTime > 0 && waitTime < 5000) {
Thread.sleep(waitTime);
// Do other updates
}
} catch (InterruptedException e) {
}
}
}
class CheckedSyncAction2 extends Thread {
int waitTime;
public CheckedSyncAction2(int waitTime) {
this.waitTime = waitTime;
}
@Override
public void run() {
// GOOD: enforce an upper limit on wait time
try {
if (waitTime >= 5000) {
// No action
} else {
Thread.sleep(waitTime);
}
// Do other updates
} catch (InterruptedException e) {
}
}
}
protected void doPost2(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// GOOD: Get thread pause time from init container parameter with validation
String delayTimeStr = getServletContext().getInitParameter("DelayTime");
try {
int delayTime = Integer.valueOf(delayTimeStr);
new CheckedSyncAction2(delayTime).start();
} catch (NumberFormatException e) {
}
}
protected void doHead(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// BAD: Get thread pause time from request cookie without validation
Cookie[] cookies = request.getCookies();
for ( int i=0; i<cookies.length; i++) {
Cookie cookie = cookies[i];
if (cookie.getName().equals("DelayTime")) {
String delayTimeStr = cookie.getValue();
try {
int delayTime = Integer.valueOf(delayTimeStr);
TimeUnit.MILLISECONDS.sleep(delayTime);
// Do other updates
} catch (NumberFormatException ne) {
} catch (InterruptedException ie) {
}
}
}
}
int parseRetryAfter(String value) {
if (value == null || value.isEmpty()) {
return DEFAULT_RETRY_AFTER;
}
try {
int n = Integer.parseInt(value);
if (n < 0) {
return DEFAULT_RETRY_AFTER;
}
return Math.min(n, MAX_RETRY_AFTER);
} catch (NumberFormatException e) {
return DEFAULT_RETRY_AFTER;
}
}
protected void doHead2(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// BAD: Get thread pause time from request header without validation
String header = request.getHeader("Retry-After");
int retryAfter = Integer.parseInt(header);
try {
Thread.sleep(retryAfter);
} catch (InterruptedException ignore) {
// ignore
}
}
protected void doHead3(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// GOOD: Get thread pause time from request header with validation
String header = request.getHeader("Retry-After");
int retryAfter = parseRetryAfter(header);
try {
Thread.sleep(retryAfter);
} catch (InterruptedException ignore) {
// ignore
}
}
private long getContentLength(HttpServletRequest request) {
long size = -1;
try {
size = Long.parseLong(request.getHeader("Content-length"));
} catch (NumberFormatException e) {
}
return size;
}
protected void doHead4(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// BAD: Get thread pause time from request header without validation
try {
String uploadDelayStr = request.getParameter("delay");
int uploadDelay = Integer.parseInt(uploadDelayStr);
UploadListener listener = new UploadListener(uploadDelay, getContentLength(request));
} catch (Exception e) { }
}
}

View File

@@ -0,0 +1 @@
experimental/Security/CWE/CWE-400/ThreadResourceAbuse.ql

View File

@@ -0,0 +1,40 @@
package test.cwe400.cwe.examples;
import java.io.Serializable;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload2.ProgressListener;
public class UploadListener implements ProgressListener, Serializable {
protected int slowUploads = 0;
private Long bytesRead = 0L;
private long contentLength = 0L;
public UploadListener(int sleepMilliseconds, long requestSize) {
slowUploads = sleepMilliseconds;
contentLength = requestSize;
}
public long getPercent() {
return contentLength != 0 ? bytesRead * 100 / contentLength : 0;
}
public long getBytesRead() {
return bytesRead;
}
public void update(long done, long total, int item) {
bytesRead = done;
contentLength = total;
// Just a way to slow down the upload process and see the progress bar in fast networks.
if (slowUploads > 0 && done < total) {
try {
Thread.sleep(slowUploads);
} catch (Exception e) {
}
}
}
}

View File

@@ -0,0 +1 @@
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/apache-commons-fileupload-1.4

View File

@@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.fileupload2;
/**
* The {@link ProgressListener} may be used to display a progress bar
* or do stuff like that.
*/
public interface ProgressListener {
/**
* Updates the listeners status information.
*
* @param pBytesRead The total number of bytes, which have been read
* so far.
* @param pContentLength The total number of bytes, which are being
* read. May be -1, if this number is unknown.
* @param pItems The number of the field, which is currently being
* read. (0 = no item so far, 1 = first item is being read, ...)
*/
void update(long pBytesRead, long pContentLength, int pItems);
}