This is an automated email from the ASF dual-hosted git repository.

pjfanning pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/poi.git


The following commit(s) were added to refs/heads/trunk by this push:
     new d80ff2dbfb Validate EMF description bounds before allocation (#1060)
d80ff2dbfb is described below

commit d80ff2dbfba27d3b0ab303a0a4c49dabf4b4489f
Author: jmestwa-coder <[email protected]>
AuthorDate: Mon May 11 21:33:39 2026 +0530

    Validate EMF description bounds before allocation (#1060)
    
    * Validate EMF description bounds before allocation
    
    * Potential fix for pull request finding
    
    Co-authored-by: Copilot Autofix powered by AI 
<[email protected]>
    
    ---------
    
    Co-authored-by: Copilot Autofix powered by AI 
<[email protected]>
---
 .../org/apache/poi/hemf/record/emf/HemfHeader.java | 21 ++++--
 .../apache/poi/hemf/usermodel/TestHemfPicture.java | 79 +++++++++++++++++++++-
 2 files changed, 93 insertions(+), 7 deletions(-)

diff --git 
a/poi-scratchpad/src/main/java/org/apache/poi/hemf/record/emf/HemfHeader.java 
b/poi-scratchpad/src/main/java/org/apache/poi/hemf/record/emf/HemfHeader.java
index d8d3d34c7c..cb9ab2ebc5 100644
--- 
a/poi-scratchpad/src/main/java/org/apache/poi/hemf/record/emf/HemfHeader.java
+++ 
b/poi-scratchpad/src/main/java/org/apache/poi/hemf/record/emf/HemfHeader.java
@@ -32,9 +32,11 @@ import java.util.function.Supplier;
 
 import org.apache.poi.util.Dimension2DDouble;
 import org.apache.poi.util.GenericRecordJsonWriter;
+import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.Internal;
 import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.LittleEndianInputStream;
+import org.apache.poi.util.RecordFormatException;
 
 /**
  * Extracts the full header from EMF files.
@@ -155,8 +157,8 @@ public class HemfHeader implements HemfRecord {
         //reserved
         leis.skipFully(LittleEndianConsts.SHORT_SIZE);
 
-        int nDescription = (int)leis.readUInt();
-        int offDescription = (int)leis.readUInt();
+        long nDescription = leis.readUInt();
+        long offDescription = leis.readUInt();
         nPalEntries = leis.readUInt();
 
         size += 8*LittleEndianConsts.INT_SIZE;
@@ -165,10 +167,17 @@ public class HemfHeader implements HemfRecord {
         size += readDimensionInt(leis, milliDimension);
 
         if (nDescription > 0 && offDescription > 0) {
-            int skip = (int)(offDescription - (size + HEADER_SIZE));
-            leis.mark(skip+nDescription*2);
-            leis.skipFully(skip);
-            byte[] buf = new byte[(nDescription-1)*2];
+            long skip = offDescription - (size + HEADER_SIZE);
+            long descriptionBytes = (nDescription - 1) * 
LittleEndianConsts.SHORT_SIZE;
+            long descriptionEnd = offDescription + nDescription * 
LittleEndianConsts.SHORT_SIZE;
+            if (skip < 0 || descriptionEnd > recordSize + HEADER_SIZE || skip 
+ descriptionBytes > Integer.MAX_VALUE) {
+                throw new RecordFormatException("Invalid EMF header 
description bounds");
+            }
+            int maxDescriptionLength = (int)Math.min(recordSize, 
Integer.MAX_VALUE);
+            IOUtils.safelyAllocateCheck(descriptionBytes, 
maxDescriptionLength);
+            leis.mark((int)(skip + descriptionBytes));
+            leis.skipFully((int)skip);
+            byte[] buf = IOUtils.safelyAllocate(descriptionBytes, 
maxDescriptionLength);
             leis.readFully(buf);
             description = new String(buf, 
StandardCharsets.UTF_16LE).replace((char)0, ' ').trim();
             leis.reset();
diff --git 
a/poi-scratchpad/src/test/java/org/apache/poi/hemf/usermodel/TestHemfPicture.java
 
b/poi-scratchpad/src/test/java/org/apache/poi/hemf/usermodel/TestHemfPicture.java
index 22740b0cf6..7fa968d9d3 100644
--- 
a/poi-scratchpad/src/test/java/org/apache/poi/hemf/usermodel/TestHemfPicture.java
+++ 
b/poi-scratchpad/src/test/java/org/apache/poi/hemf/usermodel/TestHemfPicture.java
@@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.awt.geom.Point2D;
+import java.io.ByteArrayOutputStream;
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
@@ -49,6 +50,8 @@ import org.apache.poi.hwmf.usermodel.HwmfEmbedded;
 import org.apache.poi.hwmf.usermodel.HwmfEmbeddedType;
 import org.apache.poi.hwmf.usermodel.HwmfPicture;
 import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.LittleEndianInputStream;
+import org.apache.poi.util.LittleEndianOutputStream;
 import org.apache.poi.util.RecordFormatException;
 import org.junit.jupiter.api.Test;
 
@@ -229,6 +232,28 @@ public class TestHemfPicture {
         }
     }
 
+    @Test
+    void testHeaderDescriptionBoundsAreValidated() throws Exception {
+        HemfHeader header = new HemfHeader();
+        byte[] data = createHeaderRecordData(0x40000001L, 88);
+
+        try (LittleEndianInputStream leis = new LittleEndianInputStream(new 
ByteArrayInputStream(data))) {
+            assertThrows(RecordFormatException.class,
+                    () -> header.init(leis, data.length, 
HemfRecordType.header.id));
+        }
+    }
+
+    @Test
+    void testHeaderDescriptionParsesValidUtf16lePayload() throws Exception {
+        HemfHeader header = new HemfHeader();
+        byte[] data = createHeaderRecordDataWithDescription("POI");
+
+        try (LittleEndianInputStream leis = new LittleEndianInputStream(new 
ByteArrayInputStream(data))) {
+            header.init(leis, data.length, HemfRecordType.header.id);
+            assertEquals("POI", header.getDescription());
+        }
+    }
+
     @Test
     void nestedWmfEmf() throws Exception {
         try (InputStream is = 
sl_samples.openResourceAsStream("nested_wmf.emf")) {
@@ -253,4 +278,56 @@ public class TestHemfPicture {
 
 
     /* govdocs1 064213.doc-0.emf contains an example of extextouta */
-}
\ No newline at end of file
+
+    private static byte[] createHeaderRecordData(long nDescription, long 
offDescription) {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        LittleEndianOutputStream leos = new LittleEndianOutputStream(bos);
+
+        for (int i = 0; i < 8; i++) {
+            leos.writeInt(0);
+        }
+        leos.writeInt(0x464D4520);
+        leos.writeInt(0x00010000);
+        leos.writeUInt(0);
+        leos.writeUInt(1);
+        leos.writeShort(1);
+        leos.writeShort(0);
+        leos.writeUInt(nDescription);
+        leos.writeUInt(offDescription);
+        leos.writeUInt(0);
+        for (int i = 0; i < 4; i++) {
+            leos.writeInt(0);
+        }
+
+        return bos.toByteArray();
+    }
+
+    private static byte[] createHeaderRecordDataWithDescription(String 
description) {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        LittleEndianOutputStream leos = new LittleEndianOutputStream(bos);
+
+        byte[] descriptionBytes = 
description.getBytes(StandardCharsets.UTF_16LE);
+        long nDescription = description.length() + 1L;
+        long offDescription = 88L;
+
+        for (int i = 0; i < 8; i++) {
+            leos.writeInt(0);
+        }
+        leos.writeInt(0x464D4520);
+        leos.writeInt(0x00010000);
+        leos.writeUInt(0);
+        leos.writeUInt(1);
+        leos.writeShort(1);
+        leos.writeShort(0);
+        leos.writeUInt(nDescription);
+        leos.writeUInt(offDescription);
+        leos.writeUInt(0);
+        for (int i = 0; i < 4; i++) {
+            leos.writeInt(0);
+        }
+
+        leos.write(descriptionBytes);
+        leos.writeShort(0);
+        return bos.toByteArray();
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to