From 39dbbe9c413f9d04d6b51a20e93f2a2ff270f314 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Sun, 7 Dec 2014 14:34:19 +0000 Subject: [PATCH] #57272 - deadlock on corrupted PPT file + some refactoring in HSLFSlideShow, which will be necessary for cryptoapi support git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1643680 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/poi/hslf/HSLFSlideShow.java | 121 +++++++++++------- .../apache/poi/hslf/usermodel/TestBugs.java | 15 +++ .../57272_corrupted_usereditatom.ppt | Bin 0 -> 72192 bytes 3 files changed, 88 insertions(+), 48 deletions(-) create mode 100644 test-data/slideshow/57272_corrupted_usereditatom.ppt diff --git a/src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java b/src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java index 420bd383f6..8669caf2cd 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java +++ b/src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java @@ -20,16 +20,16 @@ package org.apache.poi.hslf; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; import org.apache.poi.POIDocument; import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException; @@ -81,7 +81,7 @@ public final class HSLFSlideShow extends POIDocument { // Embedded objects stored in storage records in the document stream, lazily populated. private ObjectData[] _objects; - + /** * Returns the underlying POIFSFileSystem for the document * that is open. @@ -195,6 +195,9 @@ public final class HSLFSlideShow extends POIDocument { // Look for any other streams readOtherStreams(); } + + + /** * Constructs a new, empty, Powerpoint document. */ @@ -269,41 +272,67 @@ public final class HSLFSlideShow extends POIDocument { _records = read(_docstream, (int)currentUser.getCurrentEditOffset()); } - private Record[] read(byte[] docstream, int usrOffset){ - ArrayList lst = new ArrayList(); - HashMap offset2id = new HashMap(); - while (usrOffset != 0){ - UserEditAtom usr = (UserEditAtom) Record.buildRecordAtOffset(docstream, usrOffset); - lst.add(usrOffset); - int psrOffset = usr.getPersistPointersOffset(); - - PersistPtrHolder ptr = (PersistPtrHolder)Record.buildRecordAtOffset(docstream, psrOffset); - lst.add(psrOffset); - Hashtable entries = ptr.getSlideLocationsLookup(); - for(Integer id : entries.keySet()) { - Integer offset = entries.get(id); - lst.add(offset); - offset2id.put(offset, id); - } - - usrOffset = usr.getLastUserEditAtomOffset(); - } + private Record[] read(byte[] docstream, int usrOffset){ //sort found records by offset. //(it is not necessary but SlideShow.findMostRecentCoreRecords() expects them sorted) - Integer a[] = lst.toArray(new Integer[lst.size()]); - Arrays.sort(a); - Record[] rec = new Record[lst.size()]; - for (int i = 0; i < a.length; i++) { - Integer offset = a[i]; - rec[i] = Record.buildRecordAtOffset(docstream, offset.intValue()); - if(rec[i] instanceof PersistRecord) { - PersistRecord psr = (PersistRecord)rec[i]; - Integer id = offset2id.get(offset); - psr.setPersistId(id.intValue()); + NavigableMap records = new TreeMap(); // offset -> record + Map persistIds = new HashMap(); // offset -> persistId + initRecordOffsets(docstream, usrOffset, records, persistIds); + + for (Map.Entry entry : records.entrySet()) { + Integer offset = entry.getKey(); + Record record = entry.getValue(); + Integer persistId = persistIds.get(offset); + if (record == null) { + // all plain records have been already added, + // only new records need to be decrypted (tbd #35897) + record = Record.buildRecordAtOffset(docstream, offset); + entry.setValue(record); } + + if (record instanceof PersistRecord) { + ((PersistRecord)record).setPersistId(persistId); + } } + + return records.values().toArray(new Record[records.size()]); + } - return rec; + private void initRecordOffsets(byte[] docstream, int usrOffset, NavigableMap recordMap, Map offset2id) { + while (usrOffset != 0){ + UserEditAtom usr = (UserEditAtom) Record.buildRecordAtOffset(docstream, usrOffset); + recordMap.put(usrOffset, usr); + + int psrOffset = usr.getPersistPointersOffset(); + PersistPtrHolder ptr = (PersistPtrHolder)Record.buildRecordAtOffset(docstream, psrOffset); + recordMap.put(psrOffset, ptr); + + for(Map.Entry entry : ptr.getSlideLocationsLookup().entrySet()) { + Integer offset = entry.getValue(); + Integer id = entry.getKey(); + recordMap.put(offset, null); // reserve a slot for the record + offset2id.put(offset, id); + } + + usrOffset = usr.getLastUserEditAtomOffset(); + + // check for corrupted user edit atom and try to repair it + // if the next user edit atom offset is already known, we would go into an endless loop + if (usrOffset > 0 && recordMap.containsKey(usrOffset)) { + // a user edit atom is usually located 36 byte before the smallest known record offset + usrOffset = recordMap.firstKey()-36; + // check that we really are located on a user edit atom + int ver_inst = LittleEndian.getUShort(docstream, usrOffset); + int type = LittleEndian.getUShort(docstream, usrOffset+2); + int len = LittleEndian.getInt(docstream, usrOffset+4); + if (ver_inst == 0 && type == 4085 && (len == 0x1C || len == 0x20)) { + logger.log(POILogger.WARN, "Repairing invalid user edit atom"); + usr.setLastUserEditAtomOffset(usrOffset); + } else { + throw new CorruptPowerPointFileException("Powerpoint document contains invalid user edit atom"); + } + } + } } /** @@ -324,34 +353,30 @@ public final class HSLFSlideShow extends POIDocument { private void readOtherStreams() { // Currently, there aren't any } - /** * Find and read in pictures contained in this presentation. * This is lazily called as and when we want to touch pictures. */ + @SuppressWarnings("unused") private void readPictures() throws IOException { _pictures = new ArrayList(); - byte[] pictstream; - - try { - DocumentEntry entry = (DocumentEntry)directory.getEntry("Pictures"); - pictstream = new byte[entry.getSize()]; - DocumentInputStream is = directory.createDocumentInputStream("Pictures"); - is.read(pictstream); - } catch (FileNotFoundException e){ - // Silently catch exceptions if the presentation doesn't - // contain pictures - will use a null set instead - return; - } + // if the presentation doesn't contain pictures - will use a null set instead + if (!directory.hasEntry("Pictures")) return; + + DocumentEntry entry = (DocumentEntry)directory.getEntry("Pictures"); + byte[] pictstream = new byte[entry.getSize()]; + DocumentInputStream is = directory.createDocumentInputStream(entry); + is.read(pictstream); + is.close(); + int pos = 0; // An empty picture record (length 0) will take up 8 bytes while (pos <= (pictstream.length-8)) { int offset = pos; - + // Image signature - @SuppressWarnings("unused") int signature = LittleEndian.getUShort(pictstream, pos); pos += LittleEndian.SHORT_SIZE; // Image type + 0xF018 diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java index c233077d38..4f5d7ae10b 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java @@ -564,4 +564,19 @@ public final class TestBugs { inputStream.close(); } } + + @Test + public void bug57272() throws Exception { + InputStream inputStream = new FileInputStream(_slTests.getFile("57272_corrupted_usereditatom.ppt")); + try { + SlideShow slideShow = new SlideShow(inputStream); + assertEquals(6, slideShow.getSlides().length); + + SlideShow slideBack = HSLFTestDataSamples.writeOutAndReadBack(slideShow); + assertNotNull(slideBack); + assertEquals(6, slideBack.getSlides().length); + } finally { + inputStream.close(); + } + } } diff --git a/test-data/slideshow/57272_corrupted_usereditatom.ppt b/test-data/slideshow/57272_corrupted_usereditatom.ppt new file mode 100644 index 0000000000000000000000000000000000000000..b811d6b043617ffe9e09cc2a609b3897d065786e GIT binary patch literal 72192 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