Tim Scheckenbach created TIKA-4830:
--------------------------------------

             Summary: JackcessParser.parse leaks Jackcess RuntimeExceptions 
(IndexOutOfBoundsException / IllegalStateException) on malformed MDBs while 
reading the global usage map
                 Key: TIKA-4830
                 URL: https://issues.apache.org/jira/browse/TIKA-4830
             Project: Tika
          Issue Type: Bug
          Components: parser
    Affects Versions: 4.0.0
            Reporter: Tim Scheckenbach
         Attachments: inputs.zip

{{JackcessParser.parse}} is declared {{{}throws IOException, SAXException, 
TikaException{}}}. Two fuzz inputs for the same open path —

{{DatabaseBuilder.open}} → {{PageChannel.initialize}} → {{UsageMap.read}} 
(global usage map, page 1, row 0)

— make Jackcess throw a {{RuntimeException}} that Tika does not wrap. The 
OSS-Fuzz target {{JackcessParserFuzzer}} swallows only 
{{{}IllegalArgumentException | TikaException | SAXException | IOException{}}}, 
so both are fuzzer crashes. {{AutoDetectParser}} / {{CompositeParser}} would 
wrap them as {{{}TikaException: Unexpected RuntimeException from 
JackcessParser{}}}; callers of {{JackcessParser}} directly do not.

Calling
{code:java}
new JackcessParser().parse(TikaInputStream.get(new 
File("crash-083680cb14186e1be3e26b93946ea43c538d2e62")),
        new ToTextContentHandler(), new Metadata(), new ParseContext());
{code}
on the first attached file (32768-byte Jet 4 MDB) results in:
{code:java}
java.lang.IndexOutOfBoundsException
    at java.base/java.nio.Buffer.checkIndex(Buffer.java:743)
    at java.base/java.nio.HeapByteBuffer.get(HeapByteBuffer.java:169)
    at com.healthmarketscience.jackcess.impl.UsageMap.read(UsageMap.java:133)
    at 
com.healthmarketscience.jackcess.impl.PageChannel.initialize(PageChannel.java:117)
    at 
com.healthmarketscience.jackcess.impl.DatabaseImpl.<init>(DatabaseImpl.java:598)
    at 
com.healthmarketscience.jackcess.impl.DatabaseImpl.open(DatabaseImpl.java:458)
    at 
com.healthmarketscience.jackcess.DatabaseBuilder.open(DatabaseBuilder.java:278)
    at 
org.apache.tika.parser.microsoft.JackcessParser.parse(JackcessParser.java:101)
{code}
{{UsageMap.read}} is declared {{throws IOException}} but does:
{code:java}
short rowStart = TableImpl.findRowStart(tableBuffer, rowNum, format);
int rowEnd = TableImpl.findRowEnd(tableBuffer, rowNum, format);
tableBuffer.limit(rowEnd);
byte mapType = tableBuffer.get(rowStart); // IndexOutOfBoundsException
{code}
Tika does not catch {{IndexOutOfBoundsException}} at all.

Calling the same parse on the second attached file, 
{{crash-107db845b1fe3fee58a3b1da3a4646e604af329b}} (24576-byte Jet 4 MDB), 
results in:
{code:java}
java.lang.IllegalStateException: invalid page number 169285950
    at 
com.healthmarketscience.jackcess.impl.PageChannel.validatePageNumber(PageChannel.java:203)
    at 
com.healthmarketscience.jackcess.impl.PageChannel.readPage(PageChannel.java:219)
    at 
com.healthmarketscience.jackcess.impl.TempPageHolder.setPage(TempPageHolder.java:86)
    at 
com.healthmarketscience.jackcess.impl.UsageMap$ReferenceHandler.<init>(UsageMap.java:724)
    at 
com.healthmarketscience.jackcess.impl.UsageMap.initHandler(UsageMap.java:146)
    at com.healthmarketscience.jackcess.impl.UsageMap.read(UsageMap.java:135)
    at 
com.healthmarketscience.jackcess.impl.PageChannel.initialize(PageChannel.java:117)
    at 
com.healthmarketscience.jackcess.impl.DatabaseImpl.open(DatabaseImpl.java:458)
    at 
com.healthmarketscience.jackcess.DatabaseBuilder.open(DatabaseBuilder.java:278)
    at 
org.apache.tika.parser.microsoft.JackcessParser.parse(JackcessParser.java:101)
{code}
Here the global usage map is a _reference_ map ({{{}MAP_TYPE_REFERENCE{}}}). 
Its constructor walks page pointers and {{validatePageNumber}} is declared 
{{throws IOException}} but does:
{code:java}
if ((pageNumber <= INVALID_PAGE_NUMBER) || (pageNumber >= nextPageNumber)) {
    throw new IllegalStateException("invalid page number " + pageNumber);
}
{code}
Page 169285950 is far past the end of a 6-page file. Tika already catches 
{{{}IllegalStateException{}}}, but only to map "Incorrect password"; every 
other one is rethrown:
{code:java}
} catch (IllegalStateException e) {
    if (e.getMessage() != null && e.getMessage().contains("Incorrect 
password")) {
        throw new EncryptedDocumentException(e);
    }
    throw e;
}
{code}
Suggested Tika-side fix: catch {{RuntimeException}} from 
{{DatabaseBuilder.open}} (or at least {{IndexOutOfBoundsException}} and 
remaining {{{}IllegalStateException{}}}) and rethrow as {{TikaException}} or 
{{{}CorruptedFileException{}}}. One catch covers both inputs. The Jackcess-side 
fix is to throw {{IOException}} for a corrupt usage map, matching the declared 
contracts of {{UsageMap.read}} and {{{}validatePageNumber{}}}.

I have attached both files as an archive to the issue.

Found by the CISPA Fandango Team



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to