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

swebb2066 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4cxx.git


The following commit(s) were added to refs/heads/master by this push:
     new 22a5fc82 Sanitize CRLF characters in SMTPAppender header fields (#672)
22a5fc82 is described below

commit 22a5fc82c884b928b0b2c3841826c21cc1046505
Author: metsw24-max <[email protected]>
AuthorDate: Sat May 16 10:54:59 2026 +0530

    Sanitize CRLF characters in SMTPAppender header fields (#672)
---
 src/main/cpp/smtpappender.cpp             | 43 ++++++++++++++++---
 src/test/cpp/net/smtpappendertestcase.cpp | 71 +++++++++++++++++++++++++++++++
 2 files changed, 109 insertions(+), 5 deletions(-)

diff --git a/src/main/cpp/smtpappender.cpp b/src/main/cpp/smtpappender.cpp
index 535e3916..496c3d53 100644
--- a/src/main/cpp/smtpappender.cpp
+++ b/src/main/cpp/smtpappender.cpp
@@ -44,6 +44,39 @@ using namespace LOG4CXX_NS::spi;
        #include <libesmtp.h>
 #endif
 
+namespace
+{
+// RFC 5322 §2.1 defines header fields as CRLF-terminated lines, so an embedded
+// CR or LF in a configured Subject/From/To/Cc/Bcc value would split it across
+// header boundaries on the wire — a caller who controls a configured field
+// (e.g. through ${...} property substitution from an environment variable)
+// could inject arbitrary additional headers such as Bcc. The library already
+// owns SMTP wire-format sanitization (see SMTPSession::toAscii, which silently
+// rewrites non-ASCII to '?'); strip CR/LF in the public setters so the same
+// boundary is enforced regardless of how the value reaches the appender.
+LogString stripSmtpControl(const LogString& value, const logchar* field)
+{
+       if (value.find_first_of(LOG4CXX_STR("\r\n")) == LogString::npos)
+       {
+               return value;
+       }
+       LogString warning(LOG4CXX_STR("SMTPAppender "));
+       warning.append(field);
+       warning.append(LOG4CXX_STR(" contains CR or LF; stripping to prevent 
SMTP header injection."));
+       LogLog::warn(warning);
+       LogString out;
+       out.reserve(value.size());
+       for (auto ch : value)
+       {
+               if (ch != 0x0D && ch != 0x0A)
+               {
+                       out.append(1, ch);
+               }
+       }
+       return out;
+}
+} // namespace
+
 namespace LOG4CXX_NS
 {
 namespace net
@@ -451,7 +484,7 @@ LogString SMTPAppender::getFrom() const
 
 void SMTPAppender::setFrom(const LogString& newVal)
 {
-       _priv->from = newVal;
+       _priv->from = stripSmtpControl(newVal, LOG4CXX_STR("From"));
 }
 
 
@@ -462,7 +495,7 @@ LogString SMTPAppender::getSubject() const
 
 void SMTPAppender::setSubject(const LogString& newVal)
 {
-       _priv->subject = newVal;
+       _priv->subject = stripSmtpControl(newVal, LOG4CXX_STR("Subject"));
 }
 
 LogString SMTPAppender::getSMTPHost() const
@@ -697,7 +730,7 @@ LogString SMTPAppender::getTo() const
 
 void SMTPAppender::setTo(const LogString& addressStr)
 {
-       _priv->to = addressStr;
+       _priv->to = stripSmtpControl(addressStr, LOG4CXX_STR("To"));
 }
 
 LogString SMTPAppender::getCc() const
@@ -707,7 +740,7 @@ LogString SMTPAppender::getCc() const
 
 void SMTPAppender::setCc(const LogString& addressStr)
 {
-       _priv->cc = addressStr;
+       _priv->cc = stripSmtpControl(addressStr, LOG4CXX_STR("Cc"));
 }
 
 LogString SMTPAppender::getBcc() const
@@ -717,7 +750,7 @@ LogString SMTPAppender::getBcc() const
 
 void SMTPAppender::setBcc(const LogString& addressStr)
 {
-       _priv->bcc = addressStr;
+       _priv->bcc = stripSmtpControl(addressStr, LOG4CXX_STR("Bcc"));
 }
 
 /**
diff --git a/src/test/cpp/net/smtpappendertestcase.cpp 
b/src/test/cpp/net/smtpappendertestcase.cpp
index 2b3797cf..5ff10c40 100644
--- a/src/test/cpp/net/smtpappendertestcase.cpp
+++ b/src/test/cpp/net/smtpappendertestcase.cpp
@@ -76,6 +76,9 @@ class SMTPAppenderTestCase : public AppenderSkeletonTestCase
                LOGUNIT_TEST(testTrigger);
                LOGUNIT_TEST(testInvalid);
                LOGUNIT_TEST(testNegativeBufferSizeOption);
+               LOGUNIT_TEST(testSubjectStripsCRLF);
+               LOGUNIT_TEST(testAddressFieldsStripCRLF);
+               LOGUNIT_TEST(testCleanFieldsArePreserved);
 //#define LOG4CXX_TEST_EMAIL_AND_SMTP_HOST_ARE_IN_ENVIRONMENT_VARIABLES
 #ifdef LOG4CXX_TEST_EMAIL_AND_SMTP_HOST_ARE_IN_ENVIRONMENT_VARIABLES
                // This test requires the following environment variables:
@@ -141,6 +144,74 @@ class SMTPAppenderTestCase : public 
AppenderSkeletonTestCase
                        LOGUNIT_ASSERT_EQUAL(1, appender.getBufferSize());
                }
 
+               /**
+                * SMTPSession::toAscii is the library's sanitization step for 
values
+                * destined for SMTP headers via libesmtp; it rewrites 
non-ASCII to '?'
+                * but does not touch CR/LF. Before the fix, a Subject 
containing CRLF
+                * was passed verbatim to smtp_set_header — RFC 5322 §2.1 
treats CRLF
+                * as the header-field terminator, so an attacker controlling a
+                * configured Subject (e.g. via property substitution) could 
inject a
+                * fresh Bcc/Cc header. Each public setter must strip CR (0x0D) 
and
+                * LF (0x0A) so the boundary is enforced regardless of how the 
value
+                * reaches the appender.
+                */
+               void testSubjectStripsCRLF()
+               {
+                       SMTPAppender appender;
+                       appender.setSubject(LOG4CXX_STR("Notification\r\nBcc: 
[email protected]"));
+                       LOGUNIT_ASSERT_EQUAL(
+                               LogString(LOG4CXX_STR("NotificationBcc: 
[email protected]")),
+                               appender.getSubject());
+
+                       appender.setSubject(LOG4CXX_STR("alert\nfollow-up"));
+                       LOGUNIT_ASSERT_EQUAL(
+                               LogString(LOG4CXX_STR("alertfollow-up")),
+                               appender.getSubject());
+
+                       appender.setSubject(LOG4CXX_STR("loose\rCR"));
+                       LOGUNIT_ASSERT_EQUAL(
+                               LogString(LOG4CXX_STR("looseCR")),
+                               appender.getSubject());
+               }
+
+               void testAddressFieldsStripCRLF()
+               {
+                       SMTPAppender appender;
+                       
appender.setFrom(LOG4CXX_STR("[email protected]\r\nBcc: x@y"));
+                       appender.setTo(LOG4CXX_STR("[email protected]\nBcc: 
x@y"));
+                       appender.setCc(LOG4CXX_STR("a@b\r,c@d"));
+                       appender.setBcc(LOG4CXX_STR("[email protected]\n"));
+
+                       LOGUNIT_ASSERT_EQUAL(
+                               LogString(LOG4CXX_STR("[email protected]: 
x@y")),
+                               appender.getFrom());
+                       LOGUNIT_ASSERT_EQUAL(
+                               LogString(LOG4CXX_STR("[email protected]: 
x@y")),
+                               appender.getTo());
+                       LOGUNIT_ASSERT_EQUAL(
+                               LogString(LOG4CXX_STR("a@b,c@d")),
+                               appender.getCc());
+                       LOGUNIT_ASSERT_EQUAL(
+                               LogString(LOG4CXX_STR("[email protected]")),
+                               appender.getBcc());
+               }
+
+               void testCleanFieldsArePreserved()
+               {
+                       // Whitespace, tabs, commas, and non-ASCII are 
deliberately untouched
+                       // here: only CR/LF are stripped. toAscii continues to 
handle non-ASCII
+                       // at SMTP-message construction time.
+                       SMTPAppender appender;
+                       appender.setSubject(LOG4CXX_STR("  Spaced subject\t"));
+                       appender.setTo(LOG4CXX_STR("[email protected], 
[email protected]"));
+                       LOGUNIT_ASSERT_EQUAL(
+                               LogString(LOG4CXX_STR("  Spaced subject\t")),
+                               appender.getSubject());
+                       LOGUNIT_ASSERT_EQUAL(
+                               LogString(LOG4CXX_STR("[email protected], 
[email protected]")),
+                               appender.getTo());
+               }
+
                void testValid()
                {
                        auto status = 
xml::DOMConfigurator::configure("input/xml/smtpAppenderValid.xml");

Reply via email to