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

chibenwa pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-jspf.git

commit f37dede4ab09358d6732e7c66dcd0c7c305996fb
Author: Quan Tran <[email protected]>
AuthorDate: Tue Nov 3 10:45:49 2026 +0700

    [FIX] MacroExpand: truncate a long domain in linear time
    
    An expanded domain longer than 255 characters lost its left labels one
    at a time, each time copying the rest of the name: quadratic in its
    length. A macro-string repeating %{d} expands to the checked domain once
    per macro: with a domain of 252 characters made of one-letter labels,
    "v=spf1 include:" followed by 4000 times "%{d}." took 19 seconds to
    check, 12000 times 203 seconds. The record and the domain both belong
    to the sender. Before the domain-spec regexes stopped recursing once per
    character, the stack overflowed first.
    
    Cut the name at once, after the first dot that leaves at most 255
    characters, or else after the last dot, where the loop ended.
---
 .../main/java/org/apache/james/jspf/core/MacroExpand.java  | 11 +++++++----
 .../java/org/apache/james/jspf/SPFExecutorBaseTest.java    | 14 ++++++++++++++
 .../java/org/apache/james/jspf/core/MacroExpandTest.java   | 12 ++++++++++++
 3 files changed, 33 insertions(+), 4 deletions(-)

diff --git a/resolver/src/main/java/org/apache/james/jspf/core/MacroExpand.java 
b/resolver/src/main/java/org/apache/james/jspf/core/MacroExpand.java
index a997623..61edd25 100644
--- a/resolver/src/main/java/org/apache/james/jspf/core/MacroExpand.java
+++ b/resolver/src/main/java/org/apache/james/jspf/core/MacroExpand.java
@@ -241,10 +241,13 @@ public class MacroExpand {
         if (domainName.indexOf('\r') >= 0 || domainName.indexOf('\n') >= 0) {
             throw new PermErrorException("Macro-expanded domain contains 
illegal CR or LF characters");
         }
-        // reduce to less than 255 characters, deleting subdomains from left
-        int split = 0;
-        while (domainName.length() > 255 && split > -1) {
-            split = domainName.indexOf(".");
+        // reduce to less than 255 characters, deleting subdomains from left: 
at once, after the first
+        // dot that leaves at most 255 characters, or else after the last dot
+        if (domainName.length() > 255) {
+            int split = domainName.indexOf('.', domainName.length() - 256);
+            if (split < 0) {
+                split = domainName.lastIndexOf('.');
+            }
             domainName = domainName.substring(split + 1);
         }
 
diff --git 
a/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java 
b/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java
index 122505a..907ce1b 100644
--- a/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java
+++ b/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java
@@ -706,4 +706,18 @@ public abstract class SPFExecutorBaseTest {
         // SPFResult gives "" when there is no explanation
         assertEquals("", result.getExplanation());
     }
+
+    @Test(timeout = 10000)
+    public void shouldTruncateALongExpandedIncludeTarget() throws IOException {
+        // 225 characters and 102 dots, expanded 8000 times: the include 
target is close to 2 MB before
+        // its truncation to a subdomain of hostname, with no record
+        String hostname = "a.".repeat(100) + "truncate.spfexecutor.fake";
+        FakeResolver fakeResolver = new FakeResolver();
+        fakeResolver.addRecord(longTxtRecord(hostname, "v=spf1 include:" + 
"%{d}.".repeat(8000) + hostname + " -all"));
+
+        SPF spf = createCustomSPF(new DNSServiceXBillImpl(fakeResolver));
+        SPFResult result = spf.checkSPF("1.2.3.4", "a_user@" + hostname, 
hostname);
+
+        assertEquals(SPFErrorConstants.PERM_ERROR_CONV, result.getResult());
+    }
 }
diff --git 
a/resolver/src/test/java/org/apache/james/jspf/core/MacroExpandTest.java 
b/resolver/src/test/java/org/apache/james/jspf/core/MacroExpandTest.java
index dcf2682..753354f 100644
--- a/resolver/src/test/java/org/apache/james/jspf/core/MacroExpandTest.java
+++ b/resolver/src/test/java/org/apache/james/jspf/core/MacroExpandTest.java
@@ -165,6 +165,18 @@ public class MacroExpandTest extends TestCase {
         assertTrue(domain.endsWith(".email.example.com.com"));
     }
 
+    public void 
testLongDomainIsCutAfterTheFirstDotThatLeavesAtMost255Characters() throws 
PermErrorException {
+        String domain = defIp4me.expand("a.".repeat(200) + "com", new 
rfcIP4MacroData(), MacroExpand.DOMAIN);
+
+        assertEquals("a.".repeat(126) + "com", domain);
+    }
+
+    public void testLongLastLabelOfALongDomainIsKeptWhole() throws 
PermErrorException {
+        String domain = defIp4me.expand("%{d}." + "a".repeat(300), new 
rfcIP4MacroData(), MacroExpand.DOMAIN);
+
+        assertEquals("a".repeat(300), domain);
+    }
+
     public void 
testLongLiteralDomainSpecIsTruncatedWithoutOverflowingTheStack() throws 
PermErrorException {
         String domain = defIp4me.expand("a.".repeat(5000) + "com", new 
rfcIP4MacroData(), MacroExpand.DOMAIN);
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to