From d85d12f6ee2caab43cbfd311fa9176aa359cf61c Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Sat, 25 Jul 2026 07:32:41 -0400 Subject: [PATCH 1/2] fix: null-check AppenderDynamicMBean setLayout instantiation OptionConverter.instantiateByClassName returns null when the class is missing or not a Layout. setLayout immediately called appender.setLayout and could NPE on invalid JMX setLayout class names. Log an error and skip attach instead. Sibling of the LoggerDynamicMBean.addAppender guard (#4185). Signed-off-by: Sebastien Tardif --- .../log4j/jmx/AppenderDynamicMBean.java | 5 ++ .../log4j/jmx/AppenderDynamicMBeanTest.java | 57 +++++++++++++++++++ ...fix_appender_dynamic_mbean_null_layout.xml | 8 +++ 3 files changed, 70 insertions(+) create mode 100644 log4j-1.2-api/src/test/java/org/apache/log4j/jmx/AppenderDynamicMBeanTest.java create mode 100644 src/changelog/.2.x.x/fix_appender_dynamic_mbean_null_layout.xml diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/AppenderDynamicMBean.java b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/AppenderDynamicMBean.java index 2ee683240ad..ff774512d92 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/AppenderDynamicMBean.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/AppenderDynamicMBean.java @@ -187,6 +187,11 @@ public Object invoke(final String operationName, final Object params[], final St } else if (operationName.equals("setLayout")) { final Layout layout = (Layout) OptionConverter.instantiateByClassName((String) params[0], Layout.class, null); + if (layout == null) { + cat.error("Could not instantiate layout class [" + params[0] + "] for appender [" + + getAppenderName(appender) + "]."); + return "Could not instantiate layout class."; + } appender.setLayout(layout); registerLayoutMBean(layout); } diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/jmx/AppenderDynamicMBeanTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/jmx/AppenderDynamicMBeanTest.java new file mode 100644 index 00000000000..485b79af35c --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/jmx/AppenderDynamicMBeanTest.java @@ -0,0 +1,57 @@ +/* + * 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. + */ +package org.apache.log4j.jmx; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.log4j.ConsoleAppender; +import org.apache.log4j.PatternLayout; +import org.junit.jupiter.api.Test; + +/** + * Regression for JMX {@code setLayout}: instantiateByClassName may return null. + */ +class AppenderDynamicMBeanTest { + + @Test + void setLayoutDoesNotNpeWhenClassCannotBeInstantiated() throws Exception { + final ConsoleAppender appender = new ConsoleAppender(); + appender.setName("jmx-layout-test"); + final AppenderDynamicMBean mbean = new AppenderDynamicMBean(appender); + + final Object result = assertDoesNotThrow( + () -> mbean.invoke("setLayout", new Object[] {"this.class.does.not.exist.MissingLayout"}, new String[] { + String.class.getName() + })); + assertTrue(result == null || result.toString().contains("Could not instantiate")); + assertNull(appender.getLayout()); + } + + @Test + void setLayoutStillAttachesValidLayout() throws Exception { + final ConsoleAppender appender = new ConsoleAppender(); + appender.setName("jmx-layout-valid"); + final AppenderDynamicMBean mbean = new AppenderDynamicMBean(appender); + + mbean.invoke("setLayout", new Object[] {PatternLayout.class.getName()}, new String[] {String.class.getName()}); + assertNotNull(appender.getLayout()); + assertTrue(appender.getLayout() instanceof PatternLayout); + } +} diff --git a/src/changelog/.2.x.x/fix_appender_dynamic_mbean_null_layout.xml b/src/changelog/.2.x.x/fix_appender_dynamic_mbean_null_layout.xml new file mode 100644 index 00000000000..9d096dc52be --- /dev/null +++ b/src/changelog/.2.x.x/fix_appender_dynamic_mbean_null_layout.xml @@ -0,0 +1,8 @@ + + + + Fix NPE in log4j-1.2-api `AppenderDynamicMBean.setLayout` when the layout class cannot be instantiated + From e8239eda22cb5f86966404f6732bee4b85236438 Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Sat, 25 Jul 2026 07:34:07 -0400 Subject: [PATCH 2/2] changelog: set issue id to PR #4219 Signed-off-by: Sebastien Tardif --- src/changelog/.2.x.x/fix_appender_dynamic_mbean_null_layout.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/changelog/.2.x.x/fix_appender_dynamic_mbean_null_layout.xml b/src/changelog/.2.x.x/fix_appender_dynamic_mbean_null_layout.xml index 9d096dc52be..6ede9b60f3d 100644 --- a/src/changelog/.2.x.x/fix_appender_dynamic_mbean_null_layout.xml +++ b/src/changelog/.2.x.x/fix_appender_dynamic_mbean_null_layout.xml @@ -3,6 +3,6 @@ xmlns="https://logging.apache.org/xml/ns" xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" type="fixed"> - + Fix NPE in log4j-1.2-api `AppenderDynamicMBean.setLayout` when the layout class cannot be instantiated