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 7bc068a3faf..5b63adcd824 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.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 static Level toLevel(final String sArg, final Level defaultLevel) { * @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 e58fa6436e2..ab9b497de99 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.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 static void assertStreamEquals( * @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 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 static Object serializeClone(final Object obj) throws IOException, ClassN } 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 6fc46f6f936..a5af542e862 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.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 @@ public final class SerializableMatchers { public static Matcher serializesRoundTrip(final Matcher 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 Matcher serializesRoundTrip( + final Matcher matcher, final Collection allowedExtraClasses) { return new FeatureMatcher(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 static Matcher serializesRoundTrip() { 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 serializesRoundTrip(final Collection 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 707fee87d3e..34600f0c831 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.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 static byte[] serialize(final Serializable... objs) { * @param data byte array representing the serialized object * @return the deserialized object */ - @SuppressWarnings("unchecked") @SuppressFBWarnings("OBJECT_DESERIALIZATION") public static 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 deserialize(final byte[] data, final Collection 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 static T deserialize(final byte[] data) { */ @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 allowedExtraClasses) throws IOException { final ByteArrayInputStream bas = new ByteArrayInputStream(data); - return getObjectInputStream(bas); + return getObjectInputStream(bas, allowedExtraClasses); } /** @@ -97,8 +123,18 @@ public static ObjectInputStream getObjectInputStream(final byte[] data) throws I */ @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 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 f048e320fe0..ca324bf73a2 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.26.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 a867f216a55..36413766c5a 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.26.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 f2a35b2474a..f49bb1d08b2 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 @@ 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 @@ void testSafeAfterGetFormattedMessageIsCalled() { // LOG4J2-763 } @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 832230d53c8..7449acf714e 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 @@ 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 @@ @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) class LocalizedMessageTest { - private 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 cdfb2c9bd26..8acd13b3d71 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 @@ 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 @@ void testGetParameters() { 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 29309b36592..3c3ea2345e4 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.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 @@ void testSafeAfterGetFormattedMessageIsCalled() { // LOG4J2-763 } @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 31c4040251d..fa3a514b374 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.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 int hashCode() { } 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 c3152224d93..86193938225 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.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 @@ private void writeObject(final ObjectOutputStream out) throws IOException { } 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 609bf77f4e8..b767962d2ba 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.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 @@ private void writeObject(final ObjectOutputStream out) throws IOException { } 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 ffd83974b0a..b30b51f647d 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.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 int hashCode() { } 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 f7f1dd1308d..48399021172 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.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 @@ private void writeObject(final ObjectOutputStream out) throws IOException { } 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 d1eba763d1c..7e27bf519a1 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.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 @@ private void writeObject(final ObjectOutputStream out) throws IOException { } 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 88ac55fca8c..2473c01dcc0 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.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 @@ protected Object writeReplace() { } private void readObject(final ObjectInputStream stream) throws InvalidObjectException { + SerializationUtil.assertFiltered(stream); throw new InvalidObjectException("Proxy required"); } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/internal/package-info.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/internal/package-info.java new file mode 100644 index 00000000000..1d176839216 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/internal/package-info.java @@ -0,0 +1,33 @@ +/* + * 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 + * + * http://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. + */ +/** + * Utilities for safely serializing and deserializing Log4j objects. + *

Internal usage only!

+ *

+ * This package is intended only for internal Log4j usage. + * Log4j users should not use this package! + * This package is not subject to any backward compatibility concerns. + *

+ * + * @since 2.26.0 + */ +@Export +@Version("2.26.0") +package org.apache.logging.log4j.util.internal; + +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 71d1b55f5d4..2afb007387c 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.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 @@ private Object writeReplace() throws IOException { } 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 e47fa88049e..471b1cd5bea 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.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 static Log4jLogEvent deserialize(final Serializable event) { } 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 151f4d4bfa5..9a99eae624d 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.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 @@ protected Object writeReplace() { } 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 7e6f426f511..20f7184122a 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.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.logging.log4j.core.util.Throwables; +import org.apache.logging.log4j.util.internal.SerializationUtil; /** *

FastDatePrinter is a fast and thread-safe version of @@ -639,6 +640,7 @@ public String toString() { * @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 5e1f3f4fc59..c03e06790c6 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.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 int hashCode() { @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 2889a31d07d..3fe685cc90b 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.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 String getName() { * 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 d8633a1863b..f16c5975799 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.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 59607e28ed6..a5dd86240a3 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.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 String getName() { * 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 cdea35ada64..1c400038e05 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.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 00000000000..3088f8bf556 --- /dev/null +++ b/src/changelog/.2.x.x/harden_message_deserialization.xml @@ -0,0 +1,12 @@ + + + + + Harden `readObject(ObjectInputStream)` method argument checks in serializable API models + +