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

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


The following commit(s) were added to refs/heads/master by this push:
     new 8f273f2d Reject deserialization of bytes containing a proxy class 
descriptor
8f273f2d is described below

commit 8f273f2d0910089d32c89263165b124bb37a0e6f
Author: Alex Herbert <[email protected]>
AuthorDate: Fri Aug 21 19:24:57 2026 +0100

    Reject deserialization of bytes containing a proxy class descriptor
---
 .../commons/rng/core/source32/JDKRandom.java       | 17 ++++++++
 .../commons/rng/core/source32/JDKRandomTest.java   | 50 ++++++++++++++++++++++
 src/changes/changes.xml                            |  4 ++
 3 files changed, 71 insertions(+)

diff --git 
a/commons-rng-core/src/main/java/org/apache/commons/rng/core/source32/JDKRandom.java
 
b/commons-rng-core/src/main/java/org/apache/commons/rng/core/source32/JDKRandom.java
index 3c36c868..8a457139 100644
--- 
a/commons-rng-core/src/main/java/org/apache/commons/rng/core/source32/JDKRandom.java
+++ 
b/commons-rng-core/src/main/java/org/apache/commons/rng/core/source32/JDKRandom.java
@@ -78,6 +78,23 @@ public class JDKRandom extends IntProvider {
             }
             return super.resolveClass(osc);
         }
+
+        /**
+         * {@inheritDoc}
+         *
+         * <p>The only expected class is {@link java.util.Random} which is not 
a proxy
+         * class. The allowlist enforced by {@link 
#resolveClass(ObjectStreamClass)}
+         * must also hold on the proxy class-resolution path so this method
+         * unconditionally rejects the stream.
+         *
+         * @throws IllegalStateException Always: no legitimate stream contains 
a proxy
+         * class descriptor.
+         */
+        @Override
+        protected Class<?> resolveProxyClass(final String[] interfaces) throws 
IOException,
+            ClassNotFoundException {
+            throw new IllegalStateException("Stream contains a proxy class 
descriptor");
+        }
     }
 
     /**
diff --git 
a/commons-rng-core/src/test/java/org/apache/commons/rng/core/source32/JDKRandomTest.java
 
b/commons-rng-core/src/test/java/org/apache/commons/rng/core/source32/JDKRandomTest.java
index fde0913a..72f28dd7 100644
--- 
a/commons-rng-core/src/test/java/org/apache/commons/rng/core/source32/JDKRandomTest.java
+++ 
b/commons-rng-core/src/test/java/org/apache/commons/rng/core/source32/JDKRandomTest.java
@@ -21,6 +21,9 @@ import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
 import java.util.Random;
 
 import org.apache.commons.rng.RandomProviderState;
@@ -58,6 +61,19 @@ class JDKRandomTest {
         }
     }
 
+    /**
+     * An InvocationHandler that is Serializable so a dynamic proxy instance
+     * can be written to an ObjectOutputStream.
+     */
+    static class SerializableInvocationHandler implements InvocationHandler, 
Serializable {
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        public Object invoke(Object proxy, Method method, Object[] args) {
+            return null;
+        }
+    }
+
     @Test
     void testReferenceCode() {
         final long refSeed = -1357111213L;
@@ -125,4 +141,38 @@ class JDKRandomTest {
         final JDKRandom rng = new JDKRandom(13L);
         Assertions.assertThrows(IllegalStateException.class, () -> 
rng.restoreState(dummyState));
     }
+
+    /**
+     * Test the deserialization code rejects a state containing a proxy class 
descriptor.
+     * The java.util.Random allowlist must hold on the proxy class-resolution 
path;
+     * no legitimate state stream contains a proxy class.
+     *
+     * @throws IOException Signals that an I/O exception has occurred.
+     */
+    @Test
+    void testRestoreWithProxyClass() throws IOException  {
+        // Serialize a dynamic proxy instance
+        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
+            
oos.writeObject(Proxy.newProxyInstance(JDKRandomTest.class.getClassLoader(),
+                new Class<?>[] {Runnable.class}, new 
SerializableInvocationHandler()));
+        }
+
+        // Compose the size with the state.
+        // This is what is expected by the JDKRandom class.
+        final byte[] state = bos.toByteArray();
+        final int stateSize = state.length;
+        final byte[] sizeAndState = new byte[4 + stateSize];
+        System.arraycopy(NumberFactory.makeByteArray(stateSize), 0, 
sizeAndState, 0, 4);
+        System.arraycopy(state, 0, sizeAndState, 4, stateSize);
+
+        final RandomProviderDefaultState dummyState = new 
RandomProviderDefaultState(sizeAndState);
+
+        final JDKRandom rng = new JDKRandom(13L);
+        final IllegalStateException ex = 
Assertions.assertThrows(IllegalStateException.class,
+            () -> rng.restoreState(dummyState));
+        // The stream must be rejected on the proxy class-resolution path, not 
by a
+        // downstream failure to resolve a normal class.
+        Assertions.assertTrue(ex.getMessage().contains("proxy"), () -> 
ex.getMessage());
+    }
 }
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index ad886f69..dc18e076 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -56,6 +56,10 @@ If the output is not quite correct, check for invisible 
trailing spaces!
     <release version="1.8" date="TBD" description="
 New features, updates and bug fixes (requires Java 8).
 ">
+      <action dev="aherbert" type="update" due-to="Security scan, Alex 
Herbert">
+        "JDKRandom": Fail fast when restoring from a saved state if the
+        serialized bytes contains a proxy class descriptor.
+      </action>
       <action dev="aherbert" type="fix" due-to="Security scan, Alex Herbert" 
issue="RNG-194">
         Samplers to validate parameters are finite to avoid creating a sampler 
that
         returns NaN, infinite or degenerate-constant samples forever; or 
enters an

Reply via email to