mirror of
https://github.com/github/codeql.git
synced 2025-12-21 19:26:31 +01:00
Query to detect uncontrolled thread resource consumption
This commit is contained in:
@@ -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") }
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user