Fix Received-SPF parser mishandling '=' in VERP envelope-from addresses (issues #206, #221)#300
Open
thegushi wants to merge 1 commit into
Open
Fix Received-SPF parser mishandling '=' in VERP envelope-from addresses (issues #206, #221)#300thegushi wants to merge 1 commit into
thegushi wants to merge 1 commit into
Conversation
…es (issues #206, #221) The key=value parser in dmarcf_parse_received_spf() treated every '=' as a switch from key-collection to value-collection mode, even when already collecting a value or inside a quoted string. VERP addresses embed the original recipient's address in the local part using '=' as a separator (e.g. bounces+nonce=recipient@sender.example.com), so a '=' in the local part reset the value buffer mid-parse. When the pre-'=' portion was longer than the post-'=' portion, the new write did not reach far enough to overwrite the leftover bytes, which then corrupted the extracted domain (e.g. "sender.com" became "sender.comnts"). The corrupted domain failed the envdomain comparison and the SPF pass was discarded as neutral, causing a false DMARC fail. In issue #221 (Twitter VERP with triple '==='), multiple resets accidentally walked the parser to the correct final fragment, producing the right answer for the wrong reason. Variants where the pre-last-'=' string is longer than the post-last-'=' string expose the same corruption and produce wrong results with the old code. Fix: track whether value-collection has started (in_value flag) so that '=' is only treated as a mode-switch once per key=value pair. Also honour the quoting flag for both '=' and ';' to handle quoted envelope-from values correctly. Extracted dmarcf_parse_received_spf() into opendmarc-spf-parse.c so it can be unit tested without depending on libmilter headers. Regression tests cover both reported cases plus variants.
This was referenced May 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The
dmarcf_parse_received_spf()key=value parser treated every=as a mode switch even when already collecting a value or inside a quoted string. VERP addresses (e.g.bounces+nonce=recipient@sender.example.com) contain=in the local part, which reset the value buffer mid-parse. When the pre-=portion was longer than the post-=portion, leftover bytes corrupted the extracted domain, causing a false DMARC fail.in_valueflag:=is now only treated as a key→value separator once per pair; subsequent=chars are collected as part of the value.=and;inside quoted strings are no longer treated as delimiters.dmarcf_parse_received_spf()moved toopendmarc-spf-parse.cso it can be unit tested without libmilter headers.Closes #206. Related: #221. Tracked in #299.
Test plan
make checkpasses (8/8, including newtest_spf_parse)test_spf_parsecovers: clean addresses, wrong identity, domain mismatch, DMARC result wrong when local part exceeds 45 characters #206 quoted VERP, DMARC result wrong when local part exceeds 45 characters #206 unquoted variant, Possible issue when matching Original-Mail-From containing a domain name in the local part #221 triple-===, Possible issue when matching Original-Mail-From containing a domain name in the local part #221 double-==corruption variant, comment blocks, no trailing semicolon