Add properties for new max part count and max part header size
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run
Details
Build and Deploy Snapshot / Trigger Docs Build (push) Blocked by required conditions
Details
Build and Deploy Snapshot / Verify (push) Blocked by required conditions
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:false version:17]) (push) Waiting to run
Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:true version:21]) (push) Waiting to run
Details
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run
Details
Build and Deploy Snapshot / Trigger Docs Build (push) Blocked by required conditions
Details
Build and Deploy Snapshot / Verify (push) Blocked by required conditions
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:false version:17]) (push) Waiting to run
Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:true version:21]) (push) Waiting to run
Details
To address CVE-2025-48976 and CVE-2025-48988, Tomcat 10.1.42 has introduced two new configuration settings – maxPartCount and maxPartHeaderSize. The default values for these configuration settings have proven hard to get right and some applications have had to increase the default limits. To ease their configuration in Spring Boot, this commit introduces configuration properties for the new settings: - server.tomcat.max-part-count (maxPartCount) - server.tomcat.max-part-header-size (maxPartHeaderSize) The defaults are aligned with those of Tomcat 10.1.42 (10 and 512 bytes respectively). Closes gh-45869
This commit is contained in:
parent
0f77dcb402
commit
d9e4b66eee
|
@ -412,6 +412,20 @@ public class ServerProperties {
|
|||
*/
|
||||
private DataSize maxHttpFormPostSize = DataSize.ofMegabytes(2);
|
||||
|
||||
/**
|
||||
* Maximum per-part header size permitted in a multipart/form-data request.
|
||||
* Requests that exceed this limit will be rejected. A value of less than 0 means
|
||||
* no limit.
|
||||
*/
|
||||
private DataSize maxPartHeaderSize = DataSize.ofBytes(512);
|
||||
|
||||
/**
|
||||
* Maximum total number of parts permitted in a multipart/form-data request.
|
||||
* Requests that exceed this limit will be rejected. A value of less than 0 means
|
||||
* no limit.
|
||||
*/
|
||||
private int maxPartCount = 10;
|
||||
|
||||
/**
|
||||
* Maximum amount of request body to swallow.
|
||||
*/
|
||||
|
@ -528,6 +542,22 @@ public class ServerProperties {
|
|||
this.maxHttpFormPostSize = maxHttpFormPostSize;
|
||||
}
|
||||
|
||||
public DataSize getMaxPartHeaderSize() {
|
||||
return this.maxPartHeaderSize;
|
||||
}
|
||||
|
||||
public void setMaxPartHeaderSize(DataSize maxPartHeaderSize) {
|
||||
this.maxPartHeaderSize = maxPartHeaderSize;
|
||||
}
|
||||
|
||||
public int getMaxPartCount() {
|
||||
return this.maxPartCount;
|
||||
}
|
||||
|
||||
public void setMaxPartCount(int maxPartCount) {
|
||||
this.maxPartCount = maxPartCount;
|
||||
}
|
||||
|
||||
public Accesslog getAccesslog() {
|
||||
return this.accesslog;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -119,6 +119,10 @@ public class TomcatWebServerFactoryCustomizer
|
|||
.asInt(DataSize::toBytes)
|
||||
.when((maxHttpFormPostSize) -> maxHttpFormPostSize != 0)
|
||||
.to((maxHttpFormPostSize) -> customizeMaxHttpFormPostSize(factory, maxHttpFormPostSize));
|
||||
map.from(properties::getMaxPartHeaderSize)
|
||||
.asInt(DataSize::toBytes)
|
||||
.to((maxPartHeaderSize) -> customizeMaxPartHeaderSize(factory, maxPartHeaderSize));
|
||||
map.from(properties::getMaxPartCount).to((maxPartCount) -> customizeMaxPartCount(factory, maxPartCount));
|
||||
map.from(properties::getAccesslog)
|
||||
.when(ServerProperties.Tomcat.Accesslog::isEnabled)
|
||||
.to((enabled) -> customizeAccessLog(factory));
|
||||
|
@ -304,6 +308,28 @@ public class TomcatWebServerFactoryCustomizer
|
|||
factory.addConnectorCustomizers((connector) -> connector.setMaxPostSize(maxHttpFormPostSize));
|
||||
}
|
||||
|
||||
private void customizeMaxPartCount(ConfigurableTomcatWebServerFactory factory, int maxPartCount) {
|
||||
factory.addConnectorCustomizers((connector) -> {
|
||||
try {
|
||||
connector.setMaxPartCount(maxPartCount);
|
||||
}
|
||||
catch (NoSuchMethodError ex) {
|
||||
// Tomcat < 10.1.42
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void customizeMaxPartHeaderSize(ConfigurableTomcatWebServerFactory factory, int maxPartHeaderSize) {
|
||||
factory.addConnectorCustomizers((connector) -> {
|
||||
try {
|
||||
connector.setMaxPartHeaderSize(maxPartHeaderSize);
|
||||
}
|
||||
catch (NoSuchMethodError ex) {
|
||||
// Tomcat < 10.1.42
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void customizeAccessLog(ConfigurableTomcatWebServerFactory factory) {
|
||||
ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat();
|
||||
AccessLogValve valve = new AccessLogValve();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -253,6 +253,18 @@ class ServerPropertiesTests {
|
|||
assertThat(this.properties.getTomcat().getThreads().getMinSpare()).isEqualTo(10);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeTomcatMaxPartCount() {
|
||||
bind("server.tomcat.max-part-count", "5");
|
||||
assertThat(this.properties.getTomcat().getMaxPartCount()).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeTomcatMaxPartHeaderSize() {
|
||||
bind("server.tomcat.max-part-header-size", "128");
|
||||
assertThat(this.properties.getTomcat().getMaxPartHeaderSize()).isEqualTo(DataSize.ofBytes(128));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomizeJettyAcceptors() {
|
||||
bind("server.jetty.threads.acceptors", "10");
|
||||
|
@ -392,6 +404,17 @@ class ServerPropertiesTests {
|
|||
.isEqualTo(getDefaultConnector().getMaxPostSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
void tomcatMaxPartCountMatchesConnectorDefault() {
|
||||
assertThat(this.properties.getTomcat().getMaxPartCount()).isEqualTo(getDefaultConnector().getMaxPartCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void tomcatMaxPartHeaderSizeMatchesConnectorDefault() {
|
||||
assertThat(this.properties.getTomcat().getMaxPartHeaderSize().toBytes())
|
||||
.isEqualTo(getDefaultConnector().getMaxPartHeaderSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
void tomcatUriEncodingMatchesConnectorDefault() {
|
||||
assertThat(this.properties.getTomcat().getUriEncoding().name())
|
||||
|
|
|
@ -37,6 +37,8 @@ import org.springframework.boot.autoconfigure.web.ServerProperties.ForwardHeader
|
|||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
|
||||
import org.springframework.boot.testsupport.classpath.ClassPathOverrides;
|
||||
import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
|
||||
import org.springframework.boot.web.server.WebServer;
|
||||
|
@ -45,6 +47,7 @@ import org.springframework.test.context.support.TestPropertySourceUtils;
|
|||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
|
||||
/**
|
||||
* Tests for {@link TomcatWebServerFactoryCustomizer}
|
||||
|
@ -60,6 +63,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
* @author Parviz Rozikov
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
@DirtiesUrlFactories
|
||||
class TomcatWebServerFactoryCustomizerTests {
|
||||
|
||||
private MockEnvironment environment;
|
||||
|
@ -177,6 +181,37 @@ class TomcatWebServerFactoryCustomizerTests {
|
|||
(server) -> assertThat(server.getTomcat().getConnector().getMaxPostSize()).isEqualTo(10000));
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultMaxPartCount() {
|
||||
customizeAndRunServer(
|
||||
(server) -> assertThat(server.getTomcat().getConnector().getMaxPartCount()).isEqualTo(10));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customMaxPartCount() {
|
||||
bind("server.tomcat.max-part-count=5");
|
||||
customizeAndRunServer((server) -> assertThat(server.getTomcat().getConnector().getMaxPartCount()).isEqualTo(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultMaxPartHeaderSize() {
|
||||
customizeAndRunServer(
|
||||
(server) -> assertThat(server.getTomcat().getConnector().getMaxPartHeaderSize()).isEqualTo(512));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customMaxPartHeaderSize() {
|
||||
bind("server.tomcat.max-part-header-size=4KB");
|
||||
customizeAndRunServer(
|
||||
(server) -> assertThat(server.getTomcat().getConnector().getMaxPartHeaderSize()).isEqualTo(4096));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ClassPathOverrides("org.apache.tomcat.embed:tomcat-embed-core:10.1.41")
|
||||
void customizerIsCompatibleWithTomcatVersionsWithoutMaxPartCountAndMaxPartHeaderSize() {
|
||||
assertThatNoException().isThrownBy(this::customizeAndRunServer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultMaxHttpRequestHeaderSize() {
|
||||
customizeAndRunServer((server) -> assertThat(
|
||||
|
@ -586,11 +621,17 @@ class TomcatWebServerFactoryCustomizerTests {
|
|||
Bindable.ofInstance(this.serverProperties));
|
||||
}
|
||||
|
||||
private void customizeAndRunServer() {
|
||||
customizeAndRunServer(null);
|
||||
}
|
||||
|
||||
private void customizeAndRunServer(Consumer<TomcatWebServer> consumer) {
|
||||
TomcatWebServer server = customizeAndGetServer();
|
||||
server.start();
|
||||
try {
|
||||
consumer.accept(server);
|
||||
if (consumer != null) {
|
||||
consumer.accept(server);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
server.stop();
|
||||
|
|
Loading…
Reference in New Issue