From 09fbfd5be45c7597cb1517a95e18b19429c47d58 Mon Sep 17 00:00:00 2001 From: Dominik Stadler Date: Tue, 16 Jul 2024 05:26:42 +0000 Subject: [PATCH] Bug 66425: Avoid exceptions found via poi-fuzz Avoid a possible OutOfMemoryException with many child-records This avoids having too many children in EscherRecords, the limit of 100_000 is arbitrarily chosen and can be adjusted if needed Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=62924 and maybe others git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1919272 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/hslf/dev/BaseTestPPTIterating.java | 1 + .../apache/poi/hslf/dev/TestPPTXMLDump.java | 1 + .../poi/hslf/dev/TestSlideShowDumper.java | 10 +++++ .../apache/poi/ddf/EscherContainerRecord.java | 11 ++++++ .../java/org/apache/poi/ddf/EscherRecord.java | 18 +++++++++ .../apache/poi/ddf/UnknownEscherRecord.java | 11 ++++++ .../poi/ddf/TestEscherContainerRecord.java | 37 +++++++++++++++--- .../poi/ddf/TestUnknownEscherRecord.java | 28 ++++++++++++- ...nimized-POIHSLFFuzzer-6614960949821440.ppt | Bin 0 -> 7232 bytes test-data/spreadsheet/stress.xls | Bin 109056 -> 109568 bytes 10 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 test-data/slideshow/clusterfuzz-testcase-minimized-POIHSLFFuzzer-6614960949821440.ppt diff --git a/poi-scratchpad/src/test/java/org/apache/poi/hslf/dev/BaseTestPPTIterating.java b/poi-scratchpad/src/test/java/org/apache/poi/hslf/dev/BaseTestPPTIterating.java index 5372a23bbf..e1458db0cb 100644 --- a/poi-scratchpad/src/test/java/org/apache/poi/hslf/dev/BaseTestPPTIterating.java +++ b/poi-scratchpad/src/test/java/org/apache/poi/hslf/dev/BaseTestPPTIterating.java @@ -72,6 +72,7 @@ public abstract class BaseTestPPTIterating { EXCLUDED.put("clusterfuzz-testcase-minimized-POIHSLFFuzzer-4624961081573376.ppt", FileNotFoundException.class); EXCLUDED.put("clusterfuzz-testcase-minimized-POIHSLFFuzzer-5018229722382336.ppt", RuntimeException.class); EXCLUDED.put("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6192650357112832.ppt", RuntimeException.class); + EXCLUDED.put("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6614960949821440.ppt", RuntimeException.class); } public static Stream files() { diff --git a/poi-scratchpad/src/test/java/org/apache/poi/hslf/dev/TestPPTXMLDump.java b/poi-scratchpad/src/test/java/org/apache/poi/hslf/dev/TestPPTXMLDump.java index ca4d09be24..213b05387c 100644 --- a/poi-scratchpad/src/test/java/org/apache/poi/hslf/dev/TestPPTXMLDump.java +++ b/poi-scratchpad/src/test/java/org/apache/poi/hslf/dev/TestPPTXMLDump.java @@ -36,6 +36,7 @@ public class TestPPTXMLDump extends BaseTestPPTIterating { LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6032591399288832.ppt"); LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6360479850954752.ppt"); LOCAL_EXCLUDED.add("ppt_with_png_encrypted.ppt"); + LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6614960949821440.ppt"); } @Test diff --git a/poi-scratchpad/src/test/java/org/apache/poi/hslf/dev/TestSlideShowDumper.java b/poi-scratchpad/src/test/java/org/apache/poi/hslf/dev/TestSlideShowDumper.java index 06a742c191..290616ce10 100644 --- a/poi-scratchpad/src/test/java/org/apache/poi/hslf/dev/TestSlideShowDumper.java +++ b/poi-scratchpad/src/test/java/org/apache/poi/hslf/dev/TestSlideShowDumper.java @@ -41,6 +41,11 @@ public class TestSlideShowDumper extends BaseTestPPTIterating { FAILING.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6360479850954752.ppt"); } + static final Set LOCAL_EXCLUDED = new HashSet<>(); + static { + LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6614960949821440.ppt"); + } + @Test void testMain() throws IOException { // SlideShowDumper calls IOUtils.toByteArray(is), which would fail if a different size is defined @@ -71,6 +76,11 @@ public class TestSlideShowDumper extends BaseTestPPTIterating { throw e; } } + + // these fail everywhere else, so also should fail here + if (LOCAL_EXCLUDED.contains(pFile.getName())) { + throw new RuntimeException(); + } } @Override diff --git a/poi/src/main/java/org/apache/poi/ddf/EscherContainerRecord.java b/poi/src/main/java/org/apache/poi/ddf/EscherContainerRecord.java index 747dbde1a5..8a0e555736 100644 --- a/poi/src/main/java/org/apache/poi/ddf/EscherContainerRecord.java +++ b/poi/src/main/java/org/apache/poi/ddf/EscherContainerRecord.java @@ -208,6 +208,12 @@ public final class EscherContainerRecord extends EscherRecord implements Iterabl if (childRecords == _childRecords) { throw new IllegalStateException("Child records private data member has escaped"); } + + if (childRecords.size() > MAX_NUMBER_OF_CHILDREN) { + throw new IllegalStateException("Cannot add more than " + MAX_NUMBER_OF_CHILDREN + + " child records, you can use 'EscherRecord.setMaxNumberOfChildren()' to increase the allow size"); + } + _childRecords.clear(); _childRecords.addAll(childRecords); } @@ -261,6 +267,11 @@ public final class EscherContainerRecord extends EscherRecord implements Iterabl * @param record the record to be added */ public void addChildRecord(EscherRecord record) { + if (_childRecords.size() >= MAX_NUMBER_OF_CHILDREN) { + throw new IllegalStateException("Cannot add more than " + MAX_NUMBER_OF_CHILDREN + + " child records, you can use 'EscherRecord.setMaxNumberOfChildren()' to increase the allow size"); + } + _childRecords.add(record); } diff --git a/poi/src/main/java/org/apache/poi/ddf/EscherRecord.java b/poi/src/main/java/org/apache/poi/ddf/EscherRecord.java index 3d9aeacca8..e633a92079 100644 --- a/poi/src/main/java/org/apache/poi/ddf/EscherRecord.java +++ b/poi/src/main/java/org/apache/poi/ddf/EscherRecord.java @@ -45,6 +45,10 @@ public abstract class EscherRecord implements Duplicatable, GenericRecord { private short _options; private short _recordId; + // arbitrarily selected; may need to increase + private static final int DEFAULT_MAX_NUMBER_OF_CHILDREN = 100_000; + protected static int MAX_NUMBER_OF_CHILDREN = DEFAULT_MAX_NUMBER_OF_CHILDREN; + /** * Create a new instance */ @@ -367,4 +371,18 @@ public abstract class EscherRecord implements Duplicatable, GenericRecord { @Override public abstract EscherRecord copy(); + + /** + * @param length the max record length allowed for EscherArrayProperty + */ + public static void setMaxNumberOfChildren(int length) { + MAX_NUMBER_OF_CHILDREN = length; + } + + /** + * @return the max record length allowed for EscherArrayProperty + */ + public static int getMaxNumberOfChildren() { + return MAX_NUMBER_OF_CHILDREN; + } } \ No newline at end of file diff --git a/poi/src/main/java/org/apache/poi/ddf/UnknownEscherRecord.java b/poi/src/main/java/org/apache/poi/ddf/UnknownEscherRecord.java index a81286c5c7..67b6bc6317 100644 --- a/poi/src/main/java/org/apache/poi/ddf/UnknownEscherRecord.java +++ b/poi/src/main/java/org/apache/poi/ddf/UnknownEscherRecord.java @@ -160,6 +160,12 @@ public final class UnknownEscherRecord extends EscherRecord { if (childRecords == _childRecords) { return; } + + if (childRecords.size() > MAX_NUMBER_OF_CHILDREN) { + throw new IllegalStateException("Cannot add more than " + MAX_NUMBER_OF_CHILDREN + + " child records, you can use 'EscherRecord.setMaxNumberOfChildren()' to increase the allow size"); + } + _childRecords.clear(); _childRecords.addAll(childRecords); } @@ -170,6 +176,11 @@ public final class UnknownEscherRecord extends EscherRecord { } public void addChildRecord(EscherRecord childRecord) { + if (_childRecords.size() >= MAX_NUMBER_OF_CHILDREN) { + throw new IllegalStateException("Cannot add more than " + MAX_NUMBER_OF_CHILDREN + + " child records, you can use 'EscherRecord.setMaxNumberOfChildren()' to increase the allow size"); + } + getChildRecords().add( childRecord ); } diff --git a/poi/src/test/java/org/apache/poi/ddf/TestEscherContainerRecord.java b/poi/src/test/java/org/apache/poi/ddf/TestEscherContainerRecord.java index 4b1cfd6291..d13188a53d 100644 --- a/poi/src/test/java/org/apache/poi/ddf/TestEscherContainerRecord.java +++ b/poi/src/test/java/org/apache/poi/ddf/TestEscherContainerRecord.java @@ -20,19 +20,23 @@ package org.apache.poi.ddf; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotSame; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertThrows; +import java.util.Arrays; import java.util.List; import org.apache.poi.POIDataSamples; import org.apache.poi.util.HexDump; import org.apache.poi.util.HexRead; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; /** * Tests for {@link EscherContainerRecord} */ +@Isolated // this test changes global static MAX_NUMBER_OF_CHILDREN final class TestEscherContainerRecord { private static final POIDataSamples _samples = POIDataSamples.getDDFInstance(); @@ -42,7 +46,7 @@ final class TestEscherContainerRecord { byte[] data = HexRead.readFromString("0F 02 11 F1 00 00 00 00"); EscherRecord r = f.createRecord(data, 0); r.fillFields(data, 0, f); - assertTrue(r instanceof EscherContainerRecord); + assertInstanceOf(EscherContainerRecord.class, r); assertEquals((short) 0x020F, r.getOptions()); assertEquals((short) 0xF111, r.getRecordId()); @@ -91,7 +95,7 @@ final class TestEscherContainerRecord { "\t, \"recordSize\": 8\n" + "\t, \"isContainer\": true\n" + "}"; - expected = expected.replace("\n", System.getProperty("line.separator")); + expected = expected.replace("\n", System.lineSeparator()); assertEquals(expected, r.toString()); EscherOptRecord r2 = new EscherOptRecord(); @@ -121,7 +125,7 @@ final class TestEscherContainerRecord { "\t\t}\n" + "\t]\n" + "}"; - expected = expected.replace("\n", System.getProperty("line.separator")); + expected = expected.replace("\n", System.lineSeparator()); assertEquals(expected, r.toString()); r.addChildRecord(r2); @@ -155,7 +159,7 @@ final class TestEscherContainerRecord { "\t\t}\n" + "\t]\n" + "}"; - expected = expected.replace("\n", System.getProperty("line.separator")); + expected = expected.replace("\n", System.lineSeparator()); assertEquals(expected, r.toString()); } @@ -170,7 +174,7 @@ final class TestEscherContainerRecord { @Override public String getRecordName() { return ""; } @Override - public Enum getGenericRecordType() { return EscherRecordTypes.UNKNOWN; } + public Enum getGenericRecordType() { return EscherRecordTypes.UNKNOWN; } @Override public DummyEscherRecord copy() { return null; } } @@ -231,4 +235,25 @@ final class TestEscherContainerRecord { assertEquals(chC, children2.get(0)); assertEquals(chA, children2.get(1)); } + + @Test + void testTooManyChildren() { + EscherContainerRecord ecr = new EscherContainerRecord(); + + int before = EscherRecord.getMaxNumberOfChildren(); + try { + EscherRecord.setMaxNumberOfChildren(2); + + ecr.addChildRecord(new EscherDgRecord()); + ecr.addChildRecord(new EscherDgRecord()); + assertThrows(IllegalStateException.class, + () -> ecr.addChildRecord(new EscherDgRecord())); + + ecr.setChildRecords(Arrays.asList(new EscherDgRecord(), new EscherDgRecord())); + assertThrows(IllegalStateException.class, + () -> ecr.setChildRecords(Arrays.asList(new EscherDgRecord(), new EscherDgRecord(), new EscherDgRecord()))); + } finally { + EscherRecord.setMaxNumberOfChildren(before); + } + } } diff --git a/poi/src/test/java/org/apache/poi/ddf/TestUnknownEscherRecord.java b/poi/src/test/java/org/apache/poi/ddf/TestUnknownEscherRecord.java index fa00b7c739..697fd854cb 100644 --- a/poi/src/test/java/org/apache/poi/ddf/TestUnknownEscherRecord.java +++ b/poi/src/test/java/org/apache/poi/ddf/TestUnknownEscherRecord.java @@ -19,12 +19,17 @@ package org.apache.poi.ddf; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.apache.poi.util.HexDump; import org.apache.poi.util.HexRead; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; +import java.util.Arrays; + +@Isolated // this test changes global static MAX_NUMBER_OF_CHILDREN final class TestUnknownEscherRecord { @Test void testFillFields() { @@ -159,7 +164,28 @@ final class TestUnknownEscherRecord { "\t, \"recordSize\": 8\n" + "\t, \"data\": \"\"\n" + "}"; - expected = expected.replace("\n", System.getProperty("line.separator")); + expected = expected.replace("\n", System.lineSeparator()); assertEquals(expected, r.toString() ); } + + @Test + void testTooManyChildren() { + UnknownEscherRecord ecr = new UnknownEscherRecord(); + + int before = EscherRecord.getMaxNumberOfChildren(); + try { + EscherRecord.setMaxNumberOfChildren(2); + + ecr.addChildRecord(new EscherDgRecord()); + ecr.addChildRecord(new EscherDgRecord()); + assertThrows(IllegalStateException.class, + () -> ecr.addChildRecord(new EscherDgRecord())); + + ecr.setChildRecords(Arrays.asList(new EscherDgRecord(), new EscherDgRecord())); + assertThrows(IllegalStateException.class, + () -> ecr.setChildRecords(Arrays.asList(new EscherDgRecord(), new EscherDgRecord(), new EscherDgRecord()))); + } finally { + EscherRecord.setMaxNumberOfChildren(before); + } + } } diff --git a/test-data/slideshow/clusterfuzz-testcase-minimized-POIHSLFFuzzer-6614960949821440.ppt b/test-data/slideshow/clusterfuzz-testcase-minimized-POIHSLFFuzzer-6614960949821440.ppt new file mode 100644 index 0000000000000000000000000000000000000000..43ed103f4563cdd6fb9699aee14c7fc9fc74f762 GIT binary patch literal 7232 zcmeHMeQXp(6n}errB~as*IuinF|ZPi!~|`P8dRdEAZntap^#{zNNulhq3tzyi-0ll zvS8=DjyFZ{ED_|G46--q&irmNDrnG3A(7B9lURBLqua608z;%=5ee2KawEjWn=8 zGWa=S%XQL@GApaOloL%ao+(8Vh|9eYfB3%Ur{;fLvF4y`k?TL3QWT<8|Ka9si#EP% zlmnM9)(!QTeWj!@FQK&J!Irszn`DKwp@m$96K5N#&ya`_LDfVa-7?Z=EX4jt)$SE= z-=OAclSU=`jhL@RmIEfn0Y@2N3MbAVMRTBo!aYZ~baN0=(Q&el$4{mpKeI4@F!u@N zKceTP=(DK=q%!0o45a?Z64#4op34(MEQJ5YhEp7E;rhvhE$KI( z1fzx=FlS(%{Fx$OVM1oYJ$|n}(WxFMx7T=@>yk*T5 zxkjMvG*-VAxGy&(auoRD56Ws;GLe?n0- z79K}4vEV+$RZj09L-o46DWmOPZ8YDKnNRMj2oR}W*kQ0lN5 zMb;(aYQvXD6;Hc>Lbjm71*3p`>>+2WVjgHgU zw5;v6>u$%NO6&7wt$-up?uq z$&MUm)oHx5!O3*w{^N&-$7bBa^X`cb&n%4l#}Ch`v=-wEoTR-7TZ_WhX7uLPkY8;7EE=!{BXDBOud<1wpTaUrvy$s;J;Is%bbkFaS z2Nc&~7kDRp&kEo9^#GUnP6qf>v%Cx^eMz<1>3Rc}3u+>}O9Q{skS@$#?%tyC+D}s; z94y=Syo@zH89UXScI=xQb51I2J81{LTh3YCniA==DE&El5Dvp+zeWdMyKt1Q2C5FE zGkxg-cRrAAPp)f}YhOsN!=B~FLKf4K3SY}wF0N|Cm#a^4q2(f;j^hG+=d`AqmfOx4 zxl*efEmp3)A=Q*=Z?;=gccog(7cNU@19)C$Mb=s7H_S`71TZx*6_m9m7A)mW%68%U9S(c{e7&qoOlPaSWYwrrf08J)P6H%&Qj4l+jZ+Mf z9_w6uFF@|)p3)~xHR;h2nXKtV!B>>~=U4S!0D!c`;&Y2Vb?*^q?5Qa#ptU^@2U^ol zMy)l{7MVKEd0A||S{&M5=%x1+FUFFHJ}-*_8`9`drJalmgzuT(3$>gs4_idg)Um|pl!aPoAO1?HMdlwH8v49pHSXTFfO&!UqAlXD zmsiU-n1Zv+L|}}iYnbqnL={SbDyHl9gD9kc011{|3VJ-+eV4Q~!?f1&@;9q+7r zXKVk!#)CT!?d%-bl<&WPeShaG16%S3H>^AGI)JT*wrxKIpzo~%efQ(7j9^Q51&REu zVra0V@J9t$8f@sf>&m%=c@s?dzgeNHgh6h_?e!=Zf(j)fv6wsTI=LPtkk9sDrO H-^lkjeQg#) literal 0 HcmV?d00001 diff --git a/test-data/spreadsheet/stress.xls b/test-data/spreadsheet/stress.xls index 78bed83bc03e28c6a52effe35ceddc2f839a0333..124e07a16f3ad80831342740eb17f0c21e1a5839 100644 GIT binary patch delta 10211 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 t^WgDu{`Cp7q{y$lQJ`Df>