diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hwmf/record/HwmfPlaceableHeader.java b/poi-scratchpad/src/main/java/org/apache/poi/hwmf/record/HwmfPlaceableHeader.java index 82aba4b12d5..7dc5baae59b 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hwmf/record/HwmfPlaceableHeader.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hwmf/record/HwmfPlaceableHeader.java @@ -22,6 +22,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; +import org.apache.poi.util.RecordFormatException; public class HwmfPlaceableHeader { public static final int WMF_HEADER_MAGIC = 0x9AC6CDD7; @@ -29,7 +30,7 @@ public class HwmfPlaceableHeader { final Rectangle2D bounds; final int unitsPerInch; - protected HwmfPlaceableHeader(LittleEndianInputStream leis) throws IOException { + protected HwmfPlaceableHeader(LittleEndianInputStream leis) throws IOException, RecordFormatException { /* * HWmf (2 bytes): The resource handle to the metafile, when the metafile is in memory. When * the metafile is on disk, this field MUST contain 0x0000. This attribute of the metafile is @@ -54,7 +55,10 @@ protected HwmfPlaceableHeader(LittleEndianInputStream leis) throws IOException { * Thus, a value of 720 specifies that the image SHOULD be rendered at twice its normal size, * and a value of 2880 specifies that the image SHOULD be rendered at half its normal size. */ - unitsPerInch = leis.readShort(); + unitsPerInch = leis.readUShort(); + if (unitsPerInch == 0) { + throw new RecordFormatException("Invalid WMF placeable header unitsPerInch: " + unitsPerInch); + } /* * Reserved (4 bytes): A field that is not used and MUST be set to 0x00000000. diff --git a/poi-scratchpad/src/test/java/org/apache/poi/hwmf/TestHwmfParsing.java b/poi-scratchpad/src/test/java/org/apache/poi/hwmf/TestHwmfParsing.java index 3b31174db3b..4a145d8479f 100644 --- a/poi-scratchpad/src/test/java/org/apache/poi/hwmf/TestHwmfParsing.java +++ b/poi-scratchpad/src/test/java/org/apache/poi/hwmf/TestHwmfParsing.java @@ -20,6 +20,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more import static org.apache.poi.POITestCase.assertContains; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.ByteArrayInputStream; @@ -27,16 +28,21 @@ Licensed to the Apache Software Foundation (ASF) under one or more import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.List; import org.apache.poi.POIDataSamples; +import org.apache.poi.hwmf.draw.HwmfImageRenderer; import org.apache.poi.hwmf.record.HwmfFont; +import org.apache.poi.hwmf.record.HwmfPlaceableHeader; import org.apache.poi.hwmf.record.HwmfRecord; import org.apache.poi.hwmf.record.HwmfRecordType; import org.apache.poi.hwmf.record.HwmfText; import org.apache.poi.hwmf.usermodel.HwmfPicture; +import org.apache.poi.sl.usermodel.PictureData; import org.apache.poi.util.IOUtils; import org.apache.poi.util.LocaleUtil; import org.apache.poi.util.RecordFormatException; @@ -166,4 +172,60 @@ void testLengths() { long cnt = rebuilt.codePoints().count(); assertEquals(4, cnt); } + + @Test + void testRejectsZeroUnitsPerInchAtParserBoundary() { + byte[] malicious = createMinimalPlaceableWmf(0, 0, 0, 32767, 32767); + RecordFormatException ex = assertThrows( + RecordFormatException.class, + () -> new HwmfPicture(new ByteArrayInputStream(malicious)) + ); + assertContains(ex.getMessage(), "unitsPerInch"); + } + + @Test + void testValidUnitsProduceFiniteDimensions() throws IOException { + byte[] benign = createMinimalPlaceableWmf(1440, 0, 0, 100, 100); + HwmfImageRenderer renderer = new HwmfImageRenderer(); + renderer.loadImage(benign, PictureData.PictureType.WMF.contentType); + + assertTrue(Double.isFinite(renderer.getDimension().getWidth())); + assertTrue(Double.isFinite(renderer.getDimension().getHeight())); + assertTrue(renderer.getDimension().getWidth() > 0); + assertTrue(renderer.getDimension().getHeight() > 0); + } + + /** + * Creates a minimal placeable WMF stream: placeable header + WMF header + EOF record. + * The payload is intentionally small because these tests target parser validation, not WMF drawing semantics. + */ + private static byte[] createMinimalPlaceableWmf(int unitsPerInch, int x1, int y1, int x2, int y2) { + ByteBuffer bb = ByteBuffer.allocate(46).order(ByteOrder.LITTLE_ENDIAN); + + // Placeable header + bb.putInt(HwmfPlaceableHeader.WMF_HEADER_MAGIC); + bb.putShort((short) 0); // hwmf handle + bb.putShort((short) x1); + bb.putShort((short) y1); + bb.putShort((short) x2); + bb.putShort((short) y2); + bb.putShort((short) unitsPerInch); + bb.putInt(0); // reserved + bb.putShort((short) 0); // checksum + + // WMF header (9 words) + bb.putShort((short) 1); // memory metafile + bb.putShort((short) 9); // header size in WORDs + bb.putShort((short) 0x0300); // version + bb.putInt(0); // file size in WORDs (unused by parser) + bb.putShort((short) 0); // number of objects + bb.putInt(0); // max record size + bb.putShort((short) 0); // number of members + + // EOF record + bb.putInt(3); // record size in WORDs + bb.putShort((short) HwmfRecordType.eof.id); + + return bb.array(); + } }