Vulnerability analysis · SPF evaluation

The error buffer allocated by SPF_response_add_error() in libspf2 survives teardown of the response that owns it [libspf2 ≤ 1.2.10, latest release checked 2026-07-30]

Yongzhe Xu, Virginia Tech  ·  yongzhe@vt.edu  ·  2026-07-31

An SPF evaluation that ends in an error allocates a buffer and never gives it back. Destroying the response does not free it, so each malformed record costs the process a little memory.

Softwarelibspf2
Vendorlibspf2 maintainers
Sourcehttps://github.com/shevek/libspf2
AffectedCurrent upstream master; the Debian package 1.2.10-8.3 was present in the test environment. Newest published release checked on 2026-07-30.
WhereSPF_response_add_error() error path; the allocated error buffer is not freed when the SPF_response is destroyed
WeaknessCWE-401 (Missing Release of Memory after Effective Lifetime)
Reachable byRemote. Each evaluation of a malformed SPF record leaks; the record is published by the sending domain.
CVSS v3.1CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L (5.3 (Medium))
VerificationLeakSanitizer-confirmed via the SPF interpretation harness over a malformed-record corpus.

1. Overview

SPF_response_add_error() allocates a buffer to hold the error text it records against an SPF_response. The teardown path for that response does not free the buffer. Every SPF evaluation that ends in an error therefore costs the process one small allocation that is never returned.

A receiver that evaluates SPF records supplied by senders repeats that cost once per malformed evaluation. Under sustained malformed traffic the growth is unbounded.

2. Analysis

LeakSanitizer attributes the allocation to the error path directly:

==ERROR== LeakSanitizer: detected memory leaks
  Direct leak of N byte(s) in M object(s) allocated from:
    ... SPF_response_add_error ...

Only the error path leaks. A well-formed record is evaluated and torn down cleanly. To reach the leak an attacker has to make the evaluation fail, and publishing a malformed SPF record for a domain they control does exactly that.

3. Reproduction

  1. Build the SPF harnesses with the sanitizer enabled.
export BUILD="$HOME/asan-build"
bash lib/build_spf.sh          # builds fuzz_spf_compile and fuzz_spf_interp
  1. Run the interpreter harness with leak detection on, either over the corpus or over

a single error-path input.

ASAN_OPTIONS=detect_leaks=1 "$BUILD/fuzz_spf_interp" corpus_spf/
# or a single error-path input:
ASAN_OPTIONS=detect_leaks=1 "$BUILD/fuzz_spf_interp" memory-safety/seeds/L1-spf-leak

4. Assessment

The exposed party is any long-lived process that evaluates SPF over records it did not write. The attacker needs no authentication and no position on the network path. The record is published from a domain they control, so the input is entirely theirs. What they get is gradual memory growth, ending in exhaustion.

The rating is low because volume is required. One malformed evaluation costs almost nothing, and the impact only becomes material after a sustained stream of them.

5. Remediation

Free the error buffer on every SPF_response teardown path.

6. Additional notes

This is a leak and nothing more. No corruption was observed, and this is the lowest severity in the reporting set.

Two items remain open before vendor contact. The leak has not been re-confirmed against the current libspf2 release. There is also no minimized single input pinning the exact error term; the confirmation so far rests on the corpus.

7. References