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

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-collections.git


The following commit(s) were added to refs/heads/master by this push:
     new 9b59357a1 Reject invalid load factor in hashed/reference map 
doReadObject (#691)
9b59357a1 is described below

commit 9b59357a14f547402f5e536f8a08525c080b5a3b
Author: Dexter.k <[email protected]>
AuthorDate: Tue Jun 23 19:01:33 2026 +0000

    Reject invalid load factor in hashed/reference map doReadObject (#691)
    
    * reject invalid load factor in hashed map doReadObject
    
    the constructors forbid a non-positive or NaN load factor; reapply that 
check in AbstractHashedMap and AbstractReferenceMap doReadObject so a crafted 
stream cannot deserialize a map with a corrupt threshold.
    
    * use parameterized test for invalid load factor cases
    
    * move reference map load factor test into ReferenceMapTest
    
    * add invalid load factor tests for remaining hashed/reference map 
subclasses
---
 .../commons/collections4/map/AbstractHashedMap.java     |  4 ++++
 .../commons/collections4/map/AbstractReferenceMap.java  |  4 ++++
 .../collections4/map/CaseInsensitiveMapTest.java        | 16 ++++++++++++++++
 .../apache/commons/collections4/map/HashedMapTest.java  | 17 +++++++++++++++++
 .../org/apache/commons/collections4/map/LRUMapTest.java | 14 ++++++++++++++
 .../apache/commons/collections4/map/LinkedMapTest.java  | 15 +++++++++++++++
 .../collections4/map/ReferenceIdentityMapTest.java      | 15 +++++++++++++++
 .../commons/collections4/map/ReferenceMapTest.java      | 15 +++++++++++++++
 8 files changed, 100 insertions(+)

diff --git 
a/src/main/java/org/apache/commons/collections4/map/AbstractHashedMap.java 
b/src/main/java/org/apache/commons/collections4/map/AbstractHashedMap.java
index aee103d8b..be761abde 100644
--- a/src/main/java/org/apache/commons/collections4/map/AbstractHashedMap.java
+++ b/src/main/java/org/apache/commons/collections4/map/AbstractHashedMap.java
@@ -17,6 +17,7 @@
 package org.apache.commons.collections4.map;
 
 import java.io.IOException;
+import java.io.InvalidObjectException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.util.AbstractCollection;
@@ -943,6 +944,9 @@ public class AbstractHashedMap<K, V> extends AbstractMap<K, 
V> implements Iterab
     @SuppressWarnings("unchecked")
     protected void doReadObject(final ObjectInputStream in) throws 
IOException, ClassNotFoundException {
         loadFactor = in.readFloat();
+        if (loadFactor <= 0.0f || Float.isNaN(loadFactor)) {
+            throw new InvalidObjectException("Load factor must be greater than 
0");
+        }
         final int capacity = in.readInt();
         final int size = in.readInt();
         init();
diff --git 
a/src/main/java/org/apache/commons/collections4/map/AbstractReferenceMap.java 
b/src/main/java/org/apache/commons/collections4/map/AbstractReferenceMap.java
index 42f84c294..e67829dc1 100644
--- 
a/src/main/java/org/apache/commons/collections4/map/AbstractReferenceMap.java
+++ 
b/src/main/java/org/apache/commons/collections4/map/AbstractReferenceMap.java
@@ -17,6 +17,7 @@
 package org.apache.commons.collections4.map;
 
 import java.io.IOException;
+import java.io.InvalidObjectException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.lang.ref.Reference;
@@ -823,6 +824,9 @@ public abstract class AbstractReferenceMap<K, V> extends 
AbstractHashedMap<K, V>
         valueType = ReferenceStrength.resolve(in.readInt());
         purgeValues = in.readBoolean();
         loadFactor = in.readFloat();
+        if (loadFactor <= 0.0f || Float.isNaN(loadFactor)) {
+            throw new InvalidObjectException("Load factor must be greater than 
0");
+        }
         final int capacity = in.readInt();
         init();
         data = new HashEntry[capacity];
diff --git 
a/src/test/java/org/apache/commons/collections4/map/CaseInsensitiveMapTest.java 
b/src/test/java/org/apache/commons/collections4/map/CaseInsensitiveMapTest.java
index f72d0356c..1b51d3dfd 100644
--- 
a/src/test/java/org/apache/commons/collections4/map/CaseInsensitiveMapTest.java
+++ 
b/src/test/java/org/apache/commons/collections4/map/CaseInsensitiveMapTest.java
@@ -18,8 +18,10 @@ package org.apache.commons.collections4.map;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.io.InvalidObjectException;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
@@ -27,6 +29,8 @@ import java.util.Set;
 
 import org.apache.commons.lang3.StringUtils;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
 /**
  * Tests for the {@link CaseInsensitiveMap} implementation.
@@ -46,6 +50,18 @@ public class CaseInsensitiveMapTest<K, V> extends 
AbstractIterableMapTest<K, V>
         return new CaseInsensitiveMap<>();
     }
 
+    /**
+     * A crafted stream can carry a load factor the constructor rejects. 
AbstractHashedMap.doReadObject
+     * must reapply that contract on read.
+     */
+    @ParameterizedTest
+    @ValueSource(floats = {0.0f, -1.0f, Float.NaN})
+    void testDeserializeRejectsInvalidLoadFactor(final float badLoadFactor) {
+        final CaseInsensitiveMap<K, V> map = new CaseInsensitiveMap<>();
+        map.loadFactor = badLoadFactor;
+        assertThrows(InvalidObjectException.class, () -> 
serializeDeserialize(map));
+    }
+
     @Test
     @SuppressWarnings("unchecked")
     void testCaseInsensitive() {
diff --git 
a/src/test/java/org/apache/commons/collections4/map/HashedMapTest.java 
b/src/test/java/org/apache/commons/collections4/map/HashedMapTest.java
index 24d024fc3..2bbf69533 100644
--- a/src/test/java/org/apache/commons/collections4/map/HashedMapTest.java
+++ b/src/test/java/org/apache/commons/collections4/map/HashedMapTest.java
@@ -18,8 +18,13 @@ package org.apache.commons.collections4.map;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.InvalidObjectException;
 
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
 /**
  * JUnit tests.
@@ -85,4 +90,16 @@ public class HashedMapTest<K, V> extends 
AbstractIterableMapTest<K, V> {
         // the threshold has changed due to calling ensureCapacity
         assertEquals(96, map.threshold);
     }
+
+    /**
+     * A crafted stream can carry a load factor the constructor rejects. 
AbstractHashedMap.doReadObject
+     * must reapply that contract on read.
+     */
+    @ParameterizedTest
+    @ValueSource(floats = {0.0f, -1.0f, Float.NaN})
+    void testDeserializeRejectsInvalidLoadFactor(final float badLoadFactor) {
+        final HashedMap<K, V> map = new HashedMap<>();
+        map.loadFactor = badLoadFactor;
+        assertThrows(InvalidObjectException.class, () -> 
serializeDeserialize(map));
+    }
 }
diff --git a/src/test/java/org/apache/commons/collections4/map/LRUMapTest.java 
b/src/test/java/org/apache/commons/collections4/map/LRUMapTest.java
index 15fe791dc..8312a9e64 100644
--- a/src/test/java/org/apache/commons/collections4/map/LRUMapTest.java
+++ b/src/test/java/org/apache/commons/collections4/map/LRUMapTest.java
@@ -37,6 +37,8 @@ import org.apache.commons.collections4.MapIterator;
 import org.apache.commons.collections4.OrderedMap;
 import org.apache.commons.collections4.ResettableIterator;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
 /**
  * JUnit tests.
@@ -167,6 +169,18 @@ public class LRUMapTest<K, V> extends 
AbstractOrderedMapTest<K, V> {
         return new LRUMap<>();
     }
 
+    /**
+     * A crafted stream can carry a load factor the constructor rejects. 
AbstractHashedMap.doReadObject
+     * must reapply that contract on read.
+     */
+    @ParameterizedTest
+    @ValueSource(floats = {0.0f, -1.0f, Float.NaN})
+    void testDeserializeRejectsInvalidLoadFactor(final float badLoadFactor) {
+        final LRUMap<K, V> map = new LRUMap<>();
+        map.loadFactor = badLoadFactor;
+        assertThrows(InvalidObjectException.class, () -> 
serializeDeserialize(map));
+    }
+
     @Test
     void testAccessOrder() {
         if (!isPutAddSupported() || !isPutChangeSupported()) {
diff --git 
a/src/test/java/org/apache/commons/collections4/map/LinkedMapTest.java 
b/src/test/java/org/apache/commons/collections4/map/LinkedMapTest.java
index 479fd5714..e7a6bcc5b 100644
--- a/src/test/java/org/apache/commons/collections4/map/LinkedMapTest.java
+++ b/src/test/java/org/apache/commons/collections4/map/LinkedMapTest.java
@@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
+import java.io.InvalidObjectException;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -33,6 +34,8 @@ import org.apache.commons.collections4.ResettableIterator;
 import org.apache.commons.collections4.list.AbstractListTest;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
 /**
  * JUnit tests.
@@ -116,6 +119,18 @@ public class LinkedMapTest<K, V> extends 
AbstractOrderedMapTest<K, V> {
         return new LinkedMap<>();
     }
 
+    /**
+     * A crafted stream can carry a load factor the constructor rejects. 
AbstractHashedMap.doReadObject
+     * must reapply that contract on read.
+     */
+    @ParameterizedTest
+    @ValueSource(floats = {0.0f, -1.0f, Float.NaN})
+    void testDeserializeRejectsInvalidLoadFactor(final float badLoadFactor) {
+        final LinkedMap<K, V> map = new LinkedMap<>();
+        map.loadFactor = badLoadFactor;
+        assertThrows(InvalidObjectException.class, () -> 
serializeDeserialize(map));
+    }
+
     @Test
     @SuppressWarnings("unchecked")
     void testClone() {
diff --git 
a/src/test/java/org/apache/commons/collections4/map/ReferenceIdentityMapTest.java
 
b/src/test/java/org/apache/commons/collections4/map/ReferenceIdentityMapTest.java
index bb107e91c..ef65f1015 100644
--- 
a/src/test/java/org/apache/commons/collections4/map/ReferenceIdentityMapTest.java
+++ 
b/src/test/java/org/apache/commons/collections4/map/ReferenceIdentityMapTest.java
@@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
+import java.io.InvalidObjectException;
 import java.lang.ref.WeakReference;
 import java.util.Iterator;
 import java.util.Map;
@@ -32,6 +33,8 @@ import java.util.Map;
 import org.apache.commons.collections4.IterableMap;
 import 
org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceStrength;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
 /**
  * Tests for ReferenceIdentityMap.
@@ -235,6 +238,18 @@ public class ReferenceIdentityMapTest<K, V> extends 
AbstractIterableMapTest<K, V
         return new ReferenceIdentityMap<>(ReferenceStrength.WEAK, 
ReferenceStrength.WEAK);
     }
 
+    /**
+     * A crafted stream can carry a load factor the constructor rejects. 
AbstractReferenceMap.doReadObject
+     * (its own override) must reapply that contract on read.
+     */
+    @ParameterizedTest
+    @ValueSource(floats = {0.0f, -1.0f, Float.NaN})
+    void testDeserializeRejectsInvalidLoadFactor(final float badLoadFactor) {
+        final ReferenceIdentityMap<K, V> map = new ReferenceIdentityMap<>();
+        map.loadFactor = badLoadFactor;
+        assertThrows(InvalidObjectException.class, () -> 
serializeDeserialize(map));
+    }
+
     @Test
     @SuppressWarnings("unchecked")
     void testBasics() {
diff --git 
a/src/test/java/org/apache/commons/collections4/map/ReferenceMapTest.java 
b/src/test/java/org/apache/commons/collections4/map/ReferenceMapTest.java
index a2413c613..1f8889d1e 100644
--- a/src/test/java/org/apache/commons/collections4/map/ReferenceMapTest.java
+++ b/src/test/java/org/apache/commons/collections4/map/ReferenceMapTest.java
@@ -27,6 +27,7 @@ import static org.junit.jupiter.api.Assertions.fail;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InvalidObjectException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
@@ -41,6 +42,8 @@ import 
org.apache.commons.collections4.map.AbstractHashedMap.HashEntry;
 import org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceEntry;
 import 
org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceStrength;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
 /**
  * Tests for ReferenceMap.
@@ -309,6 +312,18 @@ public class ReferenceMapTest<K, V> extends 
AbstractIterableMapTest<K, V> {
 
     }
 
+    /**
+     * A crafted stream can carry a load factor the constructor rejects. 
AbstractReferenceMap.doReadObject
+     * (its own override) must reapply that contract on read.
+     */
+    @ParameterizedTest
+    @ValueSource(floats = {0.0f, -1.0f, Float.NaN})
+    void testDeserializeRejectsInvalidLoadFactor(final float badLoadFactor) {
+        final ReferenceMap<K, V> map = new ReferenceMap<>();
+        map.loadFactor = badLoadFactor;
+        assertThrows(InvalidObjectException.class, () -> 
serializeDeserialize(map));
+    }
+
     /**
      * Test whether remove is not removing last entry after calling hasNext.
      * <p>

Reply via email to