This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-io.git
The following commit(s) were added to refs/heads/master by this push: new d0b580a8c ValidatingObjectInputStream does not validate dynamic proxy interfaces d0b580a8c is described below commit d0b580a8c620980674ddd2fe81b472dbed26748f Author: Gary D. Gregory <garydgreg...@gmail.com> AuthorDate: Wed Aug 20 07:52:43 2025 -0400 ValidatingObjectInputStream does not validate dynamic proxy interfaces --- src/changes/changes.xml | 3 +- .../serialization/ValidatingObjectInputStream.java | 20 +++++ .../apache/commons/io/serialization/ProxyTest.java | 88 ++++++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index d3bbf77d6..136dd3e9a 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -45,10 +45,11 @@ The <action> type attribute can be add,update,fix,remove. <title>Apache Commons IO Release Notes</title> </properties> <body> - <release version="2.21.0" date="YYYY-MM-DD" description="Version 2.20.1: Java 8 or later is required."> + <release version="2.21.0" date="YYYY-MM-DD" description="Version 2.21.0: Java 8 or later is required."> <!-- FIX --> <action type="fix" dev="ggregory" due-to="Gary Gregory">When testing on Java 21 and up, enable -XX:+EnableDynamicAgentLoading.</action> <action type="fix" dev="ggregory" due-to="Gary Gregory">When testing on Java 24 and up, don't fail FileUtilsListFilesTest for a different behavior in the JRE.</action> + <action type="fix" dev="ggregory" due-to="Stanislav Fort, Gary Gregory">ValidatingObjectInputStream does not validate dynamic proxy interfaces.</action> <!-- ADD --> <action dev="ggregory" type="add" due-to="strangelookingnerd, Gary Gregory">FileUtils#byteCountToDisplaySize() supports Zettabyte, Yottabyte, Ronnabyte and Quettabyte #763.</action> <action dev="ggregory" type="add" due-to="strangelookingnerd, Gary Gregory">Add org.apache.commons.io.FileUtils.ONE_RB #763.</action> diff --git a/src/main/java/org/apache/commons/io/serialization/ValidatingObjectInputStream.java b/src/main/java/org/apache/commons/io/serialization/ValidatingObjectInputStream.java index c3c06a32b..6141cc59b 100644 --- a/src/main/java/org/apache/commons/io/serialization/ValidatingObjectInputStream.java +++ b/src/main/java/org/apache/commons/io/serialization/ValidatingObjectInputStream.java @@ -462,9 +462,29 @@ public ValidatingObjectInputStream reject(final String... patterns) { return this; } + /** + * Checks that the given object's class name conforms to requirements and if so delegates to the superclass. + * <p> + * The reject list takes precedence over the accept list. + * </p> + */ @Override protected Class<?> resolveClass(final ObjectStreamClass osc) throws IOException, ClassNotFoundException { checkClassName(osc.getName()); return super.resolveClass(osc); } + + /** + * Checks that the given names conform to requirements and if so delegates to the superclass. + * <p> + * The reject list takes precedence over the accept list. + * </p> + */ + @Override + protected Class<?> resolveProxyClass(final String[] interfaces) throws IOException, ClassNotFoundException { + for (final String interfaceName : interfaces) { + checkClassName(interfaceName); + } + return super.resolveProxyClass(interfaces); + } } diff --git a/src/test/java/org/apache/commons/io/serialization/ProxyTest.java b/src/test/java/org/apache/commons/io/serialization/ProxyTest.java new file mode 100644 index 000000000..64b82551e --- /dev/null +++ b/src/test/java/org/apache/commons/io/serialization/ProxyTest.java @@ -0,0 +1,88 @@ +/* + * 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 + * + * https://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.commons.io.serialization; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.io.InvalidClassException; +import java.io.Serializable; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import org.apache.commons.lang3.SerializationUtils; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link ValidatingObjectInputStream}. + */ +class ProxyTest { + + public interface IFoo extends Serializable { + + void foo(); + } + + public static class InvocationHandlerImpl implements InvocationHandler, Serializable { + + @Override + public Object invoke(final Object proxy, final Method method, final Object[] args) { + return "InvocationHandlerImpl.invoke()"; + } + } + + Object newProxy() { + return Proxy.newProxyInstance(ProxyTest.class.getClassLoader(), new Class<?>[] { IFoo.class }, new InvocationHandlerImpl()); + } + + @Test + void testAcceptProxy() throws IOException, ClassNotFoundException { + final Object proxy = newProxy(); + final byte[] serialized = SerializationUtils.serialize((Serializable) proxy); + final Class<IFoo> ifaceClass = IFoo.class; + // @formatter:off + try (ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder() + .setByteArray(serialized) + .accept("*") + .get()) { + // @formatter:on + assertTrue(assertInstanceOf(ifaceClass, vois.readObject()).toString().endsWith("InvocationHandlerImpl.invoke()")); + } + } + + @Test + void testRejectProxy() throws IOException, ClassNotFoundException { + final Object proxy = newProxy(); + final byte[] serialized = SerializationUtils.serialize((Serializable) proxy); + final Class<IFoo> ifaceClass = IFoo.class; + // @formatter:off + try (ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder() + .setByteArray(serialized) + .accept("*") + .reject(ifaceClass) + .get()) { + // @formatter:on + assertThrows(InvalidClassException.class, vois::readObject); + } + } +}