Skip to content

Commit 19209a0

Browse files
committed
CVE fix
1 parent 9e0c68f commit 19209a0

2 files changed

Lines changed: 61 additions & 1 deletion

File tree

spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.Collection;
2626
import java.util.Collections;
2727
import java.util.HashMap;
28+
import java.util.LinkedHashMap;
2829
import java.util.List;
2930
import java.util.Locale;
3031
import java.util.Map;
@@ -102,7 +103,7 @@ public class SimpleFunctionRegistry implements FunctionRegistry {
102103

103104
private final Set<FunctionRegistration<?>> functionRegistrations = new CopyOnWriteArraySet<>();
104105

105-
private final Map<String, FunctionInvocationWrapper> wrappedFunctionDefinitions = new HashMap<>();
106+
private final Map<String, FunctionInvocationWrapper> wrappedFunctionDefinitions;
106107

107108
private final ConversionService conversionService;
108109

@@ -114,6 +115,8 @@ public class SimpleFunctionRegistry implements FunctionRegistry {
114115

115116
private final FunctionProperties functionProperties;
116117

118+
private int wrappedFunctionDefinitionsCacheSize = 1000;
119+
117120
@Autowired(required = false)
118121
private FunctionAroundWrapper functionAroundWrapper;
119122

@@ -127,8 +130,21 @@ public SimpleFunctionRegistry(ConversionService conversionService, CompositeMess
127130
this.messageConverter = messageConverter;
128131
this.functionInvocationHelper = functionInvocationHelper;
129132
this.functionProperties = functionProperties;
133+
this.wrappedFunctionDefinitions = new LinkedHashMap<String, FunctionInvocationWrapper>() {
134+
@Override
135+
protected boolean removeEldestEntry(Map.Entry<String, FunctionInvocationWrapper> eldest) {
136+
boolean remove = size() > wrappedFunctionDefinitionsCacheSize;
137+
if (remove) {
138+
if (logger.isDebugEnabled()) {
139+
logger.debug("Removing message channel from cache " + eldest.getKey());
140+
}
141+
}
142+
return remove;
143+
}
144+
};
130145
}
131146

147+
132148
/**
133149
* Will add provided {@link MessageConverter}s to the head of the stack of the existing MessageConverters.
134150
*
@@ -471,6 +487,19 @@ public class FunctionInvocationWrapper implements Function<Object, Object>, Cons
471487
}
472488
}
473489

490+
public int hashCode() {
491+
return this.functionDefinition.hashCode();
492+
}
493+
494+
public boolean equals(Object obj) {
495+
if (obj instanceof FunctionInvocationWrapper functionWrapper) {
496+
if (functionWrapper.getFunctionDefinition().equals(this.getFunctionDefinition())) {
497+
return true;
498+
}
499+
}
500+
return false;
501+
}
502+
474503
@SuppressWarnings("unchecked")
475504
public void postProcess() {
476505
if (this.postProcessor != null) {
@@ -666,6 +695,10 @@ public boolean isRoutingFunction() {
666695
public <V> Function<Object, V> andThen(Function<? super Object, ? extends V> after) {
667696
Assert.isTrue(after instanceof FunctionInvocationWrapper, "Composed function must be an instanceof FunctionInvocationWrapper.");
668697

698+
if (this.equals(after)) {
699+
throw new IllegalArgumentException("Attempt is made to compose '" + this
700+
+ "' function with itself '" + after + "' which is not allowed as it causes recursive condition.");
701+
}
669702
if (FunctionTypeUtils.isMultipleArgumentType(this.inputType)
670703
|| FunctionTypeUtils.isMultipleArgumentType(this.outputType)
671704
|| FunctionTypeUtils.isMultipleArgumentType(((FunctionInvocationWrapper) after).inputType)

spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistryTests.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import org.springframework.cloud.function.context.FunctionCatalog;
6262
import org.springframework.cloud.function.context.FunctionRegistration;
6363
import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper;
64+
import org.springframework.cloud.function.context.config.RoutingFunction;
6465
import org.springframework.cloud.function.json.JsonMapper;
6566
import org.springframework.context.ApplicationContext;
6667
import org.springframework.context.ConfigurableApplicationContext;
@@ -80,6 +81,7 @@
8081

8182
import static java.util.Collections.singletonList;
8283
import static org.assertj.core.api.Assertions.assertThat;
84+
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
8385

8486
/**
8587
*
@@ -108,6 +110,31 @@ public void before() {
108110
System.clearProperty("spring.cloud.function.definition");
109111
}
110112

113+
@Test
114+
public void testBoundedFunctionCache() throws Exception {
115+
FunctionCatalog catalog = this.configureCatalog(CompositionWithNullReturnInBetween.class);
116+
Field wrappedFunctionDefinitionsCacheSizeField = ReflectionUtils
117+
.findField(catalog.getClass(), "wrappedFunctionDefinitionsCacheSize");
118+
wrappedFunctionDefinitionsCacheSizeField.setAccessible(true);
119+
wrappedFunctionDefinitionsCacheSizeField.set(catalog, 10);
120+
catalog.lookup("echo1|echo2|echo1|echo2|echo1|echo2|echo1|echo2");
121+
catalog.lookup("echo2|echo1|echo2|echo1|echo2|echo1|echo2|echo1|echo2|echo1|echo2|echo1|echo2|echo1|echo2|echo1");
122+
assertThat(catalog.size()).isEqualTo(11);
123+
}
124+
125+
@Test
126+
public void testCompositionWithItself() throws Exception {
127+
FunctionCatalog catalog = this.configureCatalog(CompositionWithNullReturnInBetween.class);
128+
try {
129+
catalog.lookup(RoutingFunction.FUNCTION_NAME + "|" + RoutingFunction.FUNCTION_NAME);
130+
failBecauseExceptionWasNotThrown(IllegalArgumentException.class);
131+
}
132+
catch (IllegalArgumentException e) {
133+
// TODO: nothing
134+
}
135+
136+
}
137+
111138
@SuppressWarnings({ "rawtypes", "unchecked" })
112139
@Test
113140
public void testEmptyPojoConversion() {

0 commit comments

Comments
 (0)