Author: Weitong Li, Virginia Tech <weitongli@vt.edu>
Date: 2026-07-31
Vendor: The Trusted Domain Project
Software: OpenDMARC (libopendmarc, internal SPF engine)
Source code: https://github.com/trusteddomainproject/OpenDMARC
Affected version: OpenDMARC 1.4.2 and earlier, and current upstream master, when built --with-spf without libspf2. Latest version tested on 2026-07-30.
Vulnerable component: libopendmarc/opendmarc_spf.c, opendmarc_spf_ipv6_explode() line 729, reached from opendmarc_spf_ipv6_cidr_check() line 798
Vulnerability type: CWE-476 (NULL Pointer Dereference) / invalid pointer dereference
Attack vector: Remote. The malformed IPv6 literal comes from the attacker-published SPF record; a single message from that domain drives evaluation.
CVSS v3.1: 7.5 (High) — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Status: AddressSanitizer-confirmed; the harness crashed within about 35 iterations.
Impact. A short IPv6 literal in an attacker-published SPF record makes the parser compute NULL+1 and dereference address 0x1.
Remote crash of the OpenDMARC process on affected builds, denying DMARC and SPF evaluation and, depending on the milter failure mode, mail delivery. The attacker needs only to publish an SPF record under a domain they control and send one message.
The IPv6 expander walks up to eight colon-separated segments. When a segment contains no further colon, strchr() returns NULL, but the code advances unconditionally with cp = ep + 1, producing the pointer 0x1. If iterations remain — that is, if the literal has fewer than eight segments and no :: compression to fill the remainder — the next iteration calls strchr((char *) 0x1, ':') and faults.
cp = copy;
for (i = 7; i >= 0; i--)
{
ep = strchr(cp, ':'); /* line 729 - crashes when cp == (char *) 1 */
if (ep != NULL)
*ep = '\0';
...
cp = ep + 1; /* when ep == NULL: cp = NULL + 1 = 0x1 */
}
ERROR: AddressSanitizer: SEGV on unknown address 0x000000000001
#1 strchr
#2 opendmarc_spf_ipv6_explode opendmarc_spf.c:729
#3 opendmarc_spf_ipv6_cidr_check opendmarc_spf.c:798
The in-process harness takes <ipv6_string>\0<cidr>. A malformed literal with too few segments and no :: compression reproduces deterministically, for example a value ending ...:0:0:0:0:1 with CIDR ::/0.
./fuzz_odmarc_spf poc_ipv6
ERROR: AddressSanitizer: SEGV on unknown address 0x000000000001
... opendmarc_spf_ipv6_explode opendmarc_spf.c:729
The real-world trigger is a published SPF record:
attacker.example. IN TXT "v=spf1 ip6:<malformed literal> -all"
followed by one message from that domain, evaluated by a receiver whose OpenDMARC uses the internal SPF engine.
- cp = ep + 1;
+ if (ep == NULL)
+ break;
+ cp = ep + 1;
Do not form ep + 1 when ep is NULL, and stop the loop once the literal is exhausted.
Build-dependent. The affected code lives in the internal SPF parser, which is compiled only when OpenDMARC is built --with-spf and without libspf2. Builds that link libspf2 route SPF through opendmarc_spf2.c and never reach this function. The report should be scoped to the internal-SPF configuration.