From 017ff5f8f8a78643e8d7c33df5533d179eeb2601 Mon Sep 17 00:00:00 2001 From: Dominik Stadler Date: Fri, 9 Jan 2026 16:05:12 +0100 Subject: [PATCH] Implement lazy allocation of data for complex escher properties This may reduce memory usage if the content is never used or not populated for some reason. This should fix https://issues.oss-fuzz.com/issues/42528528 --- .../converter/TestWordToConverterSuite.java | 3 +- .../converter/TestWordToTextConverter.java | 3 +- .../apache/poi/ddf/EscherComplexProperty.java | 51 +++++++++++++++--- src/resources/ooxml-lite-report.clazz | 2 + src/resources/ooxml-lite-report.xsb | 1 + ...nimized-POIHWPFFuzzer-5832867957309440.doc | Bin 0 -> 4748 bytes test-data/spreadsheet/stress.xls | Bin 74752 -> 75264 bytes 7 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 test-data/document/clusterfuzz-testcase-minimized-POIHWPFFuzzer-5832867957309440.doc diff --git a/poi-scratchpad/src/test/java/org/apache/poi/hwpf/converter/TestWordToConverterSuite.java b/poi-scratchpad/src/test/java/org/apache/poi/hwpf/converter/TestWordToConverterSuite.java index 9cfb4b6d11..56665c679d 100644 --- a/poi-scratchpad/src/test/java/org/apache/poi/hwpf/converter/TestWordToConverterSuite.java +++ b/poi-scratchpad/src/test/java/org/apache/poi/hwpf/converter/TestWordToConverterSuite.java @@ -61,7 +61,8 @@ public class TestWordToConverterSuite { "clusterfuzz-testcase-minimized-POIHWPFFuzzer-4947285593948160.doc", "clusterfuzz-testcase-minimized-POIHWPFFuzzer-5440721166139392.doc", "clusterfuzz-testcase-minimized-POIHWPFFuzzer-5050208641482752.doc", - "clusterfuzz-testcase-minimized-POIHWPFFuzzer-6610789829836800.doc" + "clusterfuzz-testcase-minimized-POIHWPFFuzzer-6610789829836800.doc", + "clusterfuzz-testcase-minimized-POIHWPFFuzzer-5832867957309440.doc" ); public static Stream files() { diff --git a/poi-scratchpad/src/test/java/org/apache/poi/hwpf/converter/TestWordToTextConverter.java b/poi-scratchpad/src/test/java/org/apache/poi/hwpf/converter/TestWordToTextConverter.java index 9e66096b3c..c2bd0582ba 100644 --- a/poi-scratchpad/src/test/java/org/apache/poi/hwpf/converter/TestWordToTextConverter.java +++ b/poi-scratchpad/src/test/java/org/apache/poi/hwpf/converter/TestWordToTextConverter.java @@ -54,7 +54,8 @@ public class TestWordToTextConverter { "clusterfuzz-testcase-minimized-POIHWPFFuzzer-4947285593948160.doc", "clusterfuzz-testcase-minimized-POIHWPFFuzzer-5440721166139392.doc", "clusterfuzz-testcase-minimized-POIHWPFFuzzer-5050208641482752.doc", - "clusterfuzz-testcase-minimized-POIHWPFFuzzer-6610789829836800.doc" + "clusterfuzz-testcase-minimized-POIHWPFFuzzer-6610789829836800.doc", + "clusterfuzz-testcase-minimized-POIHWPFFuzzer-5832867957309440.doc" ); /** diff --git a/poi/src/main/java/org/apache/poi/ddf/EscherComplexProperty.java b/poi/src/main/java/org/apache/poi/ddf/EscherComplexProperty.java index c001707c6c..675bc7d49c 100644 --- a/poi/src/main/java/org/apache/poi/ddf/EscherComplexProperty.java +++ b/poi/src/main/java/org/apache/poi/ddf/EscherComplexProperty.java @@ -35,6 +35,7 @@ public class EscherComplexProperty extends EscherProperty { private static final int DEFAULT_MAX_RECORD_LENGTH = 100_000_000; private static int MAX_RECORD_LENGTH = DEFAULT_MAX_RECORD_LENGTH; + private int complexSize; private byte[] complexData; /** @@ -61,7 +62,15 @@ public class EscherComplexProperty extends EscherProperty { */ public EscherComplexProperty(short id, int complexSize) { super((short)(id | IS_COMPLEX)); - complexData = IOUtils.safelyAllocate(complexSize, MAX_RECORD_LENGTH); + + // lazy-allocate the data + this.complexSize = complexSize; + } + + private void ensureComplexData() { + if (this.complexData == null) { + complexData = IOUtils.safelyAllocate(complexSize, MAX_RECORD_LENGTH); + } } /** @@ -94,7 +103,7 @@ public class EscherComplexProperty extends EscherProperty { @Override public int serializeSimplePart(byte[] data, int pos) { LittleEndian.putShort(data, pos, getId()); - LittleEndian.putInt(data, pos + 2, complexData.length); + LittleEndian.putInt(data, pos + 2, complexSize); return 6; } @@ -107,8 +116,13 @@ public class EscherComplexProperty extends EscherProperty { */ @Override public int serializeComplexPart(byte[] data, int pos) { - System.arraycopy(complexData, 0, data, pos, complexData.length); - return complexData.length; + if (complexData == null) { + // initialize empty array if complexData was never allocated + Arrays.fill(data, pos, pos + complexSize, (byte)0); + } else { + System.arraycopy(complexData, 0, data, pos, complexData.length); + } + return complexSize; } /** @@ -117,6 +131,7 @@ public class EscherComplexProperty extends EscherProperty { * @return the complex bytes */ public byte[] getComplexData() { + ensureComplexData(); return complexData; } @@ -128,25 +143,31 @@ public class EscherComplexProperty extends EscherProperty { if (complexData == null) { return 0; } else { + ensureComplexData(); int copySize = Math.max(0, Math.min(this.complexData.length, complexData.length - offset)); System.arraycopy(complexData, offset, this.complexData, 0, copySize); return copySize; } } - - protected void resizeComplexData(int newSize) { resizeComplexData(newSize, Integer.MAX_VALUE); } protected void resizeComplexData(int newSize, int copyLen) { - if (newSize == complexData.length) { + if (newSize == complexSize) { return; } + + // no need to copy if data was not initialized yet + if (complexData == null) { + return; + } + byte[] newArray = IOUtils.safelyAllocate(newSize, MAX_RECORD_LENGTH); System.arraycopy(complexData, 0, newArray, 0, Math.min(Math.min(complexData.length, copyLen),newSize)); complexData = newArray; + complexSize = newSize; } /** @@ -166,6 +187,18 @@ public class EscherComplexProperty extends EscherProperty { EscherComplexProperty escherComplexProperty = (EscherComplexProperty) o; + // not equal if size differs + if (complexSize != escherComplexProperty.complexSize) { + return false; + } + + if (complexData == null) { + // if coomplexData is not initialized, it is equal only if + // complexData is also uninitialized or equals an empty array + return escherComplexProperty.complexData == null || + Arrays.equals(escherComplexProperty.complexData, new byte[escherComplexProperty.complexSize]); + } + return Arrays.equals(complexData, escherComplexProperty.complexData); } @@ -176,16 +209,18 @@ public class EscherComplexProperty extends EscherProperty { */ @Override public int getPropertySize() { - return 6 + complexData.length; + return 6 + complexSize; } @Override public int hashCode() { + ensureComplexData(); return Arrays.deepHashCode(new Object[]{complexData, getId()}); } @Override public Map> getGenericProperties() { + ensureComplexData(); return GenericRecordUtil.getGenericProperties( "base", super::getGenericProperties, "data", this::getComplexData diff --git a/src/resources/ooxml-lite-report.clazz b/src/resources/ooxml-lite-report.clazz index 144216dba8..444e098823 100644 --- a/src/resources/ooxml-lite-report.clazz +++ b/src/resources/ooxml-lite-report.clazz @@ -2551,3 +2551,5 @@ org/openxmlformats/schemas/drawingml/x2006/main/impl/CTAudioFileImpl org/openxmlformats/schemas/drawingml/x2006/main/CTAudioFile org/openxmlformats/schemas/drawingml/x2006/chart/impl/STHoleSizePercentImpl org/openxmlformats/schemas/drawingml/x2006/chart/impl/STHoleSizeUByteImpl +org/openxmlformats/schemas/drawingml/x2006/main/CTSupplementalFont +org/openxmlformats/schemas/drawingml/x2006/main/impl/CTSupplementalFontImpl diff --git a/src/resources/ooxml-lite-report.xsb b/src/resources/ooxml-lite-report.xsb index 7f3d358103..2120d975b8 100644 --- a/src/resources/ooxml-lite-report.xsb +++ b/src/resources/ooxml-lite-report.xsb @@ -1186,3 +1186,4 @@ ctindex5371type stholesizepercenta3d2type stholesizeubyte577atype chartspace67aadoctype +ctsupplementalfonta06etype diff --git a/test-data/document/clusterfuzz-testcase-minimized-POIHWPFFuzzer-5832867957309440.doc b/test-data/document/clusterfuzz-testcase-minimized-POIHWPFFuzzer-5832867957309440.doc new file mode 100644 index 0000000000000000000000000000000000000000..4f2d6de96cf6736f0b551782b18382756b667248 GIT binary patch literal 4748 zcmd5&_Fj9Oxp-b(r9QwU9|DFa92SQ z1QE3$xbTHyaU+O37mB|?tI<`9y6{o8<9F_zJ2#n`Oq1G{9_HM0?)%QU=XKw_u6uX$ zVdMLgBw?{d_fQ3mn>94wsxvVx6is2GfK?wiO;fa?04pnk(M3qJ&ow)3T-0afA3lAe z>u=$J-@4kbS$__Dy!9V31-*T0jX;PZiZ7G$zgFYk_zr`hmB1?C9AE>G+e4spf%Aa1 zzEvO?6PER?KRhBZJhSuH?2uU-eN2QCCI2ePd#0WJkD1G2mu zf!wzONGpj}fvg5bfNOvPD(N8gX5y4earG4Kr}lQ)!)36I(vW)s9bQl%1@i#Lrm2S_ zr(38SG=o`(TH5gm@LIqY6nCzI#;6tT3uIw++i3pvA`jzk`(pV9;L3KSjqO zhYmU>8_Ou`u|Mytpp>Z_Fi7dfOzSjM+DE4$h!orgE8%{q<2Cb=kr6FDbQEIh&i}jX z`4!^-FxYvq<6>c0M9D=_G#7!WtKBkt^Ou@jG8I>{Y2x9w^^vwd>3>~^A@hf7<9IFk zKc+b@IBaEGpBhHAN3%pt+qCsX^Q|xFule7xcK$j`6_3&VV0X8T97XGez~<%Vv&zkAc#)SoykbsQ%t z0@DAsb$z~gKir)DaOU>#(4*0Tdx=lyZhg8sz}~|dsmqN%EdPn3PobqkxZAAbis<98 z)FZG?wp0avW8w%i(`C)JlZ~pAKshDAnhlQD0HLe0aO8S~T?j(t^Bn_V~>7^IWoLA-VR(yl_qccxtq#eY_Mh}w?BPg{t>+SRVB z@N1Zv5cSinpwdFrH_nU!DnTqKM9G<=%o0tP5BCp~d1vrqU02~6=O z&!>s2S-iW%yn oW(4p~>VtQtw}bnf8Txp3IG5A literal 0 HcmV?d00001 diff --git a/test-data/spreadsheet/stress.xls b/test-data/spreadsheet/stress.xls index 6fb06f73a6673c769fb64ec654cd87f91c26c9dc..94775397c807109f488e3906f78a049c29f60380 100644 GIT binary patch delta 2602 zcmZ9NYfzMB6vv-ud6(Uly~5%m3t^5-GH9-f0=^KCA|et}nwYmm^8+gBOfkcZ(}>ex zV_D+Rc!rrAhYt-OoMzH3H!?9Z%2Kwtx!HcyzSrDr5h5+L zz)@Bz*M(ZEU1{x}s`)7eb7mLJ%P*XpKPOO_mlwGI@ogJJFKsfLr%VyM#Ufm>W8=$H zh1ifO#O@*?rlktewo(YEJ9K(;o%BR%5#ZO;I4SX#(LQ}^R$0V0Mm3>{NcPy-EX*R( zJTbPFC=x}6f9!FT%8bz7t$nRFtH~|aOCijm;PpMWox^x-bW9bm;eGTZ@kGxtdNSe3 z6FX^mir|TAf>a~2@m`2h=~DB=#=3%%x~#k%l+@*lovA3rx>3~TFJTKtV-I0-z(eM4?Vv{^6ry(|rWW!TK zBz)osGP*Ew`NUDEk}^kzph?PH`8sq`yaDGitjBorfF`HPFl1}^(3?=JNx$rdMvAv! z4@PD;4)$QBMI?xHylF~6SM3wW;Z$_t3dP%SYy5)Eut3krCr-dDvocgu^gshf%#dVQ zpx=>CoP^Hk!U>9Z;NtiNhhf2~7EXaDQ5j}XybF~WF@wvnfFA=ioQ7S|g@Y9DLHGCt zk6|HME%d@rbirZ4h4&!`BW6f3EO^zz2T*U3`SJ{OSnx&u5PB&-f+32tkZHv@2bC1( zp~;GqAH%B@pFlssPr+lu_za3EJ`Xq8Q;u!4XWjFsufCQDvQ6EEhy7AdaO4Gq zD8=TMV;innpf*dUKwXx!Tl+6~?=CbU?y|eE%Vk+$5s+pb`w*}wOrTYRP(;)OG+7hLGMj0lqNy0CF`|T- z#_5BG8BWtojpJWV{gZCFM22N%Rz%tNho;h(SrL&X(c<>LcYj1@*-+-o=~TIU~0+NfFDoZQ0s(-|ctbb#J#w7ggtb_qiXlw>gCH zhpRnRmC_sTUTx-tPj9u`3kyZJsG&=4n%ps0h!wMi$X+Bwf2I&4>xI~C3h@wmr}D$j zJ3`VMZOF1>AEXIMuR!lTtrt~AT{F}WhKOD~v$hMnh;GaNZz@?Ni$LDYbqbe(@UhmD z-QmOI9j@?^Xpu~7k=!pui{y9V*wk$loia0Iw}#qdJ8gZ2oMpa9O;dDo9`TF4>M}#l zllxSoA&X>Kb#Qq}Jx|5?GCKQgvcz*C7K=^)_e82}@}PXG%}^%{IalsiHam?))a7=W zC;L?$m5Ns|?XYqdQXZ!|gpE@B>Dr*RN)oyBGOTM(>hX&K^<3OCz?N5GacLHpW^sxC zTli#taX_UdTYD*_Qt=urshTBKv!rSkzQ=wss5;`7XV`KO7LR7}XcmuVNwX}ktI@b6 z#X*)gU@hmjH$sjsspBhmo!f`K-bsW-)Z%TE-_Lo}QKabv}5S(J`1mH42cQ(Pl0c!xu2?i$t zmupeW6QXLZsDIE~3qcQCqkt;_4=|hptOGp5Q5OJLYEdf_qE=Z^G4!s2pwdrP>a>2M zR|B>(lz`U*K20!a16-p;tx1Tgx1y5JTMxk)TMfVl!2BG7cECo!T7to3z_nV`+JvZe zR+Iz1>mb<0Rwv*MfI|#jfa?J#I4T9ONsDSqhzeOzspt(sP(O>TZomzI+ZlQQZv@;! zFqj6oQH$D`5Y=o&ndohf_x9zIG#zXcq{R%qfGvO_h8ci20e0oeJ53+pW+?NhF%#_O zX(hufz+0x346^~Z#9^c>Pi~O=3uJkcr?gCnO4@a&yQ$ov8UoU%LILS^#x9*GEPMF4 c>