bug 61294 -- prevent infinite loop in IOUtils' skipFully.

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1801844 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Tim Allison 2017-07-13 16:20:28 +00:00
parent d1a51f76f0
commit 278a235b08
4 changed files with 204 additions and 4 deletions

View File

@ -361,7 +361,7 @@ public final class IOUtils {
} }
/** /**
* Skips bytes from a stream. Returns -1L if EOF was hit before * Skips bytes from a stream. Returns -1L if len > available() or if EOF was hit before
* the end of the stream. * the end of the stream.
* *
* @param in inputstream * @param in inputstream
@ -370,11 +370,22 @@ public final class IOUtils {
* @throws IOException on IOException * @throws IOException on IOException
*/ */
public static long skipFully(InputStream in, long len) throws IOException { public static long skipFully(InputStream in, long len) throws IOException {
int total = 0; long total = 0;
while (true) { while (true) {
long toSkip = len-total;
//check that the stream has the toSkip available
//FileInputStream can mis-report 20k skipped on a 10k file
if (toSkip > in.available()) {
return -1L;
}
long got = in.skip(len-total); long got = in.skip(len-total);
if (got < 0) { if (got < 0) {
return -1L; return -1L;
} else if (got == 0) {
got = fallBackToReadFully(len-total, in);
if (got < 0) {
return -1L;
}
} }
total += got; total += got;
if (total == len) { if (total == len) {
@ -382,4 +393,24 @@ public final class IOUtils {
} }
} }
} }
//an InputStream can return 0 whether or not it hits EOF
//if it returns 0, back off to readFully to test for -1
private static long fallBackToReadFully(long lenToRead, InputStream in) throws IOException {
byte[] buffer = new byte[8192];
long readSoFar = 0;
while (true) {
int toSkip = (lenToRead > Integer.MAX_VALUE ||
(lenToRead-readSoFar) > buffer.length) ? buffer.length : (int)(lenToRead-readSoFar);
long readNow = readFully(in, buffer, 0, toSkip);
if (readNow < toSkip) {
return -1L;
}
readSoFar += readNow;
if (readSoFar == lenToRead) {
return readSoFar;
}
}
}
} }

View File

@ -22,6 +22,8 @@ import static org.apache.poi.POITestCase.assertContains;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -34,6 +36,8 @@ import org.apache.poi.hemf.record.HemfHeader;
import org.apache.poi.hemf.record.HemfRecord; import org.apache.poi.hemf.record.HemfRecord;
import org.apache.poi.hemf.record.HemfRecordType; import org.apache.poi.hemf.record.HemfRecordType;
import org.apache.poi.hemf.record.HemfText; import org.apache.poi.hemf.record.HemfText;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.RecordFormatException;
import org.junit.Test; import org.junit.Test;
public class HemfExtractorTest { public class HemfExtractorTest {
@ -160,8 +164,37 @@ public class HemfExtractorTest {
assertEquals(expectedParts.size(), foundExpected); assertEquals(expectedParts.size(), foundExpected);
} }
/*
@Test(expected = RecordFormatException.class)
public void testInfiniteLoopOnFile() throws Exception {
InputStream is = null;
try {
is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("61294.emf");
HemfExtractor ex = new HemfExtractor(is);
for (HemfRecord record : ex) {
}
} finally {
IOUtils.closeQuietly(is);
}
}
@Test(expected = RecordFormatException.class)
public void testInfiniteLoopOnByteArray() throws Exception {
InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("61294.emf");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
IOUtils.copy(is, bos);
is.close();
HemfExtractor ex = new HemfExtractor(new ByteArrayInputStream(bos.toByteArray()));
for (HemfRecord record : ex) {
}
}
/*
govdocs1 064213.doc-0.emf contains an example of extextouta govdocs1 064213.doc-0.emf contains an example of extextouta
*/ */
} }

View File

@ -0,0 +1,136 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.util;
import static org.junit.Assert.assertEquals;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Random;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* Class to test IOUtils
*/
public final class TestIOUtils {
static File TMP = null;
static long SEED = new Random().nextLong();
static Random RANDOM = new Random(SEED);
@BeforeClass
public static void setUp() throws IOException {
TMP = File.createTempFile("poi-ioutils-", "");
OutputStream os = new FileOutputStream(TMP);
for (int i = 0; i < RANDOM.nextInt(10000); i++) {
os.write(RANDOM.nextInt((byte)127));
}
os.flush();
os.close();
}
@AfterClass
public static void tearDown() throws IOException {
TMP.delete();
}
@Test
public void testSkipFully() throws IOException {
InputStream is = new FileInputStream(TMP);
long skipped = IOUtils.skipFully(is, 20000L);
assertEquals("seed: "+SEED, -1L, skipped);
}
@Test
public void testSkipFullyGtIntMax() throws IOException {
InputStream is = new FileInputStream(TMP);
long skipped = IOUtils.skipFully(is, Integer.MAX_VALUE + 20000L);
assertEquals("seed: "+SEED, -1L, skipped);
}
@Test
public void testSkipFullyByteArray() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
InputStream is = new FileInputStream(TMP);
IOUtils.copy(is, bos);
long skipped = IOUtils.skipFully(new ByteArrayInputStream(bos.toByteArray()), 20000L);
assertEquals("seed: "+SEED, -1L, skipped);
}
@Test
public void testSkipFullyByteArrayGtIntMax() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
InputStream is = new FileInputStream(TMP);
IOUtils.copy(is, bos);
long skipped = IOUtils.skipFully(new ByteArrayInputStream(bos.toByteArray()), Integer.MAX_VALUE+ 20000L);
assertEquals("seed: "+SEED, -1L, skipped);
}
@Test
public void testWonkyInputStream() throws IOException {
long skipped = IOUtils.skipFully(new WonkyInputStream(), 10000);
assertEquals("seed: "+SEED, 10000, skipped);
}
/**
* This returns 0 for the first call to skip and then reads
* as requested. This tests that the fallback to read() works.
*/
private static class WonkyInputStream extends InputStream {
int skipCalled = 0;
int readCalled = 0;
@Override
public int read() throws IOException {
readCalled++;
return 0;
}
@Override
public int read(byte[] arr, int offset, int len) throws IOException {
readCalled++;
return len;
}
@Override
public long skip(long len) throws IOException {
skipCalled++;
if (skipCalled == 1) {
return 0;
} else if (skipCalled > 100) {
return len;
} else {
return 100;
}
}
@Override
public int available() {
return 100000;
}
}
}

Binary file not shown.