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>