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 6d1ba8b66a985426b4990690feb73e44b07cfc6a Author: Quan Tran <[email protected]> AuthorDate: Tue Nov 3 09:59:37 2026 +0700 [FIX] Domain-spec regexes: match a toplabel in linear time The first toplabel alternative, *alphanum ALPHA *alphanum, can split an alphanumeric label around any of its letters. When the rest of the term does not match, java.util.regex tries every split, which is quadratic in the length of the label: "v=spf1 include:x." followed by 16000 letters and "!" took 5 seconds to parse, 32000 letters 20 seconds. The record comes from the DNS of the checked domain, so its owner sets the CPU each check costs. Before the domain-spec regexes stopped recursing once per character, the stack overflowed past a few thousand characters; below that, a parse took up to a second. Split the label before its first letter: *DIGIT ALPHA *alphanum matches the same toplabels (RFC 7208 7.1) one way only. --- .../java/org/apache/james/jspf/core/SPFTermsRegexps.java | 6 +++++- .../java/org/apache/james/jspf/SPFExecutorBaseTest.java | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/resolver/src/main/java/org/apache/james/jspf/core/SPFTermsRegexps.java b/resolver/src/main/java/org/apache/james/jspf/core/SPFTermsRegexps.java index a0a9da4..dc39a89 100644 --- a/resolver/src/main/java/org/apache/james/jspf/core/SPFTermsRegexps.java +++ b/resolver/src/main/java/org/apache/james/jspf/core/SPFTermsRegexps.java @@ -82,9 +82,13 @@ public interface SPFTermsRegexps { * ABNF: toplabel = ( *alphanum ALPHA *alphanum ) / ( 1*alphanum "-" *( * alphanum / "-" ) alphanum ) ; LDH rule plus additional TLD restrictions ; * (see [RFC3696], Section 2) + * + * The first alternative splits the label before its first letter, so it matches one way only: + * *alphanum ALPHA *alphanum can split it around any of its letters, and trying them all when + * the rest of the term does not match is quadratic in the length of the label. */ final String TOP_LABEL_REGEX = "(?:" - + ALPHA_DIGIT_PATTERN + "*" + SPFTermsRegexps.ALPHA_PATTERN + + "[0-9]*" + SPFTermsRegexps.ALPHA_PATTERN + "{1}" + ALPHA_DIGIT_PATTERN + "*|(?:" + ALPHA_DIGIT_PATTERN + "+" + "\\-" + "[a-zA-Z0-9\\-]*" + ALPHA_DIGIT_PATTERN + "))"; 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 fd5ae51..4e66f42 100644 --- a/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java +++ b/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java @@ -658,4 +658,17 @@ public abstract class SPFExecutorBaseTest { // RFC 7208 6.1: the result of the redirect target assertEquals(SPFErrorConstants.NEUTRAL_CONV, result.getResult()); } + + @Test(timeout = 10000) + public void shouldReturnPermErrorOnLongInvalidTopLabel() throws IOException { + String hostname = "shouldReturnPermErrorOnLongInvalidTopLabel.spfexecutor.fake"; + FakeResolver fakeResolver = new FakeResolver(); + // a toplabel of 50000 letters, then "!", which no toplabel holds: no term matches + fakeResolver.addRecord(longTxtRecord(hostname, "v=spf1 include:x." + "a".repeat(50000) + "! -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()); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
