Query to detect uncontrolled thread resource consumption

This commit is contained in:
luchua-bc
2021-09-20 02:12:47 +00:00
parent 2cbad4aed6
commit 8170f01b66
8 changed files with 315 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
/** Provides sink models related to pausing thread operations. */
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.ExternalFlow
/** 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") }
}

View File

@@ -0,0 +1,40 @@
public class ThreadResourceAbuse extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// Get thread pause time from request parameter
String delayTimeStr = request.getParameter("DelayTime");
try {
int delayTime = Integer.valueOf(delayTimeStr);
new SyncAction(delayTime).start();
} catch (NumberFormatException e) {
}
}
class SyncAction extends Thread {
int waitTime;
public SyncAction(int waitTime) {
this.waitTime = waitTime;
}
@Override
public void run() {
try {
{
// BAD: no boundary check on wait time
Thread.sleep(waitTime);
}
{
// GOOD: enforce an upper limit on wait time
if (waitTime > 0 && waitTime < 5000) {
Thread.sleep(waitTime);
}
}
//Do other updates
} catch (InterruptedException e) {
}
}
}
}

View File

@@ -0,0 +1,39 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p><code>Thread.sleep</code> method is used to pause the execution of current thread for specified
time. When it is used to keep several relevant tasks in synchronization and the sleep time is
user-controlled data, 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>Thread.wait</code> method to avoid performance
problems or even resource exhaustion.</p>
</recommendation>
<example>
<p>The following example shows the bad situation and the good situation respectively. In bad situation,
a thread is spawned with the sleep time directly from user input. In good situation, an upper range
check on maximum allowed sleep time 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)
Affecting com.googlecode.gwtupload:gwtupload artifact</a>.
</li>
<li>
gwtupload
<a href="https://github.com/manolo/gwtupload/issues/33">[Fix DOS issue] Updating the
AbstractUploadListener.java file</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,87 @@
/**
* @name Uncontrolled thread resource consumption
* @description Use user input directly to control thread sleep time could lead to performance problems
* or even resource exhaustion.
* @kind path-problem
* @id java/thread-resource-abuse
* @tags security
* external/cwe/cwe-400
*/
import java
import ThreadPauseSink
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 }
}
private class LessThanSanitizer extends DataFlow::BarrierGuard {
LessThanSanitizer() { this instanceof LTExpr }
override predicate checks(Expr e, boolean branch) {
e = this.(LTExpr).getLeftOperand() and
branch = true
}
}
/** Taint configuration of uncontrolled thread resource consumption. */
class ThreadResourceAbuse extends TaintTracking::Configuration {
ThreadResourceAbuse() { this = "ThreadResourceAbuse" }
override predicate isSource(DataFlow::Node source) {
source instanceof RemoteFlowSource or source instanceof LocalUserInput
}
override predicate isSink(DataFlow::Node sink) { sink instanceof PauseThreadSink }
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(ConditionalExpr ce | ce.getAChildExpr() = node1.asExpr() and ce = node2.asExpr()) // request.getParameter("nodelay") != null ? 0 : sleepTime
or
exists(
Method rm, ClassInstanceExpr ce, Argument arg, FieldAccess fa // thread.start() invokes the run() method of thread implementation
|
rm.hasName("run") and
ce.getConstructedType() = rm.getSourceDeclaration().getDeclaringType() and
ce.getConstructedType().getASupertype*().hasQualifiedName("java.lang", "Runnable") and
ce.getAnArgument() = arg and
fa = rm.getAnAccessedField().getAnAccess() and
arg.getType() = fa.getField().getType() and
node1.asExpr() = arg and
node2.asExpr() = fa
)
}
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"