VYPR
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.

PackageAffected versionsPatched versions
io.smallrye:smallrye-fault-tolerance-coreMaven
>= 6.3.0, < 6.4.26.4.2
io.smallrye:smallrye-fault-tolerance-coreMaven
>= 6.5.0, < 6.9.06.9.0

Patches

1
e8bcad3d5e8b

fix 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

News mentions

0

No linked articles in our index yet.