High severity7.5NVD Advisory· Published Mar 12, 2025· Updated Apr 15, 2026
CVE-2025-2240
CVE-2025-2240
Description
A flaw was found in Smallrye, where smallrye-fault-tolerance is vulnerable to an out-of-memory (OOM) issue. This vulnerability is externally triggered when calling the metrics URI. Every call creates a new object within meterMap and may lead to a denial of service (DoS) issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
io.smallrye:smallrye-fault-tolerance-coreMaven | >= 6.3.0, < 6.4.2 | 6.4.2 |
io.smallrye:smallrye-fault-tolerance-coreMaven | >= 6.5.0, < 6.9.0 | 6.9.0 |
Patches
1e8bcad3d5e8bfix memory leak in metrics
14 files changed · +92 −12
api/src/main/java/io/smallrye/faulttolerance/api/Guard.java+1 −1 modified@@ -165,7 +165,7 @@ interface Builder { * <p> * The description may be an arbitrary string. Duplicates are permitted. * <p> - * If no description is set, a random UUID is used. + * If no description is set, no metrics are emitted and a random UUID is used for other purposes. * * @param value a description, must not be {@code null} * @return this fault tolerance builder
api/src/main/java/io/smallrye/faulttolerance/api/TypedGuard.java+1 −1 modified@@ -136,7 +136,7 @@ interface Builder<T> { * <p> * The description may be an arbitrary string. Duplicates are permitted. * <p> - * If no description is set, a random UUID is used. + * If no description is set, no metrics are emitted and a random UUID is used for other purposes. * * @param value a description, must not be {@code null} * @return this fault tolerance builder
doc/modules/ROOT/pages/reference/programmatic-api.adoc+1 −1 modified@@ -264,7 +264,7 @@ private static final Guard GUARD = Guard.create() It is possible to create multiple `Guard` objects with the same description. In this case, it won't be possible to distinguish the different `Guard` objects in metrics; their values will be aggregated. -If no description is provided, a random UUID is used. +If no description is provided, metrics are not emitted. == Differences to the Specification
implementation/apiimpl/src/main/java/io/smallrye/faulttolerance/apiimpl/BasicMeteredOperationImpl.java+9 −2 modified@@ -3,6 +3,7 @@ import io.smallrye.faulttolerance.core.metrics.MeteredOperation; final class BasicMeteredOperationImpl implements MeteredOperation { + private final boolean enabled; private final String name; private final boolean mayBeAsynchronous; private final boolean hasBulkhead; @@ -12,8 +13,9 @@ final class BasicMeteredOperationImpl implements MeteredOperation { private final boolean hasRetry; private final boolean hasTimeout; - BasicMeteredOperationImpl(String name, boolean mayBeAsynchronous, boolean hasBulkhead, boolean hasCircuitBreaker, - boolean hasFallback, boolean hasRateLimit, boolean hasRetry, boolean hasTimeout) { + BasicMeteredOperationImpl(boolean enabled, String name, boolean mayBeAsynchronous, boolean hasBulkhead, + boolean hasCircuitBreaker, boolean hasFallback, boolean hasRateLimit, boolean hasRetry, boolean hasTimeout) { + this.enabled = enabled; this.name = name; this.mayBeAsynchronous = mayBeAsynchronous; this.hasBulkhead = hasBulkhead; @@ -24,6 +26,11 @@ final class BasicMeteredOperationImpl implements MeteredOperation { this.hasTimeout = hasTimeout; } + @Override + public boolean enabled() { + return enabled; + } + @Override public boolean mayBeAsynchronous() { return mayBeAsynchronous;
implementation/apiimpl/src/main/java/io/smallrye/faulttolerance/apiimpl/FaultToleranceImpl.java+4 −1 modified@@ -181,6 +181,7 @@ public static final class BuilderImpl<T, R> implements Builder<T, R> { private final Function<FaultTolerance<T>, R> finisher; private String description; + private boolean descriptionSet; private BulkheadBuilderImpl<T, R> bulkheadBuilder; private CircuitBreakerBuilderImpl<T, R> circuitBreakerBuilder; private FallbackBuilderImpl<T, R> fallbackBuilder; @@ -199,11 +200,13 @@ public BuilderImpl(BuilderEagerDependencies eagerDependencies, Supplier<BuilderL this.finisher = finisher; this.description = UUID.randomUUID().toString(); + this.descriptionSet = false; } @Override public Builder<T, R> withDescription(String value) { this.description = checkNotNull(value, "Description must be set"); + this.descriptionSet = true; return this; } @@ -476,7 +479,7 @@ private <V> FaultToleranceStrategy<V> buildAsyncStrategy(BuilderLazyDependencies } private MeteredOperation buildMeteredOperation() { - return new BasicMeteredOperationImpl(description, isAsync, bulkheadBuilder != null, + return new BasicMeteredOperationImpl(descriptionSet, description, isAsync, bulkheadBuilder != null, circuitBreakerBuilder != null, fallbackBuilder != null, rateLimitBuilder != null, retryBuilder != null, timeoutBuilder != null); }
implementation/apiimpl/src/main/java/io/smallrye/faulttolerance/apiimpl/GuardImpl.java+4 −1 modified@@ -131,6 +131,7 @@ public static class BuilderImpl implements Builder { private final BuilderEagerDependencies eagerDependencies; private final Supplier<BuilderLazyDependencies> lazyDependencies; private String description; + private boolean descriptionSet; private BulkheadBuilderImpl bulkheadBuilder; private CircuitBreakerBuilderImpl circuitBreakerBuilder; private RateLimitBuilderImpl rateLimitBuilder; @@ -144,11 +145,13 @@ public BuilderImpl(BuilderEagerDependencies eagerDependencies, Supplier<BuilderL this.lazyDependencies = lazyDependencies; this.description = UUID.randomUUID().toString(); + this.descriptionSet = false; } @Override public Builder withDescription(String value) { this.description = checkNotNull(value, "Description must be set"); + this.descriptionSet = true; return this; } @@ -314,7 +317,7 @@ final <V> FaultToleranceStrategy<V> buildStrategy(String id, BuilderLazyDependen } private MeteredOperation buildMeteredOperation() { - return new BasicMeteredOperationImpl(description, true, bulkheadBuilder != null, + return new BasicMeteredOperationImpl(descriptionSet, description, true, bulkheadBuilder != null, circuitBreakerBuilder != null, false, rateLimitBuilder != null, retryBuilder != null, timeoutBuilder != null); }
implementation/apiimpl/src/main/java/io/smallrye/faulttolerance/apiimpl/TypedGuardImpl.java+4 −1 modified@@ -122,6 +122,7 @@ public static class BuilderImpl<V, T> implements TypedGuard.Builder<T> { private final AsyncSupport<V, T> asyncSupport; private String description; + private boolean descriptionSet; private BulkheadBuilderImpl<V, T> bulkheadBuilder; private CircuitBreakerBuilderImpl<V, T> circuitBreakerBuilder; private FallbackBuilderImpl<V, T> fallbackBuilder; @@ -138,11 +139,13 @@ public BuilderImpl(BuilderEagerDependencies eagerDependencies, Supplier<BuilderL this.asyncSupport = GuardCommon.asyncSupport(valueType); this.description = UUID.randomUUID().toString(); + this.descriptionSet = false; } @Override public Builder<T> withDescription(String value) { this.description = checkNotNull(value, "Description must be set"); + this.descriptionSet = true; return this; } @@ -335,7 +338,7 @@ final FaultToleranceStrategy<V> buildStrategy(String id, BuilderLazyDependencies } private MeteredOperation buildMeteredOperation() { - return new BasicMeteredOperationImpl(description, asyncSupport != null, bulkheadBuilder != null, + return new BasicMeteredOperationImpl(descriptionSet, description, asyncSupport != null, bulkheadBuilder != null, circuitBreakerBuilder != null, false, rateLimitBuilder != null, retryBuilder != null, timeoutBuilder != null); }
implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/DelegatingMeteredOperation.java+6 −0 modified@@ -9,6 +9,12 @@ final class DelegatingMeteredOperation implements MeteredOperation { this.name = name; } + @Override + public boolean enabled() { + // always enabled, because this class is only instantiated for intercepted methods + return true; + } + @Override public boolean mayBeAsynchronous() { return operation.mayBeAsynchronous();
implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/DelegatingMetricsCollector.java+7 −2 modified@@ -27,8 +27,13 @@ public Future<V> apply(FaultToleranceContext<V> ctx) { MeteredOperation operation = name != null ? new DelegatingMeteredOperation(originalOperation, name.get()) : originalOperation; - MetricsCollector<V> delegate = cache.computeIfAbsent(operation, - ignored -> new MetricsCollector<>(this.delegate, provider.create(operation), operation)); + FaultToleranceStrategy<V> delegate; + if (operation.enabled()) { + delegate = cache.computeIfAbsent(operation, + ignored -> new MetricsCollector<>(this.delegate, provider.create(operation), operation)); + } else { + delegate = this.delegate; + } return delegate.apply(ctx); } }
implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/MeteredOperation.java+2 −0 modified@@ -1,6 +1,8 @@ package io.smallrye.faulttolerance.core.metrics; public interface MeteredOperation { + boolean enabled(); + boolean mayBeAsynchronous(); boolean hasBulkhead();
implementation/core/src/test/java/io/smallrye/faulttolerance/core/metrics/GeneralMetricsTest.java+5 −0 modified@@ -40,6 +40,11 @@ public void failingInvocation() { } private static class MockMeteredOperation implements MeteredOperation { + @Override + public boolean enabled() { + return true; + } + @Override public boolean mayBeAsynchronous() { return false;
implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/CdiMeteredOperationImpl.java+6 −0 modified@@ -17,6 +17,12 @@ public CdiMeteredOperationImpl(FaultToleranceOperation operation, InterceptionPo this.specCompatibility = specCompatibility; } + @Override + public boolean enabled() { + // always enabled, because this only applies to intercepted methods + return true; + } + @Override public boolean mayBeAsynchronous() { return specCompatibility.isOperationTrulyOrPseudoAsynchronous(operation);
implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneMetricsTest.java+21 −1 modified@@ -12,6 +12,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; @@ -62,7 +63,9 @@ public static void tearDown() throws InterruptedException { } @Test - public void test() throws Exception { + public void metricsWithDescription() throws Exception { + long oldCounters = metrics.getMeters().stream().filter(it -> it instanceof Counter).count(); + Callable<String> guarded = TypedGuard.create(String.class) .withDescription(NAME) .withFallback().handler(this::fallback).done() @@ -72,6 +75,8 @@ public void test() throws Exception { assertThat(guarded.call()).isEqualTo("fallback"); + assertThat(metrics.getMeters().stream().filter(it -> it instanceof Counter).count()).isGreaterThan(oldCounters); + assertThat(metrics.counter(MetricsConstants.INVOCATIONS_TOTAL, List.of( Tag.of("method", NAME), Tag.of("result", "valueReturned"), @@ -88,6 +93,21 @@ public void test() throws Exception { .count()).isEqualTo(1.0); } + @Test + public void noMetricsWithoutDescription() throws Exception { + long oldCounters = metrics.getMeters().stream().filter(it -> it instanceof Counter).count(); + + Callable<String> guarded = TypedGuard.create(String.class) + .withFallback().handler(this::fallback).done() + .withRetry().maxRetries(3).done() + .build() + .adaptCallable(this::action); + + assertThat(guarded.call()).isEqualTo("fallback"); + + assertThat(metrics.getMeters().stream().filter(it -> it instanceof Counter).count()).isEqualTo(oldCounters); + } + public String action() throws TestException { throw new TestException(); }
testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiMetricsTest.java+21 −1 modified@@ -20,7 +20,9 @@ public class CdiMetricsTest { private static final String NAME = CdiMetricsTest.class.getName() + " programmatic usage"; @Test - public void test(@RegistryType(type = MetricRegistry.Type.BASE) MetricRegistry metrics) throws Exception { + public void metricsWithDescription(@RegistryType(type = MetricRegistry.Type.BASE) MetricRegistry metrics) throws Exception { + int oldCounters = metrics.getCounters().size(); + Callable<String> guarded = TypedGuard.create(String.class) .withDescription(NAME) .withFallback().handler(this::fallback).done() @@ -30,6 +32,8 @@ public void test(@RegistryType(type = MetricRegistry.Type.BASE) MetricRegistry m assertThat(guarded.call()).isEqualTo("fallback"); + assertThat(metrics.getCounters().size()).isGreaterThan(oldCounters); + assertThat(metrics.counter(MetricsConstants.INVOCATIONS_TOTAL, new Tag("method", NAME), new Tag("result", "valueReturned"), @@ -46,6 +50,22 @@ public void test(@RegistryType(type = MetricRegistry.Type.BASE) MetricRegistry m .getCount()).isEqualTo(1); } + @Test + public void noMetricsWithoutDescription(@RegistryType(type = MetricRegistry.Type.BASE) MetricRegistry metrics) + throws Exception { + int oldCounters = metrics.getCounters().size(); + + Callable<String> guarded = TypedGuard.create(String.class) + .withFallback().handler(this::fallback).done() + .withRetry().maxRetries(3).done() + .build() + .adaptCallable(this::action); + + assertThat(guarded.call()).isEqualTo("fallback"); + + assertThat(metrics.getCounters().size()).isEqualTo(oldCounters); + } + public String action() throws TestException { throw new TestException(); }
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
11- github.com/advisories/GHSA-gfh6-3pqw-x2j4nvdADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-2240ghsaADVISORY
- access.redhat.com/errata/RHSA-2025:3376nvdWEB
- access.redhat.com/errata/RHSA-2025:3541nvdWEB
- access.redhat.com/errata/RHSA-2025:3543nvdWEB
- access.redhat.com/security/cve/CVE-2025-2240nvdWEB
- bugzilla.redhat.com/show_bug.cginvdWEB
- github.com/smallrye/smallrye-fault-tolerance/commit/e8bcad3d5e8bbac0a3219bd5c13661adf6ed6bbbghsaWEB
- github.com/smallrye/smallrye-fault-tolerance/pull/985ghsaWEB
- github.com/smallrye/smallrye-fault-tolerance/pull/985/filesghsaWEB
- smallrye.io/blog/fault-tolerance-6-9-0ghsaWEB
News mentions
0No linked articles in our index yet.