Skip to content

Commit 0accfaa

Browse files
Fix infinite loop in AbstractByteArrayOutputStream. (#758)
When an AbstractByteArrayOutputStream is initialized with a 0 size buffer and writeImpl(InputStream) is called an infinite loop may occur and eventually cause an OOM exception. This is due to passing 0 as the maximum bytes to read in InputStream#read which returns 0, then a new 0 size buffer is created, and then InputStream#read is called again therefore repeating the loop forever. Fix this infinite loop by updating needNewBuffer to use DEFAULT_SIZE as the initial buffer size instead of 0. This will allow InputStream#read to properly populate the buffer as expected and terminate the loop when EOF is reached. Set initial value of currentBufferIndex to -1 so it is properly initialized to 0 when needNewBuffer is called; unrelated to the infinite loop bug. Co-authored-by: Alex Benusovich <[email protected]>
1 parent 359ab3c commit 0accfaa

File tree

2 files changed

+30
-6
lines changed

2 files changed

+30
-6
lines changed

src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ protected interface InputStreamConstructor<T extends InputStream> {
9292
private byte[] currentBuffer;
9393

9494
/** The index of the current buffer. */
95-
private int currentBufferIndex;
95+
private int currentBufferIndex = -1;
9696

9797
/** The total count of bytes in all the filled buffers. */
9898
private int filledBufferSum;
@@ -147,7 +147,8 @@ protected void needNewBuffer(final int newCount) {
147147
// Creating new buffer
148148
final int newBufferSize;
149149
if (currentBuffer == null) {
150-
newBufferSize = newCount;
150+
// prevents 0 size buffers
151+
newBufferSize = newCount > 0 ? newCount : DEFAULT_SIZE;
151152
filledBufferSum = 0;
152153
} else {
153154
newBufferSize = Math.max(currentBuffer.length << 1, newCount - filledBufferSum);

src/test/java/org/apache/commons/io/output/ByteArrayOutputStreamTest.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,15 @@
2828
import java.io.IOException;
2929
import java.io.InputStream;
3030
import java.nio.charset.StandardCharsets;
31+
import java.nio.file.Files;
32+
import java.nio.file.Path;
3133
import java.util.Arrays;
3234
import java.util.stream.Stream;
3335

3436
import org.apache.commons.io.IOUtils;
3537
import org.apache.commons.io.function.IOFunction;
3638
import org.apache.commons.io.input.ClosedInputStream;
39+
import org.junit.jupiter.api.io.TempDir;
3740
import org.junit.jupiter.params.ParameterizedTest;
3841
import org.junit.jupiter.params.provider.Arguments;
3942
import org.junit.jupiter.params.provider.MethodSource;
@@ -100,14 +103,19 @@ private static boolean byteCmp(final byte[] src, final byte[] cmp) {
100103
private static Stream<Arguments> toBufferedInputStreamFunctionFactories() {
101104
final IOFunction<InputStream, InputStream> syncBaosToBufferedInputStream = ByteArrayOutputStream::toBufferedInputStream;
102105
final IOFunction<InputStream, InputStream> syncBaosToBufferedInputStreamWithSize = is -> ByteArrayOutputStream.toBufferedInputStream(is, 1024);
106+
final IOFunction<InputStream, InputStream> syncBaosToBufferedInputStreamWith0Size = is -> ByteArrayOutputStream.toBufferedInputStream(is, 0);
103107
final IOFunction<InputStream, InputStream> unSyncBaosToBufferedInputStream = UnsynchronizedByteArrayOutputStream::toBufferedInputStream;
104-
final IOFunction<InputStream, InputStream> unSyncBaosToBufferedInputStreamWithSize = is -> UnsynchronizedByteArrayOutputStream.toBufferedInputStream(is,
105-
1024);
108+
final IOFunction<InputStream, InputStream> unSyncBaosToBufferedInputStreamWithSize = is -> UnsynchronizedByteArrayOutputStream.toBufferedInputStream(
109+
is, 1024);
110+
final IOFunction<InputStream, InputStream> unSyncBaosToBufferedInputStreamWith0Size = is -> UnsynchronizedByteArrayOutputStream.toBufferedInputStream(
111+
is, 0);
106112

107113
return Stream.of(Arguments.of("ByteArrayOutputStream.toBufferedInputStream(InputStream)", syncBaosToBufferedInputStream),
108114
Arguments.of("ByteArrayOutputStream.toBufferedInputStream(InputStream, int)", syncBaosToBufferedInputStreamWithSize),
115+
Arguments.of("ByteArrayOutputStream.toBufferedInputStream(InputStream, 0)", syncBaosToBufferedInputStreamWith0Size),
109116
Arguments.of("UnsynchronizedByteArrayOutputStream.toBufferedInputStream(InputStream)", unSyncBaosToBufferedInputStream),
110-
Arguments.of("UnsynchronizedByteArrayOutputStream.toBufferedInputStream(InputStream, int)", unSyncBaosToBufferedInputStreamWithSize));
117+
Arguments.of("UnsynchronizedByteArrayOutputStream.toBufferedInputStream(InputStream, int)", unSyncBaosToBufferedInputStreamWithSize),
118+
Arguments.of("UnsynchronizedByteArrayOutputStream.toBufferedInputStream(InputStream, 0)", unSyncBaosToBufferedInputStreamWith0Size));
111119
}
112120

113121
private void checkByteArrays(final byte[] expected, final byte[] actual) {
@@ -184,7 +192,6 @@ void testToBufferedInputStream(final String baosName, final IOFunction<InputStre
184192
assertEquals(data.length, buffered.available());
185193

186194
assertArrayEquals(data, IOUtils.toByteArray(buffered));
187-
188195
}
189196
}
190197
}
@@ -198,7 +205,23 @@ void testToBufferedInputStreamEmpty(final String baosName, final IOFunction<Inpu
198205

199206
try (InputStream buffered = toBufferedInputStreamFunction.apply(bain)) {
200207
assertEquals(0, buffered.available());
208+
}
209+
}
210+
}
211+
212+
@ParameterizedTest(name = "[{index}] {0}")
213+
@MethodSource("toBufferedInputStreamFunctionFactories")
214+
void testToBufferedInputStreamEmptyFile(final String baosName, final IOFunction<InputStream, InputStream> toBufferedInputStreamFunction,
215+
final @TempDir Path temporaryFolder) throws IOException {
216+
final Path emptyFile = Files.createTempFile(temporaryFolder, getClass().getSimpleName(), "-empty.txt");
217+
218+
try (InputStream is = Files.newInputStream(emptyFile)) {
219+
assertEquals(0, is.available());
220+
221+
try (InputStream buffered = toBufferedInputStreamFunction.apply(is)) {
222+
assertEquals(0, buffered.available());
201223

224+
assertArrayEquals(IOUtils.EMPTY_BYTE_ARRAY, IOUtils.toByteArray(buffered));
202225
}
203226
}
204227
}

0 commit comments

Comments
 (0)