Bug 66425: Avoid exceptions found via poi-fuzz

We try to avoid throwing NullPointerException, ClassCastExceptions and StackOverflowException, but it was possible
to trigger them

Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=61562
https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=62068

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1912383 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Dominik Stadler 2023-09-18 06:38:37 +00:00
parent 836512cc1f
commit 88bbfbb3f7
10 changed files with 35 additions and 12 deletions

View File

@ -198,6 +198,9 @@ public abstract class POIXMLRelation {
* @since 3.16-beta3 * @since 3.16-beta3
*/ */
public InputStream getContents(PackagePart corePart) throws IOException, InvalidFormatException { public InputStream getContents(PackagePart corePart) throws IOException, InvalidFormatException {
if (corePart == null) {
throw new IllegalArgumentException("Core-Part cannot be empty");
}
PackageRelationshipCollection prc = PackageRelationshipCollection prc =
corePart.getRelationshipsByType(getRelation()); corePart.getRelationshipsByType(getRelation());
Iterator<PackageRelationship> it = prc.iterator(); Iterator<PackageRelationship> it = prc.iterator();

View File

@ -229,7 +229,7 @@ public final class POIXMLExtractorFactory implements ExtractorProvider {
// Grab the core document part, and try to identify from that // Grab the core document part, and try to identify from that
final PackagePart corePart = pkg.getPart(core.getRelationship(0)); final PackagePart corePart = pkg.getPart(core.getRelationship(0));
final String contentType = corePart.getContentType(); final String contentType = corePart == null ? null : corePart.getContentType();
// Is it XSSF? // Is it XSSF?
for (XSSFRelation rel : XSSFExcelExtractor.SUPPORTED_TYPES) { for (XSSFRelation rel : XSSFExcelExtractor.SUPPORTED_TYPES) {

View File

@ -22,8 +22,6 @@ import java.lang.reflect.Method;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys; import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer; import javax.xml.transform.Transformer;
import javax.xml.transform.dom.DOMSource; import javax.xml.transform.dom.DOMSource;
@ -53,10 +51,11 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
@Beta @Beta
public class WordToTextConverter extends AbstractWordConverter public class WordToTextConverter extends AbstractWordConverter {
{
private static final Logger LOG = LogManager.getLogger(WordToTextConverter.class); private static final Logger LOG = LogManager.getLogger(WordToTextConverter.class);
private static final int MAX_NESTED_CHILD_NODES = 500;
public static String getText( DirectoryNode root ) throws Exception public static String getText( DirectoryNode root ) throws Exception
{ {
final HWPFDocumentCore wordDocument = AbstractWordUtils.loadDoc( root ); final HWPFDocumentCore wordDocument = AbstractWordUtils.loadDoc( root );
@ -109,7 +108,7 @@ public class WordToTextConverter extends AbstractWordConverter
serializer.transform( domSource, streamResult ); serializer.transform( domSource, streamResult );
} }
private static Document process( File docFile ) throws IOException, ParserConfigurationException { private static Document process( File docFile ) throws IOException {
try (final HWPFDocumentCore wordDocument = AbstractWordUtils.loadDoc( docFile )) { try (final HWPFDocumentCore wordDocument = AbstractWordUtils.loadDoc( docFile )) {
WordToTextConverter wordToTextConverter = new WordToTextConverter( WordToTextConverter wordToTextConverter = new WordToTextConverter(
XMLHelper.newDocumentBuilder().newDocument()); XMLHelper.newDocumentBuilder().newDocument());
@ -118,7 +117,7 @@ public class WordToTextConverter extends AbstractWordConverter
} }
} }
private AtomicInteger noteCounters = new AtomicInteger( 1 ); private final AtomicInteger noteCounters = new AtomicInteger( 1 );
private Element notes; private Element notes;
@ -130,11 +129,8 @@ public class WordToTextConverter extends AbstractWordConverter
* Creates new instance of {@link WordToTextConverter}. Can be used for * Creates new instance of {@link WordToTextConverter}. Can be used for
* output several {@link HWPFDocument}s into single text document. * output several {@link HWPFDocument}s into single text document.
* *
* @throws ParserConfigurationException
* if an internal {@link DocumentBuilder} cannot be created
*/ */
public WordToTextConverter() throws ParserConfigurationException public WordToTextConverter() {
{
this.textDocumentFacade = new TextDocumentFacade( this.textDocumentFacade = new TextDocumentFacade(
XMLHelper.newDocumentBuilder().newDocument() ); XMLHelper.newDocumentBuilder().newDocument() );
} }
@ -312,6 +308,12 @@ public class WordToTextConverter extends AbstractWordConverter
Element note = textDocumentFacade.createBlock(); Element note = textDocumentFacade.createBlock();
notes.appendChild( note ); notes.appendChild( note );
// avoid StackOverflowException with very deeply nested files (mostly synthetic test files via fuzzing)
// if this limit is reached in real-word documents, we can make this configurable
if (note.getParentNode() != null && note.getParentNode().getChildNodes().getLength() > MAX_NESTED_CHILD_NODES) {
throw new IllegalStateException("Had more than the limit of " + MAX_NESTED_CHILD_NODES + " nested child notes");
}
note.appendChild( textDocumentFacade.createText( "^" + noteIndex note.appendChild( textDocumentFacade.createText( "^" + noteIndex
+ "\t " ) ); + "\t " ) );
processCharacters( wordDocument, Integer.MIN_VALUE, noteTextRange, note ); processCharacters( wordDocument, Integer.MIN_VALUE, noteTextRange, note );

View File

@ -35,6 +35,7 @@ import java.util.stream.Stream;
import org.apache.poi.POIDataSamples; import org.apache.poi.POIDataSamples;
import org.apache.poi.hslf.exceptions.EncryptedPowerPointFileException; import org.apache.poi.hslf.exceptions.EncryptedPowerPointFileException;
import org.apache.poi.hslf.exceptions.HSLFException;
import org.apache.poi.hslf.exceptions.OldPowerPointFormatException; import org.apache.poi.hslf.exceptions.OldPowerPointFormatException;
import org.apache.poi.util.IOUtils; import org.apache.poi.util.IOUtils;
import org.apache.commons.io.output.NullPrintStream; import org.apache.commons.io.output.NullPrintStream;
@ -65,6 +66,7 @@ public abstract class BaseTestPPTIterating {
EXCLUDED.put("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6710128412590080.ppt", RuntimeException.class); EXCLUDED.put("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6710128412590080.ppt", RuntimeException.class);
EXCLUDED.put("clusterfuzz-testcase-minimized-POIFuzzer-5429732352851968.ppt", FileNotFoundException.class); EXCLUDED.put("clusterfuzz-testcase-minimized-POIFuzzer-5429732352851968.ppt", FileNotFoundException.class);
EXCLUDED.put("clusterfuzz-testcase-minimized-POIFuzzer-5681320547975168.ppt", FileNotFoundException.class); EXCLUDED.put("clusterfuzz-testcase-minimized-POIFuzzer-5681320547975168.ppt", FileNotFoundException.class);
EXCLUDED.put("clusterfuzz-testcase-minimized-POIHSLFFuzzer-5962760801091584.ppt", RuntimeException.class);
} }
public static Stream<Arguments> files() { public static Stream<Arguments> files() {

View File

@ -50,6 +50,8 @@ public final class EscherContainerRecord extends EscherRecord implements Iterabl
private static final Logger LOGGER = LogManager.getLogger(EscherContainerRecord.class); private static final Logger LOGGER = LogManager.getLogger(EscherContainerRecord.class);
private static final int MAX_NESTED_CHILD_NODES = 1000;
/** /**
* in case if document contains any charts we have such document structure: * in case if document contains any charts we have such document structure:
* BOF * BOF
@ -86,12 +88,26 @@ public final class EscherContainerRecord extends EscherRecord implements Iterabl
@Override @Override
public int fillFields(byte[] data, int pOffset, EscherRecordFactory recordFactory) { public int fillFields(byte[] data, int pOffset, EscherRecordFactory recordFactory) {
return fillFields(data, pOffset, recordFactory, 0);
}
private int fillFields(byte[] data, int pOffset, EscherRecordFactory recordFactory, int nesting) {
if (nesting > MAX_NESTED_CHILD_NODES) {
throw new IllegalStateException("Had more than the limit of " + MAX_NESTED_CHILD_NODES + " nested child notes");
}
int bytesRemaining = readHeader(data, pOffset); int bytesRemaining = readHeader(data, pOffset);
int bytesWritten = 8; int bytesWritten = 8;
int offset = pOffset + 8; int offset = pOffset + 8;
while (bytesRemaining > 0 && offset < data.length) { while (bytesRemaining > 0 && offset < data.length) {
EscherRecord child = recordFactory.createRecord(data, offset); EscherRecord child = recordFactory.createRecord(data, offset);
int childBytesWritten = child.fillFields(data, offset, recordFactory);
final int childBytesWritten;
if (child instanceof EscherContainerRecord) {
childBytesWritten = ((EscherContainerRecord)child).fillFields(data, offset, recordFactory, nesting + 1);
} else {
childBytesWritten = child.fillFields(data, offset, recordFactory);
}
bytesWritten += childBytesWritten; bytesWritten += childBytesWritten;
offset += childBytesWritten; offset += childBytesWritten;
bytesRemaining -= childBytesWritten; bytesRemaining -= childBytesWritten;

Binary file not shown.