]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Add a new 'deny-answer-aliases' check in 'resolver' system test
authorAram Sargsyan <aram@isc.org>
Mon, 18 May 2026 09:14:27 +0000 (09:14 +0000)
committerArаm Sаrgsyаn <aram@isc.org>
Mon, 15 Jun 2026 09:37:07 +0000 (09:37 +0000)
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.

bin/tests/system/resolver/ans2/ans.py
bin/tests/system/resolver/ans3/ans.py
bin/tests/system/resolver/tests_resolver.py

index 9d27310527d878a1e6ab3df044c884d209ea9d8c..78a94b5e89391bacc0e6061a6de1b074453e1b64 100644 (file)
@@ -141,6 +141,7 @@ class Ns2Delegation(DelegationHandler):
 class Ns3Delegation(DelegationHandler):
     domains = [
         "example.net.",
+        "isc.org.",
         "lame.example.org.",
         "sub.example.org.",
     ]
index 8913186a7028c97fabcdbc40bbb6c60e53f7b62e..284f43e584890d13a76640e3eed243bd96342e2a 100644 (file)
@@ -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(),
     )
 
index a7d1cdef43fa7b228b1518096fe0220a7d8a70a8..1d30e4761b52597f62739b3dbdcf6dd12290bdd5 100644 (file)
@@ -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)