]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Add forward-first referral poisoning reproducer
authorAlessio Podda <alessio@isc.org>
Thu, 7 May 2026 08:03:49 +0000 (10:03 +0200)
committerAlessio Podda <alessio@isc.org>
Thu, 18 Jun 2026 12:13:32 +0000 (12:13 +0000)
Add a system test covering authority-section NS referrals returned by
configured forwarders under forward first.

The test verifies that a forwarder for fwd.hack cannot install the
parent hack zone cut and redirect resolution for the sibling zone
sibling.hack.

bin/tests/system/fwdfirst/ans1/ans.py [new file with mode: 0644]
bin/tests/system/fwdfirst/ans1/hack.db [new file with mode: 0644]
bin/tests/system/fwdfirst/ans1/root.db [new file with mode: 0644]
bin/tests/system/fwdfirst/ans1/sibling.hack.db [new file with mode: 0644]
bin/tests/system/fwdfirst/ans2/ans.py [new file with mode: 0644]
bin/tests/system/fwdfirst/ans3/ans.py [new file with mode: 0644]
bin/tests/system/fwdfirst/ns4/named.conf.j2 [new file with mode: 0644]
bin/tests/system/fwdfirst/ns5/named.conf.j2 [new file with mode: 0644]
bin/tests/system/fwdfirst/tests_fwdfirst.py [new file with mode: 0644]

diff --git a/bin/tests/system/fwdfirst/ans1/ans.py b/bin/tests/system/fwdfirst/ans1/ans.py
new file mode 100644 (file)
index 0000000..e812304
--- /dev/null
@@ -0,0 +1,22 @@
+"""
+Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+
+SPDX-License-Identifier: MPL-2.0
+
+This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0.  If a copy of the MPL was not distributed with this
+file, you can obtain one at https://mozilla.org/MPL/2.0/.
+
+See the COPYRIGHT file distributed with this work for additional
+information regarding copyright ownership.
+"""
+
+from isctest.asyncserver import AsyncDnsServer
+
+
+def main() -> None:
+    AsyncDnsServer().run()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/bin/tests/system/fwdfirst/ans1/hack.db b/bin/tests/system/fwdfirst/ans1/hack.db
new file mode 100644 (file)
index 0000000..6445016
--- /dev/null
@@ -0,0 +1,7 @@
+$TTL 300
+@                      SOA     ns.hack. . 0 0 0 0 300
+@                      NS      ns
+ns                     A       10.53.0.1
+
+sibling                        NS      ns.sibling
+ns.sibling             A       10.53.0.1
diff --git a/bin/tests/system/fwdfirst/ans1/root.db b/bin/tests/system/fwdfirst/ans1/root.db
new file mode 100644 (file)
index 0000000..70e6427
--- /dev/null
@@ -0,0 +1,8 @@
+$ORIGIN .
+$TTL 300
+@                      SOA     a.root-servers.nil. . 0 0 0 0 300
+@                      NS      a.root-servers.nil.
+a.root-servers.nil.    A       10.53.0.1
+
+hack.                  NS      ns.hack.
+ns.hack.               A       10.53.0.1
diff --git a/bin/tests/system/fwdfirst/ans1/sibling.hack.db b/bin/tests/system/fwdfirst/ans1/sibling.hack.db
new file mode 100644 (file)
index 0000000..d05d9d5
--- /dev/null
@@ -0,0 +1,5 @@
+$TTL 300
+@                      SOA     ns.sibling.hack. . 0 0 0 0 300
+@                      NS      ns
+ns                     A       10.53.0.1
+victim                 A       203.0.113.42
diff --git a/bin/tests/system/fwdfirst/ans2/ans.py b/bin/tests/system/fwdfirst/ans2/ans.py
new file mode 100644 (file)
index 0000000..cf40fbe
--- /dev/null
@@ -0,0 +1,45 @@
+"""
+Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+
+SPDX-License-Identifier: MPL-2.0
+
+This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0.  If a copy of the MPL was not distributed with this
+file, you can obtain one at https://mozilla.org/MPL/2.0/.
+
+See the COPYRIGHT file distributed with this work for additional
+information regarding copyright ownership.
+"""
+
+import dns.rcode
+import dns.rdataclass
+import dns.rdatatype
+import dns.rrset
+
+from isctest.asyncserver import AsyncDnsServer, DomainHandler, StaticResponseHandler
+
+
+class PoisonReferralForwarder(DomainHandler, StaticResponseHandler):
+    domains = ["fwd.hack."]
+    authority = [
+        dns.rrset.from_text(
+            "hack.", 300, dns.rdataclass.IN, dns.rdatatype.NS, "ns.fwd.hack."
+        )
+    ]
+    additional = [
+        dns.rrset.from_text(
+            "ns.fwd.hack.", 300, dns.rdataclass.IN, dns.rdatatype.A, "10.53.0.3"
+        )
+    ]
+    rcode = dns.rcode.NOERROR
+    authoritative = False
+
+
+def main() -> None:
+    server = AsyncDnsServer()
+    server.install_response_handler(PoisonReferralForwarder())
+    server.run()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/bin/tests/system/fwdfirst/ans3/ans.py b/bin/tests/system/fwdfirst/ans3/ans.py
new file mode 100644 (file)
index 0000000..8a7ecf8
--- /dev/null
@@ -0,0 +1,50 @@
+"""
+Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+
+SPDX-License-Identifier: MPL-2.0
+
+This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0.  If a copy of the MPL was not distributed with this
+file, you can obtain one at https://mozilla.org/MPL/2.0/.
+
+See the COPYRIGHT file distributed with this work for additional
+information regarding copyright ownership.
+"""
+
+from collections.abc import AsyncGenerator
+
+import dns.rcode
+import dns.rdatatype
+import dns.rrset
+
+from isctest.asyncserver import (
+    AsyncDnsServer,
+    DnsResponseSend,
+    QueryContext,
+    ResponseAction,
+    ResponseHandler,
+)
+
+
+class AttackerAuthority(ResponseHandler):
+    async def get_responses(
+        self, qctx: QueryContext
+    ) -> AsyncGenerator[ResponseAction, None]:
+        if qctx.qtype == dns.rdatatype.A:
+            qctx.response.answer.append(
+                dns.rrset.from_text(
+                    qctx.qname, 300, qctx.qclass, dns.rdatatype.A, "6.6.6.6"
+                )
+            )
+
+        yield DnsResponseSend(qctx.response)
+
+
+def main() -> None:
+    server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR)
+    server.install_response_handler(AttackerAuthority())
+    server.run()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/bin/tests/system/fwdfirst/ns4/named.conf.j2 b/bin/tests/system/fwdfirst/ns4/named.conf.j2
new file mode 100644 (file)
index 0000000..46d5dd8
--- /dev/null
@@ -0,0 +1,31 @@
+options {
+       query-source address 10.53.0.4;
+       notify-source 10.53.0.4;
+       transfer-source 10.53.0.4;
+       port @PORT@;
+       pid-file "named.pid";
+       listen-on { 10.53.0.4; };
+       listen-on-v6 { none; };
+       recursion yes;
+       allow-query { any; };
+       allow-recursion { any; };
+       dnssec-validation no;
+       qname-minimization off;
+};
+
+controls {
+       inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+include "../../_common/rndc.key";
+
+zone "." {
+       type hint;
+       file "../../_common/root.hint";
+};
+
+zone "fwd.hack" {
+       type forward;
+       forward first;
+       forwarders { 10.53.0.2 port @PORT@; };
+};
diff --git a/bin/tests/system/fwdfirst/ns5/named.conf.j2 b/bin/tests/system/fwdfirst/ns5/named.conf.j2
new file mode 100644 (file)
index 0000000..82068f1
--- /dev/null
@@ -0,0 +1,27 @@
+options {
+       query-source address 10.53.0.5;
+       notify-source 10.53.0.5;
+       transfer-source 10.53.0.5;
+       port @PORT@;
+       pid-file "named.pid";
+       listen-on { 10.53.0.5; };
+       listen-on-v6 { none; };
+       recursion yes;
+       allow-query { any; };
+       allow-recursion { any; };
+       dnssec-validation no;
+       qname-minimization off;
+       forward first;
+       forwarders { 10.53.0.2 port @PORT@; };
+};
+
+controls {
+       inet 10.53.0.5 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+include "../../_common/rndc.key";
+
+zone "." {
+       type hint;
+       file "../../_common/root.hint";
+};
diff --git a/bin/tests/system/fwdfirst/tests_fwdfirst.py b/bin/tests/system/fwdfirst/tests_fwdfirst.py
new file mode 100644 (file)
index 0000000..4ad3fa9
--- /dev/null
@@ -0,0 +1,49 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0.  If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+import dns.name
+import dns.rdataclass
+import dns.rdatatype
+import pytest
+
+from isctest.instance import NamedInstance
+
+import isctest
+
+
+def _a_records(response, qname: str) -> set[str]:
+    rrset = response.get_rrset(
+        response.answer,
+        dns.name.from_text(qname),
+        dns.rdataclass.IN,
+        dns.rdatatype.A,
+    )
+    if rrset is None:
+        return set()
+    return {rdata.address for rdata in rrset}
+
+
+@pytest.mark.parametrize("resolver_name", ["ns4", "ns5"])
+def test_forward_first_referral_ns_above_forward_zone_not_cached(
+    servers: dict[str, NamedInstance],
+    resolver_name: str,
+) -> None:
+    resolver = servers[resolver_name]
+    resolver.rndc("flush")
+
+    trigger = isctest.query.create("trigger.fwd.hack.", "A", dnssec=False)
+    isctest.query.tcp(trigger, resolver.ip)
+
+    sibling = isctest.query.create("victim.sibling.hack.", "A", dnssec=False)
+    response = isctest.query.tcp(sibling, resolver.ip)
+    isctest.check.noerror(response)
+
+    assert _a_records(response, "victim.sibling.hack.") == {"203.0.113.42"}