You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by ck...@apache.org on 2021/12/18 01:24:33 UTC

[logging-log4j2] branch release-2.x updated (4a4b753 -> 8e18c13)

This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a change to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git.


    from 4a4b753  [LOG4J2-3242] Rename JNDI enablement property from 'log4j2.enableJndi' to 'log4j2.enableJndiLookup', 'log4j2.enableJndiJms', and 'log4j2.enableJndiContextSelector'.
     new 8060232  Fix string substitution recursion
     new ff844c0  [DOC] add CVE-2021-45105 for 2.17.0 and 2.12.3
     new 0b980ab  Prep for releaes
     new 7eccaa4  Fix bug in template
     new 5476a16  [DOC] fix typos and rephrase mitigation for CVE-2021-45105
     new f9c4cf7  [DOC] fix missing newline before bullet points
     new 5fa35e1  [DOC] fix typo: this is not a velocity template page
     new 43131f4  [DOC] update JMS Appender docs; only java: protocol supported from 2.17
     new 49de894  [DOC] update JNDI Lookup docs; property should be `enableJndiLookup`
     new 8b0fff5  [DOC] update JndiContextSelector javadoc; mention property `enableJndiContextSelector`
     new d35b5e5  [DOC] update JndiContextSelector section; mention property `enableJndiContextSelector`
     new 4294992  Update pages
     new c8c4242  [DOC] mention property `enableJndiLookup` is required for JNDI lookup
     new 8868bac  [DOC] fix property name, should be `enableJndiContextSelector`
     new 80eaa5c  [DOC] replace old `allowedLdap*` properties with `enableJndiContextSelector`
     new 8e18c13  Remove non-applicable JNDI stuff

The 16 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../org/apache/log4j/builders/AbstractBuilder.java |   5 +-
 .../apache/log4j/config/Log4j1Configuration.java   |   1 +
 .../log4j/config/Log4j1ConfigurationParser.java    |   5 +-
 .../apache/logging/log4j/spi/AbstractLogger.java   |  16 +--
 .../log4j/core/config/AbstractConfiguration.java   |  19 ++-
 .../logging/log4j/core/config/AppenderControl.java |   4 +-
 .../logging/log4j/core/config/Configuration.java   |   9 ++
 .../log4j/core/config/ConfigurationFactory.java    |   3 +-
 .../config/composite/CompositeConfiguration.java   |   2 +-
 .../log4j/core/config/json/JsonConfiguration.java  |   2 +-
 .../core/config/plugins/util/PluginBuilder.java    |  16 ++-
 .../log4j/core/config/xml/XmlConfiguration.java    |   2 +-
 .../core/lookup/ConfigurationStrSubstitutor.java   |  47 +++++++
 .../log4j/core/lookup/ContextMapLookup.java        |   2 +-
 .../logging/log4j/core/lookup/DateLookup.java      |   2 +-
 .../logging/log4j/core/lookup/EventLookup.java     |   3 +
 .../log4j/core/lookup/RuntimeStrSubstitutor.java   |  45 +++++++
 .../logging/log4j/core/lookup/StrSubstitutor.java  | 143 +++++++++++++++++----
 .../log4j/core/selector/JndiContextSelector.java   |   1 +
 ...rnResolverDoesNotEvaluateThreadContextTest.java | 116 +++++++++++++++++
 .../appender/rolling/RollingFileManagerTest.java   |   2 +-
 .../RoutingAppenderKeyLookupEvaluationTest.java    |  94 ++++++++++++++
 .../log4j/core/lookup/StrSubstitutorTest.java      | 133 +++++++++++++++++++
 .../src/test/resources/log4j-routing-2767.xml      |   6 +-
 ...yBlockingQueue.xml => log4j-routing-lookup.xml} |  21 ++-
 .../src/test/resources/log4j-routing-purge.xml     |   8 +-
 log4j-core/src/test/resources/log4j-routing.json   |   5 +-
 .../src/test/resources/log4j-routing.properties    |   4 +-
 log4j-core/src/test/resources/log4j-routing.xml    |   5 +-
 log4j-core/src/test/resources/log4j-routing2.json  |   5 +-
 ....xml => log4j2-pattern-layout-with-context.xml} |   4 +-
 .../logging/log4j/web/Log4jWebInitializerImpl.java |   3 +-
 .../apache/logging/log4j/lookup/CustomLookup.java  |   3 +
 .../logging/log4j/lookup/MapMessageLookup.java     |   3 +
 .../logging/log4j/web/Log4jWebInitializerImpl.java |   3 +-
 pom.xml                                            |   6 +-
 src/changes/changes.xml                            |   7 +
 src/site/markdown/index.md.vm                      |  46 +++++--
 src/site/markdown/security.md                      |  57 ++++++++
 src/site/xdoc/manual/appenders.xml                 |  30 +----
 src/site/xdoc/manual/configuration.xml.vm          |  28 +---
 src/site/xdoc/manual/extending.xml                 |   9 +-
 src/site/xdoc/manual/logsep.xml                    |   4 +-
 src/site/xdoc/manual/lookups.xml                   |  12 +-
 src/site/xdoc/manual/webapp.xml                    |   1 +
 45 files changed, 759 insertions(+), 183 deletions(-)
 create mode 100644 log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ConfigurationStrSubstitutor.java
 create mode 100644 log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/RuntimeStrSubstitutor.java
 create mode 100644 log4j-core/src/test/java/org/apache/logging/log4j/core/PatternResolverDoesNotEvaluateThreadContextTest.java
 create mode 100644 log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderKeyLookupEvaluationTest.java
 copy log4j-core/src/test/resources/{BlockingQueueFactory-ArrayBlockingQueue.xml => log4j-routing-lookup.xml} (68%)
 copy log4j-core/src/test/resources/{log4j2-pattern-layout.xml => log4j2-pattern-layout-with-context.xml} (86%)

[logging-log4j2] 11/16: [DOC] update JndiContextSelector section; mention property `enableJndiContextSelector`

Posted by ck...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit d35b5e58febb73584d642da5006fdd5ca7319577
Author: Remko Popma <re...@yahoo.com>
AuthorDate: Sat Dec 18 09:41:41 2021 +0900

    [DOC] update JndiContextSelector section; mention property `enableJndiContextSelector`
---
 src/site/xdoc/manual/webapp.xml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/site/xdoc/manual/webapp.xml b/src/site/xdoc/manual/webapp.xml
index a7f27fd..444f471 100644
--- a/src/site/xdoc/manual/webapp.xml
+++ b/src/site/xdoc/manual/webapp.xml
@@ -245,6 +245,7 @@
           Note that in this case you must also set the "Log4jContextSelector" system property to
           "<kbd>org.apache.logging.log4j.core.selector.JndiContextSelector</kbd>".
         </p>
+        <p>For security reasons, from Log4j 2.17.0, JNDI must be enabled by setting system property <code>log4j2.enableJndiContextSelector=true</code>.</p>
       </subsection>
       <subsection name="Using Web Application Information During the Configuration">
         <a name="WebLookup" />

[logging-log4j2] 06/16: [DOC] fix missing newline before bullet points

Posted by ck...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit f9c4cf76f1fdf6aeada0f6ee565ce44d09b1823e
Author: Remko Popma <re...@yahoo.com>
AuthorDate: Sat Dec 18 09:08:44 2021 +0900

    [DOC] fix missing newline before bullet points
---
 src/site/markdown/index.md.vm | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/site/markdown/index.md.vm b/src/site/markdown/index.md.vm
index 74ae75a..a157009 100644
--- a/src/site/markdown/index.md.vm
+++ b/src/site/markdown/index.md.vm
@@ -46,6 +46,7 @@ From version 2.17.0 (for Java 8), only lookup strings in configuration are expan
 in any other usage, only the top-level lookup is resolved, and any nested lookups are not resolved.
 
 In prior releases this issue can be mitigated by ensuring your logging configuration does the following:
+
 * In PatternLayout in the logging configuration, replace Context Lookups like `${dollar}{ctx:loginId}`or `${dollar}${dollar}{ctx:loginId}` with Thread Context Map patterns (%X, %mdc, or %MDC).
 * Otherwise, in the configuration, remove references to Context Lookups like `${dollar}{ctx:loginId}` or `${dollar}${dollar}{ctx:loginId}` where they originate from sources external to the application such as HTTP headers or user input.
 

[logging-log4j2] 08/16: [DOC] update JMS Appender docs; only java: protocol supported from 2.17

Posted by ck...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 43131f4e4f86959991569cf0a85d0388c198ee76
Author: Remko Popma <re...@yahoo.com>
AuthorDate: Sat Dec 18 09:27:16 2021 +0900

    [DOC] update JMS Appender docs; only java: protocol supported from 2.17
---
 src/site/xdoc/manual/appenders.xml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml
index a59f2e4..d7035ab 100644
--- a/src/site/xdoc/manual/appenders.xml
+++ b/src/site/xdoc/manual/appenders.xml
@@ -1611,6 +1611,7 @@ public class ConnectionFactory {
               <td><em>Required</em></td>
               <td>The URL of the provider to use as defined by
                 <a class="javadoc" href="http://download.oracle.com/javase/7/docs/api/javax/naming/Context.html#PROVIDER_URL">PROVIDER_URL</a>.
+                From Log4j 2.17, only the <code>java:</code> protocol is supported.
               </td>
             </tr>
             <tr>

[logging-log4j2] 04/16: Fix bug in template

Posted by ck...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 7eccaa4fb27511827bb44f6c845e9b3fb2a93bf6
Author: Ralph Goers <rg...@apache.org>
AuthorDate: Fri Dec 17 16:20:03 2021 -0700

    Fix bug in template
---
 src/site/markdown/index.md.vm | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/site/markdown/index.md.vm b/src/site/markdown/index.md.vm
index 400ced9..b96e581 100644
--- a/src/site/markdown/index.md.vm
+++ b/src/site/markdown/index.md.vm
@@ -46,9 +46,9 @@ From version 2.17.0 (for Java 8), only lookup strings in configuration are expan
 in any other usage, only the top-level lookup is resolved, and any nested lookups are not resolved.
 
 In prior releases this issue can be mitigated by ensuring your logging configuration does the following:
-* Replace Context Lookups like `$${ctx:loginId}` in PatternLayout with Thread Context Map patterns (%X, %mdc, or %MDC)
+* Replace Context Lookups like `${dollar}${dollar}{ctx:loginId}` in PatternLayout with Thread Context Map patterns (%X, %mdc, or %MDC)
   in the logging configuration.
-* Remove refrences to Context Lookups like `$${ctx:loginId}` in the configuration where they originate
+* Remove refrences to Context Lookups like `${dollar}${dollar}{ctx:loginId}` in the configuration where they originate
   from sources external to the application such as HTTP headers or user input.
 
 $h4 Reference

[logging-log4j2] 09/16: [DOC] update JNDI Lookup docs; property should be `enableJndiLookup`

Posted by ck...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 49de894e9a1cd15d3d702b6487bf9802e3ae3376
Author: Remko Popma <re...@yahoo.com>
AuthorDate: Sat Dec 18 09:31:30 2021 +0900

    [DOC] update JNDI Lookup docs; property should be `enableJndiLookup`
---
 src/site/xdoc/manual/lookups.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/site/xdoc/manual/lookups.xml b/src/site/xdoc/manual/lookups.xml
index b7df4fa..9bf6b80 100644
--- a/src/site/xdoc/manual/lookups.xml
+++ b/src/site/xdoc/manual/lookups.xml
@@ -267,9 +267,9 @@
         <a name="JndiLookup"/>
         <subsection name="Jndi Lookup">
           <p>
-            As of Log4j 2.16.0 JNDI operations require that <code>log4j2.enableJndiJava=true</code> be set as a system
+            As of Log4j 2.16.0 JNDI operations require that <code>log4j2.enableJndiLookup=true</code> be set as a system
             property or the corresponding environment variable for this lookup to function. See the
-            <a href="./configuration.html#enableJndiJava">enableJndiJava</a> system property.
+            <a href="./configuration.html#enableJndiLookup">enableJndiLookup</a> system property.
           </p>
           <p>
             The JndiLookup allows variables to be retrieved via JNDI. By default the key will be prefixed with

[logging-log4j2] 01/16: Fix string substitution recursion

Posted by ck...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 806023265f8c905b2dd1d81fd2458f64b2ea0b5e
Author: Carter Kozak <ck...@apache.org>
AuthorDate: Thu Dec 16 17:11:56 2021 -0500

    Fix string substitution recursion
---
 .../org/apache/log4j/builders/AbstractBuilder.java |   5 +-
 .../apache/log4j/config/Log4j1Configuration.java   |   1 +
 .../log4j/config/Log4j1ConfigurationParser.java    |   5 +-
 .../apache/logging/log4j/spi/AbstractLogger.java   |  16 +--
 .../log4j/core/config/AbstractConfiguration.java   |  19 ++-
 .../logging/log4j/core/config/AppenderControl.java |   4 +-
 .../logging/log4j/core/config/Configuration.java   |   9 ++
 .../log4j/core/config/ConfigurationFactory.java    |   3 +-
 .../config/composite/CompositeConfiguration.java   |   2 +-
 .../log4j/core/config/json/JsonConfiguration.java  |   2 +-
 .../core/config/plugins/util/PluginBuilder.java    |  16 ++-
 .../log4j/core/config/xml/XmlConfiguration.java    |   2 +-
 .../core/lookup/ConfigurationStrSubstitutor.java   |  47 +++++++
 .../log4j/core/lookup/ContextMapLookup.java        |   2 +-
 .../logging/log4j/core/lookup/DateLookup.java      |   2 +-
 .../logging/log4j/core/lookup/EventLookup.java     |   3 +
 .../log4j/core/lookup/RuntimeStrSubstitutor.java   |  45 +++++++
 .../logging/log4j/core/lookup/StrSubstitutor.java  | 143 +++++++++++++++++----
 ...rnResolverDoesNotEvaluateThreadContextTest.java | 116 +++++++++++++++++
 .../appender/rolling/RollingFileManagerTest.java   |   2 +-
 .../RoutingAppenderKeyLookupEvaluationTest.java    |  94 ++++++++++++++
 .../log4j/core/lookup/StrSubstitutorTest.java      | 133 +++++++++++++++++++
 .../src/test/resources/log4j-routing-2767.xml      |   6 +-
 ...j-routing-2767.xml => log4j-routing-lookup.xml} |  32 ++---
 .../src/test/resources/log4j-routing-purge.xml     |   8 +-
 log4j-core/src/test/resources/log4j-routing.json   |   5 +-
 .../src/test/resources/log4j-routing.properties    |   4 +-
 log4j-core/src/test/resources/log4j-routing.xml    |   5 +-
 log4j-core/src/test/resources/log4j-routing2.json  |   5 +-
 .../log4j2-pattern-layout-with-context.xml         |  34 +++++
 .../logging/log4j/web/Log4jWebInitializerImpl.java |   3 +-
 .../apache/logging/log4j/lookup/CustomLookup.java  |   3 +
 .../logging/log4j/lookup/MapMessageLookup.java     |   3 +
 .../logging/log4j/web/Log4jWebInitializerImpl.java |   3 +-
 src/changes/changes.xml                            |   7 +
 35 files changed, 680 insertions(+), 109 deletions(-)

diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/AbstractBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/AbstractBuilder.java
index 35c44fb..9cd7851 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/AbstractBuilder.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/AbstractBuilder.java
@@ -24,6 +24,7 @@ import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.core.filter.CompositeFilter;
 import org.apache.logging.log4j.core.filter.ThresholdFilter;
+import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor;
 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
 import org.apache.logging.log4j.status.StatusLogger;
 
@@ -54,7 +55,7 @@ public abstract class AbstractBuilder {
     public AbstractBuilder() {
         this.prefix = null;
         this.props = new Properties();
-        strSubstitutor = new StrSubstitutor(System.getProperties());
+        strSubstitutor = new ConfigurationStrSubstitutor(System.getProperties());
     }
 
     public AbstractBuilder(String prefix, Properties props) {
@@ -63,7 +64,7 @@ public abstract class AbstractBuilder {
         Map<String, String> map = new HashMap<>();
         System.getProperties().forEach((k, v) -> map.put(k.toString(), v.toString()));
         props.forEach((k, v) -> map.put(k.toString(), v.toString()));
-        strSubstitutor = new StrSubstitutor(map);
+        strSubstitutor = new ConfigurationStrSubstitutor(map);
     }
 
     public String getProperty(String key) {
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.java
index 3603fbf..8be2ae8 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.java
@@ -55,6 +55,7 @@ public class Log4j1Configuration extends AbstractConfiguration implements Reconf
     @Override
     public void initialize() {
         getStrSubstitutor().setConfiguration(this);
+        getConfigurationStrSubstitutor().setConfiguration(this);
         super.getScheduler().start();
         doConfigure();
         setState(State.INITIALIZED);
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationParser.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationParser.java
index 2667f7c..8fa269a 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationParser.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationParser.java
@@ -39,6 +39,7 @@ import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder;
 import org.apache.logging.log4j.core.config.builder.api.LoggerComponentBuilder;
 import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder;
 import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
+import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor;
 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.Strings;
@@ -89,8 +90,8 @@ public class Log4j1ConfigurationParser {
             throws IOException {
         try {
             properties.load(input);
-            strSubstitutorProperties = new StrSubstitutor(properties);
-            strSubstitutorSystem = new StrSubstitutor(System.getProperties());
+            strSubstitutorProperties = new ConfigurationStrSubstitutor(properties);
+            strSubstitutorSystem = new ConfigurationStrSubstitutor(System.getProperties());
             final String rootCategoryValue = getLog4jValue(ROOTCATEGORY);
             final String rootLoggerValue = getLog4jValue(ROOTLOGGER);
             if (rootCategoryValue == null && rootLoggerValue == null) {
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
index b5114c8..1990a73 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
@@ -2104,7 +2104,7 @@ public abstract class AbstractLogger implements ExtendedLogger, LocationAwareLog
         try {
             incrementRecursionDepth();
             log(level, marker, fqcn, location, message, throwable);
-        } catch (Exception ex) {
+        } catch (Throwable ex) {
             handleLogMessageException(ex, fqcn, message);
         } finally {
             decrementRecursionDepth();
@@ -2203,9 +2203,9 @@ public abstract class AbstractLogger implements ExtendedLogger, LocationAwareLog
                                final Throwable throwable) {
         try {
             log(level, marker, fqcn, location, message, throwable);
-        } catch (final Exception e) {
+        } catch (final Throwable t) {
             // LOG4J2-1990 Log4j2 suppresses all exceptions that occur once application called the logger
-            handleLogMessageException(e, fqcn, message);
+            handleLogMessageException(t, fqcn, message);
         }
     }
 
@@ -2218,16 +2218,16 @@ public abstract class AbstractLogger implements ExtendedLogger, LocationAwareLog
 
     // LOG4J2-1990 Log4j2 suppresses all exceptions that occur once application called the logger
     // TODO Configuration setting to propagate exceptions back to the caller *if requested*
-    private void handleLogMessageException(final Exception exception, final String fqcn, final Message message) {
-        if (exception instanceof LoggingException) {
-            throw (LoggingException) exception;
+    private void handleLogMessageException(final Throwable throwable, final String fqcn, final Message message) {
+        if (throwable instanceof LoggingException) {
+            throw (LoggingException) throwable;
         }
         StatusLogger.getLogger().warn("{} caught {} logging {}: {}",
                 fqcn,
-                exception.getClass().getName(),
+                throwable.getClass().getName(),
                 message.getClass().getSimpleName(),
                 message.getFormat(),
-                exception);
+                throwable);
     }
 
     @Override
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
index cb3ab40..d5f6b5e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
@@ -56,8 +56,10 @@ import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
 import org.apache.logging.log4j.core.config.plugins.util.PluginType;
 import org.apache.logging.log4j.core.filter.AbstractFilterable;
 import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor;
 import org.apache.logging.log4j.core.lookup.Interpolator;
 import org.apache.logging.log4j.core.lookup.MapLookup;
+import org.apache.logging.log4j.core.lookup.RuntimeStrSubstitutor;
 import org.apache.logging.log4j.core.lookup.StrLookup;
 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
 import org.apache.logging.log4j.core.net.Advertiser;
@@ -129,7 +131,8 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
     private List<CustomLevelConfig> customLevels = Collections.emptyList();
     private final ConcurrentMap<String, String> propertyMap = new ConcurrentHashMap<>();
     private final StrLookup tempLookup = new Interpolator(propertyMap);
-    private final StrSubstitutor subst = new StrSubstitutor(tempLookup);
+    private final StrSubstitutor subst = new RuntimeStrSubstitutor(tempLookup);
+    private final StrSubstitutor configurationStrSubstitutor = new ConfigurationStrSubstitutor(subst);
     private LoggerConfig root = new LoggerConfig();
     private final ConcurrentMap<String, Object> componentMap = new ConcurrentHashMap<>();
     private final ConfigurationSource configurationSource;
@@ -217,6 +220,7 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
     public void initialize() {
         LOGGER.debug(Version.getProductString() + " initializing configuration {}", this);
         subst.setConfiguration(this);
+        configurationStrSubstitutor.setConfiguration(this);
         try {
             scriptManager = new ScriptManager(this, watchManager);
         } catch (final LinkageError | Exception e) {
@@ -623,12 +627,16 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
             final Node first = rootNode.getChildren().get(0);
             createConfiguration(first, null);
             if (first.getObject() != null) {
-                subst.setVariableResolver((StrLookup) first.getObject());
+                StrLookup lookup = (StrLookup) first.getObject();
+                subst.setVariableResolver(lookup);
+                configurationStrSubstitutor.setVariableResolver(lookup);
             }
         } else {
             final Map<String, String> map = this.getComponent(CONTEXT_PROPERTIES);
             final StrLookup lookup = map == null ? null : new MapLookup(map);
-            subst.setVariableResolver(new Interpolator(lookup, pluginPackages));
+            Interpolator interpolator = new Interpolator(lookup, pluginPackages);
+            subst.setVariableResolver(interpolator);
+            configurationStrSubstitutor.setVariableResolver(interpolator);
         }
 
         boolean setLoggers = false;
@@ -805,6 +813,11 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
     }
 
     @Override
+    public StrSubstitutor getConfigurationStrSubstitutor() {
+        return configurationStrSubstitutor;
+    }
+
+    @Override
     public void setAdvertiser(final Advertiser advertiser) {
         this.advertiser = advertiser;
     }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderControl.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderControl.java
index dc9d990..0c73a1f 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderControl.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderControl.java
@@ -161,8 +161,8 @@ public class AppenderControl extends AbstractFilterable {
             appender.append(event);
         } catch (final RuntimeException error) {
             handleAppenderError(event, error);
-        } catch (final Exception error) {
-            handleAppenderError(event, new AppenderLoggingException(error));
+        } catch (final Throwable throwable) {
+            handleAppenderError(event, new AppenderLoggingException(throwable));
         }
     }
 
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java
index b607571..eac94c5 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java
@@ -27,6 +27,7 @@ import org.apache.logging.log4j.core.Logger;
 import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.async.AsyncLoggerConfigDelegate;
 import org.apache.logging.log4j.core.filter.Filterable;
+import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor;
 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
 import org.apache.logging.log4j.core.net.Advertiser;
 import org.apache.logging.log4j.core.script.ScriptManager;
@@ -116,6 +117,14 @@ public interface Configuration extends Filterable {
 
     StrSubstitutor getStrSubstitutor();
 
+    default StrSubstitutor getConfigurationStrSubstitutor() {
+        StrSubstitutor defaultSubstitutor = getStrSubstitutor();
+        if (defaultSubstitutor == null) {
+            return new ConfigurationStrSubstitutor();
+        }
+        return new ConfigurationStrSubstitutor(defaultSubstitutor);
+    }
+
     void createConfiguration(Node node, LogEvent event);
 
     <T> T getComponent(String name);
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java
index 9a9c8a2..ea905dd 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java
@@ -23,6 +23,7 @@ import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFact
 import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
 import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
 import org.apache.logging.log4j.core.config.plugins.util.PluginType;
+import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor;
 import org.apache.logging.log4j.core.lookup.Interpolator;
 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
 import org.apache.logging.log4j.core.net.UrlConnectionFactory;
@@ -141,7 +142,7 @@ public abstract class ConfigurationFactory extends ConfigurationBuilderFactory {
 
     private static ConfigurationFactory configFactory = new Factory();
 
-    protected final StrSubstitutor substitutor = new StrSubstitutor(new Interpolator());
+    protected final StrSubstitutor substitutor = new ConfigurationStrSubstitutor(new Interpolator());
 
     private static final Lock LOCK = new ReentrantLock();
 
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/CompositeConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/CompositeConfiguration.java
index 2be2a9f..c99d5dc 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/CompositeConfiguration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/CompositeConfiguration.java
@@ -79,7 +79,7 @@ public class CompositeConfiguration extends AbstractConfiguration implements Rec
                 .withStatus(getDefaultStatus());
         for (final Map.Entry<String, String> entry : rootNode.getAttributes().entrySet()) {
             final String key = entry.getKey();
-            final String value = getStrSubstitutor().replace(entry.getValue());
+            final String value = getConfigurationStrSubstitutor().replace(entry.getValue());
             if ("status".equalsIgnoreCase(key)) {
                 statusConfig.withStatus(value.toUpperCase());
             } else if ("dest".equalsIgnoreCase(key)) {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java
index c41a032..3e9dd9c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java
@@ -71,7 +71,7 @@ public class JsonConfiguration extends AbstractConfiguration implements Reconfig
             int monitorIntervalSeconds = 0;
             for (final Map.Entry<String, String> entry : rootNode.getAttributes().entrySet()) {
                 final String key = entry.getKey();
-                final String value = getStrSubstitutor().replace(entry.getValue());
+                final String value = getConfigurationStrSubstitutor().replace(entry.getValue());
                 // TODO: this duplicates a lot of the XmlConfiguration constructor
                 if ("status".equalsIgnoreCase(key)) {
                     statusConfig.withStatus(value);
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java
index f726a3a..69623d3 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java
@@ -124,20 +124,20 @@ public class PluginBuilder implements Builder<Object> {
         } catch (final ConfigurationException e) { // LOG4J2-1908
             LOGGER.error("Could not create plugin of type {} for element {}", this.clazz, node.getName(), e);
             return null; // no point in trying the factory method
-        } catch (final Exception e) {
+        } catch (final Throwable t) {
             LOGGER.error("Could not create plugin of type {} for element {}: {}",
                     this.clazz, node.getName(),
-                    (e instanceof InvocationTargetException ? ((InvocationTargetException) e).getCause() : e).toString(), e);
+                    (t instanceof InvocationTargetException ? ((InvocationTargetException) t).getCause() : t).toString(), t);
         }
         // or fall back to factory method if no builder class is available
         try {
             final Method factory = findFactoryMethod(this.clazz);
             final Object[] params = generateParameters(factory);
             return factory.invoke(null, params);
-        } catch (final Exception e) {
+        } catch (final Throwable t) {
             LOGGER.error("Unable to invoke factory method in {} for element {}: {}",
                     this.clazz, this.node.getName(),
-                    (e instanceof InvocationTargetException ? ((InvocationTargetException) e).getCause() : e).toString(), e);
+                    (t instanceof InvocationTargetException ? ((InvocationTargetException) t).getCause() : t).toString(), t);
             return null;
         }
     }
@@ -180,7 +180,9 @@ public class PluginBuilder implements Builder<Object> {
                     final Object value = visitor.setAliases(aliases)
                         .setAnnotation(a)
                         .setConversionType(field.getType())
-                        .setStrSubstitutor(configuration.getStrSubstitutor())
+                        .setStrSubstitutor(event == null
+                                ? configuration.getConfigurationStrSubstitutor()
+                                : configuration.getStrSubstitutor())
                         .setMember(field)
                         .visit(configuration, node, event, log);
                     // don't overwrite default values if the visitor gives us no value to inject
@@ -253,7 +255,9 @@ public class PluginBuilder implements Builder<Object> {
                     final Object value = visitor.setAliases(aliases)
                         .setAnnotation(a)
                         .setConversionType(types[i])
-                        .setStrSubstitutor(configuration.getStrSubstitutor())
+                        .setStrSubstitutor(event == null
+                                ? configuration.getConfigurationStrSubstitutor()
+                                : configuration.getStrSubstitutor())
                         .setMember(factory)
                         .visit(configuration, node, event, log);
                     // don't overwrite existing values if the visitor gives us no value to inject
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java
index 6a6c116..939c36c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java
@@ -110,7 +110,7 @@ public class XmlConfiguration extends AbstractConfiguration implements Reconfigu
             int monitorIntervalSeconds = 0;
             for (final Map.Entry<String, String> entry : attrs.entrySet()) {
                 final String key = entry.getKey();
-                final String value = getStrSubstitutor().replace(entry.getValue());
+                final String value = getConfigurationStrSubstitutor().replace(entry.getValue());
                 if ("status".equalsIgnoreCase(key)) {
                     statusConfig.withStatus(value);
                 } else if ("dest".equalsIgnoreCase(key)) {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ConfigurationStrSubstitutor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ConfigurationStrSubstitutor.java
new file mode 100644
index 0000000..eb191e7
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ConfigurationStrSubstitutor.java
@@ -0,0 +1,47 @@
+package org.apache.logging.log4j.core.lookup;
+
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * {@link RuntimeStrSubstitutor} is a {@link StrSubstitutor} which only supports recursive evaluation of lookups.
+ * This can be dangerous when combined with user-provided inputs, and should only be used on data directly from
+ * a configuration.
+ */
+public final class ConfigurationStrSubstitutor extends StrSubstitutor {
+
+    public ConfigurationStrSubstitutor() {
+    }
+
+    public ConfigurationStrSubstitutor(final Map<String, String> valueMap) {
+        super(valueMap);
+    }
+
+    public ConfigurationStrSubstitutor(final Properties properties) {
+        super(properties);
+    }
+
+    public ConfigurationStrSubstitutor(final StrLookup lookup) {
+        super(lookup);
+    }
+
+    public ConfigurationStrSubstitutor(final StrSubstitutor other) {
+        super(other);
+    }
+
+    @Override
+    boolean isRecursiveEvaluationAllowed() {
+        return true;
+    }
+
+    @Override
+    void setRecursiveEvaluationAllowed(final boolean recursiveEvaluationAllowed) {
+        throw new UnsupportedOperationException(
+                "recursiveEvaluationAllowed cannot be modified within ConfigurationStrSubstitutor");
+    }
+
+    @Override
+    public String toString() {
+        return "ConfigurationStrSubstitutor{" + super.toString() + "}";
+    }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ContextMapLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ContextMapLookup.java
index 220d17a..a8be6e2 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ContextMapLookup.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ContextMapLookup.java
@@ -55,6 +55,6 @@ public class ContextMapLookup implements StrLookup {
      */
     @Override
     public String lookup(final LogEvent event, final String key) {
-        return event.getContextData().getValue(key);
+        return event == null ? null : event.getContextData().getValue(key);
     }
 }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/DateLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/DateLookup.java
index 3e630b0..3bcecf7 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/DateLookup.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/DateLookup.java
@@ -54,7 +54,7 @@ public class DateLookup implements StrLookup {
      */
     @Override
     public String lookup(final LogEvent event, final String key) {
-        return formatDate(event.getTimeMillis(), key);
+        return event == null ? lookup(key) : formatDate(event.getTimeMillis(), key);
     }
 
     private String formatDate(final long date, final String format) {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/EventLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/EventLookup.java
index e5128f1..b4587fc 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/EventLookup.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/EventLookup.java
@@ -33,6 +33,9 @@ public class EventLookup extends AbstractLookup {
      */
     @Override
     public String lookup(final LogEvent event, final String key) {
+        if (event == null) {
+            return null;
+        }
         switch (key) {
             case "Marker": {
                 return event.getMarker() != null ? event.getMarker().getName() : null;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/RuntimeStrSubstitutor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/RuntimeStrSubstitutor.java
new file mode 100644
index 0000000..eb938ce
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/RuntimeStrSubstitutor.java
@@ -0,0 +1,45 @@
+package org.apache.logging.log4j.core.lookup;
+
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * {@link RuntimeStrSubstitutor} is a {@link StrSubstitutor} which only supports evaluation of top-level lookups.
+ */
+public final class RuntimeStrSubstitutor extends StrSubstitutor {
+
+    public RuntimeStrSubstitutor() {
+    }
+
+    public RuntimeStrSubstitutor(final Map<String, String> valueMap) {
+        super(valueMap);
+    }
+
+    public RuntimeStrSubstitutor(final Properties properties) {
+        super(properties);
+    }
+
+    public RuntimeStrSubstitutor(final StrLookup lookup) {
+        super(lookup);
+    }
+
+    public RuntimeStrSubstitutor(final StrSubstitutor other) {
+        super(other);
+    }
+
+    @Override
+    boolean isRecursiveEvaluationAllowed() {
+        return false;
+    }
+
+    @Override
+    void setRecursiveEvaluationAllowed(final boolean recursiveEvaluationAllowed) {
+        throw new UnsupportedOperationException(
+                "recursiveEvaluationAllowed cannot be modified within RuntimeStrSubstitutor");
+    }
+
+    @Override
+    public String toString() {
+        return "RuntimeStrSubstitutor{" + super.toString() + "}";
+    }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java
index 8faf6f2..4818ab0 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java
@@ -22,11 +22,13 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Properties;
 
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.config.ConfigurationAware;
+import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.Strings;
 
 /**
@@ -207,6 +209,8 @@ public class StrSubstitutor implements ConfigurationAware {
      */
     private Configuration configuration;
 
+    private boolean recursiveEvaluationAllowed;
+
     //-----------------------------------------------------------------------
     /**
      * Creates a new instance with defaults for variable prefix and suffix
@@ -369,8 +373,8 @@ public class StrSubstitutor implements ConfigurationAware {
      * @throws IllegalArgumentException if the prefix or suffix is null
      */
     public StrSubstitutor(final StrLookup variableResolver, final StrMatcher prefixMatcher,
-            final StrMatcher suffixMatcher, final char escape, final StrMatcher valueDelimiterMatcher,
-            final StrMatcher valueEscapeMatcher) {
+                          final StrMatcher suffixMatcher, final char escape, final StrMatcher valueDelimiterMatcher,
+                          final StrMatcher valueEscapeMatcher) {
         this.setVariableResolver(variableResolver);
         this.setVariablePrefixMatcher(prefixMatcher);
         this.setVariableSuffixMatcher(suffixMatcher);
@@ -379,6 +383,20 @@ public class StrSubstitutor implements ConfigurationAware {
         valueEscapeDelimiterMatcher = valueEscapeMatcher;
     }
 
+    StrSubstitutor(final StrSubstitutor other) {
+        Objects.requireNonNull(other, "other");
+        this.setVariableResolver(other.getVariableResolver());
+        this.setVariablePrefixMatcher(other.getVariablePrefixMatcher());
+        this.setVariableSuffixMatcher(other.getVariableSuffixMatcher());
+        this.setEscapeChar(other.getEscapeChar());
+        this.setValueDelimiterMatcher(other.valueDelimiterMatcher);
+        this.valueEscapeDelimiterMatcher = other.valueEscapeDelimiterMatcher;
+        this.configuration = other.configuration;
+        this.recursiveEvaluationAllowed = other.isRecursiveEvaluationAllowed();
+        this.enableSubstitutionInVariables = other.isEnableSubstitutionInVariables();
+        this.valueDelimiterString = other.valueDelimiterString;
+    }
+
     //-----------------------------------------------------------------------
     /**
      * Replaces all the occurrences of variables in the given source object with
@@ -439,6 +457,11 @@ public class StrSubstitutor implements ConfigurationAware {
         return map;
     }
 
+    private static String handleFailedReplacement(String input, Throwable throwable) {
+        StatusLogger.getLogger().error("Replacement failed on {}", input, throwable);
+        return input;
+    }
+
     //-----------------------------------------------------------------------
     /**
      * Replaces all the occurrences of variables with their matching values
@@ -464,8 +487,12 @@ public class StrSubstitutor implements ConfigurationAware {
             return null;
         }
         final StringBuilder buf = new StringBuilder(source);
-        if (!substitute(event, buf, 0, source.length())) {
-            return source;
+        try {
+            if (!substitute(event, buf, 0, source.length())) {
+                return source;
+            }
+        } catch (Throwable t) {
+            return handleFailedReplacement(source, t);
         }
         return buf.toString();
     }
@@ -506,8 +533,12 @@ public class StrSubstitutor implements ConfigurationAware {
             return null;
         }
         final StringBuilder buf = new StringBuilder(length).append(source, offset, length);
-        if (!substitute(event, buf, 0, length)) {
-            return source.substring(offset, offset + length);
+        try {
+            if (!substitute(event, buf, 0, length)) {
+                return source.substring(offset, offset + length);
+            }
+        } catch (Throwable t) {
+            return handleFailedReplacement(source, t);
         }
         return buf.toString();
     }
@@ -540,7 +571,11 @@ public class StrSubstitutor implements ConfigurationAware {
             return null;
         }
         final StringBuilder buf = new StringBuilder(source.length).append(source);
-        substitute(event, buf, 0, source.length);
+        try {
+            substitute(event, buf, 0, source.length);
+        } catch (Throwable t) {
+            return handleFailedReplacement(new String(source), t);
+        }
         return buf.toString();
     }
 
@@ -582,7 +617,11 @@ public class StrSubstitutor implements ConfigurationAware {
             return null;
         }
         final StringBuilder buf = new StringBuilder(length).append(source, offset, length);
-        substitute(event, buf, 0, length);
+        try {
+            substitute(event, buf, 0, length);
+        } catch (Throwable t) {
+            return handleFailedReplacement(new String(source, offset, length), t);
+        }
         return buf.toString();
     }
 
@@ -614,7 +653,11 @@ public class StrSubstitutor implements ConfigurationAware {
             return null;
         }
         final StringBuilder buf = new StringBuilder(source.length()).append(source);
-        substitute(event, buf, 0, buf.length());
+        try {
+            substitute(event, buf, 0, buf.length());
+        } catch (Throwable t) {
+            return handleFailedReplacement(source.toString(), t);
+        }
         return buf.toString();
     }
 
@@ -656,7 +699,11 @@ public class StrSubstitutor implements ConfigurationAware {
             return null;
         }
         final StringBuilder buf = new StringBuilder(length).append(source, offset, length);
-        substitute(event, buf, 0, length);
+        try {
+            substitute(event, buf, 0, length);
+        } catch (Throwable t) {
+            return handleFailedReplacement(source.substring(offset, offset + length), t);
+        }
         return buf.toString();
     }
 
@@ -688,7 +735,11 @@ public class StrSubstitutor implements ConfigurationAware {
             return null;
         }
         final StringBuilder buf = new StringBuilder(source.length()).append(source);
-        substitute(event, buf, 0, buf.length());
+        try {
+            substitute(event, buf, 0, buf.length());
+        } catch (Throwable t) {
+            return handleFailedReplacement(source.toString(), t);
+        }
         return buf.toString();
     }
     /**
@@ -729,7 +780,11 @@ public class StrSubstitutor implements ConfigurationAware {
             return null;
         }
         final StringBuilder buf = new StringBuilder(length).append(source, offset, length);
-        substitute(event, buf, 0, length);
+        try {
+            substitute(event, buf, 0, length);
+        } catch (Throwable t) {
+            return handleFailedReplacement(source.substring(offset, offset + length), t);
+        }
         return buf.toString();
     }
 
@@ -759,8 +814,13 @@ public class StrSubstitutor implements ConfigurationAware {
         if (source == null) {
             return null;
         }
-        final StringBuilder buf = new StringBuilder().append(source);
-        substitute(event, buf, 0, buf.length());
+        String stringValue = String.valueOf(source);
+        final StringBuilder buf = new StringBuilder(stringValue.length()).append(stringValue);
+        try {
+            substitute(event, buf, 0, buf.length());
+        } catch (Throwable t) {
+            return handleFailedReplacement(stringValue, t);
+        }
         return buf.toString();
     }
 
@@ -818,7 +878,12 @@ public class StrSubstitutor implements ConfigurationAware {
             return false;
         }
         final StringBuilder buf = new StringBuilder(length).append(source, offset, length);
-        if (!substitute(event, buf, 0, length)) {
+        try {
+            if (!substitute(event, buf, 0, length)) {
+                return false;
+            }
+        } catch (Throwable t) {
+            StatusLogger.getLogger().error("Replacement failed on {}", source, t);
             return false;
         }
         source.replace(offset, offset + length, buf.toString());
@@ -974,8 +1039,12 @@ public class StrSubstitutor implements ConfigurationAware {
                         if (nestedVarCount == 0) {
                             String varNameExpr = new String(chars, startPos + startMatchLen, pos - startPos - startMatchLen);
                             if (substitutionInVariablesEnabled) {
+                                // initialize priorVariables if they're not already set
+                                if (priorVariables == null) {
+                                    priorVariables = new ArrayList<>();
+                                }
                                 final StringBuilder bufName = new StringBuilder(varNameExpr);
-                                substitute(event, bufName, 0, bufName.length());
+                                substitute(event, bufName, 0, bufName.length(), priorVariables);
                                 varNameExpr = bufName.toString();
                             }
                             pos += endMatchLen;
@@ -1026,11 +1095,10 @@ public class StrSubstitutor implements ConfigurationAware {
                             }
 
                             // handle cyclic substitution
-                            checkCyclicSubstitution(varName, priorVariables);
-                            priorVariables.add(varName);
+                            boolean isCyclic = isCyclicSubstitution(varName, priorVariables);
 
                             // resolve the variable
-                            String varValue = resolveVariable(event, varName, buf, startPos, endPos);
+                            String varValue = isCyclic ? null : resolveVariable(event, varName, buf, startPos, endPos);
                             if (varValue == null) {
                                 varValue = varDefaultValue;
                             }
@@ -1039,7 +1107,9 @@ public class StrSubstitutor implements ConfigurationAware {
                                 final int varLen = varValue.length();
                                 buf.replace(startPos, endPos, varValue);
                                 altered = true;
-                                int change = substitute(event, buf, startPos, varLen, priorVariables);
+                                int change = isRecursiveEvaluationAllowed()
+                                        ? substitute(event, buf, startPos, varLen, priorVariables)
+                                        : 0;
                                 change = change + (varLen - (endPos - startPos));
                                 pos += change;
                                 bufEnd += change;
@@ -1048,7 +1118,9 @@ public class StrSubstitutor implements ConfigurationAware {
                             }
 
                             // remove variable from the cyclic stack
-                            priorVariables.remove(priorVariables.size() - 1);
+                            if (!isCyclic) {
+                                priorVariables.remove(priorVariables.size() - 1);
+                            }
                             break;
                         }
                         nestedVarCount--;
@@ -1064,21 +1136,23 @@ public class StrSubstitutor implements ConfigurationAware {
     }
 
     /**
-     * Checks if the specified variable is already in the stack (list) of variables.
+     * Checks if the specified variable is already in the stack (list) of variables, adding the value
+     * if it's not already present.
      *
      * @param varName  the variable name to check
      * @param priorVariables  the list of prior variables
+     * @return true if this is a cyclic substitution
      */
-    private void checkCyclicSubstitution(final String varName, final List<String> priorVariables) {
+    private boolean isCyclicSubstitution(final String varName, final List<String> priorVariables) {
         if (!priorVariables.contains(varName)) {
-            return;
+            priorVariables.add(varName);
+            return false;
         }
         final StringBuilder buf = new StringBuilder(BUF_SIZE);
         buf.append("Infinite loop in property interpolation of ");
-        buf.append(priorVariables.remove(0));
-        buf.append(": ");
         appendWithSeparators(buf, priorVariables, "->");
-        throw new IllegalStateException(buf.toString());
+        StatusLogger.getLogger().warn(buf);
+        return true;
     }
 
     /**
@@ -1107,7 +1181,12 @@ public class StrSubstitutor implements ConfigurationAware {
         if (resolver == null) {
             return null;
         }
-        return resolver.lookup(event, variableName);
+        try {
+            return resolver.lookup(event, variableName);
+        } catch (Throwable t) {
+            StatusLogger.getLogger().error("Resolver failed to lookup {}", variableName, t);
+            return null;
+        }
     }
 
     // Escape
@@ -1396,6 +1475,14 @@ public class StrSubstitutor implements ConfigurationAware {
         this.enableSubstitutionInVariables = enableSubstitutionInVariables;
     }
 
+    boolean isRecursiveEvaluationAllowed() {
+        return recursiveEvaluationAllowed;
+    }
+
+    void setRecursiveEvaluationAllowed(final boolean recursiveEvaluationAllowed) {
+        this.recursiveEvaluationAllowed = recursiveEvaluationAllowed;
+    }
+
     private char[] getChars(final StringBuilder sb) {
         final char[] chars = new char[sb.length()];
         sb.getChars(0, sb.length(), chars, 0);
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/PatternResolverDoesNotEvaluateThreadContextTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/PatternResolverDoesNotEvaluateThreadContextTest.java
new file mode 100644
index 0000000..173101c
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/PatternResolverDoesNotEvaluateThreadContextTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.logging.log4j.core;
+
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class PatternResolverDoesNotEvaluateThreadContextTest {
+
+
+    private static final String CONFIG = "log4j2-pattern-layout-with-context.xml";
+    private static final String PARAMETER = "user";
+    private ListAppender listAppender;
+
+    @ClassRule
+    public static LoggerContextRule context = new LoggerContextRule(CONFIG);
+
+    @Before
+    public void before() {
+        listAppender = context.getRequiredAppender("list", ListAppender.class);
+        listAppender.clear();
+    }
+
+    @Test
+    public void testNoUserSet() {
+        Logger logger = context.getLogger(getClass());
+        logger.info("This is a test");
+        List<String> messages = listAppender.getMessages();
+        assertTrue(messages != null && messages.size() > 0, "No messages returned");
+        String message = messages.get(0);
+        assertEquals("INFO org.apache.logging.log4j.core." +
+                "PatternResolverDoesNotEvaluateThreadContextTest ${ctx:user} This is a test", message);
+    }
+
+    @Test
+    public void testMessageIsNotLookedUp() {
+        Logger logger = context.getLogger(getClass());
+        logger.info("This is a ${upper:test}");
+        List<String> messages = listAppender.getMessages();
+        assertTrue(messages != null && messages.size() > 0, "No messages returned");
+        String message = messages.get(0);
+        assertEquals("INFO org.apache.logging.log4j.core." +
+                "PatternResolverDoesNotEvaluateThreadContextTest ${ctx:user} This is a ${upper:test}", message);
+    }
+
+    @Test
+    public void testUser() {
+        Logger logger = context.getLogger(getClass());
+        ThreadContext.put(PARAMETER, "123");
+        try {
+            logger.info("This is a test");
+        } finally {
+            ThreadContext.remove(PARAMETER);
+        }
+        List<String> messages = listAppender.getMessages();
+        assertTrue(messages != null && messages.size() > 0, "No messages returned");
+        String message = messages.get(0);
+        assertEquals("INFO org.apache.logging.log4j.core." +
+                "PatternResolverDoesNotEvaluateThreadContextTest 123 This is a test", message);
+    }
+
+    @Test
+    public void testUserIsLookup() {
+        Logger logger = context.getLogger(getClass());
+        ThreadContext.put(PARAMETER, "${java:version}");
+        try {
+            logger.info("This is a test");
+        } finally {
+            ThreadContext.remove(PARAMETER);
+        }
+        List<String> messages = listAppender.getMessages();
+        assertTrue(messages != null && messages.size() > 0, "No messages returned");
+        String message = messages.get(0);
+        assertEquals("INFO org.apache.logging.log4j.core." +
+                "PatternResolverDoesNotEvaluateThreadContextTest ${java:version} This is a test", message);
+    }
+
+    @Test
+    public void testUserHasLookup() {
+        Logger logger = context.getLogger(getClass());
+        ThreadContext.put(PARAMETER, "user${java:version}name");
+        try {
+            logger.info("This is a test");
+        } finally {
+            ThreadContext.remove(PARAMETER);
+        }
+        List<String> messages = listAppender.getMessages();
+        assertTrue(messages != null && messages.size() > 0, "No messages returned");
+        String message = messages.get(0);
+        assertEquals("INFO org.apache.logging.log4j.core." +
+                "PatternResolverDoesNotEvaluateThreadContextTest user${java:version}name This is a test", message);
+    }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManagerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManagerTest.java
index d134525..9c0159d 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManagerTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManagerTest.java
@@ -69,7 +69,7 @@ public class RollingFileManagerTest {
                     .withFilePattern("FilePattern")
                     .setName("RollingFileAppender")
                     .setConfiguration(config)
-                    .withStrategy(new CustomDirectFileRolloverStrategy(file, config.getStrSubstitutor()))
+                    .withStrategy(new CustomDirectFileRolloverStrategy(file, config.getConfigurationStrSubstitutor()))
                     .withPolicy(new SizeBasedTriggeringPolicy(100))
                     .build();
 
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderKeyLookupEvaluationTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderKeyLookupEvaluationTest.java
new file mode 100644
index 0000000..8a31d18
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderKeyLookupEvaluationTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.logging.log4j.core.appender.routing;
+
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class RoutingAppenderKeyLookupEvaluationTest {
+    private static final String CONFIG = "log4j-routing-lookup.xml";
+
+    private static final String KEY = "user";
+    private ListAppender app;
+
+    @Rule
+    public final LoggerContextRule loggerContextRule = new LoggerContextRule(CONFIG);
+
+    @Before
+    public void setUp() throws Exception {
+        ThreadContext.remove(KEY);
+        this.app = this.loggerContextRule.getListAppender("List");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        this.app.clear();
+        this.loggerContextRule.getLoggerContext().stop();
+        ThreadContext.remove(KEY);
+    }
+
+    @Test
+    public void testRoutingNoUser() {
+        Logger logger = loggerContextRule.getLogger(getClass());
+        logger.warn("no user");
+        String message = app.getMessages().get(0);
+        assertEquals("WARN ${ctx:user} no user", message);
+    }
+
+    @Test
+    public void testRoutingDoesNotMatchRoute() {
+        Logger logger = loggerContextRule.getLogger(getClass());
+        ThreadContext.put(KEY, "noRouteExists");
+        logger.warn("unmatched user");
+        assertTrue(app.getMessages().isEmpty());
+    }
+
+    @Test
+    public void testRoutingContainsLookup() {
+        Logger logger = loggerContextRule.getLogger(getClass());
+        ThreadContext.put(KEY, "${java:version}");
+        logger.warn("naughty user");
+        String message = app.getMessages().get(0);
+        assertEquals("WARN ${java:version} naughty user", message);
+    }
+
+    @Test
+    public void testRoutingMatchesEscapedLookup() {
+        Logger logger = loggerContextRule.getLogger(getClass());
+        ThreadContext.put(KEY, "${upper:name}");
+        logger.warn("naughty user");
+        String message = app.getMessages().get(0);
+        assertEquals("WARN ${upper:name} naughty user", message);
+    }
+
+    @Test
+    public void testRoutesThemselvesNotEvaluated() {
+        Logger logger = loggerContextRule.getLogger(getClass());
+        ThreadContext.put(KEY, "NAME");
+        logger.warn("unmatched user");
+        assertTrue(app.getMessages().isEmpty());
+    }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/StrSubstitutorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/StrSubstitutorTest.java
index 4edbd4e..375ff30 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/StrSubstitutorTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/StrSubstitutorTest.java
@@ -20,6 +20,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.LogEvent;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
@@ -74,4 +75,136 @@ public class StrSubstitutorTest {
         final String value = subst.replace("${sys:TestKey1:-${ctx:TestKey}}");
         assertEquals("TestValue", value);
     }
+
+    @Test
+    public void testDefaultReferencesLookupValue() {
+        final Map<String, String> map = new HashMap<>();
+        map.put(TESTKEY, "${java:version}");
+        final StrLookup lookup = new Interpolator(new MapLookup(map));
+        final StrSubstitutor subst = new StrSubstitutor(lookup);
+        subst.setRecursiveEvaluationAllowed(false);
+        final String value = subst.replace("${sys:TestKey1:-${ctx:TestKey}}");
+        assertEquals("${java:version}", value);
+    }
+
+    @Test
+    public void testInfiniteSubstitutionOnString() {
+        final StrLookup lookup = new Interpolator(new MapLookup(new HashMap<>()));
+        final StrSubstitutor subst = new StrSubstitutor(lookup);
+        subst.setRecursiveEvaluationAllowed(true);
+        String infiniteSubstitution = "${${::-${::-$${::-j}}}}";
+        assertEquals(infiniteSubstitution, subst.replace(infiniteSubstitution));
+    }
+
+    @Test
+    public void testInfiniteSubstitutionOnStringBuilder() {
+        final StrLookup lookup = new Interpolator(new MapLookup(new HashMap<>()));
+        final StrSubstitutor subst = new StrSubstitutor(lookup);
+        subst.setRecursiveEvaluationAllowed(true);
+        String infiniteSubstitution = "${${::-${::-$${::-j}}}}";
+        assertEquals(infiniteSubstitution, subst.replace(null, new StringBuilder(infiniteSubstitution)));
+    }
+
+    @Test
+    public void testRecursiveSubstitution() {
+        final Map<String, String> map = new HashMap<>();
+        map.put("first", "${ctx:first}");
+        map.put("second", "secondValue");
+        final StrLookup lookup = new Interpolator(new MapLookup(map));
+        final StrSubstitutor subst = new StrSubstitutor(lookup);
+        subst.setRecursiveEvaluationAllowed(true);
+        assertEquals("${ctx:first} and secondValue", subst.replace("${ctx:first} and ${ctx:second}"));
+    }
+
+    @Test
+    public void testRecursiveWithDefault() {
+        final Map<String, String> map = new HashMap<>();
+        map.put("first", "${ctx:first:-default}");
+        final StrLookup lookup = new Interpolator(new MapLookup(map));
+        final StrSubstitutor subst = new StrSubstitutor(lookup);
+        subst.setRecursiveEvaluationAllowed(true);
+        assertEquals("default", subst.replace("${ctx:first}"));
+    }
+
+    @Test
+    public void testRecursiveWithRecursiveDefault() {
+        final Map<String, String> map = new HashMap<>();
+        map.put("first", "${ctx:first:-${ctx:first}}");
+        final StrLookup lookup = new Interpolator(new MapLookup(map));
+        final StrSubstitutor subst = new StrSubstitutor(lookup);
+        subst.setRecursiveEvaluationAllowed(true);
+        assertEquals("${ctx:first}", subst.replace("${ctx:first}"));
+    }
+
+    @Test
+    public void testNestedSelfReferenceWithRecursiveEvaluation() {
+        final Map<String, String> map = new HashMap<>();
+        map.put("first", "${${ctx:first}}");
+        final StrLookup lookup = new Interpolator(new MapLookup(map));
+        final StrSubstitutor subst = new StrSubstitutor(lookup);
+        subst.setRecursiveEvaluationAllowed(true);
+        assertEquals("${${ctx:first}}", subst.replace("${ctx:first}"));
+    }
+
+    @Test
+    public void testRandomWithRecursiveDefault() {
+        final Map<String, String> map = new HashMap<>();
+        map.put("first", "${env:RANDOM:-${ctx:first}}");
+        final StrLookup lookup = new Interpolator(new MapLookup(map));
+        final StrSubstitutor subst = new StrSubstitutor(lookup);
+        subst.setRecursiveEvaluationAllowed(true);
+        assertEquals("${ctx:first}", subst.replace("${ctx:first}"));
+    }
+
+    @Test
+    public void testNoRecursiveEvaluationWithDefault() {
+        final Map<String, String> map = new HashMap<>();
+        map.put("first", "${java:version}");
+        map.put("second", "${java:runtime}");
+        final StrLookup lookup = new Interpolator(new MapLookup(map));
+        final StrSubstitutor subst = new StrSubstitutor(lookup);
+        subst.setRecursiveEvaluationAllowed(false);
+        assertEquals("${java:version}", subst.replace("${ctx:first:-${ctx:second}}"));
+    }
+
+    @Test
+    public void testNoRecursiveEvaluationWithDepthOne() {
+        final Map<String, String> map = new HashMap<>();
+        map.put("first", "${java:version}");
+        final StrLookup lookup = new Interpolator(new MapLookup(map));
+        final StrSubstitutor subst = new StrSubstitutor(lookup);
+        subst.setRecursiveEvaluationAllowed(false);
+        assertEquals("${java:version}", subst.replace("${ctx:first}"));
+    }
+
+    @Test
+    public void testLookupsNestedWithoutRecursiveEvaluation() {
+        final Map<String, String> map = new HashMap<>();
+        map.put("first", "${java:version}");
+        final StrLookup lookup = new Interpolator(new MapLookup(map));
+        final StrSubstitutor subst = new StrSubstitutor(lookup);
+        subst.setRecursiveEvaluationAllowed(false);
+        assertEquals("${java:version}", subst.replace("${${lower:C}t${lower:X}:first}"));
+    }
+
+    @Test
+    public void testLookupThrows() {
+        final StrSubstitutor subst = new StrSubstitutor(new Interpolator(new StrLookup() {
+
+            @Override
+            public String lookup(String key) {
+                if ("throw".equals(key)) {
+                    throw new RuntimeException();
+                }
+                return "success";
+            }
+
+            @Override
+            public String lookup(LogEvent event, String key) {
+                return lookup(key);
+            }
+        }));
+        subst.setRecursiveEvaluationAllowed(false);
+        assertEquals("success ${foo:throw} success", subst.replace("${foo:a} ${foo:throw} ${foo:c}"));
+    }
 }
diff --git a/log4j-core/src/test/resources/log4j-routing-2767.xml b/log4j-core/src/test/resources/log4j-routing-2767.xml
index 34b9c30..4002659 100644
--- a/log4j-core/src/test/resources/log4j-routing-2767.xml
+++ b/log4j-core/src/test/resources/log4j-routing-2767.xml
@@ -17,10 +17,6 @@
 
 -->
 <Configuration status="WARN" name="RoutingTest">
-  <Properties>
-    <Property name="filename">target/routing1/routingtest-$${sd:type}.log</Property>
-  </Properties>
-
   <Appenders>
     <Console name="STDOUT">
       <PatternLayout pattern="%m%n"/>
@@ -28,7 +24,7 @@
     <Routing name="Routing">
       <Routes>
         <Route>
-          <RollingFile name="Routing-${sd:type}" fileName="${filename}"
+          <RollingFile name="Routing-${sd:type}" fileName="target/routing1/routingtest-${sd:type}.log"
                        filePattern="target/routing1/test1-${sd:type}.%i.log.gz">
             <PatternLayout>
               <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
diff --git a/log4j-core/src/test/resources/log4j-routing-2767.xml b/log4j-core/src/test/resources/log4j-routing-lookup.xml
similarity index 55%
copy from log4j-core/src/test/resources/log4j-routing-2767.xml
copy to log4j-core/src/test/resources/log4j-routing-lookup.xml
index 34b9c30..92f31c3 100644
--- a/log4j-core/src/test/resources/log4j-routing-2767.xml
+++ b/log4j-core/src/test/resources/log4j-routing-lookup.xml
@@ -16,36 +16,22 @@
  limitations under the License.
 
 -->
-<Configuration status="WARN" name="RoutingTest">
-  <Properties>
-    <Property name="filename">target/routing1/routingtest-$${sd:type}.log</Property>
-  </Properties>
-
+<Configuration status="OFF" name="RoutingAppenderKeyLookupEvaluationTest">
   <Appenders>
-    <Console name="STDOUT">
-      <PatternLayout pattern="%m%n"/>
-    </Console>
+    <List name="List">
+      <PatternLayout pattern="%p $${ctx:user} $${event:Message}"/>
+    </List>
     <Routing name="Routing">
-      <Routes>
-        <Route>
-          <RollingFile name="Routing-${sd:type}" fileName="${filename}"
-                       filePattern="target/routing1/test1-${sd:type}.%i.log.gz">
-            <PatternLayout>
-              <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
-            </PatternLayout>
-            <SizeBasedTriggeringPolicy size="500" />
-          </RollingFile>
-        </Route>
+      <Routes pattern="$${ctx:user:-none}">
+        <Route ref="List" key="$${upper:name}"/>
+        <Route ref="List" key="none"/>
+        <Route ref="List" key="$${java:version}"/>
       </Routes>
     </Routing>
   </Appenders>
 
   <Loggers>
-    <Logger name="EventLogger" level="info" additivity="false">
-      <AppenderRef ref="Routing"/>
-    </Logger>
-
-    <Root level="info">
+    <Root level="debug">
       <AppenderRef ref="Routing"/>
     </Root>
   </Loggers>
diff --git a/log4j-core/src/test/resources/log4j-routing-purge.xml b/log4j-core/src/test/resources/log4j-routing-purge.xml
index d1de288..910ba80 100644
--- a/log4j-core/src/test/resources/log4j-routing-purge.xml
+++ b/log4j-core/src/test/resources/log4j-routing-purge.xml
@@ -17,10 +17,6 @@
 
 -->
 <Configuration status="OFF" name="RoutingTest">
-  <Properties>
-    <Property name="filename-idle">target/routing-purge-idle/routingtest-$${sd:id}.log</Property>
-    <Property name="filename-manual">target/routing-purge-manual/routingtest-$${sd:id}.log</Property>
-  </Properties>
   <ThresholdFilter level="debug"/>
 
   <Appenders>
@@ -34,7 +30,7 @@
     <Routing name="RoutingPurgeIdle">
       <Routes pattern="$${sd:id}">
         <Route>
-          <File name="Routing-${sd:id}" fileName="${filename-idle}">
+          <File name="Routing-${sd:id}" fileName="target/routing-purge-idle/routingtest-${sd:id}.log">
             <PatternLayout>
               <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
             </PatternLayout>
@@ -58,7 +54,7 @@
     <Routing name="RoutingPurgeManual">
       <Routes pattern="$${sd:id}">
         <Route>
-          <File name="Routing-${sd:id}" fileName="${filename-manual}">
+          <File name="Routing-${sd:id}" fileName="target/routing-purge-manual/routingtest-${sd:id}.log">
             <PatternLayout>
               <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
             </PatternLayout>
diff --git a/log4j-core/src/test/resources/log4j-routing.json b/log4j-core/src/test/resources/log4j-routing.json
index 809b3c2..4322d8d 100644
--- a/log4j-core/src/test/resources/log4j-routing.json
+++ b/log4j-core/src/test/resources/log4j-routing.json
@@ -15,9 +15,6 @@
  * limitations under the license.
  */
 { "configuration": { "status": "error", "name": "RoutingTest",
-      "properties": {
-        "property": { "name": "filename", "value" : "target/rolling1/rollingtest-$${sd:type}.log" }
-      },
     "ThresholdFilter": { "level": "debug" },
     "appenders": {
       "Console": { "name": "STDOUT",
@@ -31,7 +28,7 @@
           "Route": [
             {
               "RollingFile": {
-                "name": "Rolling-${sd:type}", "fileName": "${filename}",
+                "name": "Rolling-${sd:type}", "fileName": "target/rolling1/rollingtest-${sd:type}.log",
                 "filePattern": "target/rolling1/test1-${sd:type}.%i.log.gz",
                 "PatternLayout": {"pattern": "%d %p %C{1.} [%t] %m%n"},
                 "SizeBasedTriggeringPolicy": { "size": "500" }
diff --git a/log4j-core/src/test/resources/log4j-routing.properties b/log4j-core/src/test/resources/log4j-routing.properties
index c365e35..0dc7547 100644
--- a/log4j-core/src/test/resources/log4j-routing.properties
+++ b/log4j-core/src/test/resources/log4j-routing.properties
@@ -16,8 +16,6 @@
 status = error
 name = RoutingTest
 
-property.filename = target/routing1/routingtestProps-$${sd:type}.log
-
 filter.threshold.type = ThresholdFilter
 filter.threshold.level = debug
 
@@ -33,7 +31,7 @@ appender.routing.routes.pattern = $${sd:type}
 appender.routing.routes.route1.type = Route
 appender.routing.routes.route1.rolling.type = RollingFile
 appender.routing.routes.route1.rolling.name = Routing-${sd:type}
-appender.routing.routes.route1.rolling.fileName = ${filename}
+appender.routing.routes.route1.rolling.fileName = target/routing1/routingtestProps-${sd:type}.log
 appender.routing.routes.route1.rolling.filePattern = target/routing1/test1-${sd:type}.%i.log.gz
 appender.routing.routes.route1.rolling.layout.type = PatternLayout
 appender.routing.routes.route1.rolling.layout.pattern = %d %p %C{1.} [%t] %m%n
diff --git a/log4j-core/src/test/resources/log4j-routing.xml b/log4j-core/src/test/resources/log4j-routing.xml
index 4d83886..003f7d7 100644
--- a/log4j-core/src/test/resources/log4j-routing.xml
+++ b/log4j-core/src/test/resources/log4j-routing.xml
@@ -17,9 +17,6 @@
 
 -->
 <Configuration status="OFF" name="RoutingTest">
-  <Properties>
-    <Property name="filename">target/routing1/routingtest-$${sd:type}.log</Property>
-  </Properties>
   <ThresholdFilter level="debug"/>
 
   <Appenders>
@@ -32,7 +29,7 @@
     <Routing name="Routing">
       <Routes pattern="$${sd:type}">
         <Route>
-          <RollingFile name="Routing-${sd:type}" fileName="${filename}"
+          <RollingFile name="Routing-${sd:type}" fileName="target/routing1/routingtest-${sd:type}.log"
                        filePattern="target/routing1/test1-${sd:type}.%i.log.gz">
             <PatternLayout>
               <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
diff --git a/log4j-core/src/test/resources/log4j-routing2.json b/log4j-core/src/test/resources/log4j-routing2.json
index fe50b37..af67454 100644
--- a/log4j-core/src/test/resources/log4j-routing2.json
+++ b/log4j-core/src/test/resources/log4j-routing2.json
@@ -15,9 +15,6 @@
  * limitations under the license.
  */
 { "configuration": { "status": "error", "name": "RoutingTest",
-      "properties": {
-        "property": { "name": "filename", "value" : "target/rolling1/rollingtest-$${sd:type}.log" }
-      },
     "ThresholdFilter": { "level": "debug" },
     "appenders": {
       "appender": [
@@ -28,7 +25,7 @@
             "Route": [
               {
                 "RollingFile": {
-                  "name": "Rolling-${sd:type}", "fileName": "${filename}",
+                  "name": "Rolling-${sd:type}", "fileName": "target/rolling1/rollingtest-${sd:type}.log",
                   "filePattern": "target/rolling1/test1-${sd:type}.%i.log.gz",
                   "PatternLayout": {"pattern": "%d %p %C{1.} [%t] %m%n"},
                   "SizeBasedTriggeringPolicy": { "size": "500" }
diff --git a/log4j-core/src/test/resources/log4j2-pattern-layout-with-context.xml b/log4j-core/src/test/resources/log4j2-pattern-layout-with-context.xml
new file mode 100644
index 0000000..281e311
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j2-pattern-layout-with-context.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+
+-->
+<Configuration status="DEBUG" packages="">
+	<Properties>
+		<Property name="pattern">%p %c $${ctx:user} $${event:Message}</Property>
+	</Properties>
+	<Appenders>
+		<List name="list">
+			<PatternLayout pattern="${pattern}"/>
+		</List>
+	</Appenders>
+
+	<Loggers>
+		<Root level="INFO">
+			<AppenderRef ref="list"/>
+		</Root>
+	</Loggers>
+</Configuration>
diff --git a/log4j-jakarta-web/src/main/java/org/apache/logging/log4j/web/Log4jWebInitializerImpl.java b/log4j-jakarta-web/src/main/java/org/apache/logging/log4j/web/Log4jWebInitializerImpl.java
index bb5d51d..b56270b 100644
--- a/log4j-jakarta-web/src/main/java/org/apache/logging/log4j/web/Log4jWebInitializerImpl.java
+++ b/log4j-jakarta-web/src/main/java/org/apache/logging/log4j/web/Log4jWebInitializerImpl.java
@@ -24,6 +24,7 @@ import org.apache.logging.log4j.core.async.AsyncLoggerContext;
 import org.apache.logging.log4j.core.config.Configurator;
 import org.apache.logging.log4j.core.impl.ContextAnchor;
 import org.apache.logging.log4j.core.impl.Log4jContextFactory;
+import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor;
 import org.apache.logging.log4j.core.lookup.Interpolator;
 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
 import org.apache.logging.log4j.core.selector.ContextSelector;
@@ -57,7 +58,7 @@ final class Log4jWebInitializerImpl extends AbstractLifeCycle implements Log4jWe
     }
 
     private final Map<String, String> map = new ConcurrentHashMap<>();
-    private final StrSubstitutor substitutor = new StrSubstitutor(new Interpolator(map));
+    private final StrSubstitutor substitutor = new ConfigurationStrSubstitutor(new Interpolator(map));
     private final ServletContext servletContext;
 
     private String name;
diff --git a/log4j-samples/log4j-samples-loggerProperties/src/main/java/org/apache/logging/log4j/lookup/CustomLookup.java b/log4j-samples/log4j-samples-loggerProperties/src/main/java/org/apache/logging/log4j/lookup/CustomLookup.java
index 8bd3415..2f9108e 100644
--- a/log4j-samples/log4j-samples-loggerProperties/src/main/java/org/apache/logging/log4j/lookup/CustomLookup.java
+++ b/log4j-samples/log4j-samples-loggerProperties/src/main/java/org/apache/logging/log4j/lookup/CustomLookup.java
@@ -50,6 +50,9 @@ public class CustomLookup extends AbstractLookup {
      */
     @Override
     public String lookup(final LogEvent event, final String key) {
+        if (event == null) {
+            return null;
+        }
         try {
             final Map<String, String> properties = loggerProperties.get(event.getLoggerName());
             if (properties == null) {
diff --git a/log4j-samples/log4j-samples-loggerProperties/src/main/java/org/apache/logging/log4j/lookup/MapMessageLookup.java b/log4j-samples/log4j-samples-loggerProperties/src/main/java/org/apache/logging/log4j/lookup/MapMessageLookup.java
index 7cd7885..f41ce41 100644
--- a/log4j-samples/log4j-samples-loggerProperties/src/main/java/org/apache/logging/log4j/lookup/MapMessageLookup.java
+++ b/log4j-samples/log4j-samples-loggerProperties/src/main/java/org/apache/logging/log4j/lookup/MapMessageLookup.java
@@ -51,6 +51,9 @@ public class MapMessageLookup extends AbstractLookup {
      */
     @Override
     public String lookup(final LogEvent event, final String key) {
+        if (event == null) {
+            return null;
+        }
         final Message msg = event.getMessage();
         if (msg instanceof MapMessage) {
             try {
diff --git a/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jWebInitializerImpl.java b/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jWebInitializerImpl.java
index 323ccbb..32b08c4 100644
--- a/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jWebInitializerImpl.java
+++ b/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jWebInitializerImpl.java
@@ -23,6 +23,7 @@ import org.apache.logging.log4j.core.async.AsyncLoggerContext;
 import org.apache.logging.log4j.core.config.Configurator;
 import org.apache.logging.log4j.core.impl.ContextAnchor;
 import org.apache.logging.log4j.core.impl.Log4jContextFactory;
+import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor;
 import org.apache.logging.log4j.core.lookup.Interpolator;
 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
 import org.apache.logging.log4j.core.selector.ContextSelector;
@@ -57,7 +58,7 @@ final class Log4jWebInitializerImpl extends AbstractLifeCycle implements Log4jWe
     }
 
     private final Map<String, String> map = new ConcurrentHashMap<>();
-    private final StrSubstitutor substitutor = new StrSubstitutor(new Interpolator(map));
+    private final StrSubstitutor substitutor = new ConfigurationStrSubstitutor(new Interpolator(map));
     private final ServletContext servletContext;
 
     private String name;
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 08e3fd5..2f9473b 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -30,10 +30,17 @@
          - "remove" - Removed
     -->
     <release version="2.17.0" date="2021-MM-dd" description="GA Release 2.17.0">
+      <action issue="LOG4J2-3230" dev="ckozak" type="fix">
+        Fix string substitution recursion.
+      </action>
       <action issue="LOG4J2-3242" dev="rgoers, ggregory" type="fix">
         Limit JNDI to the java protocol only. JNDI will remain disabled by default. Rename JNDI enablement property from
         'log4j2.enableJndi' to 'log4j2.enableJndiLookup', 'log4j2.enableJndiJms', and 'log4j2.enableJndiContextSelector'.
       </action>
+      <action issue="LOG4J2-3242" dev="rgoers" type="fix">
+        Limit JNDI to the java protocol only. JNDI will remain disabled by default. The enablement
+        property has been renamed to 'log4j2.enableJndiJava'
+      </action>
       <action issue="LOG4J2-3241" dev="rgoers" type="fix">
         Do not declare log4j-api-java9 and log4j-core-java9 as dependencies as it causes problems with the
         Maven enforcer plugin.

[logging-log4j2] 14/16: [DOC] fix property name, should be `enableJndiContextSelector`

Posted by ck...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 8868bac2b9636e87f5e999187b462ad31de618b8
Author: Remko Popma <re...@yahoo.com>
AuthorDate: Sat Dec 18 09:53:09 2021 +0900

    [DOC] fix property name, should be `enableJndiContextSelector`
---
 src/site/xdoc/manual/logsep.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/site/xdoc/manual/logsep.xml b/src/site/xdoc/manual/logsep.xml
index a0ae707..17dc7dd 100644
--- a/src/site/xdoc/manual/logsep.xml
+++ b/src/site/xdoc/manual/logsep.xml
@@ -111,9 +111,9 @@
                   to use JNDI to locate each web application's <code>LoggerContext</code>. Be sure to set the
                   <code>isLog4jContextSelectorNamed</code> context parameter to <kbd>true</kbd> and also set the
                   <code>log4jContextName</code> and <code>log4jConfiguration</code> context parameters.
-                  Note that the JndiContextSelector will not work unless <code>log4j2.enableJndiJava=true</code> is set as a
+                  Note that the JndiContextSelector will not work unless <code>log4j2.enableJndiContextSelector=true</code> is set as a
                   system property or environment variable. See the
-                  <a href="./configuration.html#enableJndiJava">enableJndiJava</a> system property.
+                  <a href="./configuration.html#enableJndiContextSelector">enableJndiContextSelector</a> system property.
                 </li>
               </ol>
             <p>

[logging-log4j2] 13/16: [DOC] mention property `enableJndiLookup` is required for JNDI lookup

Posted by ck...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit c8c4242013f9223b4d99654ee4924096049587b1
Author: Remko Popma <re...@yahoo.com>
AuthorDate: Sat Dec 18 09:51:40 2021 +0900

    [DOC] mention property `enableJndiLookup` is required for JNDI lookup
---
 src/site/xdoc/manual/configuration.xml.vm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/site/xdoc/manual/configuration.xml.vm b/src/site/xdoc/manual/configuration.xml.vm
index 86acc17..7ddd97c 100644
--- a/src/site/xdoc/manual/configuration.xml.vm
+++ b/src/site/xdoc/manual/configuration.xml.vm
@@ -1216,7 +1216,7 @@ rootLogger.appenderRef.stdout.ref = STDOUT
               </tr>
               <tr>
                 <td>jndi</td>
-                <td>A value set in the default JNDI Context.</td>
+                <td>A value set in the default JNDI Context. (Requires system property <code>log4j2.enableJndiLookup</code> to be set to <code>true</code>.)</td>
               </tr>
               <tr>
                 <td>jvmrunargs</td>

[logging-log4j2] 03/16: Prep for releaes

Posted by ck...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 0b980aba0680e4984d64eb89e02d5c1561405202
Author: Ralph Goers <rg...@apache.org>
AuthorDate: Fri Dec 17 16:03:27 2021 -0700

    Prep for releaes
---
 pom.xml                                   |  6 +++---
 src/site/markdown/index.md.vm             |  6 ++++++
 src/site/markdown/security.md             | 16 ++++++----------
 src/site/xdoc/manual/appenders.xml        | 27 ---------------------------
 src/site/xdoc/manual/configuration.xml.vm | 26 --------------------------
 5 files changed, 15 insertions(+), 66 deletions(-)

diff --git a/pom.xml b/pom.xml
index 4ff075f..8003f1a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -188,13 +188,13 @@
   <properties>
     <!-- make sure to update these for each release! -->
     <log4jParentDir>${basedir}</log4jParentDir>
-    <Log4jReleaseVersion>2.16.0</Log4jReleaseVersion>
+    <Log4jReleaseVersion>2.17.0</Log4jReleaseVersion>
     <Log4jReleaseVersionJava7>2.12.2</Log4jReleaseVersionJava7>
     <Log4jReleaseVersionJava6>2.3</Log4jReleaseVersionJava6>
     <!--Log4jReleaseManager>Ralph Goers</Log4jReleaseManager-->
     <!--Log4jReleaseKey>B3D8E1BA</Log4jReleaseKey-->
-    <Log4jReleaseManager>Matt Sicker</Log4jReleaseManager>
-    <Log4jReleaseKey>748F15B2CF9BA8F024155E6ED7C92B70FA1C814D</Log4jReleaseKey>
+    <Log4jReleaseManager>Ralph Goers</Log4jReleaseManager>
+    <Log4jReleaseKey>B3D8E1BA</Log4jReleaseKey>
     <!-- note that any properties you want available in velocity templates must not use periods! -->
     <slf4jVersion>1.7.25</slf4jVersion>
     <logbackVersion>1.2.3</logbackVersion>
diff --git a/src/site/markdown/index.md.vm b/src/site/markdown/index.md.vm
index 4e4502f..400ced9 100644
--- a/src/site/markdown/index.md.vm
+++ b/src/site/markdown/index.md.vm
@@ -45,6 +45,12 @@ $h4 Mitigation
 From version 2.17.0 (for Java 8), only lookup strings in configuration are expanded recursively;
 in any other usage, only the top-level lookup is resolved, and any nested lookups are not resolved.
 
+In prior releases this issue can be mitigated by ensuring your logging configuration does the following:
+* Replace Context Lookups like `$${ctx:loginId}` in PatternLayout with Thread Context Map patterns (%X, %mdc, or %MDC)
+  in the logging configuration.
+* Remove refrences to Context Lookups like `$${ctx:loginId}` in the configuration where they originate
+  from sources external to the application such as HTTP headers or user input.
+
 $h4 Reference
 Please refer to the [Security page](security.html#CVE-2021-45105) for details and mitigation measures for older versions of Log4j.
 
diff --git a/src/site/markdown/security.md b/src/site/markdown/security.md
index f1a1e05..3409747 100644
--- a/src/site/markdown/security.md
+++ b/src/site/markdown/security.md
@@ -54,8 +54,8 @@ Apache Log4j2 does not always protect from infinite recursion in lookup evaluati
 
 | [CVE-2021-45105](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-45105) | Denial of Service |
 | ---------------   | -------- |
-| Severity          | XXXXX |
-| Base CVSS Score   | X.X (XXXX) |
+| Severity          | High |
+| Base CVSS Score   | 7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H) |
 | Versions Affected | All versions from 2.0-beta9 to 2.16.0 |
 
 ### Description
@@ -64,13 +64,6 @@ When the logging configuration uses a non-default Pattern Layout with a Context
 attackers with control over Thread Context Map (MDC) input data can craft malicious input data that contains a recursive lookup,
 resulting in a StackOverflowError that will terminate the process. This is also known as a DOS (Denial of Service) attack.
 
-$h4 Mitigation
-From version 2.17.0 (for Java 8), only lookup strings in configuration are expanded recursively;
-in any other usage, only the top-level lookup is resolved, and any nested lookups are not resolved.
-Furthermore, error handling has been made more extensive, to catch all Throwables that arise in logging code instead of only Exceptions.
-Finally, when JNDI is enabled, it will only support the `java` protocol.
-The property to enable JNDI has been renamed to `log4j2.enableJndiJava` from `log4j2.enableJndi`.
-
 
 ### Mitigation
 
@@ -83,7 +76,10 @@ Log4j 1.x is not impacted by this vulnerability.
 Implement one of the following mitigation techniques:
 
 * Java 8 (or later) users should upgrade to release 2.17.0.
-* Replace Context Lookups like `$${ctx:loginId}` with Thread Context Map patterns (%X, %mdc, or %MDC) in the logging configuration.
+* Replace Context Lookups like `$${ctx:loginId}` in PatternLayout with Thread Context Map patterns (%X, %mdc, or %MDC) 
+in the logging configuration.
+* Remove refrences to Context Lookups like `$${ctx:loginId}` in the configuration where they originate 
+from sources external to the application such as HTTP headers or user input.
 
 Note that only the log4j-core JAR file is impacted by this vulnerability.
 Applications using only the log4j-api JAR file without the log4j-core JAR file are not impacted by this vulnerability.
diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml
index 35d88c0..a59f2e4 100644
--- a/src/site/xdoc/manual/appenders.xml
+++ b/src/site/xdoc/manual/appenders.xml
@@ -1559,33 +1559,6 @@ public class ConnectionFactory {
               <th>Description</th>
             </tr>
             <tr>
-              <td>allowdLdapClasses</td>
-              <td>String</td>
-              <td>null</td>
-              <td>
-                A comma separated list of fully qualified class names that may be accessed by LDAP. The classes
-                must implement Serializable. Only applies when the JMS Appender By default only Java primitive classes are allowed.
-              </td>
-            </tr>
-            <tr>
-              <td>allowdLdapHosts</td>
-              <td>String</td>
-              <td>null</td>
-              <td>
-                A comma separated list of host names or ip addresses that may be accessed by LDAP. By default only
-                the local host names and ip addresses are allowed.
-              </td>
-            </tr>
-            <tr>
-              <td>allowdJndiProtocols</td>
-              <td>String</td>
-              <td>null</td>
-              <td>
-                A comma separated list of protocol names that JNDI will allow. By default only java, ldap, and ldaps
-                are the only allowed protocols.
-              </td>
-            </tr>
-            <tr>
               <td>factoryBindingName</td>
               <td>String</td>
               <td><em>Required</em></td>
diff --git a/src/site/xdoc/manual/configuration.xml.vm b/src/site/xdoc/manual/configuration.xml.vm
index 2c1c315..86acc17 100644
--- a/src/site/xdoc/manual/configuration.xml.vm
+++ b/src/site/xdoc/manual/configuration.xml.vm
@@ -2173,32 +2173,6 @@ public class AwesomeTest {
     </td>
   </tr>
   <tr>
-    <td><a name="allowedLdapClasses"/>log4j2.allowedLdapClasses</td>
-    <td>LOG4J_ALLOWED_LDAP_CLASSES</td>
-    <td>&nbsp;</td>
-    <td>
-      System property that specifies fully qualified class names that may be accessed by LDAP. The classes
-      must implement Serializable. By default only Java primitive classes are allowed.
-    </td>
-  </tr>
-  <tr>
-    <td><a name="allowedLdapHosts"/>log4j2.allowedLdapHosts</td>
-    <td>LOG4J_ALLOWED_LDAP_HOSTS</td>
-    <td>&nbsp;</td>
-    <td>
-      System property that adds host names or ip addresses that may be access by LDAP. By default it only allows
-      the local host names and ip addresses.
-    </td>
-  </tr>
-  <tr>
-    <td><a name="allowedJndiProtocols"/>log4j2.allowedJndiProtocols</td>
-    <td>LOG4J_ALLOWED_JNDI_PROTOCOLS</td>
-    <td>&nbsp;</td>
-    <td>
-      System property that adds protocol names that JNDI will allow. By default it only allows java, ldap, and ldaps.
-    </td>
-  </tr>
-  <tr>
     <td><a name="uuidSequence"/>log4j2.uuidSequence
       <br />
       (<a name="org.apache.logging.log4j.uuidSequence"/>org.apache.logging.log4j.uuidSequence)

[logging-log4j2] 12/16: Update pages

Posted by ck...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 42949922fd66e321dd17bdd0df754354f7dc248c
Author: Ralph Goers <rg...@apache.org>
AuthorDate: Fri Dec 17 17:42:39 2021 -0700

    Update pages
---
 src/site/markdown/index.md.vm      | 15 ++++++---------
 src/site/xdoc/manual/appenders.xml |  2 +-
 src/site/xdoc/manual/lookups.xml   |  2 +-
 3 files changed, 8 insertions(+), 11 deletions(-)

diff --git a/src/site/markdown/index.md.vm b/src/site/markdown/index.md.vm
index a157009..0e358bf 100644
--- a/src/site/markdown/index.md.vm
+++ b/src/site/markdown/index.md.vm
@@ -237,11 +237,13 @@ dependencies.
 
 $h2 News
 
-Log4j 2.16.0 has been released solely to disable access to JNDI by default and completely remove the ability to use Lookups in messages.
-The CVE noted below was fixed in the 2.15.0 release.
-2.16.0 is a recommended upgrade to ensure that JNDI will not be abused and that message Lookups are no longer possible.
+Log4j 2.17.0 has been released solely to:
 
-$h3 Other News
+* Address CVE-2021-45105.
+* Require components that use JNDI to be enabled individually via system properties.
+* Remove LDAP and LDAPS as supported protocols from JNDI.
+
+2.17.0 is a recommended upgrade to ensure that recursive lookups do not cause services to fail.
 
 Log4j $Log4jReleaseVersion is now available for production. The API for Log4j 2 is not compatible with Log4j 1.x, however an adapter is
 available to allow applications to continue to use the Log4j 1.x API. Adapters are also available for Apache Commons
@@ -251,9 +253,4 @@ Log4j $Log4jReleaseVersion is the latest release of Log4j. As of Log4j 2.13.0 Lo
 runtime. This release contains new features and fixes which can be found
 in the latest [changes report](changes-report.html#a$Log4jReleaseVersion).
 
-The changes in Log4j 2.16.0 are:
-
-* Disabling JNDI functionality by default.
-* Removing Message Lookups.
-
 Log4j $Log4jReleaseVersion maintains binary compatibility with previous releases.
diff --git a/src/site/xdoc/manual/appenders.xml b/src/site/xdoc/manual/appenders.xml
index d7035ab..bcde753 100644
--- a/src/site/xdoc/manual/appenders.xml
+++ b/src/site/xdoc/manual/appenders.xml
@@ -1540,7 +1540,7 @@ public class ConnectionFactory {
         <a name="JMSTopicAppender"/>
         <subsection name="JMS Appender">
           <p>The JMS Appender sends the formatted log event to a JMS Destination.</p>
-          <p>The JMS Appender requires JNDI support so as of release 2.16.0 this appender will not function unless
+          <p>The JMS Appender requires JNDI support so as of release 2.17.0 this appender will not function unless
             <code>log4j2.enableJndiJms=true</code> is configured as a system property or environment
             variable. See the <a href="./configuration.html#enableJndiJms">enableJndiJms</a> system property.</p>
           <p>
diff --git a/src/site/xdoc/manual/lookups.xml b/src/site/xdoc/manual/lookups.xml
index 9bf6b80..82051de 100644
--- a/src/site/xdoc/manual/lookups.xml
+++ b/src/site/xdoc/manual/lookups.xml
@@ -267,7 +267,7 @@
         <a name="JndiLookup"/>
         <subsection name="Jndi Lookup">
           <p>
-            As of Log4j 2.16.0 JNDI operations require that <code>log4j2.enableJndiLookup=true</code> be set as a system
+            As of Log4j 2.17.0 JNDI operations require that <code>log4j2.enableJndiLookup=true</code> be set as a system
             property or the corresponding environment variable for this lookup to function. See the
             <a href="./configuration.html#enableJndiLookup">enableJndiLookup</a> system property.
           </p>

[logging-log4j2] 05/16: [DOC] fix typos and rephrase mitigation for CVE-2021-45105

Posted by ck...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 5476a16845cb0b98e0eec711b11d0df8f41fb71d
Author: Remko Popma <re...@yahoo.com>
AuthorDate: Sat Dec 18 08:30:51 2021 +0900

    [DOC] fix typos and rephrase mitigation for CVE-2021-45105
---
 src/site/markdown/index.md.vm | 6 ++----
 src/site/markdown/security.md | 8 +++++---
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/site/markdown/index.md.vm b/src/site/markdown/index.md.vm
index b96e581..74ae75a 100644
--- a/src/site/markdown/index.md.vm
+++ b/src/site/markdown/index.md.vm
@@ -46,10 +46,8 @@ From version 2.17.0 (for Java 8), only lookup strings in configuration are expan
 in any other usage, only the top-level lookup is resolved, and any nested lookups are not resolved.
 
 In prior releases this issue can be mitigated by ensuring your logging configuration does the following:
-* Replace Context Lookups like `${dollar}${dollar}{ctx:loginId}` in PatternLayout with Thread Context Map patterns (%X, %mdc, or %MDC)
-  in the logging configuration.
-* Remove refrences to Context Lookups like `${dollar}${dollar}{ctx:loginId}` in the configuration where they originate
-  from sources external to the application such as HTTP headers or user input.
+* In PatternLayout in the logging configuration, replace Context Lookups like `${dollar}{ctx:loginId}`or `${dollar}${dollar}{ctx:loginId}` with Thread Context Map patterns (%X, %mdc, or %MDC).
+* Otherwise, in the configuration, remove references to Context Lookups like `${dollar}{ctx:loginId}` or `${dollar}${dollar}{ctx:loginId}` where they originate from sources external to the application such as HTTP headers or user input.
 
 $h4 Reference
 Please refer to the [Security page](security.html#CVE-2021-45105) for details and mitigation measures for older versions of Log4j.
diff --git a/src/site/markdown/security.md b/src/site/markdown/security.md
index 3409747..9b04065 100644
--- a/src/site/markdown/security.md
+++ b/src/site/markdown/security.md
@@ -76,9 +76,11 @@ Log4j 1.x is not impacted by this vulnerability.
 Implement one of the following mitigation techniques:
 
 * Java 8 (or later) users should upgrade to release 2.17.0.
-* Replace Context Lookups like `$${ctx:loginId}` in PatternLayout with Thread Context Map patterns (%X, %mdc, or %MDC) 
-in the logging configuration.
-* Remove refrences to Context Lookups like `$${ctx:loginId}` in the configuration where they originate 
+
+Alternatively, this can be mitigated in configuration:
+
+* In PatternLayout in the logging configuration, replace Context Lookups like `${ctx:loginId}` or `$${ctx:loginId}` with Thread Context Map patterns (%X, %mdc, or %MDC).
+* Otherwise, in the configuration, remove references to Context Lookups like `${ctx:loginId}` or `$${ctx:loginId}` where they originate 
 from sources external to the application such as HTTP headers or user input.
 
 Note that only the log4j-core JAR file is impacted by this vulnerability.

[logging-log4j2] 07/16: [DOC] fix typo: this is not a velocity template page

Posted by ck...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 5fa35e116e00dfbf49e0b65492191fb189930842
Author: Remko Popma <re...@yahoo.com>
AuthorDate: Sat Dec 18 09:11:48 2021 +0900

    [DOC] fix typo: this is not a velocity template page
---
 src/site/markdown/security.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/site/markdown/security.md b/src/site/markdown/security.md
index 9b04065..b047964 100644
--- a/src/site/markdown/security.md
+++ b/src/site/markdown/security.md
@@ -60,7 +60,7 @@ Apache Log4j2 does not always protect from infinite recursion in lookup evaluati
 
 ### Description
 Apache Log4j2 versions 2.0-alpha1 through 2.16.0 did not protect from uncontrolled recursion from self-referential lookups.
-When the logging configuration uses a non-default Pattern Layout with a Context Lookup (for example, ``${dollar}${dollar}{ctx:loginId}``),
+When the logging configuration uses a non-default Pattern Layout with a Context Lookup (for example, ``$${ctx:loginId}``),
 attackers with control over Thread Context Map (MDC) input data can craft malicious input data that contains a recursive lookup,
 resulting in a StackOverflowError that will terminate the process. This is also known as a DOS (Denial of Service) attack.
 

[logging-log4j2] 02/16: [DOC] add CVE-2021-45105 for 2.17.0 and 2.12.3

Posted by ck...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit ff844c0a4d8eb4afe260494be1c2dc1b52cbf50d
Author: Remko Popma <re...@yahoo.com>
AuthorDate: Fri Dec 17 19:14:46 2021 +0900

    [DOC] add CVE-2021-45105 for 2.17.0 and 2.12.3
---
 src/site/markdown/index.md.vm | 26 +++++++++++++++++--
 src/site/markdown/security.md | 59 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 83 insertions(+), 2 deletions(-)

diff --git a/src/site/markdown/index.md.vm b/src/site/markdown/index.md.vm
index 3507e3e..4e4502f 100644
--- a/src/site/markdown/index.md.vm
+++ b/src/site/markdown/index.md.vm
@@ -27,6 +27,28 @@ Apache Log4j 2 is an upgrade to Log4j that provides significant improvements ove
 provides many of the improvements available in Logback while fixing some inherent problems in Logback's architecture.
 
 
+<a name="CVE-2021-45105"/>
+$h2 Important: Security Vulnerability CVE-2021-45105
+
+The Log4j team has been made aware of a security vulnerability, CVE-2021-45105, that has been addressed in
+Log4j 2.17.0 for Java 8 and up.
+
+Summary: Apache Log4j2 does not always protect from infinite recursion in lookup evaluation.
+
+$h4 Details
+Apache Log4j2 versions 2.0-alpha1 through 2.16.0 did not protect from uncontrolled recursion from self-referential lookups.
+When the logging configuration uses a non-default Pattern Layout with a Context Lookup (for example, ``${dollar}${dollar}{ctx:loginId}``),
+attackers with control over Thread Context Map (MDC) input data can craft malicious input data that contains a recursive lookup,
+resulting in a StackOverflowError that will terminate the process. This is also known as a DOS (Denial of Service) attack.
+
+$h4 Mitigation
+From version 2.17.0 (for Java 8), only lookup strings in configuration are expanded recursively;
+in any other usage, only the top-level lookup is resolved, and any nested lookups are not resolved.
+
+$h4 Reference
+Please refer to the [Security page](security.html#CVE-2021-45105) for details and mitigation measures for older versions of Log4j.
+
+
 <a name="CVE-2021-45046"/>
 $h2 Important: Security Vulnerability CVE-2021-45046
 
@@ -111,9 +133,9 @@ in front of other logging implementations such as Logback. The Log4j API has sev
 2. The Log4j API supports lambda expressions.
 3. The Log4j API provides many more logging methods than SLF4J.
 4. In addition to the "parameterized logging" format supported by SLF4J, the Log4j API also supports events using
-the java.text.MessageFormat syntax as well printf-style messages.
+   the java.text.MessageFormat syntax as well printf-style messages.
 5. The Log4j API provides a LogManager.shutdown() method. The underlying logging implementation must implement the
-Terminable interface for the method to have effect.
+   Terminable interface for the method to have effect.
 6. Other constructs such as Markers, log Levels, and ThreadContext (aka MDC) are fully supported.
 
 $h3 Improved Performance
diff --git a/src/site/markdown/security.md b/src/site/markdown/security.md
index c6d8ff6..f1a1e05 100644
--- a/src/site/markdown/security.md
+++ b/src/site/markdown/security.md
@@ -46,6 +46,65 @@ that has security impact, or if the descriptions here are incomplete, please rep
 privately to the [Log4j Security Team](mailto:private@logging.apache.org). Thank you.
 
 
+<a name="CVE-2021-45105"/><a name="cve-2021-45046"/>
+## <a name="log4j-2.17.0"/> Fixed in Log4j 2.17.0 (Java 8)
+
+[CVE-2021-45105](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-45105):  
+Apache Log4j2 does not always protect from infinite recursion in lookup evaluation
+
+| [CVE-2021-45105](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-45105) | Denial of Service |
+| ---------------   | -------- |
+| Severity          | XXXXX |
+| Base CVSS Score   | X.X (XXXX) |
+| Versions Affected | All versions from 2.0-beta9 to 2.16.0 |
+
+### Description
+Apache Log4j2 versions 2.0-alpha1 through 2.16.0 did not protect from uncontrolled recursion from self-referential lookups.
+When the logging configuration uses a non-default Pattern Layout with a Context Lookup (for example, ``${dollar}${dollar}{ctx:loginId}``),
+attackers with control over Thread Context Map (MDC) input data can craft malicious input data that contains a recursive lookup,
+resulting in a StackOverflowError that will terminate the process. This is also known as a DOS (Denial of Service) attack.
+
+$h4 Mitigation
+From version 2.17.0 (for Java 8), only lookup strings in configuration are expanded recursively;
+in any other usage, only the top-level lookup is resolved, and any nested lookups are not resolved.
+Furthermore, error handling has been made more extensive, to catch all Throwables that arise in logging code instead of only Exceptions.
+Finally, when JNDI is enabled, it will only support the `java` protocol.
+The property to enable JNDI has been renamed to `log4j2.enableJndiJava` from `log4j2.enableJndi`.
+
+
+### Mitigation
+
+#### Log4j 1.x mitigation
+
+Log4j 1.x is not impacted by this vulnerability.
+
+#### Log4j 2.x mitigation
+
+Implement one of the following mitigation techniques:
+
+* Java 8 (or later) users should upgrade to release 2.17.0.
+* Replace Context Lookups like `$${ctx:loginId}` with Thread Context Map patterns (%X, %mdc, or %MDC) in the logging configuration.
+
+Note that only the log4j-core JAR file is impacted by this vulnerability.
+Applications using only the log4j-api JAR file without the log4j-core JAR file are not impacted by this vulnerability.
+
+Also note that Apache Log4j is the only Logging Services subproject affected by this vulnerability.
+Other projects like Log4net and Log4cxx are not impacted by this.
+
+### Work in progress
+The Log4j team will continue to actively update this page as more information becomes known.
+
+### Credit
+This issue was discovered by Hideki Okamoto of Akamai Technologies and another anonymous vulnerability researcher.
+
+### References
+- [CVE-2021-45105](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-45105)
+- [LOG4J2-3230](https://issues.apache.org/jira/browse/LOG4J2-3230)
+
+
+
+
+
 <a name="CVE-2021-45046"/><a name="cve-2021-45046"/>
 ## <a name="log4j-2.16.0"/> Fixed in Log4j 2.12.2 (Java 7) and Log4j 2.16.0 (Java 8)
 

[logging-log4j2] 16/16: Remove non-applicable JNDI stuff

Posted by ck...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 8e18c130c7094b12138d7fab6b91bd8701ff00ee
Author: Ralph Goers <rg...@apache.org>
AuthorDate: Fri Dec 17 18:18:50 2021 -0700

    Remove non-applicable JNDI stuff
---
 src/site/xdoc/manual/lookups.xml | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/src/site/xdoc/manual/lookups.xml b/src/site/xdoc/manual/lookups.xml
index 82051de..30f600d 100644
--- a/src/site/xdoc/manual/lookups.xml
+++ b/src/site/xdoc/manual/lookups.xml
@@ -275,13 +275,7 @@
             The JndiLookup allows variables to be retrieved via JNDI. By default the key will be prefixed with
             java:comp/env/, however if the key contains a ":" no prefix will be added.
           </p>
-          <p>By default the JDNI Lookup only supports the java, ldap, and ldaps protocols or no protocol. Additional
-            protocols may be supported by specifying them on the <code>log4j2.allowedJndiProtocols</code> property.
-            When using LDAP Java classes that implement the Referenceable interface are not supported for security
-            reasons. Only the Java primitive classes are supported by default as well as any classes specified by the
-            <code>log4j2.allowedLdapClasses</code> property. When using LDAP only references to the local host name
-            or ip address are supported along with any hosts or ip addresses listed in the
-            <code>log4j2.allowedLdapHosts</code> property.</p>
+          <p>The JDNI Lookup only supports the java protocol or no protocol (as shown in the example below).</p>
           <pre class="prettyprint linenums"><![CDATA[
 <File name="Application" fileName="application.log">
   <PatternLayout>

[logging-log4j2] 15/16: [DOC] replace old `allowedLdap*` properties with `enableJndiContextSelector`

Posted by ck...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 80eaa5cdaef536c2163aa3a230ddaee24e03a4ff
Author: Remko Popma <re...@yahoo.com>
AuthorDate: Sat Dec 18 10:01:38 2021 +0900

    [DOC] replace old `allowedLdap*` properties with `enableJndiContextSelector`
---
 src/site/xdoc/manual/extending.xml | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/site/xdoc/manual/extending.xml b/src/site/xdoc/manual/extending.xml
index 04c742a..e56decb 100644
--- a/src/site/xdoc/manual/extending.xml
+++ b/src/site/xdoc/manual/extending.xml
@@ -92,10 +92,11 @@
               <dd>Associates LoggerContexts with the ClassLoader that created the caller of the getLogger call. This is
               the default ContextSelector.</dd>
               <dt><a class="javadoc" href="../log4j-core/apidocs/org/apache/logging/log4j/core/selector/JndiContextSelector.html">JndiContextSelector</a></dt>
-              <dd>Locates the LoggerContext by querying JNDI. Please see <a href="../manual/configuration.html#allowedJndiProtocols">log4j2.allowedJndiProtocols</a>,
-                <a href="../manual/configuration.html#allowedLdapClasses">log4j2.allowedLdapClasses</a>, and
-                <a href="../manual/configuration.html#allowedLdapHosts">log4j2.allowedLdapHosts</a> for restrictions on using JNDI
-                with Log4j.</dd>
+              <dd>Locates the LoggerContext by querying JNDI.
+                As of Log4j 2.17.0 JNDI operations require that <code>log4j2.enableJndiContextSelector=true</code> be set as a system
+                property or the corresponding environment variable for this lookup to function. See the
+                <a href="./configuration.html#enableJndiContextSelector">enableJndiContextSelector</a> system property.
+              </dd>
               <dt><a class="javadoc" href="../log4j-core/apidocs/org/apache/logging/log4j/core/async/AsyncLoggerContextSelector.html">AsyncLoggerContextSelector</a></dt>
               <dd>Creates a LoggerContext that ensures that all loggers are AsyncLoggers.</dd>
               <dt><a class="javadoc" href="../log4j-core/apidocs/org/apache/logging/log4j/core/osgi/BundleContextSelector.html">BundleContextSelector</a></dt>

[logging-log4j2] 10/16: [DOC] update JndiContextSelector javadoc; mention property `enableJndiContextSelector`

Posted by ck...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 8b0fff5bbff36c90b10944b7503bc66733a5496f
Author: Remko Popma <re...@yahoo.com>
AuthorDate: Sat Dec 18 09:37:40 2021 +0900

    [DOC] update JndiContextSelector javadoc; mention property `enableJndiContextSelector`
---
 .../java/org/apache/logging/log4j/core/selector/JndiContextSelector.java | 1 +
 1 file changed, 1 insertion(+)

diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java
index a4df310..cfa3532 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java
@@ -41,6 +41,7 @@ import org.apache.logging.log4j.status.StatusLogger;
  * context to look up the value of the entry. The logging context of the web-application will depend on the value the
  * env-entry. The JNDI context which is looked up by this class is <code>java:comp/env/log4j/context-name</code>.
  *
+ * <p>For security reasons, JNDI must be enabled by setting system property <code>log4j2.enableJndiContextSelector=true</code>.</p>
  * <p>
  * Here is an example of an <code>env-entry</code>:
  * </p>