Compare commits

...

7 Commits

Author SHA1 Message Date
Dominik Stadler
44598bd030 Avoid OOM with incorrect property sizes
Add allocation check to verify size >= 0 and < 1mio
Also reformat code to match general coding style

Fixes https://issues.oss-fuzz.com/issues/485091380
2026-02-21 15:39:55 +01:00
Dominik Stadler
23369586da Avoid assertion when handling slightly corrupted emf-file
Also remove methods which are identical to the ones in the super-class

Fixes https://issues.oss-fuzz.com/issues/486039135
2026-02-21 15:39:54 +01:00
Dominik Stadler
9d9865c9b8 Avoid NPE when handling diagrams in pptx
Fixes https://issues.oss-fuzz.com/issues/484589690
2026-02-21 15:39:54 +01:00
Dominik Stadler
e9e9612a1f Avoid ClassCastException when reading headers of EMF files 2026-02-21 15:39:54 +01:00
Dominik Stadler
1594baf696 Avoid NPE when updating cell-anchors 2026-02-21 15:39:54 +01:00
Dominik Stadler
839ce4a0f4 Avoid NPE in HSSFShapeGroup.setShapeId() 2026-02-21 15:39:54 +01:00
Dominik Stadler
260b22fb09 Handle slightly broken file with empty BlipFill properly 2026-02-21 15:39:54 +01:00
20 changed files with 66 additions and 60 deletions

View File

@ -148,8 +148,12 @@ public class XSLFDiagram extends XSLFGraphicFrame {
shapeCt.setSpPr(msShapeCt.getSpPr()); shapeCt.setSpPr(msShapeCt.getSpPr());
CTShapeNonVisual nonVisualCt = shapeCt.addNewNvSpPr(); CTShapeNonVisual nonVisualCt = shapeCt.addNewNvSpPr();
nonVisualCt.setCNvPr(msShapeCt.getNvSpPr().getCNvPr()); com.microsoft.schemas.office.drawing.x2008.diagram.CTShapeNonVisual nvSpPr = msShapeCt.getNvSpPr();
nonVisualCt.setCNvSpPr(msShapeCt.getNvSpPr().getCNvSpPr()); if (nvSpPr == null) {
nvSpPr = msShapeCt.addNewNvSpPr();
}
nonVisualCt.setCNvPr(nvSpPr.getCNvPr());
nonVisualCt.setCNvSpPr(nvSpPr.getCNvSpPr());
nonVisualCt.setNvPr(CTApplicationNonVisualDrawingProps.Factory.newInstance()); nonVisualCt.setNvPr(CTApplicationNonVisualDrawingProps.Factory.newInstance());
shapeCt.setNvSpPr(nonVisualCt); shapeCt.setNvSpPr(nonVisualCt);

View File

@ -427,6 +427,10 @@ public class XSLFTable extends XSLFGraphicFrame implements Iterable<XSLFTableRow
for (int col2=col+1; col2<col+tc.getGridSpan(); col2++) { for (int col2=col+1; col2<col+tc.getGridSpan(); col2++) {
assert(col2 < cols); assert(col2 < cols);
XSLFTableCell tc2 = getCell(row, col2); XSLFTableCell tc2 = getCell(row, col2);
if (tc2 == null) {
LOG.warn("unavailable table span - rendering result is probably wrong");
continue;
}
if (tc2.getGridSpan() != 1 || tc2.getRowSpan() != 1) { if (tc2.getGridSpan() != 1 || tc2.getRowSpan() != 1) {
LOG.warn("invalid table span - rendering result is probably wrong"); LOG.warn("invalid table span - rendering result is probably wrong");
} }
@ -435,6 +439,10 @@ public class XSLFTable extends XSLFGraphicFrame implements Iterable<XSLFTableRow
for (int row2=row+1; row2<row+tc.getRowSpan(); row2++) { for (int row2=row+1; row2<row+tc.getRowSpan(); row2++) {
assert(row2 < rows); assert(row2 < rows);
XSLFTableCell tc2 = getCell(row2, col); XSLFTableCell tc2 = getCell(row2, col);
if (tc2 == null) {
LOG.warn("unavailable table span - rendering result is probably wrong");
continue;
}
if (tc2.getGridSpan() != 1 || tc2.getRowSpan() != 1) { if (tc2.getGridSpan() != 1 || tc2.getRowSpan() != 1) {
LOG.warn("invalid table span - rendering result is probably wrong"); LOG.warn("invalid table span - rendering result is probably wrong");
} }

View File

@ -268,7 +268,7 @@ public final class XSSFPicture extends XSSFShape implements Picture {
*/ */
@Override @Override
public XSSFPictureData getPictureData() { public XSSFPictureData getPictureData() {
if (ctPicture.getBlipFill().getBlip() == null) { if (ctPicture.getBlipFill() == null || ctPicture.getBlipFill().getBlip() == null) {
return null; return null;
} }

View File

@ -35,6 +35,7 @@ import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream; import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
import org.apache.logging.log4j.Logger;
import org.apache.poi.hemf.draw.HemfDrawProperties; import org.apache.poi.hemf.draw.HemfDrawProperties;
import org.apache.poi.hemf.draw.HemfGraphics; import org.apache.poi.hemf.draw.HemfGraphics;
import org.apache.poi.hwmf.draw.HwmfGraphics; import org.apache.poi.hwmf.draw.HwmfGraphics;
@ -45,6 +46,7 @@ import org.apache.poi.hwmf.record.HwmfFill;
import org.apache.poi.hwmf.record.HwmfFill.ColorUsage; import org.apache.poi.hwmf.record.HwmfFill.ColorUsage;
import org.apache.poi.hwmf.record.HwmfRegionMode; import org.apache.poi.hwmf.record.HwmfRegionMode;
import org.apache.poi.hwmf.record.HwmfTernaryRasterOp; import org.apache.poi.hwmf.record.HwmfTernaryRasterOp;
import org.apache.poi.logging.PoiLogManager;
import org.apache.poi.util.GenericRecordJsonWriter; import org.apache.poi.util.GenericRecordJsonWriter;
import org.apache.poi.util.GenericRecordUtil; import org.apache.poi.util.GenericRecordUtil;
import org.apache.poi.util.IOUtils; import org.apache.poi.util.IOUtils;
@ -52,6 +54,8 @@ import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream; import org.apache.poi.util.LittleEndianInputStream;
public final class HemfFill { public final class HemfFill {
private static final Logger LOG = PoiLogManager.getLogger(HemfFill.class);
private HemfFill() {} private HemfFill() {}
/** /**
@ -194,11 +198,6 @@ public final class HemfFill {
super.draw(ctx); super.draw(ctx);
} }
@Override
public String toString() {
return GenericRecordJsonWriter.marshal(this);
}
public Rectangle2D getBounds() { public Rectangle2D getBounds() {
return bounds; return bounds;
} }
@ -321,14 +320,8 @@ public final class HemfFill {
public HemfRecordType getEmfRecordType() { public HemfRecordType getEmfRecordType() {
return HemfRecordType.bitBlt; return HemfRecordType.bitBlt;
} }
@Override
protected boolean srcEqualsDstDimension() {
return false;
}
} }
/** The EMR_FRAMERGN record draws a border around the specified region using the specified brush. */ /** The EMR_FRAMERGN record draws a border around the specified region using the specified brush. */
public static class EmfFrameRgn extends HwmfDraw.WmfFrameRegion implements HemfRecord { public static class EmfFrameRgn extends HwmfDraw.WmfFrameRegion implements HemfRecord {
private final Rectangle2D bounds = new Rectangle2D.Double(); private final Rectangle2D bounds = new Rectangle2D.Double();
@ -611,9 +604,13 @@ public final class HemfFill {
size += readBounds2(leis, destRect); size += readBounds2(leis, destRect);
blendOperation = leis.readByte(); blendOperation = leis.readByte();
assert (blendOperation == 0); if (blendOperation != 0) {
LOG.atWarn().log("Unexpected blend-operation in emf file: {}", blendOperation);
}
blendFlags = leis.readByte(); blendFlags = leis.readByte();
assert (blendOperation == 0); if (blendFlags != 0) {
LOG.atWarn().log("Unexpected blend-flags in emf file: {}", blendFlags);
}
srcConstantAlpha = leis.readUByte(); srcConstantAlpha = leis.readUByte();
alphaFormat = leis.readByte(); alphaFormat = leis.readByte();

View File

@ -81,6 +81,8 @@ public class HemfPicture implements Iterable<HemfRecord>, GenericRecord {
List<HemfRecord> r = getRecords(); List<HemfRecord> r = getRecords();
if (r.isEmpty()) { if (r.isEmpty()) {
throw new RecordFormatException("No records could be parsed - your .emf file is invalid"); throw new RecordFormatException("No records could be parsed - your .emf file is invalid");
} else if (!(r.get(0) instanceof HemfHeader)) {
throw new RecordFormatException("Could not convert object of type " + r.get(0).getClass() + " + - your .emf file is invalid");
} else { } else {
return (HemfHeader)r.get(0); return (HemfHeader)r.get(0);
} }

View File

@ -33,6 +33,7 @@ public class TestSlideIdListing extends BaseTestPPTIterating {
LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-5306877435838464.ppt"); LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-5306877435838464.ppt");
LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6360479850954752.ppt"); LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6360479850954752.ppt");
LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6028723156746240.ppt"); LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6028723156746240.ppt");
LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-4983252485210112.ppt");
} }
@Test @Test

View File

@ -33,6 +33,7 @@ public class TestSlideShowRecordDumper extends BaseTestPPTIterating {
static { static {
LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6360479850954752.ppt"); LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6360479850954752.ppt");
LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6028723156746240.ppt"); LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6028723156746240.ppt");
LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-4983252485210112.ppt");
} }
@Test @Test

View File

@ -32,6 +32,7 @@ public class TestUserEditAndPersistListing extends BaseTestPPTIterating {
static { static {
LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6360479850954752.ppt"); LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6360479850954752.ppt");
LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6028723156746240.ppt"); LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-6028723156746240.ppt");
LOCAL_EXCLUDED.add("clusterfuzz-testcase-minimized-POIHSLFFuzzer-4983252485210112.ppt");
} }
@Test @Test

View File

@ -23,6 +23,7 @@ import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.apache.poi.util.GenericRecordUtil; import org.apache.poi.util.GenericRecordUtil;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndian;
/** /**
@ -30,6 +31,9 @@ import org.apache.poi.util.LittleEndian;
* {@link EscherTertiaryOptRecord} * {@link EscherTertiaryOptRecord}
*/ */
public abstract class AbstractEscherOptRecord extends EscherRecord { public abstract class AbstractEscherOptRecord extends EscherRecord {
// arbitrary limit, can be adjusted if it turns out to be too low
private static final int MAX_PROPERTY_SIZE = 1_000_000;
private final List<EscherProperty> properties = new ArrayList<>(); private final List<EscherProperty> properties = new ArrayList<>();
protected AbstractEscherOptRecord() {} protected AbstractEscherOptRecord() {}
@ -39,21 +43,17 @@ public abstract class AbstractEscherOptRecord extends EscherRecord {
properties.addAll(other.properties); properties.addAll(other.properties);
} }
/** /**
* Add a property to this record. * Add a property to this record.
* *
* @param prop the escher property to add * @param prop the escher property to add
*/ */
public void addEscherProperty( EscherProperty prop ) public void addEscherProperty( EscherProperty prop ) {
{
properties.add( prop ); properties.add( prop );
} }
@Override @Override
public int fillFields( byte[] data, int offset, public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory ) {
EscherRecordFactory recordFactory )
{
int bytesRemaining = readHeader( data, offset ); int bytesRemaining = readHeader( data, offset );
if (bytesRemaining < 0) { if (bytesRemaining < 0) {
throw new IllegalStateException("Invalid value for bytesRemaining: " + bytesRemaining); throw new IllegalStateException("Invalid value for bytesRemaining: " + bytesRemaining);
@ -72,8 +72,7 @@ public abstract class AbstractEscherOptRecord extends EscherRecord {
* *
* @return the list of properties * @return the list of properties
*/ */
public List<EscherProperty> getEscherProperties() public List<EscherProperty> getEscherProperties() {
{
return properties; return properties;
} }
@ -83,26 +82,23 @@ public abstract class AbstractEscherOptRecord extends EscherRecord {
* @param index the ordinal index of the property * @param index the ordinal index of the property
* @return the escher property * @return the escher property
*/ */
public EscherProperty getEscherProperty( int index ) public EscherProperty getEscherProperty( int index ) {
{
return properties.get( index ); return properties.get( index );
} }
private int getPropertiesSize() {
private int getPropertiesSize()
{
int totalSize = 0; int totalSize = 0;
for ( EscherProperty property : properties ) for ( EscherProperty property : properties ) {
{ int propertySize = property.getPropertySize();
totalSize += property.getPropertySize(); IOUtils.safelyAllocateCheck(propertySize, MAX_PROPERTY_SIZE);
totalSize += propertySize;
} }
return totalSize; return totalSize;
} }
@Override @Override
public int getRecordSize() public int getRecordSize() {
{
return 8 + getPropertiesSize(); return 8 + getPropertiesSize();
} }
@ -116,9 +112,7 @@ public abstract class AbstractEscherOptRecord extends EscherRecord {
} }
@Override @Override
public int serialize( int offset, byte[] data, public int serialize( int offset, byte[] data, EscherSerializationListener listener ) {
EscherSerializationListener listener )
{
listener.beforeRecordSerialize( offset, getRecordId(), this ); listener.beforeRecordSerialize( offset, getRecordId(), this );
LittleEndian.putShort( data, offset, getOptions() ); LittleEndian.putShort( data, offset, getOptions() );

View File

@ -70,8 +70,7 @@ public abstract class EscherRecord implements Duplicatable, GenericRecord {
* *
* @see #fillFields(byte[], int, org.apache.poi.ddf.EscherRecordFactory) * @see #fillFields(byte[], int, org.apache.poi.ddf.EscherRecordFactory)
*/ */
protected int fillFields( byte[] data, EscherRecordFactory f ) protected int fillFields( byte[] data, EscherRecordFactory f ) {
{
return fillFields( data, 0, f ); return fillFields( data, 0, f );
} }
@ -154,8 +153,7 @@ public abstract class EscherRecord implements Duplicatable, GenericRecord {
* @return The options field for this record. All records have one. * @return The options field for this record. All records have one.
*/ */
@Internal @Internal
public short getOptions() public short getOptions() {
{
return _options; return _options;
} }
@ -183,8 +181,7 @@ public abstract class EscherRecord implements Duplicatable, GenericRecord {
* @return the serialized record. * @return the serialized record.
* @see #serialize(int, byte[]) * @see #serialize(int, byte[])
*/ */
public byte[] serialize() public byte[] serialize() {
{
byte[] retval = new byte[getRecordSize()]; byte[] retval = new byte[getRecordSize()];
serialize( 0, retval ); serialize( 0, retval );
@ -201,8 +198,7 @@ public abstract class EscherRecord implements Duplicatable, GenericRecord {
* *
* @see #serialize(int, byte[], org.apache.poi.ddf.EscherSerializationListener) * @see #serialize(int, byte[], org.apache.poi.ddf.EscherSerializationListener)
*/ */
public int serialize( int offset, byte[] data) public int serialize( int offset, byte[] data) {
{
return serialize( offset, data, new NullEscherSerializationListener() ); return serialize( offset, data, new NullEscherSerializationListener() );
} }
@ -252,7 +248,9 @@ public abstract class EscherRecord implements Duplicatable, GenericRecord {
* *
* @see EscherContainerRecord * @see EscherContainerRecord
*/ */
public List<EscherRecord> getChildRecords() { return Collections.emptyList(); } public List<EscherRecord> getChildRecords() {
return Collections.emptyList();
}
/** /**
* Sets the child records for this record. By default this will throw * Sets the child records for this record. By default this will throw
@ -281,8 +279,7 @@ public abstract class EscherRecord implements Duplicatable, GenericRecord {
* @param w The print writer to output to. * @param w The print writer to output to.
* @param indent The current indent level. * @param indent The current indent level.
*/ */
public void display(PrintWriter w, int indent) public void display(PrintWriter w, int indent) {
{
for (int i = 0; i < indent * 4; i++) { for (int i = 0; i < indent * 4; i++) {
w.print(' '); w.print(' ');
} }
@ -301,8 +298,7 @@ public abstract class EscherRecord implements Duplicatable, GenericRecord {
* *
* @return The instance part of the record * @return The instance part of the record
*/ */
public short getInstance() public short getInstance() {
{
return fInstance.getShortValue( _options ); return fInstance.getShortValue( _options );
} }
@ -311,8 +307,7 @@ public abstract class EscherRecord implements Duplicatable, GenericRecord {
* *
* @param value instance part value * @param value instance part value
*/ */
public void setInstance( short value ) public void setInstance( short value ) {
{
_options = fInstance.setShortValue( _options, value ); _options = fInstance.setShortValue( _options, value );
} }
@ -321,8 +316,7 @@ public abstract class EscherRecord implements Duplicatable, GenericRecord {
* *
* @return The version part of the option record * @return The version part of the option record
*/ */
public short getVersion() public short getVersion() {
{
return fVersion.getShortValue( _options ); return fVersion.getShortValue( _options );
} }
@ -331,12 +325,11 @@ public abstract class EscherRecord implements Duplicatable, GenericRecord {
* *
* @param value version part value * @param value version part value
*/ */
public void setVersion( short value ) public void setVersion( short value ) {
{
_options = fVersion.setShortValue( _options, value ); _options = fVersion.setShortValue( _options, value );
} }
public String toXml(){ public String toXml() {
return toXml(""); return toXml("");
} }
@ -344,7 +337,7 @@ public abstract class EscherRecord implements Duplicatable, GenericRecord {
* @param tab - each children must be indented right relative to its parent * @param tab - each children must be indented right relative to its parent
* @return xml representation of this record * @return xml representation of this record
*/ */
public final String toXml(String tab){ public final String toXml(String tab) {
return GenericRecordXmlWriter.marshal(this); return GenericRecordXmlWriter.marshal(this);
} }

View File

@ -355,7 +355,11 @@ public class HSSFShapeGroup extends HSSFShape implements HSSFShapeContainer {
EscherContainerRecord containerRecord = getEscherContainer().getChildById(EscherContainerRecord.SP_CONTAINER); EscherContainerRecord containerRecord = getEscherContainer().getChildById(EscherContainerRecord.SP_CONTAINER);
EscherSpRecord spRecord = containerRecord.getChildById(EscherSpRecord.RECORD_ID); EscherSpRecord spRecord = containerRecord.getChildById(EscherSpRecord.RECORD_ID);
spRecord.setShapeId(shapeId); spRecord.setShapeId(shapeId);
CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) getObjRecord().getSubRecords().get(0); ObjRecord objRecord = getObjRecord();
if (objRecord == null) {
throw new IllegalStateException("Did not have an ObjRecord for the HSSFShapeGroup");
}
CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) objRecord.getSubRecords().get(0);
cod.setObjectId((short) (shapeId % 1024)); cod.setObjectId((short) (shapeId % 1024));
} }

View File

@ -139,7 +139,8 @@ class TestDrawingAggregate {
!file.getName().equals("clusterfuzz-testcase-minimized-POIHSSFFuzzer-5285517825277952.xls") && !file.getName().equals("clusterfuzz-testcase-minimized-POIHSSFFuzzer-5285517825277952.xls") &&
!file.getName().equals("clusterfuzz-testcase-minimized-POIHSSFFuzzer-4977868385681408.xls") && !file.getName().equals("clusterfuzz-testcase-minimized-POIHSSFFuzzer-4977868385681408.xls") &&
!file.getName().equals("crash-e329fca9087fe21bca4a80c8bc472a661c98d860.xls") && !file.getName().equals("crash-e329fca9087fe21bca4a80c8bc472a661c98d860.xls") &&
!file.getName().equals("cf9f845e73447b092477d0472402a5baea4b8c9f.xls")). !file.getName().equals("cf9f845e73447b092477d0472402a5baea4b8c9f.xls") &&
!file.getName().equals("LIBRE_OFFICE-94379-0.zip-57.xls")).
map(Arguments::of); map(Arguments::of);
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.