diff --git a/src/main/ql/WebClientMissingIdleTimeout.qhelp b/src/main/ql/WebClientMissingIdleTimeout.qhelp new file mode 100644 index 0000000..e344adc --- /dev/null +++ b/src/main/ql/WebClientMissingIdleTimeout.qhelp @@ -0,0 +1,45 @@ + + + +

+ A WebClient created without a WebClientOptions that explicitly sets an idle + timeout may hold open connections indefinitely. This can lead to resource exhaustion, increased latency, + and denial-of-service conditions if remote servers stop responding without closing their connections. +

+
+ + +

+ Always create a WebClientOptions instance, call setIdleTimeout on it with + an appropriate timeout value, and pass the options when calling WebClient.create. +

+
+ + +

Instead of creating a WebClient without an idle timeout, such as in the example below:

+ + + +

+ create a WebClientOptions with an explicit setIdleTimeout and pass it to + WebClient.create, as shown here: +

+ + +
+ + +
  • + + Vert.x Web Client documentation + +
  • +
  • + + Vert.x WebClientOptions API + +
  • +
    +
    diff --git a/src/main/ql/WebClientMissingIdleTimeout.ql b/src/main/ql/WebClientMissingIdleTimeout.ql new file mode 100644 index 0000000..e5a846f --- /dev/null +++ b/src/main/ql/WebClientMissingIdleTimeout.ql @@ -0,0 +1,73 @@ +/** + * @name WebClient created without idle timeout + * @description A WebClient is created without a WebClientOptions that sets an explicit idle timeout. + * This can lead to connections hanging indefinitely, potentially exhausting resources. + * @kind problem + * @problem.severity warning + * @precision high + * @security-severity 5.0 + * @id java/vertx/webclient-missing-idle-timeout + * @tags security java/vertx reliability + */ + +import java + +class WebClient extends RefType { + WebClient() { + this.getASourceSupertype*().hasQualifiedName("io.vertx.ext.web.client", "WebClient") + } +} + +class WebClientOptions extends RefType { + WebClientOptions() { + this.getASourceSupertype*().hasQualifiedName("io.vertx.ext.web.client", "WebClientOptions") + } +} + +class WebClientCreateMethodAccess extends MethodAccess { + WebClientCreateMethodAccess() { + exists(Method m | + this.getMethod() = m and + m.getName() = "create" and + m.getDeclaringType() instanceof WebClient + ) + } +} + +class SetIdleTimeoutMethodAccess extends MethodAccess { + SetIdleTimeoutMethodAccess() { + exists(Method m | + this.getMethod() = m and + m.getName() = "setIdleTimeout" and + m.getDeclaringType() instanceof WebClientOptions + ) + } +} + +/** + * Holds if the given WebClientOptions variable has setIdleTimeout called on it. + */ +predicate optionsHasIdleTimeout(Variable optionsVar) { + exists(SetIdleTimeoutMethodAccess setCall | + setCall.getQualifier().(VarAccess).getVariable() = optionsVar + ) +} + +from WebClientCreateMethodAccess call +where + not call.getEnclosingCallable().getDeclaringType() instanceof WebClient and + not call.getLocation().getFile().getRelativePath().matches("%/src/test/%") and + ( + // No WebClientOptions argument at all + call.getNumArgument() < 2 + or + // A WebClientOptions is passed but setIdleTimeout was never called on it + exists(Variable optionsVar | + call.getArgument(1).(VarAccess).getVariable() = optionsVar and + optionsVar.getType() instanceof WebClientOptions and + not optionsHasIdleTimeout(optionsVar) + ) + ) +select + call, + "WebClient is created without a WebClientOptions that sets an explicit idle timeout." diff --git a/src/main/ql/java/org/carlspring/security/vertx/webclient/InsecureWebClient.java b/src/main/ql/java/org/carlspring/security/vertx/webclient/InsecureWebClient.java new file mode 100644 index 0000000..181fbe0 --- /dev/null +++ b/src/main/ql/java/org/carlspring/security/vertx/webclient/InsecureWebClient.java @@ -0,0 +1,23 @@ +package org.carlspring.security.vertx.webclient; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Vertx; +import io.vertx.ext.web.client.WebClient; + +/** + * @author carlspring + */ +public class InsecureWebClient + extends AbstractVerticle +{ + + @Override + public void start() + { + // Create a WebClient without any WebClientOptions (no idle timeout set) + WebClient client = WebClient.create(vertx); + + // Use client... + } + +} diff --git a/src/main/ql/java/org/carlspring/security/vertx/webclient/SecureWebClient.java b/src/main/ql/java/org/carlspring/security/vertx/webclient/SecureWebClient.java new file mode 100644 index 0000000..c17f0f3 --- /dev/null +++ b/src/main/ql/java/org/carlspring/security/vertx/webclient/SecureWebClient.java @@ -0,0 +1,31 @@ +package org.carlspring.security.vertx.webclient; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Vertx; +import io.vertx.ext.web.client.WebClient; +import io.vertx.ext.web.client.WebClientOptions; + +import java.util.concurrent.TimeUnit; + +/** + * @author carlspring + */ +public class SecureWebClient + extends AbstractVerticle +{ + + @Override + public void start() + { + // Create a WebClientOptions with an explicit idle timeout + WebClientOptions options = new WebClientOptions() + .setIdleTimeout(30) + .setIdleTimeoutUnit(TimeUnit.SECONDS); + + // Create a WebClient with the options + WebClient client = WebClient.create(vertx, options); + + // Use client... + } + +} diff --git a/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/InsecureWebClient.java b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/InsecureWebClient.java new file mode 100644 index 0000000..7891860 --- /dev/null +++ b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/InsecureWebClient.java @@ -0,0 +1,22 @@ +package org.carlspring.security.vertx.webclient; + +import io.vertx.core.AbstractVerticle; +import io.vertx.ext.web.client.WebClient; + +/** + * @author carlspring + */ +public class InsecureWebClient + extends AbstractVerticle +{ + + @Override + public void start() + { + // Create a WebClient without any WebClientOptions (no idle timeout set) + WebClient client = WebClient.create(vertx); + + // Use client... + } + +} diff --git a/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/SecureWebClient.java b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/SecureWebClient.java new file mode 100644 index 0000000..9396f3d --- /dev/null +++ b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/SecureWebClient.java @@ -0,0 +1,30 @@ +package org.carlspring.security.vertx.webclient; + +import io.vertx.core.AbstractVerticle; +import io.vertx.ext.web.client.WebClient; +import io.vertx.ext.web.client.WebClientOptions; + +import java.util.concurrent.TimeUnit; + +/** + * @author carlspring + */ +public class SecureWebClient + extends AbstractVerticle +{ + + @Override + public void start() + { + // Create a WebClientOptions with an explicit idle timeout + WebClientOptions options = new WebClientOptions() + .setIdleTimeout(30) + .setIdleTimeoutUnit(TimeUnit.SECONDS); + + // Create a WebClient with the options + WebClient client = WebClient.create(vertx, options); + + // Use client... + } + +} diff --git a/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/WebClientMissingIdleTimeout.expected b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/WebClientMissingIdleTimeout.expected new file mode 100644 index 0000000..ab00300 --- /dev/null +++ b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/WebClientMissingIdleTimeout.expected @@ -0,0 +1 @@ +| InsecureWebClient.java:17:28:17:50 | create(...) | WebClient is created without a WebClientOptions that sets an explicit idle timeout. | diff --git a/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/WebClientMissingIdleTimeout.qlref b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/WebClientMissingIdleTimeout.qlref new file mode 100644 index 0000000..1d3af31 --- /dev/null +++ b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/WebClientMissingIdleTimeout.qlref @@ -0,0 +1 @@ +WebClientMissingIdleTimeout.ql diff --git a/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/options b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/options new file mode 100644 index 0000000..a16b40d --- /dev/null +++ b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args -cp ${testdir}/../../stubs/ -source 17 diff --git a/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/pom.xml b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/pom.xml new file mode 100644 index 0000000..fa26a06 --- /dev/null +++ b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + org.carlspring.security + vertx-vulns-test-webclient-missing-idle-timeout + 1.0.0-SNAPSHOT + + + 4.4.4 + + + + + + org.apache.maven.plugins + maven-clean-plugin + 3.3.1 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + 17 + true + + + + org.apache.maven.plugins + maven-install-plugin + 3.1.1 + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + true + + + + + + + + io.vertx + vertx-core + ${version.vertx} + + + io.vertx + vertx-web + ${version.vertx} + + + io.vertx + vertx-web-client + ${version.vertx} + + + + + diff --git a/src/test/ql/test/stubs/io/vertx/ext/web/client/WebClient.java b/src/test/ql/test/stubs/io/vertx/ext/web/client/WebClient.java new file mode 100644 index 0000000..c884f62 --- /dev/null +++ b/src/test/ql/test/stubs/io/vertx/ext/web/client/WebClient.java @@ -0,0 +1,12 @@ +// Generated automatically from io.vertx.ext.web.client.WebClient for testing purposes + +package io.vertx.ext.web.client; + +import io.vertx.core.Vertx; + +public class WebClient +{ + public static WebClient create(Vertx vertx){ return null; } + public static WebClient create(Vertx vertx, WebClientOptions options){ return null; } + public WebClient(){} +} diff --git a/src/test/ql/test/stubs/io/vertx/ext/web/client/WebClientOptions.java b/src/test/ql/test/stubs/io/vertx/ext/web/client/WebClientOptions.java new file mode 100644 index 0000000..3b33659 --- /dev/null +++ b/src/test/ql/test/stubs/io/vertx/ext/web/client/WebClientOptions.java @@ -0,0 +1,12 @@ +// Generated automatically from io.vertx.ext.web.client.WebClientOptions for testing purposes + +package io.vertx.ext.web.client; + +import java.util.concurrent.TimeUnit; + +public class WebClientOptions +{ + public WebClientOptions setIdleTimeout(int timeout){ return this; } + public WebClientOptions setIdleTimeoutUnit(TimeUnit unit){ return this; } + public WebClientOptions(){} +}