OpenARC NULL-pointer dereference in arc_name_to_code() via an ARC-Message-Signature c= tag without a body canonicalisation (OpenARC (master); latest version tested 2026-07-30)

Author: Weitong Li, Virginia Tech <weitongli@vt.edu>
Date: 2026-07-31
Vendor: The Trusted Domain Project
Software: OpenARC (libopenarc)
Source code: https://github.com/trusteddomainproject/OpenARC
Affected version: Current upstream master. Latest version tested on 2026-07-30.
Vulnerable component: 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()
Vulnerability type: CWE-476 (NULL Pointer Dereference)
Attack vector: Remote, unauthenticated. One forwarded e-mail carrying an ARC chain.
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 proposed fix was applied, rebuilt, and the real crashing input then ran clean.

Impact. A spec-valid 'c=relaxed' on an ARC-Message-Signature crashes the verifier, so the trigger fires on legitimate forwarded mail as well as on attacks.

Description

Remote crash of the ARC verifier, and therefore a denial of service against mail processing on any deployment that verifies ARC chains. What raises the practical severity is that the trigger is specification-valid: c=relaxed without an explicit body canonicalisation is a legal ARC-Message-Signature, so this can fire on ordinary forwarded mail from a conforming signer, not only on a deliberately crafted message. ARC is specifically the mechanism that carries authentication results across forwarding hops, so the affected input arrives from third parties by design.

arc_parse_canon_t() splits the c= tag value on / into a header canonicalisation name and a body canonicalisation name, and passes each to arc_name_to_code(). When c= carries only the header form — for example c=relaxed, with the body canonicalisation defaulting to simple — the second strtok_r() returns NULL and arc_name_to_code() dereferences it inside strcasecmp().

RFC 6376 section 3.5, which RFC 8617 inherits, explicitly allows the body canonicalisation to be omitted. The crashing signature is therefore well-formed: a verifier is required to apply the default, not to fault.

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 */

and in arc-tables.c:

if (strcasecmp(tbl[c].tbl_name, name) == 0)   /* strcasecmp(valid, NULL) -> SEGV */
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

Proof of Concept

A complete ARC instance — ARC-Seal, ARC-Message-Signature and ARC-Authentication-Results, all at i=1 — where the ARC-Message-Signature carries c=relaxed with no body canonicalisation:

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

A bare ARC-Message-Signature on its own does not reach the canonicalisation parse in arc_eoh_verify(); the full instance is required. Build libopenarc with AddressSanitizer, link the verification harness (arc_message(VERIFY), arc_header_field(), arc_eoh(), arc_body(), arc_eom()) and feed it the message.

Mitigation

A defensive NULL guard in arc_name_to_code(), verified by rebuild against the real crashing input:

-                if (strcasecmp(tbl[c].tbl_name, name) == 0)
+                if (name != NULL && strcasecmp(tbl[c].tbl_name, name) == 0)

The better fix is in arc_parse_canon_t(): treat a missing body-canonicalisation token as the specified default simple rather than passing NULL down, so that a legal signature is processed correctly instead of merely not crashing.

Notes

Found in the ARC (forwarding) code path, which appears less exercised than the DKIM path; openarc-canon-header-leak, a per-message memory leak, sits in the same path and was found in the same session.

References