This is an automated email from the ASF dual-hosted git repository. ppkarwasz pushed a commit to branch feat/2.x/serial-util-java9-filter in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit 513e55a18123f63e468dcfa2522039b129e66b20 Author: Piotr P. Karwasz <[email protected]> AuthorDate: Wed Aug 26 23:58:58 2026 +0200 Apply the deserialization filter in test helpers on Java 9+ The `SerialUtil` test helper returned a plain, unfiltered `ObjectInputStream` on Java 9 and later, so the deserialization allowlist was only ever exercised by tests running on a Java 8 toolchain. It now installs `DefaultObjectInputFilter` (reflectively, since the class must still compile and run on Java 8), combined with an `ObjectInputFilter.Config.createFilter` delegate built from the caller-supplied extra allowed classes, reproducing the `FilteredObjectInputStream` semantics on modern JDKs. When `DefaultObjectInputFilter` is absent (it only exists in the packaged multi-release JAR), the helper falls back to `FilteredObjectInputStream` on any Java version. `SerializationTestHelper` in `log4j-1.2-api` now delegates to `SerialUtil`, so its `org.apache.log4j.*` extras are honored on all Java versions as well. Part of the hardening series from #4168. Assisted-By: Claude Fable 5 <[email protected]> --- .../apache/log4j/util/SerializationTestHelper.java | 18 +++---- .../logging/log4j/test/SerializableMatchers.java | 6 +-- .../logging/log4j/test/junit/SerialUtil.java | 61 +++++++++++++++++----- .../enforce_test_deserialization_filter_java9.xml | 11 ++++ 4 files changed, 70 insertions(+), 26 deletions(-) diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/util/SerializationTestHelper.java b/log4j-1.2-api/src/test/java/org/apache/log4j/util/SerializationTestHelper.java index ab9b497de9..ba97511126 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/util/SerializationTestHelper.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/util/SerializationTestHelper.java @@ -30,8 +30,7 @@ import java.io.ObjectOutputStream; import java.util.Arrays; import java.util.Collection; import org.apache.commons.io.FileUtils; -import org.apache.logging.log4j.util.Constants; -import org.apache.logging.log4j.util.FilteredObjectInputStream; +import org.apache.logging.log4j.test.junit.SerialUtil; /** * Utiities for serialization tests. @@ -114,15 +113,12 @@ public final class SerializationTestHelper { } private static ObjectInputStream newObjectInputStream(final InputStream in) throws IOException { - if (Constants.JAVA_MAJOR_VERSION == 8) { - // FilteredObjectInputStream's default allow-list covers `org.apache.logging.log4j.` but - // not the `org.apache.log4j.` 1.2-compatibility namespace, so we have to enumerate the - // 1.2 classes that the tests in this module deserialize on Java 8. - final Collection<String> allowedLog4j12Classes = - Arrays.asList("org.apache.log4j.Level", "org.apache.log4j.LevelTest$CustomLevel"); - return new FilteredObjectInputStream(in, allowedLog4j12Classes); - } - return new ObjectInputStream(in); + // The default allow-list covers `org.apache.logging.log4j.` but + // not the `org.apache.log4j.` 1.2-compatibility namespace, so we have to enumerate the + // 1.2 classes that the tests in this module deserialize. + final Collection<String> allowedLog4j12Classes = + Arrays.asList("org.apache.log4j.Level", "org.apache.log4j.LevelTest$CustomLevel"); + return SerialUtil.getObjectInputStream(in, allowedLog4j12Classes); } /** diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/SerializableMatchers.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/SerializableMatchers.java index a5af542e86..2aff5fefd3 100644 --- a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/SerializableMatchers.java +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/SerializableMatchers.java @@ -39,7 +39,7 @@ public final class SerializableMatchers { /** * Same as {@link #serializesRoundTrip(Matcher)} but extends the default deserialization - * allow-list on Java 8 (see {@link SerialUtil#deserialize(byte[], Collection)}). + * allowlist (see {@link SerialUtil#deserialize(byte[], Collection)}). */ public static <T extends Serializable> Matcher<T> serializesRoundTrip( final Matcher<T> matcher, final Collection<String> allowedExtraClasses) { @@ -64,8 +64,8 @@ public final class SerializableMatchers { } /** - * Same as {@link #serializesRoundTrip()} but extends the default deserialization allow-list on - * Java 8 (see {@link SerialUtil#deserialize(byte[], Collection)}). + * Same as {@link #serializesRoundTrip()} but extends the default deserialization allow-list + * (see {@link SerialUtil#deserialize(byte[], Collection)}). */ public static Matcher<? super Serializable> serializesRoundTrip(final Collection<String> allowedExtraClasses) { return serializesRoundTrip(any(Serializable.class), allowedExtraClasses); diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/SerialUtil.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/SerialUtil.java index 34600f0c83..89e783928d 100644 --- a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/SerialUtil.java +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/SerialUtil.java @@ -24,6 +24,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; import org.apache.logging.log4j.test.internal.annotation.SuppressFBWarnings; @@ -35,6 +36,34 @@ import org.apache.logging.log4j.util.FilteredObjectInputStream; */ public class SerialUtil { + // On Java 9+ streams are filtered with `DefaultObjectInputFilter`, which must be accessed reflectively. + private static final Method createFilter; + private static final Method newDefaultObjectInputFilter; + private static final Method setObjectInputFilter; + + static { + Method createFilterMethod = null; + Method newInstanceMethod = null; + Method setFilterMethod = null; + if (Constants.JAVA_MAJOR_VERSION != 8) { + try { + final Class<?> filterClass = Class.forName("java.io.ObjectInputFilter"); + createFilterMethod = + Class.forName("java.io.ObjectInputFilter$Config").getMethod("createFilter", String.class); + newInstanceMethod = Class.forName("org.apache.logging.log4j.util.internal.DefaultObjectInputFilter") + .getMethod("newInstance", filterClass); + setFilterMethod = ObjectInputStream.class.getMethod("setObjectInputFilter", filterClass); + } catch (final ReflectiveOperationException e) { + createFilterMethod = null; + newInstanceMethod = null; + // setFilterMethod is already null + } + } + createFilter = createFilterMethod; + newDefaultObjectInputFilter = newInstanceMethod; + setObjectInputFilter = setFilterMethod; + } + private SerialUtil() {} /** @@ -76,12 +105,10 @@ public class SerialUtil { } /** - * Deserialize an object from the specified byte array using a {@link FilteredObjectInputStream} - * extended with the supplied allow-list (Java 8 only — Java 9+ uses the JVM's serialization - * filter, so the allow-list is ignored). + * Deserialize an object from the specified byte array using a stream that applies Log4j's + * deserialization allow-list, extended with the supplied extra classes. * @param data byte array representing the serialized object - * @param allowedExtraClasses fully-qualified class names to add to {@link - * FilteredObjectInputStream}'s default allow-list on Java 8 + * @param allowedExtraClasses fully-qualified class names to add to the default allow-list * @return the deserialized object */ @SuppressWarnings("unchecked") @@ -106,8 +133,8 @@ public class SerialUtil { } /** - * Creates an {@link ObjectInputStream} adapted to the current Java version, extended with the - * supplied allow-list on Java 8. + * Creates an {@link ObjectInputStream} adapted to the current Java version, applying Log4j's + * deserialization allow-list extended with the supplied extra classes. */ @SuppressFBWarnings("OBJECT_DESERIALIZATION") public static ObjectInputStream getObjectInputStream( @@ -127,14 +154,24 @@ public class SerialUtil { } /** - * Creates an {@link ObjectInputStream} adapted to the current Java version, extended with the - * supplied allow-list on Java 8. + * Creates an {@link ObjectInputStream} adapted to the current Java version, applying Log4j's + * deserialization allowlist extended with the supplied extra classes. */ @SuppressFBWarnings("OBJECT_DESERIALIZATION") public static ObjectInputStream getObjectInputStream( final InputStream stream, final Collection<String> allowedExtraClasses) throws IOException { - return Constants.JAVA_MAJOR_VERSION == 8 - ? new FilteredObjectInputStream(stream, allowedExtraClasses) - : new ObjectInputStream(stream); + if (Constants.JAVA_MAJOR_VERSION == 8 || newDefaultObjectInputFilter == null) { + return new FilteredObjectInputStream(stream, allowedExtraClasses); + } + final ObjectInputStream ois = new ObjectInputStream(stream); + try { + final Object extraClassesFilter = allowedExtraClasses.isEmpty() + ? null + : createFilter.invoke(null, String.join(";", allowedExtraClasses)); + setObjectInputFilter.invoke(ois, newDefaultObjectInputFilter.invoke(null, extraClassesFilter)); + } catch (final ReflectiveOperationException e) { + throw new IllegalStateException("Unable to install the deserialization filter", e); + } + return ois; } } diff --git a/src/changelog/.2.x.x/enforce_test_deserialization_filter_java9.xml b/src/changelog/.2.x.x/enforce_test_deserialization_filter_java9.xml new file mode 100644 index 0000000000..6a4ca0505f --- /dev/null +++ b/src/changelog/.2.x.x/enforce_test_deserialization_filter_java9.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<entry xmlns="https://logging.apache.org/xml/ns" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation=" + https://logging.apache.org/xml/ns + https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" + type="changed"> + <description format="asciidoc"> + The `SerialUtil` test helper now applies the deserialization allowlist also on Java 9 and later. + </description> +</entry>
