DESCRIPTION
upgraded Mail-DMARC 1.20260612's new RFC 9989 tree_walk implementation causes Perl deep recursion warnings in Mail::Milter::Authentication::Resolver (CPAN). The warnings appear for every email processed with DMARC checking enabled.
Risk of stack exhaustion on deeply nested subdomain structures.
in Fedora44 mock env + Perl v5.42.2, CPAN build of Mail::Milter::Authentication v4.20260521 prior to 6/12 picks up
Mail-DMARC: 1.20260325 (released 2026-02-26)
after, it grabs new release of Mail::DMARC
Mail-DMARC v1.20260612 (released 2026-06-12)
and now logs repeating deep recursion warnings; e.g.,
fm-auth-milter[3008]: Warning: Deep recursion on subroutine "Mail::Milter::Authentication::Resolver::_do" at /usr/local/fm-auth-milter/lib/perl5/Mail/Milter/Authentication/Resolver.pm line 124.<LF>
fm-auth-milter[3008]: Warning: Deep recursion on subroutine "Mail::DMARC::PurePerl::get_organizational_domain" at /usr/local/fm-auth-milter/lib/perl5/Mail/Milter/Authentication/Resolver.pm line 59.<LF>
fm-auth-milter[3008]: Warning: Deep recursion on subroutine "Mail::DMARC::PurePerl::tree_walk" at /usr/local/fm-auth-milter/lib/perl5/Mail/DMARC/PurePerl.pm line 476.<LF>
fm-auth-milter[3008]: Warning: Deep recursion on subroutine "Mail::Milter::Authentication::Resolver::send" at /usr/local/fm-auth-milter/lib/perl5/Mail/DMARC/PurePerl.pm line 404.<LF>
checking, every DNS operation (send, query, search) calls _do(), which unconditionally calls get_organizational_domain() on line 59 when DMARC is loaded
Mail::Milter::Authentication::Resolver::_do()
https://github.com/fastmail/authentication_milter/blob/master/lib/Mail/Milter/Authentication/Resolver.pm#L59
sub _do { ## no critic
my $self = shift;
my $what = shift;
...
my $domain = $_[0];
my $org_domain = $_[0];
my $query = $_[1];
if ( $handler->is_handler_loaded( 'DMARC' ) ) {
my $dmarc_object = $handler->get_handler('DMARC')->get_dmarc_object();
!! $org_domain = eval{ $dmarc_object->get_organizational_domain( $org_domain ) };
$handler->handle_exception( $@ );
}
Mail::DMARC v1.20260612 introduces RFC 9989 DMARC support,
cc: @msimerson
implement RFC 9989
https://github.com/msimerson/mail-dmarc/pull/297
RFC 9989 tree_walk (Mail/DMARC/PurePerl.pm) now performs iterative DNS queries in a loop, up to ~ 8 queries per organizational domain lookup.
At line 404, it calls:
my $query = $self->get_resolver->send( "_dmarc.$target", 'TXT' );
and triggers a recursive call chain,
Mail/Milter/Authentication/Resolver.pm:122-125 (send method)
-> Resolver.pm:45-110 (_do method)
-> Resolver.pm:59 (get_organizational_domain call)
-> Mail/DMARC/PurePerl.pm:470-486 (get_organizational_domain method)
-> Mail/DMARC/PurePerl.pm:476 (tree_walk call)
-> Mail/DMARC/PurePerl.pm:404 (get_resolver->send call)
-> Resolver.pm:122 (send method) [recursion repeats]
where each tree_walk iteration (querying _dmarc.domain, _dmarc.subdomain, etc.) triggers get_organizational_domain() again, creating unmatched recursion.
that nested recursion scales, and approaches/exceeds Perl's default ~100-level limit; per perldoc perldiag
...
1938 Deep recursion on anonymous subroutine
Deep recursion on subroutine "%s"
(W recursion) This subroutine has called itself (directly or
indirectly) 100 times more than it has returned. This probably
indicates an infinite recursion, unless you're writing strange
benchmark programs, in which case it indicates something else.
...
ATM here, it's just WARNINGS logged; processing continues, and mail's checked/delivered.
As long as the recursion depth stays under the available stack.
IIUC, that's currently un-checked.
This triggers on every DMARC-checked message.
A domain structure with deep (dunno HOW deep ... yet) subdomain hierarchies could exceed that limit, exhaust the stack exhaustion and, i suspect, trigger a fatal overflow.
i think Resolver.pm needs to skip (limit?) the get_organizational_domain() call for the synthetic DNS domains that tree_walk creates for lookups -- i.e., the _dmarc.*
e.g.,
sub _do { ## no critic
my $self = shift;
my $what = shift;
...
my $domain = $_[0];
my $org_domain = $_[0];
my $query = $_[1];
- if ( $handler->is_handler_loaded( 'DMARC' ) ) {
+ if ( $handler->is_handler_loaded( 'DMARC' ) && $_[0] !~ /^_dmarc\./ ) {
my $dmarc_object = $handler->get_handler('DMARC')->get_dmarc_object();
$org_domain = eval{ $dmarc_object->get_organizational_domain( $org_domain ) };
$handler->handle_exception( $@ );
}
DESCRIPTION
upgraded Mail-DMARC 1.20260612's new RFC 9989 tree_walk implementation causes Perl deep recursion warnings in Mail::Milter::Authentication::Resolver (CPAN). The warnings appear for every email processed with DMARC checking enabled.
Risk of stack exhaustion on deeply nested subdomain structures.
in Fedora44 mock env + Perl v5.42.2, CPAN build of Mail::Milter::Authentication v4.20260521 prior to 6/12 picks up
after, it grabs new release of Mail::DMARC
and now logs repeating deep recursion warnings; e.g.,
checking, every DNS operation (send, query, search) calls _do(), which unconditionally calls get_organizational_domain() on line 59 when DMARC is loaded
Mail::DMARC v1.20260612 introduces RFC 9989 DMARC support,
cc: @msimerson
RFC 9989 tree_walk (Mail/DMARC/PurePerl.pm) now performs iterative DNS queries in a loop, up to ~ 8 queries per organizational domain lookup.
At line 404, it calls:
and triggers a recursive call chain,
where each tree_walk iteration (querying _dmarc.domain, _dmarc.subdomain, etc.) triggers get_organizational_domain() again, creating unmatched recursion.
that nested recursion scales, and approaches/exceeds Perl's default ~100-level limit; per
perldoc perldiagATM here, it's just WARNINGS logged; processing continues, and mail's checked/delivered.
As long as the recursion depth stays under the available stack.
IIUC, that's currently un-checked.
This triggers on every DMARC-checked message.
A domain structure with deep (dunno HOW deep ... yet) subdomain hierarchies could exceed that limit, exhaust the stack exhaustion and, i suspect, trigger a fatal overflow.
i think Resolver.pm needs to skip (limit?) the
get_organizational_domain()call for the synthetic DNS domains that tree_walk creates for lookups -- i.e., the _dmarc.*e.g.,