Vulnerability analysis · ARC verification
'c=relaxed' is a legal ARC-Message-Signature tag value under RFC 6376 section 3.5. OpenARC segfaults on it, so ordinary forwarded mail from a conforming signer takes the verifier down.
| Software | OpenARC (libopenarc) |
|---|---|
| Vendor | The Trusted Domain Project |
| Source | https://github.com/trusteddomainproject/OpenARC |
| Affected | Current upstream master. Newest published release checked on 2026-07-30. |
| Where | libopenarc/arc-canon.c, arc_parse_canon_t(); crash in libopenarc/arc-tables.c, arc_name_to_code() line 219, reached from arc_eoh_verify() |
| Weakness | CWE-476 (NULL Pointer Dereference) |
| Reachable by | Remote, unauthenticated. One forwarded e-mail carrying an ARC chain. |
| CVSS v3.1 | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H (7.5 (High)) |
| Verification | AddressSanitizer-confirmed; the proposed fix was applied, rebuilt, and the real crashing input then ran clean. |
The c= tag on an ARC-Message-Signature names two canonicalizations separated by a slash, one for the headers and one for the body. arc_parse_canon_t() in libopenarc/arc-canon.c splits the value on / and hands each half to arc_name_to_code(). The second half is optional. When the tag reads c=relaxed, the body canonicalization defaults to simple and there is nothing after the slash to split off, so the second strtok_r() returns NULL. That NULL travels into arc_name_to_code() and reaches strcasecmp(), which dereferences it.
The signature that triggers this is not malformed. RFC 6376 section 3.5, inherited by RFC 8617, allows the body canonicalization to be omitted. A verifier is required to apply the default. OpenARC faults instead.
The parse in arc-canon.c does not check the second token:
token = strtok_r(tag, "/", &last); /* header canonicalisation */
code = arc_name_to_code(canonicalizations, token);
...
token = strtok_r(NULL, "/", &last); /* NULL when c= has no '/' */
code = arc_name_to_code(canonicalizations, token); /* token may be NULL */
The lookup in arc-tables.c does not check its name argument either, and passes it straight to strcasecmp():
if (strcasecmp(tbl[c].tbl_name, name) == 0) /* strcasecmp(valid, NULL) -> SEGV */
AddressSanitizer reports the fault at a null address, in that call, reached from the end-of-headers verification step:
ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
#0 strcasecmp
#1 arc_name_to_code arc-tables.c:219
#2 arc_parse_canon_t
#3 arc_eoh_verify
libopenarc with AddressSanitizer and link the verification harness, whichcalls arc_message(VERIFY), arc_header_field(), arc_eoh(), arc_body() and arc_eom().
i=1, meaning all three ofARC-Seal, ARC-Message-Signature and ARC-Authentication-Results, with the signature's c= tag written as c=relaxed.
ARC-Seal: i=1; a=rsa-sha256; t=...; cv=none; d=fwd.example; s=s1; b=...
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed; d=fwd.example; s=s1;
h=from:to:subject; bh=...; b=...
ARC-Authentication-Results: i=1; fwd.example; spf=pass; dkim=pass; dmarc=pass
From: a@orig.example
To: b@dest.example
Subject: test
body
The full instance matters. An ARC-Message-Signature on its own never reaches the canonicalization parse inside arc_eoh_verify().
Expected (RFC 6376 section 3.5): the verifier applies the default body canonicalization simple and continues.
Observed: segmentation fault at arc-tables.c:219.
Any deployment that verifies ARC chains is exposed. An attacker needs one forwarded message and no credentials, and the result is a crashed verifier, which stops mail processing.
Two things widen the exposure beyond a crafted attack. First, the trigger is legal input, so a conforming signer that omits the body canonicalization crashes the verifier during ordinary forwarding. Second, ARC exists to carry authentication results across forwarding hops, so this input arrives from third parties as a matter of design rather than as an exception.
The immediate guard goes in arc_name_to_code(). This patch was applied, rebuilt, and the real crashing input then ran clean:
- if (strcasecmp(tbl[c].tbl_name, name) == 0)
+ if (name != NULL && strcasecmp(tbl[c].tbl_name, name) == 0)
The better fix belongs in arc_parse_canon_t(). A missing body-canonicalization token should become the default simple that the specification names, rather than a NULL passed down the call chain. That processes a legal signature correctly instead of merely surviving it.
This sits in the ARC forwarding code path, which looks less exercised than the DKIM path. A per-message memory leak in the same path, openarc-canon-header-leak, was found during the same session.