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]