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 a7d9bbfcd7 HSLF:  reject oversized PersistPtr sheet offsets (#1078)
a7d9bbfcd7 is described below

commit a7d9bbfcd726c2423704c4305b91b66a507ef947
Author: metsw24-max <[email protected]>
AuthorDate: Tue May 26 02:58:58 2026 +0530

    HSLF:  reject oversized PersistPtr sheet offsets (#1078)
    
    HSLF: reject oversized PersistPtr sheet offsets
---
 .../apache/poi/hslf/record/PersistPtrHolder.java   |  9 ++-
 .../poi/hslf/record/TestPersistPtrHolder.java      | 82 ++++++++++++++++++++++
 2 files changed, 90 insertions(+), 1 deletion(-)

diff --git 
a/poi-scratchpad/src/main/java/org/apache/poi/hslf/record/PersistPtrHolder.java 
b/poi-scratchpad/src/main/java/org/apache/poi/hslf/record/PersistPtrHolder.java
index db3482f0aa..29697716cf 100644
--- 
a/poi-scratchpad/src/main/java/org/apache/poi/hslf/record/PersistPtrHolder.java
+++ 
b/poi-scratchpad/src/main/java/org/apache/poi/hslf/record/PersistPtrHolder.java
@@ -127,7 +127,14 @@ public final class PersistPtrHolder extends 
PositionDependentRecordAtom {
             // Grab the offsets for each of the sheets
             for(int i=0; i<offset_count; i++) {
                 int sheet_no = offset_no + i;
-                int sheet_offset = (int)LittleEndian.getUInt(_ptrData,pos);
+                // Reject sheet offsets that would silently narrow when the
+                // uint32 read here is cast to signed int. _slideLocations
+                // values feed Record.buildRecordAtOffset(docstream, offset),
+                // where a wrapped negative offset can land on valid byte
+                // positions of the docstream and cause the parser to read
+                // a record from the wrong location instead of failing
+                // cleanly on the malformed input.
+                int sheet_offset = 
Math.toIntExact(LittleEndian.getUInt(_ptrData,pos));
                 _slideLocations.put(sheet_no, sheet_offset);
 
                 // Wind on by 4 bytes per sheet found
diff --git 
a/poi-scratchpad/src/test/java/org/apache/poi/hslf/record/TestPersistPtrHolder.java
 
b/poi-scratchpad/src/test/java/org/apache/poi/hslf/record/TestPersistPtrHolder.java
new file mode 100644
index 0000000000..7db86a6aa5
--- /dev/null
+++ 
b/poi-scratchpad/src/test/java/org/apache/poi/hslf/record/TestPersistPtrHolder.java
@@ -0,0 +1,82 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hslf.record;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.poi.util.LittleEndian;
+import org.junit.jupiter.api.Test;
+
+public final class TestPersistPtrHolder {
+
+    /**
+     * Build the byte representation of a PersistPtrIncrementalBlock with a
+     * single PersistDirectoryEntry pointing to {@code sheetOffset}. The
+     * outer 8-byte record header is included so the buffer can be passed
+     * straight to {@link PersistPtrHolder#PersistPtrHolder(byte[], int, int)}.
+     */
+    private static byte[] buildRecord(long sheetOffset) {
+        byte[] buf = new byte[16];
+        // Record header (8 bytes): version/instance, type, length
+        LittleEndian.putUShort(buf, 0, 0);                  // 
version/instance (unused here)
+        LittleEndian.putUShort(buf, 2, (int) 
RecordTypes.PersistPtrIncrementalBlock.typeID);
+        LittleEndian.putInt(buf, 4, 8);                     // body length = 
info(4) + offset(4)
+
+        // Body: info field with cntPersist=1, persistId=0, then the 32-bit 
offset
+        LittleEndian.putInt(buf, 8, 0x00100000);            // cntPersistFld 
bits = 1, persistIdFld bits = 0
+        LittleEndian.putUInt(buf, 12, sheetOffset);
+        return buf;
+    }
+
+    /**
+     * A crafted PersistPtrHolder entry whose sheet offset is &gt;
+     * {@link Integer#MAX_VALUE} used to be silently narrowed via a plain
+     * {@code (int)} cast at parse time. The wrapped negative value was then
+     * stored in {@code _slideLocations} and later flowed into
+     * {@code Record.buildRecordAtOffset(docstream, offset)} - where a small
+     * negative offset can land on valid byte positions of the docstream
+     * (since {@code b[offset+2]} can be a valid index when {@code offset} is
+     * -1 or -2) and cause the parser to read a record from the wrong
+     * location instead of failing on the malformed input. Reject these at
+     * parse time via {@link Math#toIntExact(long)}, matching the recent
+     * hardening of HDGF v6+ chunk Length and the PointerFactory uint32
+     * fields.
+     */
+    @Test
+    void testRejectsOversizedSheetOffset() {
+        byte[] data = buildRecord(0x80000001L);
+        assertThrows(ArithmeticException.class,
+                () -> new PersistPtrHolder(data, 0, data.length));
+    }
+
+    /**
+     * Equally important: the hardening must not introduce a new lower
+     * ceiling. Offsets up to {@link Integer#MAX_VALUE} are still
+     * representable as a signed int and must continue to parse - byte
+     * arrays in the JVM can be that large in principle, and downstream
+     * code already bounds-checks against the actual docstream length.
+     */
+    @Test
+    void testAcceptsMaxIntSheetOffset() {
+        byte[] data = buildRecord(Integer.MAX_VALUE);
+        PersistPtrHolder ptr = new PersistPtrHolder(data, 0, data.length);
+        Integer offset = ptr.getSlideLocationsLookup().get(0);
+        assertEquals(Integer.MAX_VALUE, offset.intValue());
+    }
+}


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

Reply via email to