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 bc6aa263e0 Guard HDGF PointerFactory offset/length uint32 narrowing
(#1076)
bc6aa263e0 is described below
commit bc6aa263e05620ae537d6ddac8399f771b474eb7
Author: metsw24-max <[email protected]>
AuthorDate: Fri May 15 20:56:25 2026 +0530
Guard HDGF PointerFactory offset/length uint32 narrowing (#1076)
* Guard HDGF PointerFactory offset/length uint32 narrowing
* updated
* updated
---
.../apache/poi/hdgf/pointers/PointerFactory.java | 29 ++++++-
.../poi/hdgf/pointers/TestPointerFactory.java | 92 ++++++++++++++++++++++
test-data/poi-integration-exceptions.csv | 5 +-
3 files changed, 119 insertions(+), 7 deletions(-)
diff --git
a/poi-scratchpad/src/main/java/org/apache/poi/hdgf/pointers/PointerFactory.java
b/poi-scratchpad/src/main/java/org/apache/poi/hdgf/pointers/PointerFactory.java
index 35c475b175..17fc3ebe5e 100644
---
a/poi-scratchpad/src/main/java/org/apache/poi/hdgf/pointers/PointerFactory.java
+++
b/poi-scratchpad/src/main/java/org/apache/poi/hdgf/pointers/PointerFactory.java
@@ -20,6 +20,7 @@ import org.apache.poi.hdgf.exceptions.OldVisioFormatException;
import org.apache.poi.hdgf.streams.PointerContainingStream;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.RecordFormatException;
/**
* Factor class to create the appropriate pointers, based on the version
@@ -45,8 +46,17 @@ public final class PointerFactory {
p = new PointerV6();
p.setType(LittleEndian.getInt(data, offset));
p.setAddress((int)LittleEndian.getUInt(data, offset+4));
- p.setOffset((int)LittleEndian.getUInt(data, offset+8));
- p.setLength((int)LittleEndian.getUInt(data, offset+12));
+ // Offset and Length flow into Stream.createStream as the (offset,
length)
+ // pair handed to StreamStore / CompressedStreamStore ->
IOUtils.safelyClone.
+ // Validate the uint32 values up-front: throw
RecordFormatException with the
+ // offending value rather than letting a wrapped int reach the
downstream
+ // bounds check (matches the EMF header description fix in PR
#1060).
+ final long v6Offset = LittleEndian.getUInt(data, offset+8);
+ final long v6Length = LittleEndian.getUInt(data, offset+12);
+ checkPointerOffset(v6Offset);
+ IOUtils.safelyAllocateCheck(v6Length, Integer.MAX_VALUE);
+ p.setOffset((int)v6Offset);
+ p.setLength((int)v6Length);
p.setFormat(LittleEndian.getShort(data, offset+16));
return p;
@@ -55,8 +65,12 @@ public final class PointerFactory {
p.setType(LittleEndian.getShort(data, offset));
p.setFormat(LittleEndian.getShort(data, offset+2));
p.setAddress((int)LittleEndian.getUInt(data, offset+4));
- p.setOffset((int)LittleEndian.getUInt(data, offset+8));
- p.setLength((int)LittleEndian.getUInt(data, offset+12));
+ final long v5Offset = LittleEndian.getUInt(data, offset+8);
+ final long v5Length = LittleEndian.getUInt(data, offset+12);
+ checkPointerOffset(v5Offset);
+ IOUtils.safelyAllocateCheck(v5Length, Integer.MAX_VALUE);
+ p.setOffset((int)v5Offset);
+ p.setLength((int)v5Length);
return p;
} else {
@@ -64,6 +78,13 @@ public final class PointerFactory {
}
}
+ private static void checkPointerOffset(long unsignedOffset) {
+ if (unsignedOffset > Integer.MAX_VALUE) {
+ throw new RecordFormatException(
+ "HDGF Pointer offset " + unsignedOffset + " exceeds
Integer.MAX_VALUE");
+ }
+ }
+
/**
* Parsers the {@link PointerContainingStream} contents and
* creates all the child Pointers for it
diff --git
a/poi-scratchpad/src/test/java/org/apache/poi/hdgf/pointers/TestPointerFactory.java
b/poi-scratchpad/src/test/java/org/apache/poi/hdgf/pointers/TestPointerFactory.java
index 641f8909b3..b9a52044a8 100644
---
a/poi-scratchpad/src/test/java/org/apache/poi/hdgf/pointers/TestPointerFactory.java
+++
b/poi-scratchpad/src/test/java/org/apache/poi/hdgf/pointers/TestPointerFactory.java
@@ -22,6 +22,8 @@ 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.LittleEndian;
+import org.apache.poi.util.RecordFormatException;
import org.junit.jupiter.api.Test;
/**
@@ -134,6 +136,96 @@ public final class TestPointerFactory {
assertFalse(d.destinationHasPointers());
}
+ /**
+ * A v6+ Pointer reads its Offset and Length fields as 32-bit unsigned
+ * integers, then narrows them to int and hands the pair to
+ * {@code Stream.createStream} -> {@code StreamStore} /
+ * {@code CompressedStreamStore} -> {@code IOUtils.safelyClone}. A
+ * crafted file with Length > Integer.MAX_VALUE used to be silently
+ * narrowed via a plain {@code (int)} cast, letting a wrapped value flow
+ * into the downstream bounds check. Validate the uint32 values up-front
+ * via {@code IOUtils.safelyAllocateCheck} (length) and an explicit
+ * {@code RecordFormatException} (offset) so the failure carries the
+ * actual offending value, not a bare "integer overflow".
+ */
+ @Test
+ void testCreateV6RejectsOversizedLength() {
+ PointerFactory pf = new PointerFactory(11);
+
+ byte[] ptr = new byte[18];
+ LittleEndian.putInt (ptr, 0, 0x16); // type
+ LittleEndian.putUInt (ptr, 4, 0x0143aff4L); // address
+ LittleEndian.putUInt (ptr, 8, 0x80L); // offset (valid)
+ LittleEndian.putUInt (ptr, 12, 0x80000001L); // length: would
wrap to negative int
+ LittleEndian.putShort(ptr, 16, (short)0x46); // format
+
+ assertThrows(RecordFormatException.class, () -> pf.createPointer(ptr,
0));
+ }
+
+ @Test
+ void testCreateV6RejectsOversizedOffset() {
+ PointerFactory pf = new PointerFactory(11);
+
+ byte[] ptr = new byte[18];
+ LittleEndian.putInt (ptr, 0, 0x16);
+ LittleEndian.putUInt (ptr, 4, 0x0143aff4L);
+ LittleEndian.putUInt (ptr, 8, 0xFFFFFFFFL); // offset: would
wrap to -1
+ LittleEndian.putUInt (ptr, 12, 0x54L);
+ LittleEndian.putShort(ptr, 16, (short)0x46);
+
+ assertThrows(RecordFormatException.class, () -> pf.createPointer(ptr,
0));
+ }
+
+ @Test
+ void testCreateV5RejectsOversizedLength() {
+ PointerFactory pf = new PointerFactory(5);
+
+ byte[] ptr = new byte[16];
+ LittleEndian.putShort(ptr, 0, (short)0x14);
+ LittleEndian.putShort(ptr, 2, (short)0x52);
+ LittleEndian.putUInt (ptr, 4, 0x011eb2acL);
+ LittleEndian.putUInt (ptr, 8, 0x1dd4L);
+ LittleEndian.putUInt (ptr, 12, 0x80000001L); // length: would
wrap to negative
+
+ assertThrows(RecordFormatException.class, () -> pf.createPointer(ptr,
0));
+ }
+
+ @Test
+ void testCreateV5RejectsOversizedOffset() {
+ PointerFactory pf = new PointerFactory(5);
+
+ byte[] ptr = new byte[16];
+ LittleEndian.putShort(ptr, 0, (short)0x14);
+ LittleEndian.putShort(ptr, 2, (short)0x52);
+ LittleEndian.putUInt (ptr, 4, 0x011eb2acL);
+ LittleEndian.putUInt (ptr, 8, 0xFFFFFFFFL); // offset: would
wrap to -1
+ LittleEndian.putUInt (ptr, 12, 0x14dL);
+
+ assertThrows(RecordFormatException.class, () -> pf.createPointer(ptr,
0));
+ }
+
+ /**
+ * Values up to {@code Integer.MAX_VALUE} (still nonsensically large but
+ * representable) must continue to parse — the hardening is only meant to
+ * catch the silent-narrowing case, not to introduce a new lower ceiling.
+ * Downstream {@code IOUtils.safelyClone} handles real bounding.
+ */
+ @Test
+ void testCreateV6AcceptsMaxIntOffsetAndLength() {
+ PointerFactory pf = new PointerFactory(11);
+
+ byte[] ptr = new byte[18];
+ LittleEndian.putInt (ptr, 0, 0x16);
+ LittleEndian.putUInt (ptr, 4, 0x0143aff4L);
+ LittleEndian.putUInt (ptr, 8, Integer.MAX_VALUE & 0xFFFFFFFFL);
+ LittleEndian.putUInt (ptr, 12, Integer.MAX_VALUE & 0xFFFFFFFFL);
+ LittleEndian.putShort(ptr, 16, (short)0x46);
+
+ Pointer p = pf.createPointer(ptr, 0);
+ assertEquals(Integer.MAX_VALUE, p.getOffset());
+ assertEquals(Integer.MAX_VALUE, p.getLength());
+ }
+
@Test
void testCreateV6FromMid() {
PointerFactory pf = new PointerFactory(11);
diff --git a/test-data/poi-integration-exceptions.csv
b/test-data/poi-integration-exceptions.csv
index 31e40f8f92..c3f2806697 100644
--- a/test-data/poi-integration-exceptions.csv
+++ b/test-data/poi-integration-exceptions.csv
@@ -185,10 +185,9 @@
slideshow/clusterfuzz-testcase-minimized-POIHSLFFuzzer-6710128412590080.ppt,"han
slideshow/clusterfuzz-testcase-minimized-POIHSLFFuzzer-6710128412590080.ppt,extract,HPSF,,java.lang.IllegalArgumentException,Had
unexpected type of entry for name,
slideshow/clusterfuzz-testcase-minimized-POIHSLFFuzzer-6710128412590080.ppt,additional,HPSF,,java.lang.IllegalStateException,Buffer
overrun,
slideshow/clusterfuzz-testcase-minimized-POIHSLFFuzzer-6710128412590080.ppt,handle,HPSF,,org.opentest4j.AssertionFailedError,expected:
<true> but was: <false>,
-diagram/clusterfuzz-testcase-minimized-POIHDGFFuzzer-5947849161179136.vsd,extract,"HDGF,HPSF",,java.lang.IllegalArgumentException,"Encountered
too deep nesting, cannot process stream",
-diagram/clusterfuzz-testcase-minimized-POIHDGFFuzzer-5947849161179136.vsd,handle,HDGF,,java.lang.IllegalArgumentException,"Encountered
too deep nesting, cannot process stream",
+diagram/clusterfuzz-testcase-minimized-POIHDGFFuzzer-5947849161179136.vsd,extract,"HDGF,HPSF",,org.apache.poi.util.RecordFormatException,Can't
allocate an array > 2147483647,
+diagram/clusterfuzz-testcase-minimized-POIHDGFFuzzer-5947849161179136.vsd,handle,HDGF,,org.apache.poi.util.RecordFormatException,Can't
allocate an array > 2147483647,
diagram/clusterfuzz-testcase-minimized-POIHDGFFuzzer-5947849161179136.vsd,"handle,additional",HPSF,,java.util.NoSuchElementException,Can't
read past the end of the stream,
-diagram/clusterfuzz-testcase-minimized-POIHDGFFuzzer-5947849161179136.vsd,handle,HDGF,,org.apache.poi.util.RecordFormatException,Can't
allocate an array of length < 0,
hsmf/clusterfuzz-testcase-minimized-POIHSMFFuzzer-4735011465854976.msg,extract,"HSMF,HPSF",,java.lang.IllegalArgumentException,ChunkId
and type of chunk did not match,
hsmf/clusterfuzz-testcase-minimized-POIHSMFFuzzer-4735011465854976.msg,handle,HSMF,,java.lang.IllegalArgumentException,ChunkId
and type of chunk did not match,
diagram/clusterfuzz-testcase-minimized-POIHDGFFuzzer-4913778037489664.vsd,extract,"HDGF,HPSF",,java.lang.IllegalArgumentException,"Encountered
too deep nesting, cannot process stream",
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]