Skip to content

StatusData#getFormattedStatus can throw ArrayIndexOutOfBoundsException #3562

Closed
@bjlaub

Description

@bjlaub

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

No one assigned

    Labels

    apiAffects the public APIbugIncorrect, unexpected, or unintended behavior of existing code

    Type

    No type

    Projects

    Status

    Done

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions