mirror of
https://github.com/github/codeql.git
synced 2025-12-24 04:36:35 +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"
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
edges
|
||||||
|
| ThreadResourceAbuse.java:15:25:15:57 | getParameter(...) : String | ThreadResourceAbuse.java:18:28:18:36 | delayTime : Number |
|
||||||
|
| ThreadResourceAbuse.java:18:28:18:36 | delayTime : Number | ThreadResourceAbuse.java:62:18:62:25 | waitTime |
|
||||||
|
| ThreadResourceAbuse.java:25:25:25:73 | getInitParameter(...) : String | ThreadResourceAbuse.java:28:28:28:36 | delayTime : Number |
|
||||||
|
| ThreadResourceAbuse.java:28:28:28:36 | delayTime : Number | ThreadResourceAbuse.java:62:18:62:25 | waitTime |
|
||||||
|
| ThreadResourceAbuse.java:97:27:97:43 | getValue(...) : String | ThreadResourceAbuse.java:100:34:100:42 | delayTime |
|
||||||
|
nodes
|
||||||
|
| ThreadResourceAbuse.java:15:25:15:57 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||||
|
| ThreadResourceAbuse.java:18:28:18:36 | delayTime : Number | semmle.label | delayTime : Number |
|
||||||
|
| ThreadResourceAbuse.java:25:25:25:73 | getInitParameter(...) : String | semmle.label | getInitParameter(...) : String |
|
||||||
|
| ThreadResourceAbuse.java:28:28:28:36 | delayTime : Number | semmle.label | delayTime : Number |
|
||||||
|
| ThreadResourceAbuse.java:62:18:62:25 | waitTime | semmle.label | waitTime |
|
||||||
|
| ThreadResourceAbuse.java:97:27:97:43 | getValue(...) : String | semmle.label | getValue(...) : String |
|
||||||
|
| ThreadResourceAbuse.java:100:34:100:42 | delayTime | semmle.label | delayTime |
|
||||||
|
#select
|
||||||
|
| ThreadResourceAbuse.java:62:18:62:25 | waitTime | ThreadResourceAbuse.java:15:25:15:57 | getParameter(...) : String | ThreadResourceAbuse.java:62:18:62:25 | waitTime | Vulnerability of uncontrolled resource consumption due to $@. | ThreadResourceAbuse.java:15:25:15:57 | getParameter(...) | user-provided value |
|
||||||
|
| ThreadResourceAbuse.java:62:18:62:25 | waitTime | ThreadResourceAbuse.java:25:25:25:73 | getInitParameter(...) : String | ThreadResourceAbuse.java:62:18:62:25 | waitTime | Vulnerability of uncontrolled resource consumption due to $@. | ThreadResourceAbuse.java:25:25:25:73 | getInitParameter(...) | user-provided value |
|
||||||
|
| ThreadResourceAbuse.java:100:34:100:42 | delayTime | ThreadResourceAbuse.java:97:27:97:43 | getValue(...) : String | ThreadResourceAbuse.java:100:34:100:42 | delayTime | Vulnerability of uncontrolled resource consumption due to $@. | ThreadResourceAbuse.java:97:27:97:43 | getValue(...) | user-provided value |
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
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 {
|
||||||
|
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 UncheckedSyncAction(delayTime).start();
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||||
|
// Get thread pause time from init container parameter
|
||||||
|
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 {
|
||||||
|
// Get thread pause time from request cookie
|
||||||
|
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
|
||||||
|
// BAD: no boundary check on wait time
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
Thread.sleep(waitTime);
|
||||||
|
// Do other updates
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CheckedSyncAction extends Thread {
|
||||||
|
int waitTime;
|
||||||
|
|
||||||
|
public CheckedSyncAction(int waitTime) {
|
||||||
|
this.waitTime = waitTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOOD: enforce an upper limit on wait time
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
if (waitTime > 0 && waitTime < 5000) {
|
||||||
|
Thread.sleep(waitTime);
|
||||||
|
// Do other updates
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doHead(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||||
|
// Get thread pause time from request cookie
|
||||||
|
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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
experimental/Security/CWE/CWE-400/ThreadResourceAbuse.ql
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4
|
||||||
Reference in New Issue
Block a user