diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java index a471ef5be66d..f368d31e17dd 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java @@ -44,6 +44,8 @@ public final class RequestUtils { public static final String EMPTY_BODY = ""; + private static final int MAX_FORM_PARAMS = 1000; + private RequestUtils() {} public static boolean isRestRequest(HttpRequest request) { @@ -97,8 +99,13 @@ public static Map> getFormParametersMap(HttpRequest request if (paramNames.isEmpty()) { return Collections.emptyMap(); } - Map> params = CollectionUtils.newLinkedHashMap(paramNames.size()); + int limit = Math.min(paramNames.size(), MAX_FORM_PARAMS); + Map> params = CollectionUtils.newLinkedHashMap(limit); + int count = 0; for (String paramName : paramNames) { + if (count++ >= MAX_FORM_PARAMS) { + break; + } params.put(paramName, request.formParameterValues(paramName)); } return params; diff --git a/src/test/java/RequestUtilsInvariantTest.java b/src/test/java/RequestUtilsInvariantTest.java new file mode 100644 index 000000000000..54e3a224bcdf --- /dev/null +++ b/src/test/java/RequestUtilsInvariantTest.java @@ -0,0 +1,56 @@ +package org.apache.dubbo.rpc.protocol.tri.rest.util; + +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.HttpRequest; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +class RequestUtilsSecurityTest { + + @ParameterizedTest + @ValueSource(ints = {1000000, 100000, 10}) + void testFormParametersMapMaintainsMemoryBounds(int paramCount) { + // Invariant: Processing form parameters must not consume unbounded memory + + HttpRequest mockRequest = new HttpRequest() { + private final Set names = generateParamNames(paramCount); + + @Override + public Collection formParameterNames() { + return names; + } + + @Override + public List formParameterValues(String name) { + return Collections.singletonList("value"); + } + }; + + long beforeMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); + + Map> result = RequestUtils.getFormParametersMap(mockRequest); + + long afterMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); + long memoryUsed = afterMemory - beforeMemory; + + // Memory usage should be proportional to input size with reasonable constant factor + long maxExpectedMemory = paramCount * 200L; // ~200 bytes per parameter (generous bound) + + assertTrue(memoryUsed < maxExpectedMemory || paramCount <= 10, + String.format("Memory usage %d exceeded bound %d for %d parameters", + memoryUsed, maxExpectedMemory, paramCount)); + + assertEquals(paramCount, result.size()); + } + + private Set generateParamNames(int count) { + Set names = new HashSet<>(); + for (int i = 0; i < count; i++) { + names.add("param" + i); + } + return names; + } +} \ No newline at end of file