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 145ca469d9 Harden HPSF allocation-size arithmetic against silent 
overflow (#1064)
145ca469d9 is described below

commit 145ca469d93f5cb3f9f476d5d3b9195ac3ada744
Author: metsw24-max <[email protected]>
AuthorDate: Tue May 12 18:02:13 2026 +0530

    Harden HPSF allocation-size arithmetic against silent overflow (#1064)
    
    * Harden HPSF allocation-size arithmetic against silent overflow
    
    * updated
    
    * updated
    
    * Revert "updated"
    
    This reverts commit e137e3bef7a0448a2805d351364214bd465c7166.
    
    * use RecordFormatException
    
    * Update poi-integration-exceptions.csv
    
    ---------
    
    Co-authored-by: PJ Fanning <[email protected]>
---
 poi/src/main/java/org/apache/poi/hpsf/Array.java   | 10 ++-
 .../java/org/apache/poi/hpsf/UnicodeString.java    | 14 +++-
 .../org/apache/poi/hpsf/TestOverflowHardening.java | 79 ++++++++++++++++++++++
 test-data/poi-integration-exceptions.csv           |  2 +-
 4 files changed, 102 insertions(+), 3 deletions(-)

diff --git a/poi/src/main/java/org/apache/poi/hpsf/Array.java 
b/poi/src/main/java/org/apache/poi/hpsf/Array.java
index e366e3cee2..076f1cb60a 100644
--- a/poi/src/main/java/org/apache/poi/hpsf/Array.java
+++ b/poi/src/main/java/org/apache/poi/hpsf/Array.java
@@ -19,6 +19,7 @@ package org.apache.poi.hpsf;
 import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.Internal;
 import org.apache.poi.util.LittleEndianByteArrayInputStream;
+import org.apache.poi.util.RecordFormatException;
 
 @Internal
 public class Array {
@@ -71,7 +72,14 @@ public class Array {
         long getNumberOfScalarValues() {
             long result = 1;
             for ( ArrayDimension dimension : _dimensions ) {
-                result *= dimension._size;
+                try {
+                    // Math.multiplyExact rejects compounding dimensions that 
would silently
+                    // overflow long math and bypass the > Integer.MAX_VALUE 
guard in Array.read.
+                    result = Math.multiplyExact(result, dimension._size);
+                } catch (ArithmeticException e) {
+                    throw new RecordFormatException(
+                            "Overflow when calculating the number of scalar 
values", e);
+                }
             }
             return result;
         }
diff --git a/poi/src/main/java/org/apache/poi/hpsf/UnicodeString.java 
b/poi/src/main/java/org/apache/poi/hpsf/UnicodeString.java
index 99f2ded8e7..31192220f2 100644
--- a/poi/src/main/java/org/apache/poi/hpsf/UnicodeString.java
+++ b/poi/src/main/java/org/apache/poi/hpsf/UnicodeString.java
@@ -28,6 +28,7 @@ import org.apache.poi.util.Internal;
 import org.apache.poi.util.LittleEndian;
 import org.apache.poi.util.LittleEndianByteArrayInputStream;
 import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.RecordFormatException;
 import org.apache.poi.util.StringUtil;
 
 @Internal
@@ -36,9 +37,20 @@ public class UnicodeString {
 
     private byte[] _value;
 
+    /**
+     * @param lei a LittleEndianByteArrayInputStream
+     * @throws RecordFormatException if there is a problem with calculated 
length
+     */
     public void read(LittleEndianByteArrayInputStream lei) {
         final int length = lei.readInt();
-        final int unicodeBytes = length*2;
+        // Math.multiplyExact rejects crafted lengths that would silently wrap 
signed-int math
+        // (e.g. length == 0x40000001 -> length*2 == 0x80000002 wraps to 
negative).
+        final int unicodeBytes;
+        try {
+            unicodeBytes = Math.multiplyExact(length, 2);
+        } catch (ArithmeticException e) {
+            throw new RecordFormatException("Invalid unicode length", e);
+        }
         _value = IOUtils.safelyAllocate(unicodeBytes, 
CodePageString.getMaxRecordLength());
         
         // If Length is zero, this field MUST be zero bytes in length. If 
Length is
diff --git a/poi/src/test/java/org/apache/poi/hpsf/TestOverflowHardening.java 
b/poi/src/test/java/org/apache/poi/hpsf/TestOverflowHardening.java
new file mode 100644
index 0000000000..299200b09d
--- /dev/null
+++ b/poi/src/test/java/org/apache/poi/hpsf/TestOverflowHardening.java
@@ -0,0 +1,79 @@
+/* ====================================================================
+   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.hpsf;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianByteArrayInputStream;
+import org.apache.poi.util.RecordFormatException;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Verifies that HPSF readers reject crafted lengths whose subsequent
+ * allocation-size arithmetic would silently overflow signed integer math.
+ */
+class TestOverflowHardening {
+
+    /**
+     * Reproduces the int*2 multiplication overflow in {@link 
UnicodeString#read}:
+     * a length of {@code 0x40000001} produces {@code length*2 == 0x80000002},
+     * which wraps to a negative int. {@link Math#multiplyExact} now surfaces
+     * the overflow as an {@link ArithmeticException} at the parser, where the
+     * malformed field actually lives.
+     */
+    @Test
+    void unicodeStringLengthMultiplicationOverflowRejected() {
+        byte[] data = new byte[4];
+        LittleEndian.putInt(data, 0, 0x40000001);
+        LittleEndianByteArrayInputStream lei = new 
LittleEndianByteArrayInputStream(data, 0);
+
+        UnicodeString us = new UnicodeString();
+        assertThrows(RecordFormatException.class, () -> us.read(lei));
+    }
+
+    /**
+     * Reproduces the long*long multiplication overflow in
+     * {@link Array.ArrayHeader#getNumberOfScalarValues}: with three dimensions
+     * of size {@code 0x80000000} the unchecked product
+     * {@code 2^31 * 2^31 * 2^31 = 2^93} wraps inside a 64-bit long and the
+     * subsequent {@code > Integer.MAX_VALUE} guard at {@code Array.read} is
+     * silently bypassed. {@link Math#multiplyExact} now rejects the crafted
+     * array header with an {@link ArithmeticException}.
+     */
+    @Test
+    void arrayDimensionMultiplicationOverflowRejected() {
+        // ArrayHeader layout: type (4) + numDimensions (4) + numDimensions * 
(size:4 + indexOffset:4)
+        // 3 dimensions of size 0x80000000 each -> product overflows 2^63 
(long max)
+        byte[] data = new byte[4 + 4 + 3 * (4 + 4)];
+        int off = 0;
+        LittleEndian.putInt(data, off, Variant.VT_I4);
+        off += 4;
+        LittleEndian.putInt(data, off, 3);
+        off += 4;
+        for (int i = 0; i < 3; i++) {
+            LittleEndian.putUInt(data, off, 0x80000000L);
+            off += 4;
+            LittleEndian.putInt(data, off, 0);
+            off += 4;
+        }
+        LittleEndianByteArrayInputStream lei = new 
LittleEndianByteArrayInputStream(data, 0);
+
+        Array a = new Array();
+        assertThrows(RecordFormatException.class, () -> a.read(lei));
+    }
+}
diff --git a/test-data/poi-integration-exceptions.csv 
b/test-data/poi-integration-exceptions.csv
index 9c018180f4..823593fd63 100644
--- a/test-data/poi-integration-exceptions.csv
+++ b/test-data/poi-integration-exceptions.csv
@@ -100,7 +100,7 @@ 
spreadsheet/sample.strict.xlsx,extract,OPC,,org.apache.poi.ooxml.POIXMLException
 
spreadsheet/57914.xlsx,"handle,extract",XSSF,,org.apache.poi.ooxml.POIXMLException,"Strict
 OOXML isn't currently supported, please see bug #57699",
 
spreadsheet/57914.xlsx,extract,OPC,,org.apache.poi.ooxml.POIXMLException,"Strict
 OOXML isn't currently supported, please see bug #57699",
 
spreadsheet/poi-fuzz.xls,additional,HSSF,,org.apache.poi.util.RecordFormatException,Not
 enough data (0) to read requested (4) bytes,
-spreadsheet/poi-fuzz.xls,additional,HPSF,,org.apache.poi.util.RecordFormatException,"Tried
 to allocate an array of length 1,468,570,764, but the maximum length for this 
record type is 100,000",
+spreadsheet/poi-fuzz.xls,additional,HPSF,,org.apache.poi.util.RecordFormatException,Overflow
 when calculating the number of scalar values,
 
spreadsheet/poi-fuzz.xls,handle,HPSF,,org.opentest4j.AssertionFailedError,expected:
 <true> but was: <false>,
 
openxml4j/ContentTypeHasEntities.ooxml,"handle,extract",OPC,,org.apache.poi.openxml4j.exceptions.InvalidFormatException,Can't
 read content types part !,
 ddf/Container.dat,handle,HMEF,,java.lang.IllegalArgumentException,"TNEF 
signature not detected in file, expected 574529400 but got -268435441",


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

Reply via email to