CVE-2024-1726
Description
A flaw was discovered in the RESTEasy Reactive implementation in Quarkus. Due to security checks for some JAX-RS endpoints being performed after serialization, more processing resources are consumed while the HTTP request is checked. In certain configurations, if an attacker has knowledge of any POST, PUT, or PATCH request paths, they can potentially identify vulnerable endpoints and trigger excessive resource usage as the endpoints process the requests. This can result in a denial of service.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
io.quarkus.resteasy.reactive:resteasy-reactiveMaven | >= 3.8.0.CR1, < 3.8.0 | 3.8.0 |
io.quarkus.resteasy.reactive:resteasy-reactiveMaven | >= 3.3.0.CR1, < 3.7.4 | 3.7.4 |
io.quarkus.resteasy.reactive:resteasy-reactiveMaven | < 3.2.11.Final | 3.2.11.Final |
Patches
296d93427f3b4[3.2] Perform security checks eagerly in RR on inherited endpoints
9 files changed · +106 −3
extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/DenyAllJaxRsTest.java+33 −0 modified@@ -91,6 +91,39 @@ public void shouldDenyUnannotatedOnParentClass() { assertStatus(path, 403, 401); } + @Test + public void shouldAllowAnnotatedParentEndpoint() { + // the endpoint has @RolesAllowed, therefore default JAX-RS security should not be applied + String path = "/unsecured/parent-annotated"; + assertStatus(path, 200, 401); + } + + @Test + public void shouldAllowAnnotatedEndpointOnInterface() { + // the endpoint has @RolesAllowed, therefore default JAX-RS security should not be applied + String path = "/unsecured/interface-annotated"; + assertStatus(path, 200, 401); + } + + @Test + public void shouldDenyUnannotatedOverriddenOnInterfaceImplementor() { + // @RolesAllowed on interface, however implementor overridden the endpoint method with @Path @GET + String path = "/unsecured/interface-overridden-declared-on-implementor"; + assertStatus(path, 403, 401); + } + + @Test + public void shouldAllowAnnotatedOverriddenEndpointDeclaredOnInterface() { + // @RolesAllowed on interface and implementor didn't declare endpoint declaring annotations @GET + String path = "/unsecured/interface-overridden-declared-on-interface"; + assertStatus(path, 200, 401); + // check that response comes from the overridden method + given().auth().preemptive() + .basic("admin", "admin").get(path) + .then() + .body(Matchers.is("implementor-response")); + } + @Test public void shouldDenyUnannotatedOnInterface() { String path = "/unsecured/defaultSecurityInterface";
extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredParentResource.java+8 −0 modified@@ -1,5 +1,6 @@ package io.quarkus.resteasy.reactive.server.test.security; +import jakarta.annotation.security.RolesAllowed; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -11,4 +12,11 @@ public String defaultSecurityParent() { return "defaultSecurityParent"; } + @RolesAllowed({ "admin", "user" }) + @GET + @Path("/parent-annotated") + public String parentAnnotated() { + return "parent-annotated"; + } + }
extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredResourceInterface.java+24 −0 modified@@ -1,5 +1,6 @@ package io.quarkus.resteasy.reactive.server.test.security; +import jakarta.annotation.security.RolesAllowed; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -11,4 +12,27 @@ default String defaultSecurityInterface() { return "defaultSecurityInterface"; } + @RolesAllowed({ "admin", "user" }) + @GET + @Path("/interface-annotated") + default String interfaceAnnotated() { + return "interface-annotated"; + } + + @RolesAllowed({ "admin", "user" }) + @GET + @Path("/interface-overridden-declared-on-interface") + default String interfaceOverriddenDeclaredOnInterface() { + // this interface is overridden without @GET and @Path + return "interface-overridden-declared-on-interface"; + } + + @RolesAllowed({ "admin", "user" }) + @GET + @Path("/interface-overridden-declared-on-implementor") + default String interfaceOverriddenDeclaredOnImplementor() { + // this interface is overridden with @GET and @Path + return "interface-overridden-declared-on-implementor"; + } + }
extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredResource.java+12 −0 modified@@ -49,4 +49,16 @@ public UnsecuredSubResource sub() { public UnsecuredSubResource permitAllSub() { return new UnsecuredSubResource(); } + + @Override + public String interfaceOverriddenDeclaredOnInterface() { + return "implementor-response"; + } + + @GET + @Path("/interface-overridden-declared-on-implementor") + @Override + public String interfaceOverriddenDeclaredOnImplementor() { + return "implementor-response"; + } }
extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/EagerSecurityHandler.java+1 −1 modified@@ -57,7 +57,7 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti } SecurityCheck check = this.check; ResteasyReactiveResourceInfo lazyMethod = requestContext.getTarget().getLazyMethod(); - MethodDescription methodDescription = new MethodDescription(lazyMethod.getResourceClass().getName(), + MethodDescription methodDescription = new MethodDescription(lazyMethod.getActualDeclaringClassName(), lazyMethod.getName(), MethodDescription.typesAsStrings(lazyMethod.getParameterTypes())); if (check == null) { SecurityCheckStorage storage = Arc.container().instance(SecurityCheckStorage.class).get();
independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java+1 −0 modified@@ -183,6 +183,7 @@ protected ServerResourceMethod createResourceMethod(MethodInfo methodInfo, Class } } serverResourceMethod.setHandlerChainCustomizers(methodCustomizers); + serverResourceMethod.setActualDeclaringClassName(methodInfo.declaringClass().name().toString()); return serverResourceMethod; }
independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java+1 −1 modified@@ -189,7 +189,7 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, ResteasyReactiveResourceInfo lazyMethod = new ResteasyReactiveResourceInfo(method.getName(), resourceClass, parameterDeclaredUnresolvedTypes, classAnnotationNames, method.getMethodAnnotationNames(), - !defaultBlocking && !method.isBlocking()); + !defaultBlocking && !method.isBlocking(), method.getActualDeclaringClassName()); RuntimeInterceptorDeployment.MethodInterceptorContext interceptorDeployment = runtimeInterceptorDeployment .forMethod(method, lazyMethod);
independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerResourceMethod.java+9 −0 modified@@ -18,6 +18,7 @@ public class ServerResourceMethod extends ResourceMethod { private List<HandlerChainCustomizer> handlerChainCustomizers = new ArrayList<>(); private ParameterExtractor customerParameterExtractor; + private String actualDeclaringClassName; public ServerResourceMethod() { } @@ -70,4 +71,12 @@ public ServerResourceMethod setCustomerParameterExtractor(ParameterExtractor cus this.customerParameterExtractor = customerParameterExtractor; return this; } + + public String getActualDeclaringClassName() { + return actualDeclaringClassName; + } + + public void setActualDeclaringClassName(String actualDeclaringClassName) { + this.actualDeclaringClassName = actualDeclaringClassName; + } }
independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ResteasyReactiveResourceInfo.java+17 −1 modified@@ -26,21 +26,33 @@ public class ResteasyReactiveResourceInfo implements ResourceInfo { * If it's non-blocking method within the runtime that won't always default to blocking */ public final boolean isNonBlocking; - + /** + * This class name will only differ from {@link this#declaringClass} name when the {@link this#method} was inherited. + */ + private final String actualDeclaringClassName; private volatile Annotation[] classAnnotations; private volatile Method method; private volatile Annotation[] annotations; private volatile Type returnType; private volatile String methodId; + @Deprecated public ResteasyReactiveResourceInfo(String name, Class<?> declaringClass, Class[] parameterTypes, Set<String> classAnnotationNames, Set<String> methodAnnotationNames, boolean isNonBlocking) { + this(name, declaringClass, parameterTypes, classAnnotationNames, methodAnnotationNames, isNonBlocking, + declaringClass.getName()); + } + + public ResteasyReactiveResourceInfo(String name, Class<?> declaringClass, Class[] parameterTypes, + Set<String> classAnnotationNames, Set<String> methodAnnotationNames, boolean isNonBlocking, + String actualDeclaringClassName) { this.name = name; this.declaringClass = declaringClass; this.parameterTypes = parameterTypes; this.classAnnotationNames = classAnnotationNames; this.methodAnnotationNames = methodAnnotationNames; this.isNonBlocking = isNonBlocking; + this.actualDeclaringClassName = actualDeclaringClassName; } public String getName() { @@ -119,4 +131,8 @@ public String getMethodId() { } return methodId; } + + public String getActualDeclaringClassName() { + return actualDeclaringClassName; + } }
34c1a63baf54Perform security checks eagerly in RR on inherited endpoints
9 files changed · +106 −3
extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/DenyAllJaxRsTest.java+33 −0 modified@@ -91,6 +91,39 @@ public void shouldDenyUnannotatedOnParentClass() { assertStatus(path, 403, 401); } + @Test + public void shouldAllowAnnotatedParentEndpoint() { + // the endpoint has @RolesAllowed, therefore default JAX-RS security should not be applied + String path = "/unsecured/parent-annotated"; + assertStatus(path, 200, 401); + } + + @Test + public void shouldAllowAnnotatedEndpointOnInterface() { + // the endpoint has @RolesAllowed, therefore default JAX-RS security should not be applied + String path = "/unsecured/interface-annotated"; + assertStatus(path, 200, 401); + } + + @Test + public void shouldDenyUnannotatedOverriddenOnInterfaceImplementor() { + // @RolesAllowed on interface, however implementor overridden the endpoint method with @Path @GET + String path = "/unsecured/interface-overridden-declared-on-implementor"; + assertStatus(path, 403, 401); + } + + @Test + public void shouldAllowAnnotatedOverriddenEndpointDeclaredOnInterface() { + // @RolesAllowed on interface and implementor didn't declare endpoint declaring annotations @GET + String path = "/unsecured/interface-overridden-declared-on-interface"; + assertStatus(path, 200, 401); + // check that response comes from the overridden method + given().auth().preemptive() + .basic("admin", "admin").get(path) + .then() + .body(Matchers.is("implementor-response")); + } + @Test public void shouldDenyUnannotatedOnInterface() { String path = "/unsecured/defaultSecurityInterface";
extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredParentResource.java+8 −0 modified@@ -1,5 +1,6 @@ package io.quarkus.resteasy.reactive.server.test.security; +import jakarta.annotation.security.RolesAllowed; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -11,4 +12,11 @@ public String defaultSecurityParent() { return "defaultSecurityParent"; } + @RolesAllowed({ "admin", "user" }) + @GET + @Path("/parent-annotated") + public String parentAnnotated() { + return "parent-annotated"; + } + }
extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredResourceInterface.java+24 −0 modified@@ -1,5 +1,6 @@ package io.quarkus.resteasy.reactive.server.test.security; +import jakarta.annotation.security.RolesAllowed; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -11,4 +12,27 @@ default String defaultSecurityInterface() { return "defaultSecurityInterface"; } + @RolesAllowed({ "admin", "user" }) + @GET + @Path("/interface-annotated") + default String interfaceAnnotated() { + return "interface-annotated"; + } + + @RolesAllowed({ "admin", "user" }) + @GET + @Path("/interface-overridden-declared-on-interface") + default String interfaceOverriddenDeclaredOnInterface() { + // this interface is overridden without @GET and @Path + return "interface-overridden-declared-on-interface"; + } + + @RolesAllowed({ "admin", "user" }) + @GET + @Path("/interface-overridden-declared-on-implementor") + default String interfaceOverriddenDeclaredOnImplementor() { + // this interface is overridden with @GET and @Path + return "interface-overridden-declared-on-implementor"; + } + }
extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/security/UnsecuredResource.java+12 −0 modified@@ -57,4 +57,16 @@ public UnsecuredSubResource sub() { public UnsecuredSubResource permitAllSub() { return new UnsecuredSubResource(); } + + @Override + public String interfaceOverriddenDeclaredOnInterface() { + return "implementor-response"; + } + + @GET + @Path("/interface-overridden-declared-on-implementor") + @Override + public String interfaceOverriddenDeclaredOnImplementor() { + return "implementor-response"; + } }
extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/security/EagerSecurityHandler.java+1 −1 modified@@ -187,7 +187,7 @@ private static Map<String, Object> createEventPropsWithRoutingCtx(ResteasyReacti } static MethodDescription lazyMethodToMethodDescription(ResteasyReactiveResourceInfo lazyMethod) { - return new MethodDescription(lazyMethod.getResourceClass().getName(), + return new MethodDescription(lazyMethod.getActualDeclaringClassName(), lazyMethod.getName(), MethodDescription.typesAsStrings(lazyMethod.getParameterTypes())); }
independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java+1 −0 modified@@ -183,6 +183,7 @@ protected ServerResourceMethod createResourceMethod(MethodInfo methodInfo, Class } } serverResourceMethod.setHandlerChainCustomizers(methodCustomizers); + serverResourceMethod.setActualDeclaringClassName(methodInfo.declaringClass().name().toString()); return serverResourceMethod; }
independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java+1 −1 modified@@ -189,7 +189,7 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, ResteasyReactiveResourceInfo lazyMethod = new ResteasyReactiveResourceInfo(method.getName(), resourceClass, parameterDeclaredUnresolvedTypes, classAnnotationNames, method.getMethodAnnotationNames(), - !defaultBlocking && !method.isBlocking()); + !defaultBlocking && !method.isBlocking(), method.getActualDeclaringClassName()); RuntimeInterceptorDeployment.MethodInterceptorContext interceptorDeployment = runtimeInterceptorDeployment .forMethod(method, lazyMethod);
independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerResourceMethod.java+9 −0 modified@@ -18,6 +18,7 @@ public class ServerResourceMethod extends ResourceMethod { private List<HandlerChainCustomizer> handlerChainCustomizers = new ArrayList<>(); private ParameterExtractor customerParameterExtractor; + private String actualDeclaringClassName; public ServerResourceMethod() { } @@ -70,4 +71,12 @@ public ServerResourceMethod setCustomerParameterExtractor(ParameterExtractor cus this.customerParameterExtractor = customerParameterExtractor; return this; } + + public String getActualDeclaringClassName() { + return actualDeclaringClassName; + } + + public void setActualDeclaringClassName(String actualDeclaringClassName) { + this.actualDeclaringClassName = actualDeclaringClassName; + } }
independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/spi/ResteasyReactiveResourceInfo.java+17 −1 modified@@ -26,21 +26,33 @@ public class ResteasyReactiveResourceInfo implements ResourceInfo { * If it's non-blocking method within the runtime that won't always default to blocking */ public final boolean isNonBlocking; - + /** + * This class name will only differ from {@link this#declaringClass} name when the {@link this#method} was inherited. + */ + private final String actualDeclaringClassName; private volatile Annotation[] classAnnotations; private volatile Method method; private volatile Annotation[] annotations; private volatile Type returnType; private volatile String methodId; + @Deprecated public ResteasyReactiveResourceInfo(String name, Class<?> declaringClass, Class[] parameterTypes, Set<String> classAnnotationNames, Set<String> methodAnnotationNames, boolean isNonBlocking) { + this(name, declaringClass, parameterTypes, classAnnotationNames, methodAnnotationNames, isNonBlocking, + declaringClass.getName()); + } + + public ResteasyReactiveResourceInfo(String name, Class<?> declaringClass, Class[] parameterTypes, + Set<String> classAnnotationNames, Set<String> methodAnnotationNames, boolean isNonBlocking, + String actualDeclaringClassName) { this.name = name; this.declaringClass = declaringClass; this.parameterTypes = parameterTypes; this.classAnnotationNames = classAnnotationNames; this.methodAnnotationNames = methodAnnotationNames; this.isNonBlocking = isNonBlocking; + this.actualDeclaringClassName = actualDeclaringClassName; } public String getName() { @@ -119,4 +131,8 @@ public String getMethodId() { } return methodId; } + + public String getActualDeclaringClassName() { + return actualDeclaringClassName; + } }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-mv64-86g8-cqq7ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-1726ghsaADVISORY
- access.redhat.com/errata/RHSA-2024:1662nvdWEB
- access.redhat.com/security/cve/CVE-2024-1726nvdWEB
- bugzilla.redhat.com/show_bug.cginvdWEB
- github.com/quarkusio/quarkus/commit/34c1a63baf5401d0d578a23a1a4deb4b841ce65bghsaWEB
- github.com/quarkusio/quarkus/commit/96d93427f3b4a7d3cff34d8b7b883e13cecd359cghsaWEB
News mentions
0No linked articles in our index yet.