VYPR
Medium severity5.7NVD Advisory· Published Jun 1, 2026

CVE-2026-40989

CVE-2026-40989

Description

Spring Cloud Function versions prior to 3.2.16, 4.1.10, 4.2.6, 4.3.3, and 5.0.2 are vulnerable to infinite recursion leading to an Out-of-Memory error.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Spring Cloud Function versions prior to 3.2.16, 4.1.10, 4.2.6, 4.3.3, and 5.0.2 are vulnerable to infinite recursion leading to an Out-of-Memory error.

Vulnerability

An infinite recursion vulnerability exists in the routing layer of Spring Cloud Function, specifically in versions prior to 3.2.16, 4.1.10, 4.2.6, 4.3.3, and 5.0.2. This issue can be triggered under certain conditions related to function composition, bypassing a self-routing guard [1].

Exploitation

An attacker can exploit this vulnerability by crafting a specific request that triggers the infinite recursion within the routing layer. This typically involves manipulating function composition in a way that bypasses security checks designed to prevent such loops [1]. No specific authentication or user interaction requirements are detailed in the available references.

Impact

Successful exploitation of this vulnerability can lead to an Out-of-Memory (OOM) error. This can cause the application to become unresponsive or crash, resulting in a denial-of-service condition for legitimate users [1].

Mitigation

Patched versions of Spring Cloud Function are available: 3.2.16, 4.1.10, 4.2.6, 4.3.3, and 5.0.2. Users are advised to upgrade to these versions or later. Older, unsupported versions are also affected and should be upgraded if possible [1].

AI Insight generated on Jun 1, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

2
6bcd55803c5a

Fix unbounded cache in FunctioinRegistry and recursive composition

https://github.com/spring-cloud/spring-cloud-functionOleg ZhurakouskyApr 27, 2026Fixed in 4.3.3via llm-release-walk
2 files changed · +61 1
  • spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java+34 1 modified
    @@ -25,6 +25,7 @@
     import java.util.Collection;
     import java.util.Collections;
     import java.util.HashMap;
    +import java.util.LinkedHashMap;
     import java.util.List;
     import java.util.Locale;
     import java.util.Map;
    @@ -102,7 +103,7 @@ public class SimpleFunctionRegistry implements FunctionRegistry {
     
     	private final Set<FunctionRegistration<?>> functionRegistrations = new CopyOnWriteArraySet<>();
     
    -	private final Map<String, FunctionInvocationWrapper> wrappedFunctionDefinitions = new HashMap<>();
    +	private final Map<String, FunctionInvocationWrapper> wrappedFunctionDefinitions;
     
     	private final ConversionService conversionService;
     
    @@ -114,6 +115,8 @@ public class SimpleFunctionRegistry implements FunctionRegistry {
     
     	private final FunctionProperties functionProperties;
     
    +	private int wrappedFunctionDefinitionsCacheSize = 1000;
    +
     	@Autowired(required = false)
     	private FunctionAroundWrapper functionAroundWrapper;
     
    @@ -127,8 +130,21 @@ public SimpleFunctionRegistry(ConversionService conversionService, CompositeMess
     		this.messageConverter = messageConverter;
     		this.functionInvocationHelper = functionInvocationHelper;
     		this.functionProperties = functionProperties;
    +		this.wrappedFunctionDefinitions = new LinkedHashMap<String, FunctionInvocationWrapper>() {
    +			@Override
    +			protected boolean removeEldestEntry(Map.Entry<String, FunctionInvocationWrapper> eldest) {
    +				boolean remove = size() > wrappedFunctionDefinitionsCacheSize;
    +				if (remove) {
    +					if (logger.isDebugEnabled()) {
    +						logger.debug("Removing message channel from cache " + eldest.getKey());
    +					}
    +				}
    +				return remove;
    +			}
    +		};
     	}
     
    +
     	/**
     	 * Will add provided {@link MessageConverter}s to the head of the stack of the existing MessageConverters.
     	 *
    @@ -471,6 +487,19 @@ public class FunctionInvocationWrapper implements Function<Object, Object>, Cons
     			}
     		}
     
    +		public int hashCode() {
    +			return this.functionDefinition.hashCode();
    +		}
    +
    +		public boolean equals(Object obj) {
    +			if (obj instanceof FunctionInvocationWrapper functionWrapper) {
    +				if (functionWrapper.getFunctionDefinition().equals(this.getFunctionDefinition())) {
    +					return true;
    +				}
    +			}
    +			return false;
    +		}
    +
     		@SuppressWarnings("unchecked")
     		public void postProcess() {
     			if (this.postProcessor != null) {
    @@ -666,6 +695,10 @@ public boolean isRoutingFunction() {
     		public <V> Function<Object, V> andThen(Function<? super Object, ? extends V> after) {
     			Assert.isTrue(after instanceof FunctionInvocationWrapper, "Composed function must be an instanceof FunctionInvocationWrapper.");
     
    +			if (this.equals(after)) {
    +				throw new IllegalArgumentException("Attempt is made to compose '" + this
    +						+ "' function with itself '" + after + "' which is not allowed as it causes recursive condition.");
    +			}
     			if (FunctionTypeUtils.isMultipleArgumentType(this.inputType)
     					|| FunctionTypeUtils.isMultipleArgumentType(this.outputType)
     					|| FunctionTypeUtils.isMultipleArgumentType(((FunctionInvocationWrapper) after).inputType)
    
  • spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistryTests.java+27 0 modified
    @@ -61,6 +61,7 @@
     import org.springframework.cloud.function.context.FunctionCatalog;
     import org.springframework.cloud.function.context.FunctionRegistration;
     import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper;
    +import org.springframework.cloud.function.context.config.RoutingFunction;
     import org.springframework.cloud.function.json.JsonMapper;
     import org.springframework.context.ApplicationContext;
     import org.springframework.context.ConfigurableApplicationContext;
    @@ -80,6 +81,7 @@
     
     import static java.util.Collections.singletonList;
     import static org.assertj.core.api.Assertions.assertThat;
    +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
     
     /**
      *
    @@ -108,6 +110,31 @@ public void before() {
     		System.clearProperty("spring.cloud.function.definition");
     	}
     
    +	@Test
    +	public void testBoundedFunctionCache() throws Exception {
    +		FunctionCatalog catalog = this.configureCatalog(CompositionWithNullReturnInBetween.class);
    +		Field wrappedFunctionDefinitionsCacheSizeField = ReflectionUtils
    +				.findField(catalog.getClass(), "wrappedFunctionDefinitionsCacheSize");
    +		wrappedFunctionDefinitionsCacheSizeField.setAccessible(true);
    +		wrappedFunctionDefinitionsCacheSizeField.set(catalog, 10);
    +		catalog.lookup("echo1|echo2|echo1|echo2|echo1|echo2|echo1|echo2");
    +		catalog.lookup("echo2|echo1|echo2|echo1|echo2|echo1|echo2|echo1|echo2|echo1|echo2|echo1|echo2|echo1|echo2|echo1");
    +		assertThat(catalog.size()).isEqualTo(11);
    +	}
    +
    +	@Test
    +	public void testCompositionWithItself() throws Exception {
    +		FunctionCatalog catalog = this.configureCatalog(CompositionWithNullReturnInBetween.class);
    +		try {
    +			catalog.lookup(RoutingFunction.FUNCTION_NAME + "|" + RoutingFunction.FUNCTION_NAME);
    +			failBecauseExceptionWasNotThrown(IllegalArgumentException.class);
    +		}
    +		catch (IllegalArgumentException e) {
    +			// TODO: nothing
    +		}
    +
    +	}
    +
     	@SuppressWarnings({ "rawtypes", "unchecked" })
     	@Test
     	public void testEmptyPojoConversion() {
    
57b4a1c05a48

Fix unbounded cache in FunctioinRegistry and recursive composition

https://github.com/spring-cloud/spring-cloud-functionOleg ZhurakouskyApr 27, 2026Fixed in 5.0.2via llm-release-walk
2 files changed · +61 1
  • spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java+34 1 modified
    @@ -25,6 +25,7 @@
     import java.util.Collection;
     import java.util.Collections;
     import java.util.HashMap;
    +import java.util.LinkedHashMap;
     import java.util.List;
     import java.util.Locale;
     import java.util.Map;
    @@ -102,7 +103,7 @@ public class SimpleFunctionRegistry implements FunctionRegistry {
     
     	private final Set<FunctionRegistration<?>> functionRegistrations = new CopyOnWriteArraySet<>();
     
    -	private final Map<String, FunctionInvocationWrapper> wrappedFunctionDefinitions = new HashMap<>();
    +	private final Map<String, FunctionInvocationWrapper> wrappedFunctionDefinitions;
     
     	private final ConversionService conversionService;
     
    @@ -114,6 +115,8 @@ public class SimpleFunctionRegistry implements FunctionRegistry {
     
     	private final FunctionProperties functionProperties;
     
    +	private int wrappedFunctionDefinitionsCacheSize = 1000;
    +
     	@Autowired(required = false)
     	private FunctionAroundWrapper functionAroundWrapper;
     
    @@ -127,8 +130,21 @@ public SimpleFunctionRegistry(ConversionService conversionService, CompositeMess
     		this.messageConverter = messageConverter;
     		this.functionInvocationHelper = functionInvocationHelper;
     		this.functionProperties = functionProperties;
    +		this.wrappedFunctionDefinitions = new LinkedHashMap<String, FunctionInvocationWrapper>() {
    +			@Override
    +			protected boolean removeEldestEntry(Map.Entry<String, FunctionInvocationWrapper> eldest) {
    +				boolean remove = size() > wrappedFunctionDefinitionsCacheSize;
    +				if (remove) {
    +					if (logger.isDebugEnabled()) {
    +						logger.debug("Removing message channel from cache " + eldest.getKey());
    +					}
    +				}
    +				return remove;
    +			}
    +		};
     	}
     
    +
     	/**
     	 * Will add provided {@link MessageConverter}s to the head of the stack of the existing MessageConverters.
     	 *
    @@ -484,6 +500,19 @@ public class FunctionInvocationWrapper implements Function<Object, Object>, Cons
     			}
     		}
     
    +		public int hashCode() {
    +			return this.functionDefinition.hashCode();
    +		}
    +
    +		public boolean equals(Object obj) {
    +			if (obj instanceof FunctionInvocationWrapper functionWrapper) {
    +				if (functionWrapper.getFunctionDefinition().equals(this.getFunctionDefinition())) {
    +					return true;
    +				}
    +			}
    +			return false;
    +		}
    +
     		@SuppressWarnings("unchecked")
     		public void postProcess() {
     			if (this.postProcessor != null) {
    @@ -687,6 +716,10 @@ public boolean isRoutingFunction() {
     		public <V> Function<Object, V> andThen(Function<? super Object, ? extends V> after) {
     			Assert.isTrue(after instanceof FunctionInvocationWrapper, "Composed function must be an instanceof FunctionInvocationWrapper.");
     
    +			if (this.equals(after)) {
    +				throw new IllegalArgumentException("Attempt is made to compose '" + this
    +						+ "' function with itself '" + after + "' which is not allowed as it causes recursive condition.");
    +			}
     			if (FunctionTypeUtils.isMultipleArgumentType(this.inputType)
     					|| FunctionTypeUtils.isMultipleArgumentType(this.outputType)
     					|| FunctionTypeUtils.isMultipleArgumentType(((FunctionInvocationWrapper) after).inputType)
    
  • spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistryTests.java+27 0 modified
    @@ -61,6 +61,7 @@
     import org.springframework.cloud.function.context.FunctionCatalog;
     import org.springframework.cloud.function.context.FunctionRegistration;
     import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper;
    +import org.springframework.cloud.function.context.config.RoutingFunction;
     import org.springframework.cloud.function.json.JsonMapper;
     import org.springframework.context.ApplicationContext;
     import org.springframework.context.ConfigurableApplicationContext;
    @@ -80,6 +81,7 @@
     
     import static java.util.Collections.singletonList;
     import static org.assertj.core.api.Assertions.assertThat;
    +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
     
     /**
      *
    @@ -108,6 +110,31 @@ public void before() {
     		System.clearProperty("spring.cloud.function.definition");
     	}
     
    +	@Test
    +	public void testBoundedFunctionCache() throws Exception {
    +		FunctionCatalog catalog = this.configureCatalog(CompositionWithNullReturnInBetween.class);
    +		Field wrappedFunctionDefinitionsCacheSizeField = ReflectionUtils
    +				.findField(catalog.getClass(), "wrappedFunctionDefinitionsCacheSize");
    +		wrappedFunctionDefinitionsCacheSizeField.setAccessible(true);
    +		wrappedFunctionDefinitionsCacheSizeField.set(catalog, 10);
    +		catalog.lookup("echo1|echo2|echo1|echo2|echo1|echo2|echo1|echo2");
    +		catalog.lookup("echo2|echo1|echo2|echo1|echo2|echo1|echo2|echo1|echo2|echo1|echo2|echo1|echo2|echo1|echo2|echo1");
    +		assertThat(catalog.size()).isEqualTo(11);
    +	}
    +
    +	@Test
    +	public void testCompositionWithItself() throws Exception {
    +		FunctionCatalog catalog = this.configureCatalog(CompositionWithNullReturnInBetween.class);
    +		try {
    +			catalog.lookup(RoutingFunction.FUNCTION_NAME + "|" + RoutingFunction.FUNCTION_NAME);
    +			failBecauseExceptionWasNotThrown(IllegalArgumentException.class);
    +		}
    +		catch (IllegalArgumentException e) {
    +			// TODO: nothing
    +		}
    +
    +	}
    +
     	@SuppressWarnings({ "rawtypes", "unchecked" })
     	@Test
     	public void testCompositionWithNonExistingFunction() throws Exception {
    

Vulnerability mechanics

No source-code context for this CVE — mechanics is only generated when we can read the actual fix diff. Without that, the four sections (root cause, attack vector, affected code, fix) would be speculation rather than analysis.

References

1

News mentions

0

No linked articles in our index yet.