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

vy pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/2.x by this push:
     new 823d53d677 Harden `readObject(ObjectInputStream)` method argument 
checks (#4098)
823d53d677 is described below

commit 823d53d6772f0a1095e590dc6d33ecf1b011edf3
Author: SunWeb3Sec <[email protected]>
AuthorDate: Fri Jun 12 17:31:14 2026 +0800

    Harden `readObject(ObjectInputStream)` method argument checks (#4098)
    
    Signed-off-by: SunWeb3Sec <[email protected]>
    Co-authored-by: Volkan Yazıcı <[email protected]>
---
 .../src/main/java/org/apache/log4j/Level.java      |  2 +
 .../apache/log4j/util/SerializationTestHelper.java | 21 ++++++++++-
 .../logging/log4j/test/SerializableMatchers.java   | 23 ++++++++++-
 .../logging/log4j/test/junit/SerialUtil.java       | 44 ++++++++++++++++++++--
 .../logging/log4j/test/junit/package-info.java     |  2 +-
 .../apache/logging/log4j/test/package-info.java    |  2 +-
 .../log4j/message/FormattedMessageTest.java        | 16 ++------
 .../log4j/message/LocalizedMessageTest.java        |  7 ++--
 .../log4j/message/ObjectArrayMessageTest.java      | 13 +++++++
 .../log4j/message/StringFormattedMessageTest.java  | 16 ++------
 .../logging/log4j/message/FormattedMessage.java    |  2 +
 .../logging/log4j/message/LocalizedMessage.java    |  2 +
 .../log4j/message/MessageFormatMessage.java        |  2 +
 .../logging/log4j/message/ObjectArrayMessage.java  |  2 +
 .../logging/log4j/message/SimpleMessage.java       |  2 +
 .../log4j/message/StringFormattedMessage.java      |  2 +
 .../logging/log4j/message/ThreadDumpMessage.java   |  2 +
 .../logging/log4j/util/internal}/package-info.java | 22 ++++++++++-
 .../log4j/core/async/RingBufferLogEvent.java       |  2 +
 .../logging/log4j/core/impl/Log4jLogEvent.java     |  2 +
 .../logging/log4j/core/impl/MutableLogEvent.java   |  2 +
 .../log4j/core/util/datetime/FastDatePrinter.java  |  2 +
 .../logging/log4j/perf/nogc/OpenHashStringMap.java |  2 +
 .../java/org/apache/logging/slf4j/Log4jLogger.java |  2 +
 .../org/apache/logging/slf4j/SerializeTest.java    |  6 ++-
 .../java/org/apache/logging/slf4j/Log4jLogger.java |  2 +
 .../org/apache/logging/slf4j/SerializeTest.java    |  6 ++-
 .../.2.x.x/harden_message_deserialization.xml      | 12 ++++++
 28 files changed, 176 insertions(+), 44 deletions(-)

diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/Level.java 
b/log4j-1.2-api/src/main/java/org/apache/log4j/Level.java
index 7bc068a3fa..5b63adcd82 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/Level.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/Level.java
@@ -25,6 +25,7 @@ import java.io.ObjectStreamException;
 import java.io.Serializable;
 import org.apache.log4j.helpers.OptionConverter;
 import org.apache.logging.log4j.util.Strings;
+import org.apache.logging.log4j.util.internal.SerializationUtil;
 
 /**
  * Defines the minimum set of levels recognized by the system, that is
@@ -214,6 +215,7 @@ public class Level extends Priority implements Serializable 
{
      * @throws ClassNotFoundException if class not found.
      */
     private void readObject(final ObjectInputStream s) throws IOException, 
ClassNotFoundException {
+        SerializationUtil.assertFiltered(s);
         s.defaultReadObject();
         level = s.readInt();
         syslogEquivalent = s.readInt();
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 e58fa6436e..ab9b497de9 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
@@ -24,9 +24,14 @@ import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.ObjectInputStream;
 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;
 
 /**
  * Utiities for serialization tests.
@@ -103,11 +108,23 @@ public final class SerializationTestHelper {
      * @throws Exception thrown on IO or deserialization exception.
      */
     public static Object deserializeStream(final String witness) throws 
Exception {
-        try (final ObjectInputStream objIs = new ObjectInputStream(new 
FileInputStream(witness))) {
+        try (final ObjectInputStream objIs = newObjectInputStream(new 
FileInputStream(witness))) {
             return objIs.readObject();
         }
     }
 
+    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);
+    }
+
     /**
      * Creates a clone by serializing object and deserializing byte stream.
      *
@@ -123,7 +140,7 @@ public final class SerializationTestHelper {
         }
 
         final ByteArrayInputStream src = new 
ByteArrayInputStream(memOut.toByteArray());
-        final ObjectInputStream objIs = new ObjectInputStream(src);
+        final ObjectInputStream objIs = newObjectInputStream(src);
 
         return objIs.readObject();
     }
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 6fc46f6f93..a5af542e86 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
@@ -20,7 +20,9 @@ import static org.hamcrest.core.IsEqual.equalTo;
 import static org.hamcrest.core.IsInstanceOf.any;
 
 import java.io.Serializable;
-import org.apache.commons.lang3.SerializationUtils;
+import java.util.Collection;
+import java.util.Collections;
+import org.apache.logging.log4j.test.junit.SerialUtil;
 import org.hamcrest.FeatureMatcher;
 import org.hamcrest.Matcher;
 
@@ -32,10 +34,19 @@ import org.hamcrest.Matcher;
 public final class SerializableMatchers {
 
     public static <T extends Serializable> Matcher<T> 
serializesRoundTrip(final Matcher<T> matcher) {
+        return serializesRoundTrip(matcher, Collections.emptySet());
+    }
+
+    /**
+     * Same as {@link #serializesRoundTrip(Matcher)} but extends the default 
deserialization
+     * allow-list on Java 8 (see {@link SerialUtil#deserialize(byte[], 
Collection)}).
+     */
+    public static <T extends Serializable> Matcher<T> serializesRoundTrip(
+            final Matcher<T> matcher, final Collection<String> 
allowedExtraClasses) {
         return new FeatureMatcher<T, T>(matcher, "serializes round trip", 
"serializes round trip") {
             @Override
             protected T featureValueOf(final T actual) {
-                return SerializationUtils.roundtrip(actual);
+                return SerialUtil.deserialize(SerialUtil.serialize(actual), 
allowedExtraClasses);
             }
         };
     }
@@ -52,5 +63,13 @@ public final class SerializableMatchers {
         return serializesRoundTrip(any(Serializable.class));
     }
 
+    /**
+     * Same as {@link #serializesRoundTrip()} but extends the default 
deserialization allow-list on
+     * Java 8 (see {@link SerialUtil#deserialize(byte[], Collection)}).
+     */
+    public static Matcher<? super Serializable> serializesRoundTrip(final 
Collection<String> allowedExtraClasses) {
+        return serializesRoundTrip(any(Serializable.class), 
allowedExtraClasses);
+    }
+
     private SerializableMatchers() {}
 }
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 707fee87d3..34600f0c83 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,8 @@ import java.io.ObjectInputStream;
 import java.io.ObjectOutput;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
 import org.apache.logging.log4j.test.internal.annotation.SuppressFBWarnings;
 import org.apache.logging.log4j.util.Constants;
 import org.apache.logging.log4j.util.FilteredObjectInputStream;
@@ -68,11 +70,25 @@ public class SerialUtil {
      * @param data byte array representing the serialized object
      * @return the deserialized object
      */
-    @SuppressWarnings("unchecked")
     @SuppressFBWarnings("OBJECT_DESERIALIZATION")
     public static <T> T deserialize(final byte[] data) {
+        return deserialize(data, Collections.emptySet());
+    }
+
+    /**
+     * 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).
+     * @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
+     * @return the deserialized object
+     */
+    @SuppressWarnings("unchecked")
+    @SuppressFBWarnings("OBJECT_DESERIALIZATION")
+    public static <T> T deserialize(final byte[] data, final 
Collection<String> allowedExtraClasses) {
         try {
-            final ObjectInputStream ois = getObjectInputStream(data);
+            final ObjectInputStream ois = getObjectInputStream(data, 
allowedExtraClasses);
             return (T) ois.readObject();
         } catch (final Exception ex) {
             throw new IllegalStateException("Could not deserialize", ex);
@@ -86,8 +102,18 @@ public class SerialUtil {
      */
     @SuppressFBWarnings("OBJECT_DESERIALIZATION")
     public static ObjectInputStream getObjectInputStream(final byte[] data) 
throws IOException {
+        return getObjectInputStream(data, Collections.emptySet());
+    }
+
+    /**
+     * Creates an {@link ObjectInputStream} adapted to the current Java 
version, extended with the
+     * supplied allow-list on Java 8.
+     */
+    @SuppressFBWarnings("OBJECT_DESERIALIZATION")
+    public static ObjectInputStream getObjectInputStream(
+            final byte[] data, final Collection<String> allowedExtraClasses) 
throws IOException {
         final ByteArrayInputStream bas = new ByteArrayInputStream(data);
-        return getObjectInputStream(bas);
+        return getObjectInputStream(bas, allowedExtraClasses);
     }
 
     /**
@@ -97,8 +123,18 @@ public class SerialUtil {
      */
     @SuppressFBWarnings("OBJECT_DESERIALIZATION")
     public static ObjectInputStream getObjectInputStream(final InputStream 
stream) throws IOException {
+        return getObjectInputStream(stream, Collections.emptySet());
+    }
+
+    /**
+     * Creates an {@link ObjectInputStream} adapted to the current Java 
version, extended with the
+     * supplied allow-list on Java 8.
+     */
+    @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)
+                ? new FilteredObjectInputStream(stream, allowedExtraClasses)
                 : new ObjectInputStream(stream);
     }
 }
diff --git 
a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/package-info.java
 
b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/package-info.java
index f048e320fe..b6456b64d5 100644
--- 
a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/package-info.java
+++ 
b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/package-info.java
@@ -15,7 +15,7 @@
  * limitations under the license.
  */
 @Export
-@Version("2.25.3")
+@Version("2.27.0")
 package org.apache.logging.log4j.test.junit;
 
 import org.osgi.annotation.bundle.Export;
diff --git 
a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/package-info.java 
b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/package-info.java
index a867f216a5..aebee2d75b 100644
--- 
a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/package-info.java
+++ 
b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/package-info.java
@@ -15,7 +15,7 @@
  * limitations under the license.
  */
 @Export
-@Version("2.25.3")
+@Version("2.27.0")
 package org.apache.logging.log4j.test;
 
 import org.osgi.annotation.bundle.Export;
diff --git 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/FormattedMessageTest.java
 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/FormattedMessageTest.java
index f2a35b2474..f49bb1d08b 100644
--- 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/FormattedMessageTest.java
+++ 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/FormattedMessageTest.java
@@ -19,13 +19,9 @@ package org.apache.logging.log4j.message;
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
 import java.util.Locale;
 import org.apache.logging.log4j.test.junit.Mutable;
+import org.apache.logging.log4j.test.junit.SerialUtil;
 import org.apache.logging.log4j.util.Constants;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.parallel.ResourceAccessMode;
@@ -158,15 +154,9 @@ class FormattedMessageTest {
     }
 
     @Test
-    void testSerialization() throws IOException, ClassNotFoundException {
+    void testSerialization() {
         final FormattedMessage expected = new FormattedMessage("Msg", "a", 
"b", "c");
-        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        try (final ObjectOutputStream out = new ObjectOutputStream(baos)) {
-            out.writeObject(expected);
-        }
-        final ByteArrayInputStream bais = new 
ByteArrayInputStream(baos.toByteArray());
-        final ObjectInputStream in = new ObjectInputStream(bais);
-        final FormattedMessage actual = (FormattedMessage) in.readObject();
+        final FormattedMessage actual = 
SerialUtil.deserialize(SerialUtil.serialize(expected));
         assertEquals(expected, actual);
         assertEquals(expected.getFormat(), actual.getFormat());
         assertEquals(expected.getFormattedMessage(), 
actual.getFormattedMessage());
diff --git 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/LocalizedMessageTest.java
 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/LocalizedMessageTest.java
index 832230d53c..7449acf714 100644
--- 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/LocalizedMessageTest.java
+++ 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/LocalizedMessageTest.java
@@ -18,10 +18,9 @@ package org.apache.logging.log4j.message;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
-import java.io.Serializable;
 import java.util.Locale;
-import org.apache.commons.lang3.SerializationUtils;
 import org.apache.logging.log4j.test.junit.Mutable;
+import org.apache.logging.log4j.test.junit.SerialUtil;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.parallel.ResourceAccessMode;
 import org.junit.jupiter.api.parallel.ResourceLock;
@@ -33,8 +32,8 @@ import org.junit.jupiter.api.parallel.Resources;
 @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
 class LocalizedMessageTest {
 
-    private <T extends Serializable> T roundtrip(final T msg) {
-        return SerializationUtils.roundtrip(msg);
+    private LocalizedMessage roundtrip(final LocalizedMessage msg) {
+        return SerialUtil.deserialize(SerialUtil.serialize(msg));
     }
 
     @Test
diff --git 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectArrayMessageTest.java
 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectArrayMessageTest.java
index cdfb2c9bd2..8acd13b3d7 100644
--- 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectArrayMessageTest.java
+++ 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectArrayMessageTest.java
@@ -19,6 +19,7 @@ package org.apache.logging.log4j.message;
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
 
+import org.apache.logging.log4j.test.junit.SerialUtil;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -38,4 +39,16 @@ class ObjectArrayMessageTest {
     void testGetThrowable() {
         assertNull(OBJECT_ARRAY_MESSAGE.getThrowable());
     }
+
+    /**
+     * Round-trips through a filtered stream (see {@link 
SerialUtil#getObjectInputStream})
+     * to verify that {@code readObject}'s new {@code 
SerializationUtil.assertFiltered}
+     * check accepts streams that carry a filter.
+     */
+    @Test
+    void testSerializableRoundTripThroughFilteredStream() {
+        final ObjectArrayMessage original = new ObjectArrayMessage("A", "B", 
"C");
+        final ObjectArrayMessage restored = 
SerialUtil.deserialize(SerialUtil.serialize(original));
+        assertArrayEquals(original.getParameters(), restored.getParameters());
+    }
 }
diff --git 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/StringFormattedMessageTest.java
 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/StringFormattedMessageTest.java
index 29309b3659..3c3ea2345e 100644
--- 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/StringFormattedMessageTest.java
+++ 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/StringFormattedMessageTest.java
@@ -20,13 +20,9 @@ import static 
org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
 import java.util.Locale;
 import org.apache.logging.log4j.test.junit.Mutable;
+import org.apache.logging.log4j.test.junit.SerialUtil;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.parallel.ResourceAccessMode;
 import org.junit.jupiter.api.parallel.ResourceLock;
@@ -115,15 +111,9 @@ class StringFormattedMessageTest {
     }
 
     @Test
-    void testSerialization() throws IOException, ClassNotFoundException {
+    void testSerialization() {
         final StringFormattedMessage expected = new 
StringFormattedMessage("Msg", "a", "b", "c");
-        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        try (final ObjectOutputStream out = new ObjectOutputStream(baos)) {
-            out.writeObject(expected);
-        }
-        final ByteArrayInputStream bais = new 
ByteArrayInputStream(baos.toByteArray());
-        final ObjectInputStream in = new ObjectInputStream(bais);
-        final StringFormattedMessage actual = (StringFormattedMessage) 
in.readObject();
+        final StringFormattedMessage actual = 
SerialUtil.deserialize(SerialUtil.serialize(expected));
         assertEquals(expected, actual);
         assertEquals(expected.getFormat(), actual.getFormat());
         assertEquals(expected.getFormattedMessage(), 
actual.getFormattedMessage());
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/FormattedMessage.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/FormattedMessage.java
index 31c4040251..fa3a514b37 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/FormattedMessage.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/FormattedMessage.java
@@ -23,6 +23,7 @@ import java.text.Format;
 import java.text.MessageFormat;
 import java.util.Arrays;
 import java.util.Locale;
+import org.apache.logging.log4j.util.internal.SerializationUtil;
 
 /**
  * Handles messages that contain a format String. Dynamically determines if 
the format conforms to
@@ -243,6 +244,7 @@ public class FormattedMessage implements Message {
     }
 
     private void readObject(final ObjectInputStream in) throws IOException, 
ClassNotFoundException {
+        SerializationUtil.assertFiltered(in);
         in.defaultReadObject();
         formattedMessage = in.readUTF();
         messagePattern = in.readUTF();
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessage.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessage.java
index c3152224d9..8619393822 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessage.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessage.java
@@ -23,6 +23,7 @@ import java.util.Locale;
 import java.util.MissingResourceException;
 import java.util.ResourceBundle;
 import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.internal.SerializationUtil;
 
 /**
  * Provides some level of compatibility with Log4j 1.x and convenience but is 
not the recommended way to Localize
@@ -283,6 +284,7 @@ public class LocalizedMessage implements Message, 
LoggerNameAwareMessage {
     }
 
     private void readObject(final ObjectInputStream in) throws IOException, 
ClassNotFoundException {
+        SerializationUtil.assertFiltered(in);
         in.defaultReadObject();
         formattedMessage = in.readUTF();
         key = in.readUTF();
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFormatMessage.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFormatMessage.java
index 609bf77f4e..b767962d2b 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFormatMessage.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFormatMessage.java
@@ -25,6 +25,7 @@ import java.util.IllegalFormatException;
 import java.util.Locale;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.internal.SerializationUtil;
 
 /**
  * Handles messages that consist of a format string conforming to 
java.text.MessageFormat.
@@ -164,6 +165,7 @@ public class MessageFormatMessage implements Message {
     }
 
     private void readObject(final ObjectInputStream in) throws IOException {
+        SerializationUtil.assertFiltered(in);
         parameters = null;
         throwable = null;
         formattedMessage = in.readUTF();
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ObjectArrayMessage.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/ObjectArrayMessage.java
index ffd83974b0..b30b51f647 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ObjectArrayMessage.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/ObjectArrayMessage.java
@@ -21,6 +21,7 @@ import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.util.Arrays;
 import org.apache.logging.log4j.util.Constants;
+import org.apache.logging.log4j.util.internal.SerializationUtil;
 
 /**
  * Handles messages that contain an Object[].
@@ -117,6 +118,7 @@ public final class ObjectArrayMessage implements Message {
     }
 
     private void readObject(final ObjectInputStream in) throws IOException, 
ClassNotFoundException {
+        SerializationUtil.assertFiltered(in);
         in.defaultReadObject();
         array = (Object[]) in.readObject();
     }
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/SimpleMessage.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/SimpleMessage.java
index f7f1dd1308..4839902117 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/SimpleMessage.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/SimpleMessage.java
@@ -21,6 +21,7 @@ import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.util.Objects;
 import org.apache.logging.log4j.util.StringBuilderFormattable;
+import org.apache.logging.log4j.util.internal.SerializationUtil;
 
 /**
  * The simplest possible implementation of Message. It just returns the String 
given as the constructor argument.
@@ -152,6 +153,7 @@ public class SimpleMessage implements Message, 
StringBuilderFormattable, CharSeq
     }
 
     private void readObject(final ObjectInputStream in) throws IOException, 
ClassNotFoundException {
+        SerializationUtil.assertFiltered(in);
         in.defaultReadObject();
         charSequence = message;
     }
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormattedMessage.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormattedMessage.java
index d1eba763d1..7e27bf519a 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormattedMessage.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormattedMessage.java
@@ -24,6 +24,7 @@ import java.util.IllegalFormatException;
 import java.util.Locale;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.internal.SerializationUtil;
 
 /**
  * Handles messages that consist of a format string conforming to {@link 
java.util.Formatter}.
@@ -172,6 +173,7 @@ public class StringFormattedMessage implements Message {
     }
 
     private void readObject(final ObjectInputStream in) throws IOException, 
ClassNotFoundException {
+        SerializationUtil.assertFiltered(in);
         in.defaultReadObject();
         formattedMessage = in.readUTF();
         messagePattern = in.readUTF();
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ThreadDumpMessage.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/ThreadDumpMessage.java
index 88ac55fca8..2473c01dcc 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ThreadDumpMessage.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/ThreadDumpMessage.java
@@ -33,6 +33,7 @@ import org.apache.logging.log4j.util.Lazy;
 import org.apache.logging.log4j.util.ServiceLoaderUtil;
 import org.apache.logging.log4j.util.StringBuilderFormattable;
 import org.apache.logging.log4j.util.Strings;
+import org.apache.logging.log4j.util.internal.SerializationUtil;
 
 /**
  * Captures information about all running Threads.
@@ -131,6 +132,7 @@ public class ThreadDumpMessage implements Message, 
StringBuilderFormattable {
     }
 
     private void readObject(final ObjectInputStream stream) throws 
InvalidObjectException {
+        SerializationUtil.assertFiltered(stream);
         throw new InvalidObjectException("Proxy required");
     }
 
diff --git 
a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/package-info.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/util/internal/package-info.java
similarity index 60%
copy from 
log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/package-info.java
copy to 
log4j-api/src/main/java/org/apache/logging/log4j/util/internal/package-info.java
index f048e320fe..423ce7faa4 100644
--- 
a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/package-info.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/util/internal/package-info.java
@@ -14,9 +14,27 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
+/**
+ * Utilities for safely serializing and deserializing Log4j objects.
+ * <h2>Internal usage only!</h2>
+ * <p>
+ * This package is intended only for internal Log4j usage.
+ * <b>Log4j users should not use this package!</b>
+ * This package is not subject to any backward compatibility concerns.
+ * </p>
+ *
+ * @since 2.27.0
+ */
 @Export
-@Version("2.25.3")
-package org.apache.logging.log4j.test.junit;
+@ExportTo({
+    "org.apache.logging.log4j.core",
+    "org.apache.log4j",
+    "org.apache.logging.log4j.slf4j.impl",
+    "org.apache.logging.log4j.slf4j2.impl"
+})
+@Version("2.27.0")
+package org.apache.logging.log4j.util.internal;
 
+import aQute.bnd.annotation.jpms.ExportTo;
 import org.osgi.annotation.bundle.Export;
 import org.osgi.annotation.versioning.Version;
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java
index 71d1b55f5d..2afb007387 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java
@@ -45,6 +45,7 @@ import org.apache.logging.log4j.util.ReadOnlyStringMap;
 import org.apache.logging.log4j.util.StringBuilders;
 import org.apache.logging.log4j.util.StringMap;
 import org.apache.logging.log4j.util.Strings;
+import org.apache.logging.log4j.util.internal.SerializationUtil;
 
 /**
  * When the Disruptor is started, the RingBuffer is populated with event 
objects. These objects are then re-used during
@@ -450,6 +451,7 @@ public class RingBufferLogEvent implements LogEvent, 
ReusableMessage, CharSequen
     }
 
     private void readObject(final ObjectInputStream stream) throws 
InvalidObjectException {
+        SerializationUtil.assertFiltered(stream);
         throw new InvalidObjectException("Proxy required");
     }
 
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java
index e47fa88049..471b1cd5be 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java
@@ -48,6 +48,7 @@ import org.apache.logging.log4j.util.ReadOnlyStringMap;
 import org.apache.logging.log4j.util.StackLocatorUtil;
 import org.apache.logging.log4j.util.StringMap;
 import org.apache.logging.log4j.util.Strings;
+import org.apache.logging.log4j.util.internal.SerializationUtil;
 
 /**
  * Implementation of a LogEvent.
@@ -993,6 +994,7 @@ public class Log4jLogEvent implements LogEvent {
     }
 
     private void readObject(final ObjectInputStream stream) throws 
InvalidObjectException {
+        SerializationUtil.assertFiltered(stream);
         throw new InvalidObjectException("Proxy required");
     }
 
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
index 151f4d4bfa..9a99eae624 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
@@ -41,6 +41,7 @@ import org.apache.logging.log4j.util.StackLocatorUtil;
 import org.apache.logging.log4j.util.StringBuilders;
 import org.apache.logging.log4j.util.StringMap;
 import org.apache.logging.log4j.util.Strings;
+import org.apache.logging.log4j.util.internal.SerializationUtil;
 
 /**
  * Mutable implementation of the {@code LogEvent} interface.
@@ -493,6 +494,7 @@ public class MutableLogEvent implements LogEvent, 
ReusableMessage, ParameterVisi
     }
 
     private void readObject(final ObjectInputStream stream) throws 
InvalidObjectException {
+        SerializationUtil.assertFiltered(stream);
         throw new InvalidObjectException("Proxy required");
     }
 
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDatePrinter.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDatePrinter.java
index 7e6f426f51..20f7184122 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDatePrinter.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDatePrinter.java
@@ -31,6 +31,7 @@ import java.util.TimeZone;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import org.apache.logging.log4j.core.util.Throwables;
+import org.apache.logging.log4j.util.internal.SerializationUtil;
 
 /**
  * <p>FastDatePrinter is a fast and thread-safe version of
@@ -639,6 +640,7 @@ public class FastDatePrinter implements DatePrinter, 
Serializable {
      * @throws ClassNotFoundException if a class cannot be found.
      */
     private void readObject(final ObjectInputStream in) throws IOException, 
ClassNotFoundException {
+        SerializationUtil.assertFiltered(in);
         in.defaultReadObject();
         init();
     }
diff --git 
a/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/nogc/OpenHashStringMap.java
 
b/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/nogc/OpenHashStringMap.java
index 5e1f3f4fc5..c03e06790c 100644
--- 
a/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/nogc/OpenHashStringMap.java
+++ 
b/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/nogc/OpenHashStringMap.java
@@ -29,6 +29,7 @@ import org.apache.logging.log4j.util.BiConsumer;
 import org.apache.logging.log4j.util.ReadOnlyStringMap;
 import org.apache.logging.log4j.util.StringMap;
 import org.apache.logging.log4j.util.TriConsumer;
+import org.apache.logging.log4j.util.internal.SerializationUtil;
 
 /**
  * Open hash map-based implementation of the {@code ReadOnlyStringMap} 
interface.
@@ -690,6 +691,7 @@ public class OpenHashStringMap<K, V> implements StringMap, 
ThreadContextMap {
 
     @SuppressWarnings("unchecked")
     private void readObject(final ObjectInputStream s) throws IOException, 
ClassNotFoundException {
+        SerializationUtil.assertFiltered(s);
         s.defaultReadObject();
         arraySize = HashCommon.arraySize(size, loadFactor);
         maxFill = HashCommon.maxFill(arraySize, loadFactor);
diff --git 
a/log4j-slf4j-impl/src/main/java/org/apache/logging/slf4j/Log4jLogger.java 
b/log4j-slf4j-impl/src/main/java/org/apache/logging/slf4j/Log4jLogger.java
index 2889a31d07..3fe685cc90 100644
--- a/log4j-slf4j-impl/src/main/java/org/apache/logging/slf4j/Log4jLogger.java
+++ b/log4j-slf4j-impl/src/main/java/org/apache/logging/slf4j/Log4jLogger.java
@@ -26,6 +26,7 @@ import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.ParameterizedMessage;
 import org.apache.logging.log4j.message.SimpleMessage;
 import org.apache.logging.log4j.spi.ExtendedLogger;
+import org.apache.logging.log4j.util.internal.SerializationUtil;
 import org.slf4j.Marker;
 import org.slf4j.spi.LocationAwareLogger;
 
@@ -384,6 +385,7 @@ public class Log4jLogger implements LocationAwareLogger, 
Serializable {
      * the de-serialized object.
      */
     private void readObject(final ObjectInputStream aInputStream) throws 
ClassNotFoundException, IOException {
+        SerializationUtil.assertFiltered(aInputStream);
         // always perform the default de-serialization first
         aInputStream.defaultReadObject();
         logger = LogManager.getContext().getLogger(name);
diff --git 
a/log4j-slf4j-impl/src/test/java/org/apache/logging/slf4j/SerializeTest.java 
b/log4j-slf4j-impl/src/test/java/org/apache/logging/slf4j/SerializeTest.java
index d8633a1863..f16c597579 100644
--- a/log4j-slf4j-impl/src/test/java/org/apache/logging/slf4j/SerializeTest.java
+++ b/log4j-slf4j-impl/src/test/java/org/apache/logging/slf4j/SerializeTest.java
@@ -20,6 +20,7 @@ import static 
org.apache.logging.log4j.test.SerializableMatchers.serializesRound
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import java.io.Serializable;
+import java.util.Collections;
 import org.apache.logging.log4j.core.test.junit.LoggerContextSource;
 import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
@@ -35,6 +36,9 @@ class SerializeTest {
 
     @Test
     void testLogger() {
-        assertThat((Serializable) logger, serializesRoundTrip());
+        // `Log4jLogger` lives outside the `org.apache.logging.log4j.` 
namespace covered by
+        // FilteredObjectInputStream's default allow-list, so we have to 
enumerate it explicitly
+        // for the Java 8 surefire run that goes through 
FilteredObjectInputStream.
+        assertThat((Serializable) logger, 
serializesRoundTrip(Collections.singleton(Log4jLogger.class.getName())));
     }
 }
diff --git 
a/log4j-slf4j2-impl/src/main/java/org/apache/logging/slf4j/Log4jLogger.java 
b/log4j-slf4j2-impl/src/main/java/org/apache/logging/slf4j/Log4jLogger.java
index 59607e28ed..a5dd86240a 100644
--- a/log4j-slf4j2-impl/src/main/java/org/apache/logging/slf4j/Log4jLogger.java
+++ b/log4j-slf4j2-impl/src/main/java/org/apache/logging/slf4j/Log4jLogger.java
@@ -26,6 +26,7 @@ import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.ParameterizedMessage;
 import org.apache.logging.log4j.message.SimpleMessage;
 import org.apache.logging.log4j.spi.ExtendedLogger;
+import org.apache.logging.log4j.util.internal.SerializationUtil;
 import org.slf4j.Marker;
 import org.slf4j.spi.LocationAwareLogger;
 import org.slf4j.spi.LoggingEventBuilder;
@@ -384,6 +385,7 @@ public class Log4jLogger implements LocationAwareLogger, 
Serializable {
      * the de-serialized object.
      */
     private void readObject(final ObjectInputStream aInputStream) throws 
ClassNotFoundException, IOException {
+        SerializationUtil.assertFiltered(aInputStream);
         // always perform the default de-serialization first
         aInputStream.defaultReadObject();
         logger = LogManager.getContext().getLogger(name);
diff --git 
a/log4j-slf4j2-impl/src/test/java/org/apache/logging/slf4j/SerializeTest.java 
b/log4j-slf4j2-impl/src/test/java/org/apache/logging/slf4j/SerializeTest.java
index cdea35ada6..1c400038e0 100644
--- 
a/log4j-slf4j2-impl/src/test/java/org/apache/logging/slf4j/SerializeTest.java
+++ 
b/log4j-slf4j2-impl/src/test/java/org/apache/logging/slf4j/SerializeTest.java
@@ -20,6 +20,7 @@ import static 
org.apache.logging.log4j.test.SerializableMatchers.serializesRound
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import java.io.Serializable;
+import java.util.Collections;
 import org.apache.logging.log4j.core.test.junit.LoggerContextSource;
 import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
@@ -35,6 +36,9 @@ class SerializeTest {
 
     @Test
     void testLogger() {
-        assertThat((Serializable) logger, serializesRoundTrip());
+        // `Log4jLogger` lives outside the `org.apache.logging.log4j.` 
namespace covered by
+        // FilteredObjectInputStream's default allow-list, so we have to 
enumerate it explicitly
+        // for the Java 8 surefire run that goes through 
FilteredObjectInputStream.
+        assertThat((Serializable) logger, 
serializesRoundTrip(Collections.singleton(Log4jLogger.class.getName())));
     }
 }
diff --git a/src/changelog/.2.x.x/harden_message_deserialization.xml 
b/src/changelog/.2.x.x/harden_message_deserialization.xml
new file mode 100644
index 0000000000..3088f8bf55
--- /dev/null
+++ b/src/changelog/.2.x.x/harden_message_deserialization.xml
@@ -0,0 +1,12 @@
+<?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">
+  <issue id="4098" link="https://github.com/apache/logging-log4j2/pull/4098"/>
+  <description format="asciidoc">
+    Harden `readObject(ObjectInputStream)` method argument checks in 
serializable API models
+  </description>
+</entry>


Reply via email to