From: Aram Sargsyan Date: Mon, 18 May 2026 09:14:27 +0000 (+0000) Subject: Add a new 'deny-answer-aliases' check in 'resolver' system test X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=1645b09341806e08cc7c997c0d0f20a99db17851;p=thirdparty%2Fbind9.git Add a new 'deny-answer-aliases' check in 'resolver' system test This new check exercises an attack against guarantees given by the 'deny-answer-aliases' configuration option by caching a DNAME that is a parent of the restricted alias, and then "constructing" the restricted alias from the cache. --- diff --git a/bin/tests/system/resolver/ans2/ans.py b/bin/tests/system/resolver/ans2/ans.py index 9d27310527d..78a94b5e893 100644 --- a/bin/tests/system/resolver/ans2/ans.py +++ b/bin/tests/system/resolver/ans2/ans.py @@ -141,6 +141,7 @@ class Ns2Delegation(DelegationHandler): class Ns3Delegation(DelegationHandler): domains = [ "example.net.", + "isc.org.", "lame.example.org.", "sub.example.org.", ] diff --git a/bin/tests/system/resolver/ans3/ans.py b/bin/tests/system/resolver/ans3/ans.py index 8913186a702..284f43e5848 100644 --- a/bin/tests/system/resolver/ans3/ans.py +++ b/bin/tests/system/resolver/ans3/ans.py @@ -41,13 +41,18 @@ from ..resolver_ans import ( ) -class ApexNSHandler(QnameHandler, StaticResponseHandler): +class ApexNSHandler(QnameQtypeHandler, StaticResponseHandler): qnames = ["example.net."] qtypes = [dns.rdatatype.NS] answer = [rrset(qnames[0], dns.rdatatype.NS, f"ns.{qnames[0]}")] additional = [rrset(f"ns.{qnames[0]}", dns.rdatatype.A, "10.53.0.3")] +class AttackDnameHandler(QnameHandler, StaticResponseHandler): + qnames = ["www.example.attack.example.net", "isc.attack.example.net."] + answer = [rrset("attack.example.net.", dns.rdatatype.DNAME, "org.")] + + class BadCnameHandler(QnameHandler, StaticResponseHandler): qnames = ["badcname.example.net."] answer = [rrset(qnames[0], dns.rdatatype.CNAME, "badcname.example.org.")] @@ -64,6 +69,12 @@ class CnameSubHandler(QnameHandler, StaticResponseHandler): answer = [rrset(qnames[0], dns.rdatatype.CNAME, "ok.sub.example.org.")] +class ExampleOrgHandler(QnameQtypeHandler, StaticResponseHandler): + qnames = ["example.org."] + qtypes = [dns.rdatatype.A] + answer = [rrset(qnames[0], qtypes[0], "1.2.3.4")] + + class FooBadDnameHandler(QnameHandler, StaticResponseHandler): qnames = ["foo.baddname.example.net."] answer = [ @@ -93,12 +104,18 @@ class GoodCnameHandler(QnameHandler, StaticResponseHandler): answer = [rrset(qnames[0], dns.rdatatype.CNAME, "goodcname.example.org.")] +class IscHandler(QnameQtypeHandler, StaticResponseHandler): + qnames = ["isc.org."] + qtypes = [dns.rdatatype.A] + answer = [rrset(qnames[0], qtypes[0], "1.2.3.4")] + + class LameExampleOrgDelegation(DelegationHandler): domains = ["lame.example.org."] server_number = 3 -class LargeReferralHandler(QnameHandler, StaticResponseHandler): +class LargeReferralHandler(QnameQtypeHandler, StaticResponseHandler): qnames = ["large-referral.example.net."] qtypes = [dns.rdatatype.NS] authority = [ @@ -172,6 +189,11 @@ class WwwDnameSubHandler(QnameHandler, StaticResponseHandler): ] +class WwwGoodDnameHandler(QnameHandler, StaticResponseHandler): + qnames = ["www.example.gooddname.example.net"] + answer = [rrset("gooddname.example.net.", dns.rdatatype.DNAME, "org.")] + + class WwwHandler(QnameHandler): qnames = ["www.example.net."] @@ -195,9 +217,11 @@ def main() -> None: server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR) server.install_response_handlers( ApexNSHandler(), + AttackDnameHandler(), BadCnameHandler(), BadGoodDnameNsHandler(), CnameSubHandler(), + ExampleOrgHandler(), FooBadDnameHandler(), FooBarSubTld1Handler(), FooGoodDnameHandler(), @@ -207,6 +231,7 @@ def main() -> None: Gl6412Ns2Handler(), Gl6412Ns3Handler(), GoodCnameHandler(), + IscHandler(), LameExampleOrgDelegation(), LargeReferralHandler(), LongCnameHandler(), @@ -217,6 +242,7 @@ def main() -> None: OkSubHandler(), PartialFormerrHandler(), WwwDnameSubHandler(), + WwwGoodDnameHandler(), WwwHandler(), ) diff --git a/bin/tests/system/resolver/tests_resolver.py b/bin/tests/system/resolver/tests_resolver.py index a7d1cdef43f..1d30e4761b5 100644 --- a/bin/tests/system/resolver/tests_resolver.py +++ b/bin/tests/system/resolver/tests_resolver.py @@ -11,6 +11,8 @@ import time +import dns.message + import isctest @@ -40,3 +42,46 @@ def test_resolver_cache_reloadfails(ns1, templates): # The ttl being lower than 300 (provided by fake authoritative) proves # the cache is still in use assert res.answer[0].ttl < 300 + + +# GL#5930 +def test_resolver_dname_target_filter_attack(): + # Control check - this should return 'attack.example.net. DNAME org.', + # which then should result in resolving 'www.example.org. AAAA', which + # should be SERVAIL because example.org is in 'deny-answer-aliases'. + msg = isctest.query.create("www.example.attack.example.net.", "AAAA") + res = isctest.query.udp(msg, "10.53.0.1") + isctest.check.servfail(res) + + # Execute the attack - this should return 'attack.example.net. DNAME org.', + # which then should result in resolving isc.org and caching the DNAME. + msg = isctest.query.create("isc.attack.example.net.", "A") + res = isctest.query.udp(msg, "10.53.0.1") + answer = """;ANSWER +attack.example.net. 300 IN DNAME org. +isc.attack.example.net. 300 IN CNAME isc.org. +isc.org. 300 IN A 1.2.3.4 +;AUTHORITY +;ADDITIONAL +""" + expected_answer = dns.message.from_text(answer) + isctest.check.noerror(res) + isctest.check.rrsets_equal(res.answer, expected_answer.answer) + isctest.check.rrsets_equal(res.authority, expected_answer.authority) + isctest.check.rrsets_equal(res.additional, expected_answer.additional) + + # Vulnerability check - this should return 'attack.example.net. DNAME org.' + # which then should result in resolving 'www.example.org. A', which + # should still be SERVAIL because example.org is in 'deny-answer-aliases', + # unless the attack on the previous step was successful. + msg = isctest.query.create("www.example.attack.example.net.", "A") + res = isctest.query.udp(msg, "10.53.0.1") + isctest.check.servfail(res) + + # Exception check - this should return 'gooddname.example.net. DNAME org.' + # which then should result in resolving 'www.example.org. A', which + # should be NOERROR because while example.org is in 'deny-answer-aliases', + # gooddname.example.net is in the exceptions list. + msg = isctest.query.create("www.example.gooddname.example.net.", "A") + res = isctest.query.udp(msg, "10.53.0.1") + isctest.check.noerror(res)