From 94afeedad60972dbe79325a0e8aed2824c57f922 Mon Sep 17 00:00:00 2001 From: seonghun lee Date: Thu, 17 Sep 2026 00:26:17 +0900 Subject: [PATCH] Enforce disk usage limit when spilling part to disk Previously, PartGenerator only enforced maxDiskUsagePerPart for body buffers that arrived after a part had switched to file storage. The content accumulated in memory that triggered the switch was written to disk without any check against maxDiskUsagePerPart. As a result, a part whose last body buffer caused the in-memory overflow was accepted in full, even when its total size exceeded the configured disk usage limit. This commit checks the accumulated byte count against maxDiskUsagePerPart before switching to file storage, and emits a DataBufferLimitException, consistent with the existing check for subsequent buffers. Closes gh-35099 Signed-off-by: seonghun lee --- .../http/codec/multipart/PartGenerator.java | 11 +++++++++++ .../DefaultPartHttpMessageReaderTests.java | 15 +++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/spring-web/src/main/java/org/springframework/http/codec/multipart/PartGenerator.java b/spring-web/src/main/java/org/springframework/http/codec/multipart/PartGenerator.java index fa6074616b9..7f706f22c9b 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/multipart/PartGenerator.java +++ b/spring-web/src/main/java/org/springframework/http/codec/multipart/PartGenerator.java @@ -56,6 +56,7 @@ import org.springframework.util.FastByteArrayOutputStream; * of {@link Part} objects. * * @author Arjen Poutsma + * @author Seonghun Lee * @since 5.3 */ @SuppressWarnings("NullAway") // Dataflow analysis limitation @@ -380,6 +381,16 @@ final class PartGenerator extends BaseSubscriber { } private void switchToFile(DataBuffer current, long byteCount) { + if (PartGenerator.this.maxDiskUsagePerPart != -1 && byteCount > PartGenerator.this.maxDiskUsagePerPart) { + DataBufferUtils.release(current); + this.content.forEach(DataBufferUtils::release); + this.content.clear(); + emitError(new DataBufferLimitException( + "Part exceeded the disk usage limit of " + PartGenerator.this.maxDiskUsagePerPart + + " bytes")); + return; + } + List content = new ArrayList<>(this.content); content.add(current); this.releaseOnDispose = false; diff --git a/spring-web/src/test/java/org/springframework/http/codec/multipart/DefaultPartHttpMessageReaderTests.java b/spring-web/src/test/java/org/springframework/http/codec/multipart/DefaultPartHttpMessageReaderTests.java index e7b468cafa7..a7b8fadaa43 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/multipart/DefaultPartHttpMessageReaderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/multipart/DefaultPartHttpMessageReaderTests.java @@ -63,6 +63,7 @@ import static org.springframework.core.io.buffer.DataBufferUtils.release; * * @author Arjen Poutsma * @author Brian Clozel + * @author Seonghun Lee */ class DefaultPartHttpMessageReaderTests extends AbstractLeakCheckingTests { @@ -314,6 +315,20 @@ class DefaultPartHttpMessageReaderTests extends AbstractLeakCheckingTests { .verify(); } + @Test // gh-35099 + void exceedDiskUsageOnSpillOver() { + MockServerHttpRequest request = createRequest("files.multipart", "\"----WebKitFormBoundaryG8fJ50opQOML0oGD\""); + + DefaultPartHttpMessageReader reader = new DefaultPartHttpMessageReader(); + reader.setMaxInMemorySize(90); + reader.setMaxDiskUsagePerPart(99); + Flux result = reader.read(forClass(Part.class), request, emptyMap()); + + StepVerifier.create(result) + .expectError(DataBufferLimitException.class) + .verify(); + } + @ParameterizedDefaultPartHttpMessageReaderTest void emptyLastPart(DefaultPartHttpMessageReader reader) throws InterruptedException { MockServerHttpRequest request = createRequest(