From f318c931331a386ae3d13f93707f6cb253541d92 Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Thu, 7 May 2026 08:09:00 -0700 Subject: [PATCH] Add size validation for long-to-int casts in buildpack module Add explicit size checks before casting long values to int in Content.of(File) and LogUpdateEvent.read() to prevent silent data corruption or NegativeArraySizeException when processing large files or malformed Docker stream data. See gh-50382 Signed-off-by: Sebastien Tardif --- .../buildpack/platform/docker/LogUpdateEvent.java | 1 + .../boot/buildpack/platform/io/Content.java | 5 ++++- .../platform/docker/LogUpdateEventTests.java | 14 ++++++++++++++ .../boot/buildpack/platform/io/ContentTests.java | 11 +++++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/LogUpdateEvent.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/LogUpdateEvent.java index b7a6143fa5a..384b6b90cc5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/LogUpdateEvent.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/LogUpdateEvent.java @@ -104,6 +104,7 @@ public class LogUpdateEvent extends UpdateEvent { } private static byte[] read(InputStream inputStream, long size) throws IOException { + Assert.state(size <= Integer.MAX_VALUE, () -> "Log update event data is too large (%d bytes)".formatted(size)); byte[] data = new byte[(int) size]; int offset = 0; do { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/Content.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/Content.java index ca732c47cf9..7bf67a27be7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/Content.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/io/Content.java @@ -75,7 +75,10 @@ public interface Content { */ static Content of(File file) { Assert.notNull(file, "'file' must not be null"); - return of((int) file.length(), () -> new FileInputStream(file)); + long length = file.length(); + Assert.state(length <= Integer.MAX_VALUE, + () -> "'file' is too large (%d bytes) to be used as content".formatted(length)); + return of((int) length, () -> new FileInputStream(file)); } /** diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/LogUpdateEventTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/LogUpdateEventTests.java index 57744973e0a..e6e5167c703 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/LogUpdateEventTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/LogUpdateEventTests.java @@ -16,6 +16,7 @@ package org.springframework.boot.buildpack.platform.docker; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -60,6 +61,19 @@ class LogUpdateEventTests { assertThat(events.get(0)).hasToString("Stream type is out of bounds. Must be >= 0 and < 3, but was 3"); } + @Test + void readAllWhenPayloadSizeExceedsIntMaxReturnsErrorEvent() throws IOException { + // Docker multiplexed stream header: 1 byte stream type (1=STDOUT), + // 3 padding bytes, 4 bytes big-endian size (0xFFFFFFFF = 4294967295) + byte[] header = new byte[] { 1, 0, 0, 0, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF }; + List events = new ArrayList<>(); + try (InputStream inputStream = new ByteArrayInputStream(header)) { + LogUpdateEvent.readAll(inputStream, events::add); + } + assertThat(events).hasSize(1); + assertThat(events.get(0)).hasToString("Log update event data is too large (4294967295 bytes)"); + } + private List readAll(String name) throws IOException { List events = new ArrayList<>(); try (InputStream inputStream = getClass().getResourceAsStream(name)) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/ContentTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/ContentTests.java index 00f73c10a7e..2a2cb3b3dd4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/ContentTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/io/ContentTests.java @@ -18,6 +18,7 @@ package org.springframework.boot.buildpack.platform.io; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -26,6 +27,9 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; /** * Tests for {@link Content}. @@ -73,6 +77,13 @@ class ContentTests { assertThat(writeToAndGetBytes(writable)).isEqualTo(bytes); } + @Test + void ofFileWhenFileTooLargeThrowsException() { + File file = mock(File.class); + given(file.length()).willReturn((long) Integer.MAX_VALUE + 1); + assertThatIllegalStateException().isThrownBy(() -> Content.of(file)).withMessageContaining("too large"); + } + private byte[] writeToAndGetBytes(Content writable) throws IOException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); writable.writeTo(outputStream);