Description
Description
It's possible for StatusData#getFormattedStatus()
to throw an ArrayIndexOutOfBoundsException
when given a message with a non-null, 0-length parameters
array. This happens because there is only a null check on parameters
, but no bounds check.
I discovered this very trivially by using slf4j + log4j with no extra configuration, which by default utilizes DefaultConfiguration
with the new DefaultLayout
which uses StatusData
under the hood. The 0-length parameters array seems to be a result of a call to MutableLogEvent#setMessage
which allocates a new Object array when swapping parameters with another ReusableMessage, though I haven't fully debugged that codepath to confirm if this is the source.
Either way, the lack of a bounds check in getFormattedStatus
seems wrong, and is trivial to cause the exception to be thrown.
Configuration
Version: 2.x branch (e.g. 2.25.0-SNAPSHOT)
Operating system: macOS Sequoia 15.3
JDK: OpenJDK 64-Bit Server VM Corretto-17.0.6.10.1 (build 17.0.6+10-LTS, mixed mode, sharing)
Logs
See the unit test reproducers below. On 2.x, the test that directly calls getFormattedStatus it will fail with:
java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 0
at org.apache.logging.log4j.status.StatusData.getFormattedStatus(StatusData.java:182)
The test that logs via slf4j will not cause a failure, but will log the stacktrace for the ArrayIndexOutOfBoundsException instead of the actual log message:
2025-03-20T14:23:21.756536Z main ERROR An exception occurred processing Appender DefaultConsole-2
java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 0
at org.apache.logging.log4j.status.StatusData.getFormattedStatus(StatusData.java:182)
at org.apache.logging.log4j.core.config.DefaultLayout.toSerializable(DefaultLayout.java:50)
at org.apache.logging.log4j.core.config.DefaultLayout.toByteArray(DefaultLayout.java:55)
at org.apache.logging.log4j.core.config.DefaultLayout.encode(DefaultLayout.java:60)
at org.apache.logging.log4j.core.config.DefaultLayout.encode(DefaultLayout.java:36)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.directEncodeEvent(AbstractOutputStreamAppender.java:227)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.tryAppend(AbstractOutputStreamAppender.java:220)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.append(AbstractOutputStreamAppender.java:211)
at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:160)
at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:133)
at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:124)
at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:88)
at org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:714)
at org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:672)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:648)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:584)
at org.apache.logging.log4j.core.config.DefaultReliabilityStrategy.log(DefaultReliabilityStrategy.java:73)
at org.apache.logging.log4j.core.Logger.log(Logger.java:187)
at org.apache.logging.log4j.spi.AbstractLogger.tryLogMessage(AbstractLogger.java:2970)
at org.apache.logging.log4j.spi.AbstractLogger.logMessageTrackRecursion(AbstractLogger.java:2922)
at org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2904)
at org.apache.logging.log4j.spi.AbstractLogger.logMessage(AbstractLogger.java:2653)
at org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:2393)
at org.apache.logging.slf4j.Log4jLogger.error(Log4jLogger.java:293)
at org.apache.logging.slf4j.Slf4jDefaultConfigurationTest.test_logging_with_default_configuration(Slf4jDefaultConfigurationTest.java:13)
Reproduction
The following JUnit test demonstrates the problem:
public class StatusDataTest {
@Test
void test_getFormattedData_does_not_throw() {
Message message = new Message() {
@Override
public String getFormattedMessage() {
return "formatted";
}
@Override
public Object[] getParameters() {
return Constants.EMPTY_OBJECT_ARRAY;
}
@Override
public Throwable getThrowable() {
return null;
}
};
StatusData statusData = new StatusData(null, Level.ERROR, message, null, null);
// will throw
assertThat(statusData.getFormattedStatus()).contains("formatted");
}
}
It is also trivial to construct a test in log4j-slf4j-impl to trigger this codepath via slf4j:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Slf4jDefaultConfigurationTest {
private static final Logger logger = LoggerFactory.getLogger(Slf4jDefaultConfigurationTest.class);
@Test
void test_logging_with_default_configuration() {
// logs the ArrayIndexOutOfBoundsException, rather than the message; on 2.24.3, logs the message as expected
logger.error("foo");
}
}
Metadata
Metadata
Assignees
Type
Projects
Status