Compare commits
10 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
554b5e36e6 | ||
|
|
6fb5cf0d21 | ||
|
|
aa29051b02 | ||
|
|
b5ef622818 | ||
|
|
b1c1dc3b77 | ||
|
|
3f896eb46c | ||
|
|
72dec9a846 | ||
|
|
79b25071b7 | ||
|
|
082d1da26a | ||
|
|
3fa1236ce3 |
49 changed files with 3401 additions and 12446 deletions
24
.gitignore
vendored
24
.gitignore
vendored
|
|
@ -212,29 +212,5 @@ bind-9.7.2b1.tar.gz
|
||||||
/bind-9.18.16.tar.xz.asc
|
/bind-9.18.16.tar.xz.asc
|
||||||
/bind-9.18.17.tar.xz
|
/bind-9.18.17.tar.xz
|
||||||
/bind-9.18.17.tar.xz.asc
|
/bind-9.18.17.tar.xz.asc
|
||||||
/bind-9.18.18.tar.xz
|
|
||||||
/bind-9.18.18.tar.xz.asc
|
|
||||||
/bind-9.18.19.tar.xz
|
/bind-9.18.19.tar.xz
|
||||||
/bind-9.18.19.tar.xz.asc
|
/bind-9.18.19.tar.xz.asc
|
||||||
/bind-9.18.20.tar.xz
|
|
||||||
/bind-9.18.20.tar.xz.asc
|
|
||||||
/bind-9.18.21.tar.xz
|
|
||||||
/bind-9.18.21.tar.xz.asc
|
|
||||||
/bind-9.18.24.tar.xz
|
|
||||||
/bind-9.18.24.tar.xz.asc
|
|
||||||
/bind-9.18.26.tar.xz
|
|
||||||
/bind-9.18.26.tar.xz.asc
|
|
||||||
/bind-9.18.28.tar.xz
|
|
||||||
/bind-9.18.28.tar.xz.asc
|
|
||||||
/bind-9.18.29.tar.xz
|
|
||||||
/bind-9.18.29.tar.xz.asc
|
|
||||||
/bind-9.18.30.tar.xz
|
|
||||||
/bind-9.18.30.tar.xz.asc
|
|
||||||
/bind-9.18.31.tar.xz
|
|
||||||
/bind-9.18.31.tar.xz.asc
|
|
||||||
/bind-9.18.32.tar.xz
|
|
||||||
/bind-9.18.32.tar.xz.asc
|
|
||||||
/bind-9.18.33.tar.xz
|
|
||||||
/bind-9.18.33.tar.xz.asc
|
|
||||||
/bind-9.18.*.tar.xz
|
|
||||||
/bind-9.18.*.tar.xz.asc
|
|
||||||
|
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
From 31bd3a0996a85c0fced0c6ace3da1241b30dc397 Mon Sep 17 00:00:00 2001
|
|
||||||
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com>
|
|
||||||
Date: Thu, 30 Apr 2026 17:28:40 +0200
|
|
||||||
Subject: [PATCH] Use variable PROGRAM_SUFFIX in install target
|
|
||||||
|
|
||||||
--program-suffix is handled by configure itself. But some makefile rules
|
|
||||||
need to know it by a special value. Provide that to make multiple
|
|
||||||
versions installable together on a single system.
|
|
||||||
---
|
|
||||||
Makefile.top | 1 +
|
|
||||||
bin/check/Makefile.am | 6 +++---
|
|
||||||
bin/confgen/Makefile.am | 6 +++---
|
|
||||||
3 files changed, 7 insertions(+), 6 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/Makefile.top b/Makefile.top
|
|
||||||
index e186d15..91c076b 100644
|
|
||||||
--- a/Makefile.top
|
|
||||||
+++ b/Makefile.top
|
|
||||||
@@ -14,6 +14,7 @@ AM_CPPFLAGS = \
|
|
||||||
AM_LDFLAGS = \
|
|
||||||
$(STD_LDFLAGS)
|
|
||||||
LDADD =
|
|
||||||
+PROGRAM_SUFFIX =
|
|
||||||
|
|
||||||
if HOST_MACOS
|
|
||||||
AM_LDFLAGS += \
|
|
||||||
diff --git a/bin/check/Makefile.am b/bin/check/Makefile.am
|
|
||||||
index 8f63c35..36f232c 100644
|
|
||||||
--- a/bin/check/Makefile.am
|
|
||||||
+++ b/bin/check/Makefile.am
|
|
||||||
@@ -27,8 +27,8 @@ LDADD += \
|
|
||||||
bin_PROGRAMS = named-checkconf named-checkzone
|
|
||||||
|
|
||||||
install-exec-hook:
|
|
||||||
- ln -f $(DESTDIR)$(bindir)/named-checkzone \
|
|
||||||
- $(DESTDIR)$(bindir)/named-compilezone
|
|
||||||
+ ln -f $(DESTDIR)$(bindir)/named-checkzone$(PROGRAM_SUFFIX) \
|
|
||||||
+ $(DESTDIR)$(bindir)/named-compilezone$(PROGRAM_SUFFIX)
|
|
||||||
|
|
||||||
uninstall-hook:
|
|
||||||
- -rm -f $(DESTDIR)$(bindir)/named-compilezone
|
|
||||||
+ -rm -f $(DESTDIR)$(bindir)/named-compilezone$(PROGRAM_SUFFIX)
|
|
||||||
diff --git a/bin/confgen/Makefile.am b/bin/confgen/Makefile.am
|
|
||||||
index c1dca43..fe86dd7 100644
|
|
||||||
--- a/bin/confgen/Makefile.am
|
|
||||||
+++ b/bin/confgen/Makefile.am
|
|
||||||
@@ -23,8 +23,8 @@ libconfgen_la_SOURCES = \
|
|
||||||
sbin_PROGRAMS = tsig-keygen rndc-confgen
|
|
||||||
|
|
||||||
install-exec-hook:
|
|
||||||
- ln -f $(DESTDIR)$(sbindir)/tsig-keygen \
|
|
||||||
- $(DESTDIR)$(sbindir)/ddns-confgen
|
|
||||||
+ ln -f $(DESTDIR)$(sbindir)/tsig-keygen$(PROGRAM_SUFFIX) \
|
|
||||||
+ $(DESTDIR)$(sbindir)/ddns-confgen$(PROGRAM_SUFFIX)
|
|
||||||
|
|
||||||
uninstall-hook:
|
|
||||||
- -rm -f $(DESTDIR)$(sbindir)/ddns-confgen
|
|
||||||
+ -rm -f $(DESTDIR)$(sbindir)/ddns-confgen$(PROGRAM_SUFFIX)
|
|
||||||
--
|
|
||||||
2.54.0
|
|
||||||
|
|
||||||
|
|
@ -14,13 +14,6 @@ More details about upstream project can be found on their
|
||||||
only upstream sources and packaging instructions for
|
only upstream sources and packaging instructions for
|
||||||
[Fedora Project](https://fedoraproject.org).
|
[Fedora Project](https://fedoraproject.org).
|
||||||
|
|
||||||
Any rebase requires to be built together with
|
|
||||||
[bind-dyndb-ldap](https://src.fedoraproject.org/rpms/bind-dyndb-ldap/) to prevent conflict
|
|
||||||
at installation of [freeipa-server-dns](https://src.fedoraproject.org/rpms/freeipa).
|
|
||||||
Stable bodhi updates are checked, but rawhide are not checked explicitly.
|
|
||||||
Symbol of libraries in *bind-libs* changes with every minor version change of bind,
|
|
||||||
therefore they break any package dependent on bind-libs.
|
|
||||||
|
|
||||||
## Subpackages
|
## Subpackages
|
||||||
|
|
||||||
The package contains several subpackages, some of them can be disabled on rebuild.
|
The package contains several subpackages, some of them can be disabled on rebuild.
|
||||||
|
|
@ -28,6 +21,7 @@ The package contains several subpackages, some of them can be disabled on rebuil
|
||||||
* **bind** -- *named* daemon providing DNS server
|
* **bind** -- *named* daemon providing DNS server
|
||||||
* **bind-utils** -- set of tools to analyse DNS responses or update entries (dig, host)
|
* **bind-utils** -- set of tools to analyse DNS responses or update entries (dig, host)
|
||||||
* **bind-doc** -- documentation for current bind, *BIND 9 Administrator Reference Manual*.
|
* **bind-doc** -- documentation for current bind, *BIND 9 Administrator Reference Manual*.
|
||||||
|
* **bind-license** -- Shared license for all packages but bind-export-libs.
|
||||||
* **bind-libs** -- Shared libraries used by some others programs
|
* **bind-libs** -- Shared libraries used by some others programs
|
||||||
* **bind-devel** -- Development headers for libs. Can be disabled by `--without DEVEL`
|
* **bind-devel** -- Development headers for libs. Can be disabled by `--without DEVEL`
|
||||||
|
|
||||||
|
|
@ -36,3 +30,4 @@ The package contains several subpackages, some of them can be disabled on rebuil
|
||||||
|
|
||||||
* *GSSTSIG* -- Support for Kerberos authentication in BIND.
|
* *GSSTSIG* -- Support for Kerberos authentication in BIND.
|
||||||
* *LMDB* -- Support for dynamic database for managing runtime added zones. Provides faster removal of added zone with much less overhead. But requires lmdb linked to base libs.
|
* *LMDB* -- Support for dynamic database for managing runtime added zones. Provides faster removal of added zone with much less overhead. But requires lmdb linked to base libs.
|
||||||
|
* *DLZ* -- Support for dynamic loaded modules providing support for features *bind-sdb* provides, but only small module is required.
|
||||||
|
|
|
||||||
|
|
@ -1,854 +0,0 @@
|
||||||
From 2a4786b0edde59274f682c9bd4ae4026c2d08218 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Evan Hunt <each@isc.org>
|
|
||||||
Date: Wed, 1 Jul 2026 22:56:02 -0700
|
|
||||||
Subject: [PATCH] add isctest.mark method for ecdsa_deterinistic
|
|
||||||
|
|
||||||
This checks support for ECDSA deterministic mode in the cryptography
|
|
||||||
library.
|
|
||||||
|
|
||||||
(cherry picked from commit 6e44151466864d3dd783a20da83d01028781d3e2)
|
|
||||||
(cherry picked from commit dc3f52388427f4f94087d984d5a2088b925810e2)
|
|
||||||
|
|
||||||
Reproducer for #5874 NSEC3 impersonation
|
|
||||||
|
|
||||||
LLM generated.
|
|
||||||
|
|
||||||
(cherry picked from commit f3e2eb333be3ac636f745aa13cfb8d9ee8af87d8)
|
|
||||||
(cherry picked from commit 35e3d49d2222c13786a06021c7ed583d2a656e51)
|
|
||||||
|
|
||||||
Update reproducer #5874
|
|
||||||
|
|
||||||
Update the llm generated reproducer:
|
|
||||||
- Move server.py into ans1/ans.py
|
|
||||||
- Remove unnecessary named.conf configuration options
|
|
||||||
- Add comments describing the steps (copied from GL issue)
|
|
||||||
- Rename system test
|
|
||||||
|
|
||||||
(cherry picked from commit c1321fef165a2ef8c2bff971901c58941e8e694c)
|
|
||||||
(cherry picked from commit 833dd3b230b92596074e8da15b12298f46c939f2)
|
|
||||||
---
|
|
||||||
bin/tests/system/chain/ans3/ans.py | 10 +-
|
|
||||||
bin/tests/system/chain/ans4/ans.py | 8 +-
|
|
||||||
bin/tests/system/cookie/ans9/ans.py | 7 +-
|
|
||||||
bin/tests/system/digdelv/ans8/ans.py | 7 +-
|
|
||||||
bin/tests/system/dnssec/ans10/ans.py | 11 +-
|
|
||||||
bin/tests/system/forward/ans11/ans.py | 12 +-
|
|
||||||
bin/tests/system/isctest/mark.py | 12 +
|
|
||||||
.../system/nsec3_impersonation/ans1/ans.py | 280 ++++++++++++++++++
|
|
||||||
.../nsec3_impersonation/ns2/named.conf.j2 | 33 +++
|
|
||||||
.../tests_nsec3_impersonation.py | 152 ++++++++++
|
|
||||||
bin/tests/system/qmin/ans2/ans.py | 11 +-
|
|
||||||
bin/tests/system/qmin/ans3/ans.py | 11 +-
|
|
||||||
bin/tests/system/qmin/ans4/ans.py | 11 +-
|
|
||||||
bin/tests/system/resolver/ans10/ans.py | 12 +-
|
|
||||||
14 files changed, 529 insertions(+), 48 deletions(-)
|
|
||||||
create mode 100644 bin/tests/system/nsec3_impersonation/ans1/ans.py
|
|
||||||
create mode 100644 bin/tests/system/nsec3_impersonation/ns2/named.conf.j2
|
|
||||||
create mode 100644 bin/tests/system/nsec3_impersonation/tests_nsec3_impersonation.py
|
|
||||||
|
|
||||||
diff --git a/bin/tests/system/chain/ans3/ans.py b/bin/tests/system/chain/ans3/ans.py
|
|
||||||
index 0a031c1145..7c54c3c51a 100644
|
|
||||||
--- a/bin/tests/system/chain/ans3/ans.py
|
|
||||||
+++ b/bin/tests/system/chain/ans3/ans.py
|
|
||||||
@@ -19,10 +19,10 @@ import sys
|
|
||||||
import signal
|
|
||||||
import socket
|
|
||||||
import select
|
|
||||||
-from datetime import datetime, timedelta
|
|
||||||
-import functools
|
|
||||||
|
|
||||||
-import dns, dns.message, dns.query
|
|
||||||
+import dns
|
|
||||||
+import dns.message
|
|
||||||
+import dns.query
|
|
||||||
from dns.rdatatype import *
|
|
||||||
from dns.rdataclass import *
|
|
||||||
from dns.rcode import *
|
|
||||||
@@ -173,9 +173,9 @@ else:
|
|
||||||
while running:
|
|
||||||
try:
|
|
||||||
inputready, outputready, exceptready = select.select(input, [], [])
|
|
||||||
- except select.error as e:
|
|
||||||
+ except select.error:
|
|
||||||
break
|
|
||||||
- except socket.error as e:
|
|
||||||
+ except socket.error:
|
|
||||||
break
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
break
|
|
||||||
diff --git a/bin/tests/system/chain/ans4/ans.py b/bin/tests/system/chain/ans4/ans.py
|
|
||||||
index c969117368..de536b25bd 100755
|
|
||||||
--- a/bin/tests/system/chain/ans4/ans.py
|
|
||||||
+++ b/bin/tests/system/chain/ans4/ans.py
|
|
||||||
@@ -22,7 +22,9 @@ import select
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
import functools
|
|
||||||
|
|
||||||
-import dns, dns.message, dns.query
|
|
||||||
+import dns
|
|
||||||
+import dns.message
|
|
||||||
+import dns.query
|
|
||||||
from dns.rdatatype import *
|
|
||||||
from dns.rdataclass import *
|
|
||||||
from dns.rcode import *
|
|
||||||
@@ -371,9 +373,9 @@ else:
|
|
||||||
while running:
|
|
||||||
try:
|
|
||||||
inputready, outputready, exceptready = select.select(input, [], [])
|
|
||||||
- except select.error as e:
|
|
||||||
+ except select.error:
|
|
||||||
break
|
|
||||||
- except socket.error as e:
|
|
||||||
+ except socket.error:
|
|
||||||
break
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
break
|
|
||||||
diff --git a/bin/tests/system/cookie/ans9/ans.py b/bin/tests/system/cookie/ans9/ans.py
|
|
||||||
index 3b0f82cc1d..2710386d74 100644
|
|
||||||
--- a/bin/tests/system/cookie/ans9/ans.py
|
|
||||||
+++ b/bin/tests/system/cookie/ans9/ans.py
|
|
||||||
@@ -15,9 +15,6 @@ import sys
|
|
||||||
import signal
|
|
||||||
import socket
|
|
||||||
import select
|
|
||||||
-from datetime import datetime, timedelta
|
|
||||||
-import time
|
|
||||||
-import functools
|
|
||||||
|
|
||||||
import dns
|
|
||||||
import dns.edns
|
|
||||||
@@ -257,9 +254,9 @@ else:
|
|
||||||
while running:
|
|
||||||
try:
|
|
||||||
inputready, outputready, exceptready = select.select(input, [], [])
|
|
||||||
- except select.error as e:
|
|
||||||
+ except select.error:
|
|
||||||
break
|
|
||||||
- except socket.error as e:
|
|
||||||
+ except socket.error:
|
|
||||||
break
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
break
|
|
||||||
diff --git a/bin/tests/system/digdelv/ans8/ans.py b/bin/tests/system/digdelv/ans8/ans.py
|
|
||||||
index 3e18edc1cc..1896a2bafc 100644
|
|
||||||
--- a/bin/tests/system/digdelv/ans8/ans.py
|
|
||||||
+++ b/bin/tests/system/digdelv/ans8/ans.py
|
|
||||||
@@ -17,7 +17,8 @@ import socket
|
|
||||||
import select
|
|
||||||
import struct
|
|
||||||
|
|
||||||
-import dns, dns.message
|
|
||||||
+import dns
|
|
||||||
+import dns.message
|
|
||||||
from dns.rcode import *
|
|
||||||
|
|
||||||
modes = [
|
|
||||||
@@ -109,9 +110,9 @@ hung_conns = []
|
|
||||||
while running:
|
|
||||||
try:
|
|
||||||
inputready, outputready, exceptready = select.select(input, [], [])
|
|
||||||
- except select.error as e:
|
|
||||||
+ except select.error:
|
|
||||||
break
|
|
||||||
- except socket.error as e:
|
|
||||||
+ except socket.error:
|
|
||||||
break
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
break
|
|
||||||
diff --git a/bin/tests/system/dnssec/ans10/ans.py b/bin/tests/system/dnssec/ans10/ans.py
|
|
||||||
index 84bf0a2642..46af72a147 100644
|
|
||||||
--- a/bin/tests/system/dnssec/ans10/ans.py
|
|
||||||
+++ b/bin/tests/system/dnssec/ans10/ans.py
|
|
||||||
@@ -16,10 +16,11 @@ import signal
|
|
||||||
import socket
|
|
||||||
import select
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
-import time
|
|
||||||
-import functools
|
|
||||||
|
|
||||||
-import dns, dns.message, dns.query, dns.flags
|
|
||||||
+import dns
|
|
||||||
+import dns.message
|
|
||||||
+import dns.query
|
|
||||||
+import dns.flags
|
|
||||||
from dns.rdatatype import *
|
|
||||||
from dns.rdataclass import *
|
|
||||||
from dns.rcode import *
|
|
||||||
@@ -140,9 +141,9 @@ else:
|
|
||||||
while running:
|
|
||||||
try:
|
|
||||||
inputready, outputready, exceptready = select.select(input, [], [])
|
|
||||||
- except select.error as e:
|
|
||||||
+ except select.error:
|
|
||||||
break
|
|
||||||
- except socket.error as e:
|
|
||||||
+ except socket.error:
|
|
||||||
break
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
break
|
|
||||||
diff --git a/bin/tests/system/forward/ans11/ans.py b/bin/tests/system/forward/ans11/ans.py
|
|
||||||
index 00b5895f76..d5b8a5b037 100644
|
|
||||||
--- a/bin/tests/system/forward/ans11/ans.py
|
|
||||||
+++ b/bin/tests/system/forward/ans11/ans.py
|
|
||||||
@@ -16,11 +16,11 @@ import signal
|
|
||||||
import socket
|
|
||||||
import select
|
|
||||||
import struct
|
|
||||||
-from datetime import datetime, timedelta
|
|
||||||
-import time
|
|
||||||
-import functools
|
|
||||||
|
|
||||||
-import dns, dns.message, dns.query, dns.flags
|
|
||||||
+import dns
|
|
||||||
+import dns.message
|
|
||||||
+import dns.query
|
|
||||||
+import dns.flags
|
|
||||||
from dns.rdatatype import *
|
|
||||||
from dns.rdataclass import *
|
|
||||||
from dns.rcode import *
|
|
||||||
@@ -192,9 +192,9 @@ hung_conns = []
|
|
||||||
while running:
|
|
||||||
try:
|
|
||||||
inputready, outputready, exceptready = select.select(input, [], [])
|
|
||||||
- except select.error as e:
|
|
||||||
+ except select.error:
|
|
||||||
break
|
|
||||||
- except socket.error as e:
|
|
||||||
+ except socket.error:
|
|
||||||
break
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
break
|
|
||||||
diff --git a/bin/tests/system/isctest/mark.py b/bin/tests/system/isctest/mark.py
|
|
||||||
index 53860a806c..098af5acfc 100644
|
|
||||||
--- a/bin/tests/system/isctest/mark.py
|
|
||||||
+++ b/bin/tests/system/isctest/mark.py
|
|
||||||
@@ -88,3 +88,15 @@ softhsm2_environment = pytest.mark.skipif(
|
|
||||||
),
|
|
||||||
reason="SOFTHSM2_CONF and SOFTHSM2_MODULE environmental variables must be set and pkcs11-tool and softhsm2-util tools present",
|
|
||||||
)
|
|
||||||
+
|
|
||||||
+ecdsa_deterministic = False
|
|
||||||
+try:
|
|
||||||
+ from cryptography.hazmat.backends import default_backend
|
|
||||||
+
|
|
||||||
+ ecdsa_deterministic = default_backend().ecdsa_deterministic_supported()
|
|
||||||
+except Exception: # pylint: disable=broad-except
|
|
||||||
+ pass
|
|
||||||
+
|
|
||||||
+with_ecdsa_deterministic = pytest.mark.skipif(
|
|
||||||
+ not ecdsa_deterministic, reason="ECDSA deterministic signing is not supported"
|
|
||||||
+)
|
|
||||||
diff --git a/bin/tests/system/nsec3_impersonation/ans1/ans.py b/bin/tests/system/nsec3_impersonation/ans1/ans.py
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..177e79c195
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/nsec3_impersonation/ans1/ans.py
|
|
||||||
@@ -0,0 +1,280 @@
|
|
||||||
+#!/usr/bin/python3
|
|
||||||
+
|
|
||||||
+# 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
|
|
||||||
+from dataclasses import dataclass
|
|
||||||
+from pathlib import Path
|
|
||||||
+
|
|
||||||
+import json
|
|
||||||
+
|
|
||||||
+from cryptography.hazmat.primitives import serialization
|
|
||||||
+
|
|
||||||
+import dns.dnssec
|
|
||||||
+import dns.flags
|
|
||||||
+import dns.message
|
|
||||||
+import dns.name
|
|
||||||
+import dns.rcode
|
|
||||||
+import dns.rdata
|
|
||||||
+import dns.rdataclass
|
|
||||||
+import dns.rdatatype
|
|
||||||
+import dns.rrset
|
|
||||||
+
|
|
||||||
+from isctest.asyncserver import (
|
|
||||||
+ AsyncDnsServer,
|
|
||||||
+ DnsResponseSend,
|
|
||||||
+ QueryContext,
|
|
||||||
+ ResponseHandler,
|
|
||||||
+)
|
|
||||||
+
|
|
||||||
+TTL = 300
|
|
||||||
+TLD = "tld.test."
|
|
||||||
+APEX_HASH = "1B40241KFORIOG780N4IKSCRLVETPCTQ"
|
|
||||||
+ATTACKER = f"{APEX_HASH.lower()}.{TLD}"
|
|
||||||
+VICTIM = f"victim.{TLD}"
|
|
||||||
+AUTH_IP = "10.53.0.1"
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+@dataclass(frozen=True)
|
|
||||||
+class Key:
|
|
||||||
+ zone: dns.name.Name
|
|
||||||
+ private_key: object
|
|
||||||
+ dnskey: dns.rdata.Rdata
|
|
||||||
+ ds: dns.rdata.Rdata
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def name(text: str) -> dns.name.Name:
|
|
||||||
+ return dns.name.from_text(text)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def load_keys() -> dict[str, Key]:
|
|
||||||
+ path = Path(__file__).resolve().parent / "keys.json"
|
|
||||||
+ with path.open(encoding="utf-8") as keys_file:
|
|
||||||
+ raw_keys = json.load(keys_file)
|
|
||||||
+
|
|
||||||
+ keys = {}
|
|
||||||
+ for zone, raw_key in raw_keys.items():
|
|
||||||
+ private_key = serialization.load_pem_private_key(
|
|
||||||
+ raw_key["private_pem"].encode("ascii"),
|
|
||||||
+ password=None,
|
|
||||||
+ )
|
|
||||||
+ dnskey = dns.rdata.from_text(
|
|
||||||
+ dns.rdataclass.IN, dns.rdatatype.DNSKEY, raw_key["dnskey"]
|
|
||||||
+ )
|
|
||||||
+ ds = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DS, raw_key["ds"])
|
|
||||||
+ keys[zone] = Key(name(zone), private_key, dnskey, ds)
|
|
||||||
+
|
|
||||||
+ return keys
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def rrset(owner: str, rdtype: dns.rdatatype.RdataType, *rdatas: str) -> dns.rrset.RRset:
|
|
||||||
+ return dns.rrset.from_text(owner, TTL, dns.rdataclass.IN, rdtype, *rdatas)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def rrset_from_rdata(owner: str, rdata: dns.rdata.Rdata) -> dns.rrset.RRset:
|
|
||||||
+ return dns.rrset.from_rdata(name(owner), TTL, rdata)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def rrsig_rrset(covered: dns.rrset.RRset, signer: Key) -> dns.rrset.RRset:
|
|
||||||
+ rrsig = dns.dnssec.sign(
|
|
||||||
+ covered,
|
|
||||||
+ signer.private_key,
|
|
||||||
+ signer.zone,
|
|
||||||
+ signer.dnskey,
|
|
||||||
+ lifetime=86400,
|
|
||||||
+ verify=True,
|
|
||||||
+ )
|
|
||||||
+ return dns.rrset.from_rdata(covered.name, covered.ttl, rrsig)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def add_signed(
|
|
||||||
+ section: list[dns.rrset.RRset], covered: dns.rrset.RRset, signer: Key
|
|
||||||
+) -> None:
|
|
||||||
+ section.append(covered)
|
|
||||||
+ section.append(rrsig_rrset(covered, signer))
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def dnskey_rrset(zone: str, zone_key: Key) -> dns.rrset.RRset:
|
|
||||||
+ return rrset_from_rdata(zone, zone_key.dnskey)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def ds_rrset(zone: str, zone_key: Key) -> dns.rrset.RRset:
|
|
||||||
+ return rrset_from_rdata(zone, zone_key.ds)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def soa_rrset(zone: str) -> dns.rrset.RRset:
|
|
||||||
+ return rrset(
|
|
||||||
+ zone,
|
|
||||||
+ dns.rdatatype.SOA,
|
|
||||||
+ f"ns.{zone} hostmaster.{zone} 1 3600 600 86400 300",
|
|
||||||
+ )
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def ns_rrset(zone: str, ns_target: str) -> dns.rrset.RRset:
|
|
||||||
+ return rrset(zone, dns.rdatatype.NS, ns_target)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def glue_rrset(ns_target: str, address: str) -> dns.rrset.RRset:
|
|
||||||
+ return rrset(ns_target, dns.rdatatype.A, address)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def answer_dnskey(response: dns.message.Message, zone: str, zone_key: Key) -> None:
|
|
||||||
+ add_signed(response.answer, dnskey_rrset(zone, zone_key), zone_key)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def answer_soa(response: dns.message.Message, zone: str, zone_key: Key) -> None:
|
|
||||||
+ add_signed(response.answer, soa_rrset(zone), zone_key)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def answer_ns(
|
|
||||||
+ response: dns.message.Message, zone: str, ns_target: str, zone_key: Key
|
|
||||||
+) -> None:
|
|
||||||
+ add_signed(response.answer, ns_rrset(zone, ns_target), zone_key)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+class SignedResponseHandler(ResponseHandler):
|
|
||||||
+ def __init__(self, keys: dict[str, Key]) -> None:
|
|
||||||
+ self.keys = keys
|
|
||||||
+
|
|
||||||
+ async def get_responses(
|
|
||||||
+ self, qctx: QueryContext
|
|
||||||
+ ) -> AsyncGenerator[DnsResponseSend, None]:
|
|
||||||
+ qctx.prepare_new_response(with_zone_data=False)
|
|
||||||
+ qctx.response.flags |= dns.flags.AA
|
|
||||||
+ qctx.response.set_rcode(dns.rcode.NOERROR)
|
|
||||||
+ self.respond(qctx)
|
|
||||||
+ yield DnsResponseSend(qctx.response, authoritative=True)
|
|
||||||
+
|
|
||||||
+ def respond(self, qctx: QueryContext) -> None:
|
|
||||||
+ raise NotImplementedError
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def child_nsec3_rrset() -> dns.rrset.RRset:
|
|
||||||
+ rdata = dns.rdata.from_text(
|
|
||||||
+ dns.rdataclass.IN,
|
|
||||||
+ dns.rdatatype.NSEC3,
|
|
||||||
+ f"1 0 0 - {APEX_HASH} NS SOA RRSIG DNSKEY NSEC3PARAM",
|
|
||||||
+ )
|
|
||||||
+ return dns.rrset.from_rdata(name(f"{APEX_HASH}.{TLD}"), TTL, rdata)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def forged_nxdomain(response: dns.message.Message, keys: dict[str, Key]) -> None:
|
|
||||||
+ response.set_rcode(dns.rcode.NXDOMAIN)
|
|
||||||
+
|
|
||||||
+ add_signed(response.authority, soa_rrset(TLD), keys[TLD])
|
|
||||||
+
|
|
||||||
+ # The owner name derives zone "tld.test.", but the RRSIG signer is the
|
|
||||||
+ # malicious child zone "1b40241kforiog780n4ikscrlvetpctq.tld.test.".
|
|
||||||
+ add_signed(response.authority, child_nsec3_rrset(), keys[ATTACKER])
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+class VictimForgedNxdomainHandler(SignedResponseHandler):
|
|
||||||
+ """
|
|
||||||
+ This serves the forged response for the victim's domain.
|
|
||||||
+ """
|
|
||||||
+
|
|
||||||
+ def match(self, qctx: QueryContext) -> bool:
|
|
||||||
+ return qctx.qname == name(VICTIM) and qctx.qtype == dns.rdatatype.A
|
|
||||||
+
|
|
||||||
+ def respond(self, qctx: QueryContext) -> None:
|
|
||||||
+ forged_nxdomain(qctx.response, self.keys)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+class ChildDsHandler(SignedResponseHandler):
|
|
||||||
+ """
|
|
||||||
+ This will spoof the response for the malicious zone when qtype is DS.
|
|
||||||
+ It is actually a validly signed DS response.
|
|
||||||
+ """
|
|
||||||
+
|
|
||||||
+ def match(self, qctx: QueryContext) -> bool:
|
|
||||||
+ return qctx.qname == name(ATTACKER) and qctx.qtype == dns.rdatatype.DS
|
|
||||||
+
|
|
||||||
+ def respond(self, qctx: QueryContext) -> None:
|
|
||||||
+ response = qctx.response
|
|
||||||
+ zone = ATTACKER
|
|
||||||
+ child_key = self.keys[ATTACKER]
|
|
||||||
+ parent_key = self.keys[TLD]
|
|
||||||
+
|
|
||||||
+ add_signed(response.answer, ds_rrset(zone, child_key), parent_key)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+class AttackerZoneHandler(SignedResponseHandler):
|
|
||||||
+ """
|
|
||||||
+ Acts as the malicious authoritative name server. The zone being served
|
|
||||||
+ is the hashed label of the parent zone (tld.test). This will respond
|
|
||||||
+ for all queries qtype SOA, DNSKEY, NS at the apex. Any names below
|
|
||||||
+ the apex are answered with an NXDOMAIN with no NSEC or NSEC3 present.
|
|
||||||
+ """
|
|
||||||
+
|
|
||||||
+ def match(self, qctx: QueryContext) -> bool:
|
|
||||||
+ return qctx.qname.is_subdomain(name(ATTACKER))
|
|
||||||
+
|
|
||||||
+ def respond(self, qctx: QueryContext) -> None:
|
|
||||||
+ if qctx.qname == name(ATTACKER):
|
|
||||||
+ if qctx.qtype == dns.rdatatype.DNSKEY:
|
|
||||||
+ answer_dnskey(qctx.response, ATTACKER, self.keys[ATTACKER])
|
|
||||||
+ elif qctx.qtype == dns.rdatatype.SOA:
|
|
||||||
+ answer_soa(qctx.response, ATTACKER, self.keys[ATTACKER])
|
|
||||||
+ else:
|
|
||||||
+ answer_ns(
|
|
||||||
+ qctx.response, ATTACKER, f"ns.{ATTACKER}", self.keys[ATTACKER]
|
|
||||||
+ )
|
|
||||||
+ qctx.response.additional.append(glue_rrset(f"ns.{ATTACKER}", AUTH_IP))
|
|
||||||
+ return
|
|
||||||
+
|
|
||||||
+ qctx.response.set_rcode(dns.rcode.NXDOMAIN)
|
|
||||||
+ add_signed(qctx.response.authority, soa_rrset(ATTACKER), self.keys[ATTACKER])
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+class TldZoneHandler(SignedResponseHandler):
|
|
||||||
+ """
|
|
||||||
+ Acts as the TLD who is being used in the attack, but is not a standard
|
|
||||||
+ name server. It only responds with validly signed records for DNSKEY, SOA
|
|
||||||
+ and NS on the apex. Any names below the apex are answered with an NXDOMAIN
|
|
||||||
+ with no NSEC or NSEC3 present.
|
|
||||||
+
|
|
||||||
+ If we turn this into a regular name server than the attack won't work.
|
|
||||||
+ The attack assumes that the adversary can inject these responses on-path.
|
|
||||||
+ """
|
|
||||||
+
|
|
||||||
+ def match(self, qctx: QueryContext) -> bool:
|
|
||||||
+ return qctx.qname.is_subdomain(name(TLD))
|
|
||||||
+
|
|
||||||
+ def respond(self, qctx: QueryContext) -> None:
|
|
||||||
+ if qctx.qname == name(TLD):
|
|
||||||
+ if qctx.qtype == dns.rdatatype.DNSKEY:
|
|
||||||
+ answer_dnskey(qctx.response, TLD, self.keys[TLD])
|
|
||||||
+ elif qctx.qtype == dns.rdatatype.SOA:
|
|
||||||
+ answer_soa(qctx.response, TLD, self.keys[TLD])
|
|
||||||
+ else:
|
|
||||||
+ answer_ns(qctx.response, TLD, "ns.tld.test.", self.keys[TLD])
|
|
||||||
+ qctx.response.additional.append(glue_rrset("ns.tld.test.", AUTH_IP))
|
|
||||||
+ return
|
|
||||||
+
|
|
||||||
+ qctx.response.set_rcode(dns.rcode.NXDOMAIN)
|
|
||||||
+ add_signed(qctx.response.authority, soa_rrset(TLD), self.keys[TLD])
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def main() -> None:
|
|
||||||
+ keys = load_keys()
|
|
||||||
+ server = AsyncDnsServer(default_aa=True)
|
|
||||||
+ server.install_response_handlers(
|
|
||||||
+ VictimForgedNxdomainHandler(keys),
|
|
||||||
+ ChildDsHandler(keys),
|
|
||||||
+ AttackerZoneHandler(keys),
|
|
||||||
+ TldZoneHandler(keys),
|
|
||||||
+ )
|
|
||||||
+ server.run()
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ main()
|
|
||||||
diff --git a/bin/tests/system/nsec3_impersonation/ns2/named.conf.j2 b/bin/tests/system/nsec3_impersonation/ns2/named.conf.j2
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..2c9b0bba9e
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/nsec3_impersonation/ns2/named.conf.j2
|
|
||||||
@@ -0,0 +1,33 @@
|
|
||||||
+// validating resolver
|
|
||||||
+
|
|
||||||
+options {
|
|
||||||
+ query-source address 10.53.0.2;
|
|
||||||
+ notify-source 10.53.0.2;
|
|
||||||
+ transfer-source 10.53.0.2;
|
|
||||||
+ port @PORT@;
|
|
||||||
+ pid-file "named.pid";
|
|
||||||
+ listen-on { 10.53.0.2; };
|
|
||||||
+ listen-on-v6 { none; };
|
|
||||||
+ recursion yes;
|
|
||||||
+ dnssec-validation yes;
|
|
||||||
+};
|
|
||||||
+
|
|
||||||
+controls {
|
|
||||||
+ inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
|
|
||||||
+};
|
|
||||||
+
|
|
||||||
+include "../../_common/rndc.key";
|
|
||||||
+
|
|
||||||
+zone "." {
|
|
||||||
+ type hint;
|
|
||||||
+ file "../../_common/root.hint";
|
|
||||||
+};
|
|
||||||
+
|
|
||||||
+zone "tld.test" {
|
|
||||||
+ type static-stub;
|
|
||||||
+ server-addresses { 10.53.0.1; };
|
|
||||||
+};
|
|
||||||
+
|
|
||||||
+trust-anchors {
|
|
||||||
+ tld.test. static-key 257 3 13 "@TLD_DNSKEY@";
|
|
||||||
+};
|
|
||||||
diff --git a/bin/tests/system/nsec3_impersonation/tests_nsec3_impersonation.py b/bin/tests/system/nsec3_impersonation/tests_nsec3_impersonation.py
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..bd9bd275b6
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/nsec3_impersonation/tests_nsec3_impersonation.py
|
|
||||||
@@ -0,0 +1,152 @@
|
|
||||||
+#!/usr/bin/python3
|
|
||||||
+
|
|
||||||
+# 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 pathlib import Path
|
|
||||||
+
|
|
||||||
+import json
|
|
||||||
+
|
|
||||||
+from cryptography.hazmat.primitives import serialization
|
|
||||||
+from cryptography.hazmat.primitives.asymmetric import ec
|
|
||||||
+
|
|
||||||
+import dns.dnssec
|
|
||||||
+import dns.flags
|
|
||||||
+import dns.name
|
|
||||||
+import dns.rdataclass
|
|
||||||
+import dns.rdatatype
|
|
||||||
+import pytest
|
|
||||||
+
|
|
||||||
+import isctest
|
|
||||||
+import isctest.mark
|
|
||||||
+
|
|
||||||
+APEX_HASH = "1B40241KFORIOG780N4IKSCRLVETPCTQ"
|
|
||||||
+ATTACKER = f"{APEX_HASH.lower()}.tld.test."
|
|
||||||
+VICTIM = "victim.tld.test."
|
|
||||||
+AUTH = "10.53.0.1"
|
|
||||||
+RESOLVER = "10.53.0.2"
|
|
||||||
+
|
|
||||||
+pytestmark = [
|
|
||||||
+ isctest.mark.with_ecdsa_deterministic,
|
|
||||||
+ pytest.mark.extra_artifacts(
|
|
||||||
+ [
|
|
||||||
+ "ans*/ans.run",
|
|
||||||
+ "ans*/keys.json",
|
|
||||||
+ ]
|
|
||||||
+ ),
|
|
||||||
+]
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def _make_key(zone):
|
|
||||||
+ private_key = ec.generate_private_key(ec.SECP256R1())
|
|
||||||
+ dnskey = dns.dnssec.make_dnskey(
|
|
||||||
+ private_key.public_key(),
|
|
||||||
+ algorithm="ECDSAP256SHA256",
|
|
||||||
+ flags=257,
|
|
||||||
+ )
|
|
||||||
+ ds = dns.dnssec.make_ds(dns.name.from_text(zone), dnskey, "SHA256")
|
|
||||||
+ private_pem = private_key.private_bytes(
|
|
||||||
+ encoding=serialization.Encoding.PEM,
|
|
||||||
+ format=serialization.PrivateFormat.PKCS8,
|
|
||||||
+ encryption_algorithm=serialization.NoEncryption(),
|
|
||||||
+ ).decode("ascii")
|
|
||||||
+ return {
|
|
||||||
+ "private_pem": private_pem,
|
|
||||||
+ "dnskey": dnskey.to_text(),
|
|
||||||
+ "ds": ds.to_text(),
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def bootstrap():
|
|
||||||
+ zones = ["tld.test.", ATTACKER]
|
|
||||||
+ keys = {zone: _make_key(zone) for zone in zones}
|
|
||||||
+
|
|
||||||
+ Path("ans1/keys.json").write_text(json.dumps(keys, indent=2), encoding="ascii")
|
|
||||||
+
|
|
||||||
+ tld_dnskey = "".join(keys["tld.test."]["dnskey"].split()[3:])
|
|
||||||
+ return {"TLD_DNSKEY": tld_dnskey}
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def check_dnskey_response(zone):
|
|
||||||
+ query = isctest.query.create(zone, "DNSKEY")
|
|
||||||
+ response = isctest.query.tcp(query, AUTH)
|
|
||||||
+
|
|
||||||
+ isctest.check.noerror(response)
|
|
||||||
+ assert response.flags & dns.flags.AA
|
|
||||||
+ assert (
|
|
||||||
+ response.get_rrset(
|
|
||||||
+ response.answer,
|
|
||||||
+ dns.name.from_text(zone),
|
|
||||||
+ dns.rdataclass.IN,
|
|
||||||
+ dns.rdatatype.DNSKEY,
|
|
||||||
+ )
|
|
||||||
+ is not None
|
|
||||||
+ ), response
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def check_ds_response(zone):
|
|
||||||
+ query = isctest.query.create(zone, "DS")
|
|
||||||
+ response = isctest.query.tcp(query, AUTH)
|
|
||||||
+
|
|
||||||
+ isctest.check.noerror(response)
|
|
||||||
+ assert response.flags & dns.flags.AA
|
|
||||||
+ assert (
|
|
||||||
+ response.get_rrset(
|
|
||||||
+ response.answer,
|
|
||||||
+ dns.name.from_text(zone),
|
|
||||||
+ dns.rdataclass.IN,
|
|
||||||
+ dns.rdatatype.DS,
|
|
||||||
+ )
|
|
||||||
+ is not None
|
|
||||||
+ ), response
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def test_attack_responses():
|
|
||||||
+ check_dnskey_response("tld.test.")
|
|
||||||
+ check_dnskey_response(ATTACKER)
|
|
||||||
+ check_ds_response(ATTACKER)
|
|
||||||
+
|
|
||||||
+ query = isctest.query.create(VICTIM, "A")
|
|
||||||
+ response = isctest.query.tcp(query, AUTH)
|
|
||||||
+
|
|
||||||
+ isctest.check.nxdomain(response)
|
|
||||||
+ assert response.flags & dns.flags.AA
|
|
||||||
+
|
|
||||||
+ nsec3_owner = dns.name.from_text(f"{APEX_HASH}.tld.test.")
|
|
||||||
+ nsec3 = response.get_rrset(
|
|
||||||
+ response.authority,
|
|
||||||
+ nsec3_owner,
|
|
||||||
+ dns.rdataclass.IN,
|
|
||||||
+ dns.rdatatype.NSEC3,
|
|
||||||
+ )
|
|
||||||
+ rrsig = response.get_rrset(
|
|
||||||
+ response.authority,
|
|
||||||
+ nsec3_owner,
|
|
||||||
+ dns.rdataclass.IN,
|
|
||||||
+ dns.rdatatype.RRSIG,
|
|
||||||
+ covers=dns.rdatatype.NSEC3,
|
|
||||||
+ )
|
|
||||||
+
|
|
||||||
+ assert nsec3 is not None, response
|
|
||||||
+ assert rrsig is not None, response
|
|
||||||
+ assert rrsig[0].signer == dns.name.from_text(ATTACKER)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def test_nsec3_impersonation():
|
|
||||||
+ """
|
|
||||||
+ Reproducer for #5874:
|
|
||||||
+ F-006 DNSSEC Validation Bypass NSEC3 Apex Hash Label Parent Impersonation
|
|
||||||
+ """
|
|
||||||
+ query = isctest.query.create(VICTIM, "A")
|
|
||||||
+ response = isctest.query.tcp(query, RESOLVER)
|
|
||||||
+
|
|
||||||
+ isctest.check.noadflag(response)
|
|
||||||
+ isctest.check.servfail(response)
|
|
||||||
diff --git a/bin/tests/system/qmin/ans2/ans.py b/bin/tests/system/qmin/ans2/ans.py
|
|
||||||
index d372c2003b..9343fbc8ef 100755
|
|
||||||
--- a/bin/tests/system/qmin/ans2/ans.py
|
|
||||||
+++ b/bin/tests/system/qmin/ans2/ans.py
|
|
||||||
@@ -15,11 +15,12 @@ import sys
|
|
||||||
import signal
|
|
||||||
import socket
|
|
||||||
import select
|
|
||||||
-from datetime import datetime, timedelta
|
|
||||||
import time
|
|
||||||
-import functools
|
|
||||||
|
|
||||||
-import dns, dns.message, dns.query, dns.flags
|
|
||||||
+import dns
|
|
||||||
+import dns.message
|
|
||||||
+import dns.query
|
|
||||||
+import dns.flags
|
|
||||||
from dns.rdatatype import *
|
|
||||||
from dns.rdataclass import *
|
|
||||||
from dns.rcode import *
|
|
||||||
@@ -432,9 +433,9 @@ else:
|
|
||||||
while running:
|
|
||||||
try:
|
|
||||||
inputready, outputready, exceptready = select.select(input, [], [])
|
|
||||||
- except select.error as e:
|
|
||||||
+ except select.error:
|
|
||||||
break
|
|
||||||
- except socket.error as e:
|
|
||||||
+ except socket.error:
|
|
||||||
break
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
break
|
|
||||||
diff --git a/bin/tests/system/qmin/ans3/ans.py b/bin/tests/system/qmin/ans3/ans.py
|
|
||||||
index b5ae73c3fa..4e7250790f 100755
|
|
||||||
--- a/bin/tests/system/qmin/ans3/ans.py
|
|
||||||
+++ b/bin/tests/system/qmin/ans3/ans.py
|
|
||||||
@@ -15,11 +15,12 @@ import sys
|
|
||||||
import signal
|
|
||||||
import socket
|
|
||||||
import select
|
|
||||||
-from datetime import datetime, timedelta
|
|
||||||
import time
|
|
||||||
-import functools
|
|
||||||
|
|
||||||
-import dns, dns.message, dns.query, dns.flags
|
|
||||||
+import dns
|
|
||||||
+import dns.message
|
|
||||||
+import dns.query
|
|
||||||
+import dns.flags
|
|
||||||
from dns.rdatatype import *
|
|
||||||
from dns.rdataclass import *
|
|
||||||
from dns.rcode import *
|
|
||||||
@@ -261,9 +262,9 @@ else:
|
|
||||||
while running:
|
|
||||||
try:
|
|
||||||
inputready, outputready, exceptready = select.select(input, [], [])
|
|
||||||
- except select.error as e:
|
|
||||||
+ except select.error:
|
|
||||||
break
|
|
||||||
- except socket.error as e:
|
|
||||||
+ except socket.error:
|
|
||||||
break
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
break
|
|
||||||
diff --git a/bin/tests/system/qmin/ans4/ans.py b/bin/tests/system/qmin/ans4/ans.py
|
|
||||||
index 517217aec1..2d5556daff 100755
|
|
||||||
--- a/bin/tests/system/qmin/ans4/ans.py
|
|
||||||
+++ b/bin/tests/system/qmin/ans4/ans.py
|
|
||||||
@@ -15,11 +15,12 @@ import sys
|
|
||||||
import signal
|
|
||||||
import socket
|
|
||||||
import select
|
|
||||||
-from datetime import datetime, timedelta
|
|
||||||
import time
|
|
||||||
-import functools
|
|
||||||
|
|
||||||
-import dns, dns.message, dns.query, dns.flags
|
|
||||||
+import dns
|
|
||||||
+import dns.message
|
|
||||||
+import dns.query
|
|
||||||
+import dns.flags
|
|
||||||
from dns.rdatatype import *
|
|
||||||
from dns.rdataclass import *
|
|
||||||
from dns.rcode import *
|
|
||||||
@@ -320,9 +321,9 @@ else:
|
|
||||||
while running:
|
|
||||||
try:
|
|
||||||
inputready, outputready, exceptready = select.select(input, [], [])
|
|
||||||
- except select.error as e:
|
|
||||||
+ except select.error:
|
|
||||||
break
|
|
||||||
- except socket.error as e:
|
|
||||||
+ except socket.error:
|
|
||||||
break
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
break
|
|
||||||
diff --git a/bin/tests/system/resolver/ans10/ans.py b/bin/tests/system/resolver/ans10/ans.py
|
|
||||||
index 6e95dbbfc6..d637c63e5a 100644
|
|
||||||
--- a/bin/tests/system/resolver/ans10/ans.py
|
|
||||||
+++ b/bin/tests/system/resolver/ans10/ans.py
|
|
||||||
@@ -15,11 +15,11 @@ import sys
|
|
||||||
import signal
|
|
||||||
import socket
|
|
||||||
import select
|
|
||||||
-from datetime import datetime, timedelta
|
|
||||||
-import time
|
|
||||||
-import functools
|
|
||||||
|
|
||||||
-import dns, dns.message, dns.query, dns.flags
|
|
||||||
+import dns
|
|
||||||
+import dns.message
|
|
||||||
+import dns.query
|
|
||||||
+import dns.flags
|
|
||||||
from dns.rdatatype import *
|
|
||||||
from dns.rdataclass import *
|
|
||||||
from dns.rcode import *
|
|
||||||
@@ -128,9 +128,9 @@ else:
|
|
||||||
while running:
|
|
||||||
try:
|
|
||||||
inputready, outputready, exceptready = select.select(input, [], [])
|
|
||||||
- except select.error as e:
|
|
||||||
+ except select.error:
|
|
||||||
break
|
|
||||||
- except socket.error as e:
|
|
||||||
+ except socket.error:
|
|
||||||
break
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
break
|
|
||||||
--
|
|
||||||
2.55.0
|
|
||||||
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
From 608026780a43abe5b23a9af3af21808369032158 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Evan Hunt <each@isc.org>
|
|
||||||
Date: Thu, 21 May 2026 14:41:55 -0700
|
|
||||||
Subject: [PATCH] Check NSEC3 signer matches the owning zone
|
|
||||||
|
|
||||||
When validating NSEC3 records, reject any signature whose signer field
|
|
||||||
does not match the zone owning the NSEC3.
|
|
||||||
|
|
||||||
This ensures that a child zone cannot impersonate its parent and forge
|
|
||||||
NXDOMAIN responses for sibling domains.
|
|
||||||
|
|
||||||
Fixes: isc-projects/bind9#5874
|
|
||||||
(cherry picked from commit 6e5066bb1f0f12d090e8707adb7d6ccf74f8012b)
|
|
||||||
(cherry picked from commit c9cb6a5e24e43489cf3fd4d4cc2193b6a74499cb)
|
|
||||||
---
|
|
||||||
lib/dns/dnssec.c | 19 +++++++++++++++++--
|
|
||||||
lib/isc/result.c | 2 +-
|
|
||||||
2 files changed, 18 insertions(+), 3 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c
|
|
||||||
index b12529b5d5..9b9b1f2bb2 100644
|
|
||||||
--- a/lib/dns/dnssec.c
|
|
||||||
+++ b/lib/dns/dnssec.c
|
|
||||||
@@ -424,10 +424,25 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
- * NS, SOA and DNSKEY records are signed by their owner.
|
|
||||||
- * DS records are signed by the parent.
|
|
||||||
+ * NS, SOA and DNSKEY records are signed by their owners.
|
|
||||||
+ * NSEC3 records are signed by the apex, exactly one level up
|
|
||||||
+ * from their owner names.
|
|
||||||
+ * DS records are signed by the parent zone.
|
|
||||||
*/
|
|
||||||
switch (set->type) {
|
|
||||||
+ case dns_rdatatype_nsec3: {
|
|
||||||
+ dns_name_t apex = DNS_NAME_INITEMPTY;
|
|
||||||
+ labels = dns_name_countlabels(name);
|
|
||||||
+ if (labels <= 1) {
|
|
||||||
+ inc_stat(dns_dnssecstats_fail);
|
|
||||||
+ return DNS_R_INVALIDNSEC3;
|
|
||||||
+ }
|
|
||||||
+ dns_name_split(name, labels - 1, NULL, &apex);
|
|
||||||
+ if (!dns_name_equal(&apex, &sig.signer)) {
|
|
||||||
+ inc_stat(dns_dnssecstats_fail);
|
|
||||||
+ return DNS_R_SIGINVALID;
|
|
||||||
+ }
|
|
||||||
+ } break;
|
|
||||||
case dns_rdatatype_ns:
|
|
||||||
case dns_rdatatype_soa:
|
|
||||||
case dns_rdatatype_dnskey:
|
|
||||||
diff --git a/lib/isc/result.c b/lib/isc/result.c
|
|
||||||
index 83e8cfeed7..b76c3d1f7b 100644
|
|
||||||
--- a/lib/isc/result.c
|
|
||||||
+++ b/lib/isc/result.c
|
|
||||||
@@ -198,7 +198,7 @@ static const char *description[ISC_R_NRESULTS] = {
|
|
||||||
[DNS_R_COVERINGNSEC] = "covering NSEC record returned",
|
|
||||||
[DNS_R_MXISADDRESS] = "MX is an address",
|
|
||||||
[DNS_R_DUPLICATE] = "duplicate query",
|
|
||||||
- [DNS_R_INVALIDNSEC3] = "invalid NSEC3 owner name (wildcard)",
|
|
||||||
+ [DNS_R_INVALIDNSEC3] = "invalid NSEC3 owner name",
|
|
||||||
[DNS_R_NOTPRIMARY] = "not primary",
|
|
||||||
[DNS_R_BROKENCHAIN] = "broken trust chain",
|
|
||||||
[DNS_R_EXPIRED] = "expired",
|
|
||||||
--
|
|
||||||
2.55.0
|
|
||||||
|
|
||||||
|
|
@ -1,320 +0,0 @@
|
||||||
From a4ce4c0ce5b8d7630417730dc1b98bf554e0801f Mon Sep 17 00:00:00 2001
|
|
||||||
From: Mark Andrews <marka@isc.org>
|
|
||||||
Date: Tue, 19 May 2026 10:44:04 +1000
|
|
||||||
Subject: [PATCH] Check that dns_name_fromwire honours the active region
|
|
||||||
|
|
||||||
When reading DNS records from the wire the active region of the
|
|
||||||
source buffer is set to the end of the current record. dns_name_fromwire
|
|
||||||
should fail if it attempts to read past this setting.
|
|
||||||
|
|
||||||
(cherry picked from commit 3ed821d68b15fe4e6288e3054397d6bce7e65968)
|
|
||||||
(cherry picked from commit d413c9ac2e29a728531354a69c8c8234c01b7d1e)
|
|
||||||
|
|
||||||
Check that a short PRIVATEDNS record is rejected
|
|
||||||
|
|
||||||
A bug in dns_name_fromwire meant that short PRIVATEDNS key
|
|
||||||
records where being accepted. Test that this is no longer
|
|
||||||
the case.
|
|
||||||
|
|
||||||
(cherry picked from commit f48d48027384d8c2210b5ce9e3eac7af101ead3d)
|
|
||||||
(cherry picked from commit 19ac8b8e46aeb0a15e217bc7bdf485b31b87d9b4)
|
|
||||||
|
|
||||||
POC for PRIVATEDNS DNSKEY overrun not being detected
|
|
||||||
|
|
||||||
Construct a DNS message where a PRIVATEDNS DNSKEY identifier
|
|
||||||
overruns the record boundary by 3 byte so that the label ends
|
|
||||||
at the end of the compression pointer for the next record. The
|
|
||||||
next type is less than 256 so the next octet is 00 terminating
|
|
||||||
the identifier name. The transfered zone is then written to
|
|
||||||
disk using master-format text triggering the assertion when the
|
|
||||||
truncated identier is discovered.
|
|
||||||
|
|
||||||
Note this test will produce a false result in versions of
|
|
||||||
BIND that do not check the PRIVATEDNS identifier as it looks
|
|
||||||
for the error message when the transfer is aborted.
|
|
||||||
|
|
||||||
(cherry picked from commit 9ce3bce8bc8b4e9c6a9b1e84b5849c33eb27830e)
|
|
||||||
(cherry picked from commit 8e066d3fc369e3346f22bb5cfb67a7ab08a74034)
|
|
||||||
---
|
|
||||||
bin/tests/system/xfer/ans9/ans.py | 142 ++++++++++++++++++++++++
|
|
||||||
bin/tests/system/xfer/ns6/named.conf.in | 9 ++
|
|
||||||
bin/tests/system/xfer/tests.sh | 16 +++
|
|
||||||
tests/dns/name_test.c | 30 +++++
|
|
||||||
tests/dns/rdata_test.c | 21 ++++
|
|
||||||
5 files changed, 218 insertions(+)
|
|
||||||
create mode 100644 bin/tests/system/xfer/ans9/ans.py
|
|
||||||
|
|
||||||
diff --git a/bin/tests/system/xfer/ans9/ans.py b/bin/tests/system/xfer/ans9/ans.py
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..a9e73953ee
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/xfer/ans9/ans.py
|
|
||||||
@@ -0,0 +1,142 @@
|
|
||||||
+"""
|
|
||||||
+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.name
|
|
||||||
+import dns.rcode
|
|
||||||
+import dns.rdatatype
|
|
||||||
+import dns.rrset
|
|
||||||
+
|
|
||||||
+from isctest.asyncserver import (
|
|
||||||
+ ControllableAsyncDnsServer,
|
|
||||||
+ DnsResponseSend,
|
|
||||||
+ DomainHandler,
|
|
||||||
+ QueryContext,
|
|
||||||
+ ResponseAction,
|
|
||||||
+ ToggleResponsesCommand,
|
|
||||||
+)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+class AXFRServer(DomainHandler):
|
|
||||||
+ """
|
|
||||||
+ Yield SOA and AXFR responses. Every new AXFR response increments the SOA
|
|
||||||
+ version.
|
|
||||||
+ """
|
|
||||||
+
|
|
||||||
+ domains = ["xfr-and-reconfig", "private-dns-overrun"]
|
|
||||||
+
|
|
||||||
+ def __init__(self) -> None:
|
|
||||||
+ super().__init__()
|
|
||||||
+ self.soa_version = 0
|
|
||||||
+
|
|
||||||
+ async def get_responses(
|
|
||||||
+ self, qctx: QueryContext
|
|
||||||
+ ) -> AsyncGenerator[ResponseAction, None]:
|
|
||||||
+ # This is oversimplified because I am lazy - we are appending the SOA
|
|
||||||
+ # RRset to the ANSWER section for _every_ QTYPE. named is only
|
|
||||||
+ # expected to send a SOA query over UDP and then an AXFR query over
|
|
||||||
+ # TCP. Responses to both of those start with a SOA RRset in the ANSWER
|
|
||||||
+ # section :-)
|
|
||||||
+ soa_message = qctx.response
|
|
||||||
+ soa_rrset = dns.rrset.from_text(
|
|
||||||
+ qctx.qname,
|
|
||||||
+ 300,
|
|
||||||
+ qctx.qclass,
|
|
||||||
+ dns.rdatatype.SOA,
|
|
||||||
+ f". . {self.soa_version} 0 0 0 0",
|
|
||||||
+ )
|
|
||||||
+ soa_message.answer.append(soa_rrset)
|
|
||||||
+
|
|
||||||
+ yield DnsResponseSend(soa_message)
|
|
||||||
+
|
|
||||||
+ if qctx.qtype == dns.rdatatype.SOA:
|
|
||||||
+ # If QTYPE=SOA, the SOA record is the complete response.
|
|
||||||
+ return
|
|
||||||
+
|
|
||||||
+ if qctx.qtype != dns.rdatatype.AXFR:
|
|
||||||
+ # If QTYPE=AXFR, we will continue cramming RRsets into the ANSWER
|
|
||||||
+ # section of a subsequent DNS message below.
|
|
||||||
+ #
|
|
||||||
+ # If QTYPE was not SOA or AXFR, abort. Yeah, we just sent a broken
|
|
||||||
+ # response by yielding DnsResponseSend() with a SOA RRset in the
|
|
||||||
+ # ANSWER section above. We will have to carry that burden for the
|
|
||||||
+ # rest of our lives.
|
|
||||||
+ return
|
|
||||||
+
|
|
||||||
+ # Send just the obligatory NS RRset at zone apex in the next message.
|
|
||||||
+ # This is stupidly inefficient, but makes looping below simpler as we
|
|
||||||
+ # will already have been done with the mandatory stuff by then.
|
|
||||||
+ ns_message = qctx.prepare_new_response()
|
|
||||||
+ ns_rrset = dns.rrset.from_text(
|
|
||||||
+ qctx.qname, 300, qctx.qclass, dns.rdatatype.NS, "."
|
|
||||||
+ )
|
|
||||||
+ ns_message.answer.append(ns_rrset)
|
|
||||||
+
|
|
||||||
+ yield DnsResponseSend(ns_message)
|
|
||||||
+
|
|
||||||
+ # Generate the AXFR with a txt rrset.
|
|
||||||
+ txt_message = qctx.prepare_new_response()
|
|
||||||
+ txt_rrset = dns.rrset.from_text(
|
|
||||||
+ qctx.qname,
|
|
||||||
+ 300,
|
|
||||||
+ qctx.qclass,
|
|
||||||
+ dns.rdatatype.TXT,
|
|
||||||
+ "foo bar",
|
|
||||||
+ )
|
|
||||||
+ txt_message.answer.append(txt_rrset)
|
|
||||||
+
|
|
||||||
+ yield DnsResponseSend(txt_message)
|
|
||||||
+
|
|
||||||
+ if qctx.qname == dns.name.from_text("private-dns-overrun"):
|
|
||||||
+ # A message where the malformed DNSKEY algorithm identifier
|
|
||||||
+ # finishes on a 00 byte in the next record. Assumes the
|
|
||||||
+ # next record starts with a compression pointer which is
|
|
||||||
+ # followed by the type which starts with 00.
|
|
||||||
+
|
|
||||||
+ # Generate malformed PRIVATE DNS DNSKEY
|
|
||||||
+ dnskey_message = qctx.prepare_new_response()
|
|
||||||
+ dnskey_rrset = dns.rrset.from_text(
|
|
||||||
+ qctx.qname,
|
|
||||||
+ 300,
|
|
||||||
+ qctx.qclass,
|
|
||||||
+ dns.rdatatype.DNSKEY,
|
|
||||||
+ "\\# 12 00 00 00 fd 09 00 00 00 00 00 00 00",
|
|
||||||
+ )
|
|
||||||
+ dnskey_message.answer.append(dnskey_rrset)
|
|
||||||
+ # Generate well formed PRIVATE DNS DNSKEY
|
|
||||||
+ dnskey_rrset = dns.rrset.from_text(
|
|
||||||
+ qctx.qname,
|
|
||||||
+ 300,
|
|
||||||
+ qctx.qclass,
|
|
||||||
+ dns.rdatatype.DNSKEY,
|
|
||||||
+ "\\# 12 00 00 00 fd 06 00 00 00 00 00 00 00",
|
|
||||||
+ )
|
|
||||||
+ dnskey_message.answer.append(dnskey_rrset)
|
|
||||||
+
|
|
||||||
+ yield DnsResponseSend(dnskey_message)
|
|
||||||
+
|
|
||||||
+ # Finish the AXFR transaction by sending the second SOA RRset.
|
|
||||||
+ yield DnsResponseSend(soa_message)
|
|
||||||
+
|
|
||||||
+ # This makes sure that the next SOA request causes a new zone transfer
|
|
||||||
+ self.soa_version += 1
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ server = ControllableAsyncDnsServer(
|
|
||||||
+ default_aa=True, default_rcode=dns.rcode.NOERROR
|
|
||||||
+ )
|
|
||||||
+ server.install_control_command(ToggleResponsesCommand())
|
|
||||||
+ server.install_response_handler(AXFRServer())
|
|
||||||
+ server.run()
|
|
||||||
diff --git a/bin/tests/system/xfer/ns6/named.conf.in b/bin/tests/system/xfer/ns6/named.conf.in
|
|
||||||
index 142383c89a..63809448f0 100644
|
|
||||||
--- a/bin/tests/system/xfer/ns6/named.conf.in
|
|
||||||
+++ b/bin/tests/system/xfer/ns6/named.conf.in
|
|
||||||
@@ -83,3 +83,12 @@ zone "ixfr-too-big" {
|
|
||||||
primaries { 10.53.0.1; };
|
|
||||||
file "ixfr-too-big.bk";
|
|
||||||
};
|
|
||||||
+
|
|
||||||
+# GL#6004
|
|
||||||
+zone "private-dns-overrun" {
|
|
||||||
+ type secondary;
|
|
||||||
+ primaries { 10.53.0.9; };
|
|
||||||
+ file "private-dns-overrun.bk";
|
|
||||||
+ masterfile-format text; # force bug to be exercised
|
|
||||||
+ request-ixfr no; # ans9 supports only axfr
|
|
||||||
+};
|
|
||||||
diff --git a/bin/tests/system/xfer/tests.sh b/bin/tests/system/xfer/tests.sh
|
|
||||||
index a2c0adbc28..e08be175b7 100755
|
|
||||||
--- a/bin/tests/system/xfer/tests.sh
|
|
||||||
+++ b/bin/tests/system/xfer/tests.sh
|
|
||||||
@@ -622,5 +622,21 @@ if [ $tmp -eq 0 ]; then
|
|
||||||
fi
|
|
||||||
status=$((status + tmp))
|
|
||||||
|
|
||||||
+# def test_malformed_private_dns_identifier_overrun(ns6):
|
|
||||||
+# isctest.log.info(
|
|
||||||
+# "Check that a malformed PRIVATEDNS DNSKEY which overruns the record is rejected"
|
|
||||||
+# )
|
|
||||||
+# with ns6.watch_log_from_start(timeout=60) as watcher_transfer_completed:
|
|
||||||
+# watcher_transfer_completed.wait_for_line(
|
|
||||||
+# "zone private-dns-overrun/IN: zone transfer finished: unexpected end of input"
|
|
||||||
+# )
|
|
||||||
+n=$((n + 1))
|
|
||||||
+echo_i "Check that a malformed PRIVATEDNS DNSKEY which overruns the record is rejected ($n)"
|
|
||||||
+tmp=0
|
|
||||||
+nextpartreset ns6/named.run
|
|
||||||
+retry 60 wait_for_message "zone private-dns-overrun/IN: zone transfer finished: unexpected end of input" || tmp=1
|
|
||||||
+if test $tmp != 0; then echo_i "failed"; fi
|
|
||||||
+status=$((status + tmp))
|
|
||||||
+
|
|
||||||
echo_i "exit status: $status"
|
|
||||||
[ $status -eq 0 ] || exit 1
|
|
||||||
diff --git a/tests/dns/name_test.c b/tests/dns/name_test.c
|
|
||||||
index fb34dcace1..95f6598eb8 100644
|
|
||||||
--- a/tests/dns/name_test.c
|
|
||||||
+++ b/tests/dns/name_test.c
|
|
||||||
@@ -335,6 +335,35 @@ ISC_RUN_TEST_IMPL(fromregion) {
|
|
||||||
assert_false(dns_name_isabsolute(&name));
|
|
||||||
}
|
|
||||||
|
|
||||||
+ISC_RUN_TEST_IMPL(fromwire) {
|
|
||||||
+ dns_decompress_t dctx;
|
|
||||||
+ dns_fixedname_t fixed;
|
|
||||||
+ dns_name_t *name = dns_fixedname_initname(&fixed);
|
|
||||||
+ isc_buffer_t b;
|
|
||||||
+ unsigned char source[] = { 0x03, 'o', 'n', 'e', 0x00, 0x03,
|
|
||||||
+ 't', 'w', 'o', 0x00, 0x05, 't',
|
|
||||||
+ 'h', 'r', 'e', 'e', 0x00 };
|
|
||||||
+ isc_result_t result;
|
|
||||||
+
|
|
||||||
+ isc_buffer_init(&b, source, sizeof(source));
|
|
||||||
+ isc_buffer_add(&b, sizeof(source));
|
|
||||||
+ isc_buffer_setactive(&b, 10); /* names 'one.' and 'two.' */
|
|
||||||
+
|
|
||||||
+ /*
|
|
||||||
+ * We should only be able to read two names from the buffer
|
|
||||||
+ * as the active region has been set to cover only the first
|
|
||||||
+ * two.
|
|
||||||
+ */
|
|
||||||
+ dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_STRICT);
|
|
||||||
+ dns_decompress_setmethods(&dctx, DNS_COMPRESS_NONE);
|
|
||||||
+ result = dns_name_fromwire(name, &b, &dctx, 0, NULL);
|
|
||||||
+ assert_int_equal(result, ISC_R_SUCCESS);
|
|
||||||
+ result = dns_name_fromwire(name, &b, &dctx, 0, NULL);
|
|
||||||
+ assert_int_equal(result, ISC_R_SUCCESS);
|
|
||||||
+ result = dns_name_fromwire(name, &b, &dctx, 0, NULL);
|
|
||||||
+ assert_int_not_equal(result, ISC_R_SUCCESS);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
/* is trust-anchor-telemetry test */
|
|
||||||
ISC_RUN_TEST_IMPL(istat) {
|
|
||||||
dns_fixedname_t fixed;
|
|
||||||
@@ -778,6 +807,7 @@ ISC_TEST_LIST_START
|
|
||||||
ISC_TEST_ENTRY(fullcompare)
|
|
||||||
ISC_TEST_ENTRY(compression)
|
|
||||||
ISC_TEST_ENTRY(fromregion)
|
|
||||||
+ISC_TEST_ENTRY(fromwire)
|
|
||||||
ISC_TEST_ENTRY(istat)
|
|
||||||
ISC_TEST_ENTRY(init)
|
|
||||||
ISC_TEST_ENTRY(invalidate)
|
|
||||||
diff --git a/tests/dns/rdata_test.c b/tests/dns/rdata_test.c
|
|
||||||
index 6354819d10..7f0df6e046 100644
|
|
||||||
--- a/tests/dns/rdata_test.c
|
|
||||||
+++ b/tests/dns/rdata_test.c
|
|
||||||
@@ -2199,6 +2199,27 @@ ISC_RUN_TEST_IMPL(key) {
|
|
||||||
|
|
||||||
check_rdata(NULL, wire_ok, NULL, false, dns_rdataclass_in,
|
|
||||||
dns_rdatatype_key, sizeof(dns_rdata_key_t));
|
|
||||||
+
|
|
||||||
+ /*
|
|
||||||
+ * A valid PRIVATEDNS record with an active region shorter than the
|
|
||||||
+ * actual record length. A bug in dns_name_fromwire meant that this
|
|
||||||
+ * was previously accepted.
|
|
||||||
+ */
|
|
||||||
+ dns_decompress_t dctx;
|
|
||||||
+ unsigned char key[] = { 0x00, 0x00, 0x00, 253, 0x07, 'e', 'x',
|
|
||||||
+ 'a', 'm', 'p', 'l', 'e', 0x00 };
|
|
||||||
+ unsigned char buf[sizeof(key)];
|
|
||||||
+ isc_buffer_t source, target;
|
|
||||||
+ isc_result_t result;
|
|
||||||
+
|
|
||||||
+ isc_buffer_init(&source, key, sizeof(key));
|
|
||||||
+ isc_buffer_add(&source, sizeof(key));
|
|
||||||
+ isc_buffer_setactive(&source, sizeof(key) - 1);
|
|
||||||
+ isc_buffer_init(&target, buf, sizeof(buf));
|
|
||||||
+ dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_ANY);
|
|
||||||
+ result = dns_rdata_fromwire(NULL, dns_rdataclass_in, dns_rdatatype_key,
|
|
||||||
+ &source, &dctx, 0, &target);
|
|
||||||
+ assert_int_not_equal(result, ISC_R_SUCCESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
--
|
|
||||||
2.55.0
|
|
||||||
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
From 7596cbc240b0492461943f7c34d040fb66a7554c Mon Sep 17 00:00:00 2001
|
|
||||||
From: Mark Andrews <marka@isc.org>
|
|
||||||
Date: Tue, 19 May 2026 15:00:17 +1000
|
|
||||||
Subject: [PATCH] Fix the yaml query zone name code in dnstap-read
|
|
||||||
|
|
||||||
When the buffer to read the query zone name was constructed
|
|
||||||
isc_buffer_setactive was not called. This is now needed as
|
|
||||||
dns_name_fromwire is being corrected to check the active region.
|
|
||||||
|
|
||||||
(cherry picked from commit a25522c28c46655a81d2bf1d96374c81d834b157)
|
|
||||||
(cherry picked from commit a5f1a9d0d2ec021618924b14202ac96ead8299c1)
|
|
||||||
|
|
||||||
Fix dns_name_fromwire to honour the active region
|
|
||||||
|
|
||||||
dns_name_fromwire was not honouring the source buffer's active
|
|
||||||
region when reading names from the wire. This allowed malformed
|
|
||||||
records to be accepted when they shouldn't have been. This has
|
|
||||||
been corrected.
|
|
||||||
|
|
||||||
(cherry picked from commit 7c4f07a7ef6b571073327b02209df7f75b9363ff)
|
|
||||||
(cherry picked from commit e73b70a64453e7d97a11cb5f0afe8bb02d34aaf8)
|
|
||||||
---
|
|
||||||
bin/tools/dnstap-read.c | 1 +
|
|
||||||
lib/dns/name.c | 2 +-
|
|
||||||
2 files changed, 2 insertions(+), 1 deletion(-)
|
|
||||||
|
|
||||||
diff --git a/bin/tools/dnstap-read.c b/bin/tools/dnstap-read.c
|
|
||||||
index a1d0243a1a..bb78ae12b1 100644
|
|
||||||
--- a/bin/tools/dnstap-read.c
|
|
||||||
+++ b/bin/tools/dnstap-read.c
|
|
||||||
@@ -298,6 +298,7 @@ print_yaml(dns_dtdata_t *dt) {
|
|
||||||
|
|
||||||
isc_buffer_init(&b, m->query_zone.data, m->query_zone.len);
|
|
||||||
isc_buffer_add(&b, m->query_zone.len);
|
|
||||||
+ isc_buffer_setactive(&b, m->query_zone.len);
|
|
||||||
|
|
||||||
dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_NONE);
|
|
||||||
result = dns_name_fromwire(name, &b, &dctx, 0, NULL);
|
|
||||||
diff --git a/lib/dns/name.c b/lib/dns/name.c
|
|
||||||
index cc0e30e5b5..2ce868a2ba 100644
|
|
||||||
--- a/lib/dns/name.c
|
|
||||||
+++ b/lib/dns/name.c
|
|
||||||
@@ -1833,7 +1833,7 @@ dns_name_fromwire(dns_name_t *const name, isc_buffer_t *const source,
|
|
||||||
* The amount of the source we consumed is set once.
|
|
||||||
*/
|
|
||||||
const uint8_t *const source_buf = isc_buffer_base(source);
|
|
||||||
- const uint8_t *const source_max = isc_buffer_used(source);
|
|
||||||
+ const uint8_t *const source_max = isc_buffer_active(source);
|
|
||||||
const uint8_t *const start = isc_buffer_current(source);
|
|
||||||
const uint8_t *marker = start;
|
|
||||||
const uint8_t *cursor = start;
|
|
||||||
--
|
|
||||||
2.55.0
|
|
||||||
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
From cde8bb21e09205a7bd1f41fd07ed011fc80d8d71 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Mark Andrews <marka@isc.org>
|
|
||||||
Date: Fri, 10 Apr 2026 10:24:06 +1000
|
|
||||||
Subject: [PATCH] Fix TTL extraction from A/AAAA record
|
|
||||||
|
|
||||||
(cherry picked from commit 89c86e338db2492b92e6618c586f146c6928dc6d)
|
|
||||||
(cherry picked from commit adc8285d23e2eac6ec463f5dbc5a9596fdd36c60)
|
|
||||||
|
|
||||||
Check rpz name too long wildcard CNAME expansion handling
|
|
||||||
|
|
||||||
(cherry picked from commit 9345394e2097031b55b3ef34ceaadf5a7ebbeef2)
|
|
||||||
(cherry picked from commit 095b11f20f911f5b8059bdc349b256d6c64ece30)
|
|
||||||
---
|
|
||||||
bin/tests/system/rpz/ns2/tld2.db | 2 ++
|
|
||||||
bin/tests/system/rpz/ns4/tld4.db | 2 ++
|
|
||||||
bin/tests/system/rpz/tests.sh | 7 +++++--
|
|
||||||
3 files changed, 9 insertions(+), 2 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/bin/tests/system/rpz/ns2/tld2.db b/bin/tests/system/rpz/ns2/tld2.db
|
|
||||||
index c6f2556db5..c091ee27b7 100644
|
|
||||||
--- a/bin/tests/system/rpz/ns2/tld2.db
|
|
||||||
+++ b/bin/tests/system/rpz/ns2/tld2.db
|
|
||||||
@@ -123,3 +123,5 @@ a7-1 A 192.168.7.1
|
|
||||||
|
|
||||||
a7-2 A 192.168.7.2
|
|
||||||
TXT "a7-2 tld2 text"
|
|
||||||
+
|
|
||||||
+*.wild A 192.168.9.1
|
|
||||||
diff --git a/bin/tests/system/rpz/ns4/tld4.db b/bin/tests/system/rpz/ns4/tld4.db
|
|
||||||
index fca419c6dd..8accd76baf 100644
|
|
||||||
--- a/bin/tests/system/rpz/ns4/tld4.db
|
|
||||||
+++ b/bin/tests/system/rpz/ns4/tld4.db
|
|
||||||
@@ -59,6 +59,8 @@ a3-6.tld2 A 56.56.56.56
|
|
||||||
|
|
||||||
a3-7.sub1.tld2 A 57.57.57.57
|
|
||||||
|
|
||||||
+*.wild.sub1.tld2 A 57.57.57.57
|
|
||||||
+
|
|
||||||
a3-8.tld2 A 58.58.58.58
|
|
||||||
|
|
||||||
a3-9.sub9.tld2 A 59.59.59.59
|
|
||||||
diff --git a/bin/tests/system/rpz/tests.sh b/bin/tests/system/rpz/tests.sh
|
|
||||||
index 87e4118ca3..5297437694 100644
|
|
||||||
--- a/bin/tests/system/rpz/tests.sh
|
|
||||||
+++ b/bin/tests/system/rpz/tests.sh
|
|
||||||
@@ -391,7 +391,7 @@ addr() {
|
|
||||||
digcmd $2 >$DIGNM
|
|
||||||
#ckalive "$2" "server crashed by 'dig $2'" || return 1
|
|
||||||
ADDR_ESC=$(echo "$ADDR" | sed -e 's/\./\\./g')
|
|
||||||
- ADDR_TTL=$(sed -n -e "s/^[-.a-z0-9]\{1,\}[ ]*\([0-9]*\) IN AA* ${ADDR_ESC}\$/\1/p" $DIGNM)
|
|
||||||
+ ADDR_TTL=$(sed -n -e "s/^[-.a-z0-9]\{1,\}[ ]*\([0-9]*\)[ ]IN[ ]AA*[ ]${ADDR_ESC}\$/\1/p" $DIGNM)
|
|
||||||
if test -z "$ADDR_TTL"; then
|
|
||||||
setret "'dig $2' wrong; no address $ADDR record in $DIGNM"
|
|
||||||
return 0
|
|
||||||
@@ -516,7 +516,10 @@ nochange TCP a3-9.tld2 # 33 tcp-only
|
|
||||||
here x.servfail <<'EOF' # 34 qname-wait-recurse yes
|
|
||||||
;; status: SERVFAIL, x
|
|
||||||
EOF
|
|
||||||
-addr 35.35.35.35 "x.servfail @$ns5" # 35 qname-wait-recurse no
|
|
||||||
+addr 35.35.35.35 "x.servfail @$ns5" # 35 qname-wait-recurse no
|
|
||||||
+here aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.wild.sub1.tld2 <<'EOF' # 36 wildcard CNAME name to long
|
|
||||||
+ ;; status: YXDOMAIN, x
|
|
||||||
+EOF
|
|
||||||
end_group
|
|
||||||
ckstats $ns3 test1 ns3 22
|
|
||||||
ckstats $ns5 test1 ns5 1
|
|
||||||
--
|
|
||||||
2.55.0
|
|
||||||
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
From 49f4cc4e93f14f1d5b6a472124e6aa457167fede Mon Sep 17 00:00:00 2001
|
|
||||||
From: Mark Andrews <marka@isc.org>
|
|
||||||
Date: Fri, 10 Apr 2026 10:26:14 +1000
|
|
||||||
Subject: [PATCH] Properly handle rpz name to long wildcard expansion
|
|
||||||
|
|
||||||
Previously a self referential CNAME and the original address
|
|
||||||
record were returned. We now return a YXDOMAIN response.
|
|
||||||
|
|
||||||
(cherry picked from commit cfc4c4f69870ce492deaaa429453563d1621ded3)
|
|
||||||
(cherry picked from commit dc328a199f96222e0c30cc20b7b795bfc2c9b2e4)
|
|
||||||
---
|
|
||||||
lib/ns/query.c | 3 ++-
|
|
||||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
|
||||||
|
|
||||||
diff --git a/lib/ns/query.c b/lib/ns/query.c
|
|
||||||
index d3a10be9ba..3bd7daf79c 100644
|
|
||||||
--- a/lib/ns/query.c
|
|
||||||
+++ b/lib/ns/query.c
|
|
||||||
@@ -7591,7 +7591,8 @@ query_rpzcname(query_ctx_t *qctx, dns_name_t *cname) {
|
|
||||||
qctx->fname, NULL);
|
|
||||||
if (result == DNS_R_NAMETOOLONG) {
|
|
||||||
client->message->rcode = dns_rcode_yxdomain;
|
|
||||||
- } else if (result != ISC_R_SUCCESS) {
|
|
||||||
+ }
|
|
||||||
+ if (result != ISC_R_SUCCESS) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
--
|
|
||||||
2.55.0
|
|
||||||
|
|
||||||
|
|
@ -1,280 +0,0 @@
|
||||||
From d7e1f4495d6bac8c29b332e04e9b27140339375b Mon Sep 17 00:00:00 2001
|
|
||||||
From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@sury.org>
|
|
||||||
Date: Tue, 23 Jun 2026 10:59:38 +0200
|
|
||||||
Subject: [PATCH] Make the dns_slabheaders in the cache reference counted
|
|
||||||
|
|
||||||
Instead of only reference counting the enclosing qpcnode, add the
|
|
||||||
reference counting directly to the slabheaders. The reference is
|
|
||||||
incremented when an rdataset is bound to the header and decremented when
|
|
||||||
the rdataset is disassociated, so a stale slabheader can be removed from
|
|
||||||
the node's down chain as soon as its own reference count reaches zero,
|
|
||||||
instead of waiting for the whole qpcnode to become unreferenced.
|
|
||||||
|
|
||||||
Building on that, clean up the ancient headers eagerly: mark_ancient()
|
|
||||||
is made idempotent, releases the header's own (container) reference and
|
|
||||||
reaps the stale headers from the node's down chain as soon as their
|
|
||||||
references reach zero. A header evicted over the per-name type limit is
|
|
||||||
expired only after the new rdataset has been bound, so the bind's
|
|
||||||
increment always precedes mark_ancient()'s decrement.
|
|
||||||
|
|
||||||
Because a header can now be reclaimed independently of its node, the
|
|
||||||
rdataset iterators must keep the header they are positioned on alive:
|
|
||||||
each iterator takes a reference on its current header and releases it
|
|
||||||
when it advances or is destroyed. Iteration otherwise stays lazy and
|
|
||||||
re-reads the node on every step, so it still observes records added to
|
|
||||||
the node while the iterator is live, as zone signing requires.
|
|
||||||
|
|
||||||
The slab headers are shared with the zone databases, so the matching
|
|
||||||
increment is added to every bind path. The noqname/closest proofs hand
|
|
||||||
out rdatasets backed by bare slabs that have no header, so they are
|
|
||||||
given a separate dns_rdataproof_rdatasetmethods that leaves the
|
|
||||||
reference count untouched.
|
|
||||||
|
|
||||||
(cherry picked from commit 2dabf117e1264fd13fb33096f87e78a039fd1c6c)
|
|
||||||
(cherry picked from commit 231b1ca3edfb26389e1af39181aa6b4413e87ec4)
|
|
||||||
---
|
|
||||||
bin/tests/system/reclimit/tests.sh | 4 +-
|
|
||||||
lib/dns/include/dns/rdataslab.h | 1 +
|
|
||||||
lib/dns/rbtdb.c | 77 +++++++++++++++++++++++++-----
|
|
||||||
3 files changed, 69 insertions(+), 13 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/bin/tests/system/reclimit/tests.sh b/bin/tests/system/reclimit/tests.sh
|
|
||||||
index c15225488f..55ccac7759 100644
|
|
||||||
--- a/bin/tests/system/reclimit/tests.sh
|
|
||||||
+++ b/bin/tests/system/reclimit/tests.sh
|
|
||||||
@@ -338,13 +338,13 @@ echo_i "checking that NXDOMAIN names over the max-types-per-name limit don't get
|
|
||||||
|
|
||||||
# Query for 10 NXDOMAIN types
|
|
||||||
for ntype in $(seq 65270 65279); do
|
|
||||||
- check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR big SOA 0 || ret=1
|
|
||||||
+ check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR big SOA 120 || ret=1
|
|
||||||
done
|
|
||||||
# Wait at least 1 second
|
|
||||||
sleep 1
|
|
||||||
# Query for 10 NXDOMAIN types again - these should not be cached
|
|
||||||
for ntype in $(seq 65270 65279); do
|
|
||||||
- check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR big SOA 0 || ret=1
|
|
||||||
+ check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR big SOA 120 || ret=1
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ $ret -ne 0 ]; then echo_i "failed"; fi
|
|
||||||
diff --git a/lib/dns/include/dns/rdataslab.h b/lib/dns/include/dns/rdataslab.h
|
|
||||||
index 5729c004ca..6bd3b5997d 100644
|
|
||||||
--- a/lib/dns/include/dns/rdataslab.h
|
|
||||||
+++ b/lib/dns/include/dns/rdataslab.h
|
|
||||||
@@ -44,6 +44,7 @@
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include <isc/lang.h>
|
|
||||||
+#include <isc/refcount.h>
|
|
||||||
|
|
||||||
#include <dns/types.h>
|
|
||||||
|
|
||||||
diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c
|
|
||||||
index 62bc97d783..0b8547950f 100644
|
|
||||||
--- a/lib/dns/rbtdb.c
|
|
||||||
+++ b/lib/dns/rbtdb.c
|
|
||||||
@@ -158,6 +158,7 @@ struct noqname {
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct rdatasetheader {
|
|
||||||
+ isc_refcount_t references;
|
|
||||||
/*%
|
|
||||||
* Locked by the owning node's lock.
|
|
||||||
*/
|
|
||||||
@@ -1447,6 +1448,7 @@ init_rdataset(dns_rbtdb_t *rbtdb, rdatasetheader_t *h) {
|
|
||||||
h->heap_index = 0;
|
|
||||||
atomic_init(&h->attributes, 0);
|
|
||||||
atomic_init(&h->last_refresh_fail_ts, 0);
|
|
||||||
+ isc_refcount_init(&h->references, 1);
|
|
||||||
|
|
||||||
STATIC_ASSERT(sizeof(h->attributes) == 2,
|
|
||||||
"The .attributes field of rdatasetheader_t needs to be "
|
|
||||||
@@ -1559,6 +1561,9 @@ rollback_node(dns_rbtnode_t *node, rbtdb_serial_t serial) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+static void
|
|
||||||
+clean_stale_headers(dns_rbtdb_t *rbtdb, isc_mem_t *mctx, rdatasetheader_t *top);
|
|
||||||
+
|
|
||||||
static void
|
|
||||||
mark_header_ancient(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) {
|
|
||||||
uint_least16_t attributes = atomic_load_acquire(&header->attributes);
|
|
||||||
@@ -1584,8 +1589,12 @@ mark_header_ancient(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) {
|
|
||||||
update_rrsetstats(rbtdb, header->type, attributes, false);
|
|
||||||
header->node->dirty = 1;
|
|
||||||
|
|
||||||
+ isc_refcount_decrement(&header->references);
|
|
||||||
+
|
|
||||||
/* Increment the stats counter for the ancient RRtype. */
|
|
||||||
update_rrsetstats(rbtdb, header->type, newattributes, true);
|
|
||||||
+
|
|
||||||
+ clean_stale_headers(rbtdb, rbtdb->common.mctx, header);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
@@ -1621,12 +1630,19 @@ static void
|
|
||||||
clean_stale_headers(dns_rbtdb_t *rbtdb, isc_mem_t *mctx,
|
|
||||||
rdatasetheader_t *top) {
|
|
||||||
rdatasetheader_t *d, *down_next;
|
|
||||||
+ rdatasetheader_t *down_parent = top;
|
|
||||||
|
|
||||||
for (d = top->down; d != NULL; d = down_next) {
|
|
||||||
down_next = d->down;
|
|
||||||
- free_rdataset(rbtdb, mctx, d);
|
|
||||||
+ d->next = down_parent;
|
|
||||||
+
|
|
||||||
+ if (isc_refcount_current(&d->references) == 0) {
|
|
||||||
+ free_rdataset(rbtdb, mctx, d);
|
|
||||||
+ down_parent->down = down_next;
|
|
||||||
+ } else {
|
|
||||||
+ down_parent = d;
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
- top->down = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
@@ -1642,6 +1658,7 @@ clean_cache_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node) {
|
|
||||||
for (current = node->data; current != NULL; current = top_next) {
|
|
||||||
top_next = current->next;
|
|
||||||
clean_stale_headers(rbtdb, mctx, current);
|
|
||||||
+ INSIST(current->down == NULL);
|
|
||||||
/*
|
|
||||||
* If current is nonexistent, ancient, or stale and
|
|
||||||
* we are not keeping stale, we can clean it up.
|
|
||||||
@@ -3114,6 +3131,8 @@ bind_rdataset(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, rdatasetheader_t *header,
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ isc_refcount_increment(&header->references);
|
|
||||||
+
|
|
||||||
dns__rbtnode_acquire(rbtdb, node, locktype);
|
|
||||||
|
|
||||||
INSIST(rdataset->methods == NULL); /* We must be disassociated. */
|
|
||||||
@@ -6307,6 +6326,7 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, const dns_name_t *nodename,
|
|
||||||
bool header_nx;
|
|
||||||
bool newheader_nx;
|
|
||||||
bool merge;
|
|
||||||
+ bool do_expireheader = false;
|
|
||||||
dns_rdatatype_t rdtype, covers;
|
|
||||||
rbtdb_rdatatype_t negtype, sigtype;
|
|
||||||
dns_trust_t trust;
|
|
||||||
@@ -6856,6 +6876,7 @@ find_header:
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IS_CACHE(rbtdb) && overmaxtype(rbtdb, ntypes)) {
|
|
||||||
+ do_expireheader = true;
|
|
||||||
if (expireheader == NULL) {
|
|
||||||
expireheader = newheader;
|
|
||||||
}
|
|
||||||
@@ -6869,15 +6890,6 @@ find_header:
|
|
||||||
*/
|
|
||||||
expireheader = newheader;
|
|
||||||
}
|
|
||||||
-
|
|
||||||
- set_ttl(rbtdb, expireheader, 0);
|
|
||||||
- mark_header_ancient(rbtdb, expireheader);
|
|
||||||
- /*
|
|
||||||
- * FIXME: In theory, we should mark the RRSIG
|
|
||||||
- * and the header at the same time, but there is
|
|
||||||
- * no direct link between those two header, so
|
|
||||||
- * we would have to check the whole list again.
|
|
||||||
- */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6901,6 +6913,15 @@ find_header:
|
|
||||||
isc_rwlocktype_write, addedrdataset);
|
|
||||||
}
|
|
||||||
|
|
||||||
+ /*
|
|
||||||
+ * We need to delay the expiration of the header until we are bound to
|
|
||||||
+ * it to prevent decrement-then-increment on the header references.
|
|
||||||
+ */
|
|
||||||
+ if (do_expireheader) {
|
|
||||||
+ set_ttl(rbtdb, expireheader, 0);
|
|
||||||
+ mark_header_ancient(rbtdb, expireheader);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
return ISC_R_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -8692,6 +8713,12 @@ rdataset_disassociate(dns_rdataset_t *rdataset) {
|
|
||||||
dns_db_t *db = rdataset->private1;
|
|
||||||
dns_dbnode_t *node = rdataset->private2;
|
|
||||||
|
|
||||||
+ if (rdataset->methods == &rdataset_methods) {
|
|
||||||
+ rdatasetheader_t *header = rdataset->private3;
|
|
||||||
+ header--;
|
|
||||||
+ isc_refcount_decrement(&header->references);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
detachnode(db, &node);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -8806,6 +8833,11 @@ rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
|
|
||||||
dns_dbnode_t *cloned_node = NULL;
|
|
||||||
|
|
||||||
attachnode(db, node, &cloned_node);
|
|
||||||
+ if (source->methods == &rdataset_methods) {
|
|
||||||
+ rdatasetheader_t *header = source->private3;
|
|
||||||
+ header--;
|
|
||||||
+ isc_refcount_increment(&header->references);
|
|
||||||
+ }
|
|
||||||
INSIST(!ISC_LINK_LINKED(target, link));
|
|
||||||
*target = *source;
|
|
||||||
ISC_LINK_INIT(target, link);
|
|
||||||
@@ -8969,6 +9001,11 @@ rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp) {
|
|
||||||
|
|
||||||
rbtiterator = (rbtdb_rdatasetiter_t *)(*iteratorp);
|
|
||||||
|
|
||||||
+ if (rbtiterator->current != NULL) {
|
|
||||||
+ isc_refcount_decrement(&rbtiterator->current->references);
|
|
||||||
+ rbtiterator->current = NULL;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
if (rbtiterator->common.version != NULL) {
|
|
||||||
closeversion(rbtiterator->common.db,
|
|
||||||
&rbtiterator->common.version, false);
|
|
||||||
@@ -9046,9 +9083,18 @@ rdatasetiter_first(dns_rdatasetiter_t *iterator) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+ if (header != NULL) {
|
|
||||||
+ isc_refcount_increment0(&header->references);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
|
|
||||||
isc_rwlocktype_read);
|
|
||||||
|
|
||||||
+ if (rbtiterator->current != NULL) {
|
|
||||||
+ isc_refcount_decrement(&rbtiterator->current->references);
|
|
||||||
+ rbtiterator->current = NULL;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
rbtiterator->current = header;
|
|
||||||
|
|
||||||
if (header == NULL) {
|
|
||||||
@@ -9140,9 +9186,18 @@ rdatasetiter_next(dns_rdatasetiter_t *iterator) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+ if (header != NULL) {
|
|
||||||
+ isc_refcount_increment0(&header->references);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
|
|
||||||
isc_rwlocktype_read);
|
|
||||||
|
|
||||||
+ if (rbtiterator->current != NULL) {
|
|
||||||
+ isc_refcount_decrement(&rbtiterator->current->references);
|
|
||||||
+ rbtiterator->current = NULL;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
rbtiterator->current = header;
|
|
||||||
|
|
||||||
if (header == NULL) {
|
|
||||||
--
|
|
||||||
2.55.0
|
|
||||||
|
|
||||||
|
|
@ -1,144 +0,0 @@
|
||||||
From b08e0876639ab9f3dae3813202861fd1098f2611 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Mark Andrews <marka@isc.org>
|
|
||||||
Date: Tue, 14 Apr 2026 13:46:22 +1000
|
|
||||||
Subject: [PATCH] Test RRSIG record parsing
|
|
||||||
|
|
||||||
In particular test that labels and signer fields are consistent.
|
|
||||||
|
|
||||||
(cherry picked from commit 5a95e64731afe63d348d272cc4d3b2f9847150c2)
|
|
||||||
(cherry picked from commit 19e496ca260b6a756ae1378e8ebcbdb666b7d9ed)
|
|
||||||
---
|
|
||||||
tests/dns/rdata_test.c | 110 +++++++++++++++++++++++++++++++++++++++++
|
|
||||||
1 file changed, 110 insertions(+)
|
|
||||||
|
|
||||||
diff --git a/tests/dns/rdata_test.c b/tests/dns/rdata_test.c
|
|
||||||
index 7f0df6e046..c704d98ed1 100644
|
|
||||||
--- a/tests/dns/rdata_test.c
|
|
||||||
+++ b/tests/dns/rdata_test.c
|
|
||||||
@@ -2504,6 +2504,115 @@ ISC_RUN_TEST_IMPL(rkey) {
|
|
||||||
dns_rdatatype_rkey, sizeof(dns_rdata_rkey_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
+ISC_RUN_TEST_IMPL(rrsig) {
|
|
||||||
+ text_ok_t text_ok[] = {
|
|
||||||
+ TEXT_VALID("SOA 8 0 86400 20260426170000 20260413160000 54393 "
|
|
||||||
+ ". "
|
|
||||||
+ "tFbcoVP8MnpecUquJ/aj+XeNgV7ts9GSHVkXaXRJrJ/"
|
|
||||||
+ "TEkOZApVG0F6E "
|
|
||||||
+ "9sYpxGk2ItweLL43ujioGj0HWwZDRR+vbur+O/"
|
|
||||||
+ "dIdheiig1VvU+9HXLi "
|
|
||||||
+ "QOViY9Kc64ixdyJhYCC5K+bO1qsHxd+"
|
|
||||||
+ "KJXOaxyHbqchYkDFy4PL6qftE "
|
|
||||||
+ "VaLkueRgjXgOsq/"
|
|
||||||
+ "NxvCXDgAa5xy0+3Sl0myxIs8rJ5KeXfJQFe7qxgaw "
|
|
||||||
+ "VjJsJTKw8neOTw2rQfLaigWu2LIWw+"
|
|
||||||
+ "IyVrLjZJdLqGkiLBGd1w4X3U12 "
|
|
||||||
+ "fFxoY3eqzNgBEtduoGKPZ/"
|
|
||||||
+ "NpP9cuKJORJ18283aV8hR4WO91VR0q1zcM jLwqUg=="),
|
|
||||||
+ /* labels too short for signer */
|
|
||||||
+ TEXT_INVALID("SOA 8 0 86400 20260426170000 20260413160000 "
|
|
||||||
+ "54393 example. "
|
|
||||||
+ "tFbcoVP8MnpecUquJ/aj+XeNgV7ts9GSHVkXaXRJrJ/"
|
|
||||||
+ "TEkOZApVG0F6E "
|
|
||||||
+ "9sYpxGk2ItweLL43ujioGj0HWwZDRR+vbur+O/"
|
|
||||||
+ "dIdheiig1VvU+9HXLi "
|
|
||||||
+ "QOViY9Kc64ixdyJhYCC5K+bO1qsHxd+"
|
|
||||||
+ "KJXOaxyHbqchYkDFy4PL6qftE "
|
|
||||||
+ "VaLkueRgjXgOsq/"
|
|
||||||
+ "NxvCXDgAa5xy0+3Sl0myxIs8rJ5KeXfJQFe7qxgaw "
|
|
||||||
+ "VjJsJTKw8neOTw2rQfLaigWu2LIWw+"
|
|
||||||
+ "IyVrLjZJdLqGkiLBGd1w4X3U12 "
|
|
||||||
+ "fFxoY3eqzNgBEtduoGKPZ/"
|
|
||||||
+ "NpP9cuKJORJ18283aV8hR4WO91VR0q1zcM jLwqUg=="),
|
|
||||||
+ /*
|
|
||||||
+ * Sentinel.
|
|
||||||
+ */
|
|
||||||
+ TEXT_SENTINEL()
|
|
||||||
+ };
|
|
||||||
+ wire_ok_t wire_ok[] = {
|
|
||||||
+ WIRE_VALID(0x00, 0x06, 0x08, 0x00, 0x00, 0x01, 0x51, 0x80, 0x69,
|
|
||||||
+ 0xee, 0x44, 0x90, 0x69, 0xdd, 0x13, 0x00, 0xd4, 0x79,
|
|
||||||
+ 0x00, 0xb4, 0x56, 0xdc, 0xa1, 0x53, 0xfc, 0x32, 0x7a,
|
|
||||||
+ 0x5e, 0x71, 0x4a, 0xae, 0x27, 0xf6, 0xa3, 0xf9, 0x77,
|
|
||||||
+ 0x8d, 0x81, 0x5e, 0xed, 0xb3, 0xd1, 0x92, 0x1d, 0x59,
|
|
||||||
+ 0x17, 0x69, 0x74, 0x49, 0xac, 0x9f, 0xd3, 0x12, 0x43,
|
|
||||||
+ 0x99, 0x02, 0x95, 0x46, 0xd0, 0x5e, 0x84, 0xf6, 0xc6,
|
|
||||||
+ 0x29, 0xc4, 0x69, 0x36, 0x22, 0xdc, 0x1e, 0x2c, 0xbe,
|
|
||||||
+ 0x37, 0xba, 0x38, 0xa8, 0x1a, 0x3d, 0x07, 0x5b, 0x06,
|
|
||||||
+ 0x43, 0x45, 0x1f, 0xaf, 0x6e, 0xea, 0xfe, 0x3b, 0xf7,
|
|
||||||
+ 0x48, 0x76, 0x17, 0xa2, 0x8a, 0x0d, 0x55, 0xbd, 0x4f,
|
|
||||||
+ 0xbd, 0x1d, 0x72, 0xe2, 0x40, 0xe5, 0x62, 0x63, 0xd2,
|
|
||||||
+ 0x9c, 0xeb, 0x88, 0xb1, 0x77, 0x22, 0x61, 0x60, 0x20,
|
|
||||||
+ 0xb9, 0x2b, 0xe6, 0xce, 0xd6, 0xab, 0x07, 0xc5, 0xdf,
|
|
||||||
+ 0x8a, 0x25, 0x73, 0x9a, 0xc7, 0x21, 0xdb, 0xa9, 0xc8,
|
|
||||||
+ 0x58, 0x90, 0x31, 0x72, 0xe0, 0xf2, 0xfa, 0xa9, 0xfb,
|
|
||||||
+ 0x44, 0x55, 0xa2, 0xe4, 0xb9, 0xe4, 0x60, 0x8d, 0x78,
|
|
||||||
+ 0x0e, 0xb2, 0xaf, 0xcd, 0xc6, 0xf0, 0x97, 0x0e, 0x00,
|
|
||||||
+ 0x1a, 0xe7, 0x1c, 0xb4, 0xfb, 0x74, 0xa5, 0xd2, 0x6c,
|
|
||||||
+ 0xb1, 0x22, 0xcf, 0x2b, 0x27, 0x92, 0x9e, 0x5d, 0xf2,
|
|
||||||
+ 0x50, 0x15, 0xee, 0xea, 0xc6, 0x06, 0xb0, 0x56, 0x32,
|
|
||||||
+ 0x6c, 0x25, 0x32, 0xb0, 0xf2, 0x77, 0x8e, 0x4f, 0x0d,
|
|
||||||
+ 0xab, 0x41, 0xf2, 0xda, 0x8a, 0x05, 0xae, 0xd8, 0xb2,
|
|
||||||
+ 0x16, 0xc3, 0xe2, 0x32, 0x56, 0xb2, 0xe3, 0x64, 0x97,
|
|
||||||
+ 0x4b, 0xa8, 0x69, 0x22, 0x2c, 0x11, 0x9d, 0xd7, 0x0e,
|
|
||||||
+ 0x17, 0xdd, 0x4d, 0x76, 0x7c, 0x5c, 0x68, 0x63, 0x77,
|
|
||||||
+ 0xaa, 0xcc, 0xd8, 0x01, 0x12, 0xd7, 0x6e, 0xa0, 0x62,
|
|
||||||
+ 0x8f, 0x67, 0xf3, 0x69, 0x3f, 0xd7, 0x2e, 0x28, 0x93,
|
|
||||||
+ 0x91, 0x27, 0x5f, 0x36, 0xf3, 0x76, 0x95, 0xf2, 0x14,
|
|
||||||
+ 0x78, 0x58, 0xef, 0x75, 0x55, 0x1d, 0x2a, 0xd7, 0x37,
|
|
||||||
+ 0x0c, 0x8c, 0xbc, 0x2a, 0x52),
|
|
||||||
+ /* labels too short for signer */
|
|
||||||
+ WIRE_INVALID(
|
|
||||||
+ 0x00, 0x06, 0x08, 0x00, 0x00, 0x01, 0x51, 0x80, 0x69,
|
|
||||||
+ 0xee, 0x44, 0x90, 0x69, 0xdd, 0x13, 0x00, 0xd4, 0x79,
|
|
||||||
+ 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x00,
|
|
||||||
+ 0xb4, 0x56, 0xdc, 0xa1, 0x53, 0xfc, 0x32, 0x7a, 0x5e,
|
|
||||||
+ 0x71, 0x4a, 0xae, 0x27, 0xf6, 0xa3, 0xf9, 0x77, 0x8d,
|
|
||||||
+ 0x81, 0x5e, 0xed, 0xb3, 0xd1, 0x92, 0x1d, 0x59, 0x17,
|
|
||||||
+ 0x69, 0x74, 0x49, 0xac, 0x9f, 0xd3, 0x12, 0x43, 0x99,
|
|
||||||
+ 0x02, 0x95, 0x46, 0xd0, 0x5e, 0x84, 0xf6, 0xc6, 0x29,
|
|
||||||
+ 0xc4, 0x69, 0x36, 0x22, 0xdc, 0x1e, 0x2c, 0xbe, 0x37,
|
|
||||||
+ 0xba, 0x38, 0xa8, 0x1a, 0x3d, 0x07, 0x5b, 0x06, 0x43,
|
|
||||||
+ 0x45, 0x1f, 0xaf, 0x6e, 0xea, 0xfe, 0x3b, 0xf7, 0x48,
|
|
||||||
+ 0x76, 0x17, 0xa2, 0x8a, 0x0d, 0x55, 0xbd, 0x4f, 0xbd,
|
|
||||||
+ 0x1d, 0x72, 0xe2, 0x40, 0xe5, 0x62, 0x63, 0xd2, 0x9c,
|
|
||||||
+ 0xeb, 0x88, 0xb1, 0x77, 0x22, 0x61, 0x60, 0x20, 0xb9,
|
|
||||||
+ 0x2b, 0xe6, 0xce, 0xd6, 0xab, 0x07, 0xc5, 0xdf, 0x8a,
|
|
||||||
+ 0x25, 0x73, 0x9a, 0xc7, 0x21, 0xdb, 0xa9, 0xc8, 0x58,
|
|
||||||
+ 0x90, 0x31, 0x72, 0xe0, 0xf2, 0xfa, 0xa9, 0xfb, 0x44,
|
|
||||||
+ 0x55, 0xa2, 0xe4, 0xb9, 0xe4, 0x60, 0x8d, 0x78, 0x0e,
|
|
||||||
+ 0xb2, 0xaf, 0xcd, 0xc6, 0xf0, 0x97, 0x0e, 0x00, 0x1a,
|
|
||||||
+ 0xe7, 0x1c, 0xb4, 0xfb, 0x74, 0xa5, 0xd2, 0x6c, 0xb1,
|
|
||||||
+ 0x22, 0xcf, 0x2b, 0x27, 0x92, 0x9e, 0x5d, 0xf2, 0x50,
|
|
||||||
+ 0x15, 0xee, 0xea, 0xc6, 0x06, 0xb0, 0x56, 0x32, 0x6c,
|
|
||||||
+ 0x25, 0x32, 0xb0, 0xf2, 0x77, 0x8e, 0x4f, 0x0d, 0xab,
|
|
||||||
+ 0x41, 0xf2, 0xda, 0x8a, 0x05, 0xae, 0xd8, 0xb2, 0x16,
|
|
||||||
+ 0xc3, 0xe2, 0x32, 0x56, 0xb2, 0xe3, 0x64, 0x97, 0x4b,
|
|
||||||
+ 0xa8, 0x69, 0x22, 0x2c, 0x11, 0x9d, 0xd7, 0x0e, 0x17,
|
|
||||||
+ 0xdd, 0x4d, 0x76, 0x7c, 0x5c, 0x68, 0x63, 0x77, 0xaa,
|
|
||||||
+ 0xcc, 0xd8, 0x01, 0x12, 0xd7, 0x6e, 0xa0, 0x62, 0x8f,
|
|
||||||
+ 0x67, 0xf3, 0x69, 0x3f, 0xd7, 0x2e, 0x28, 0x93, 0x91,
|
|
||||||
+ 0x27, 0x5f, 0x36, 0xf3, 0x76, 0x95, 0xf2, 0x14, 0x78,
|
|
||||||
+ 0x58, 0xef, 0x75, 0x55, 0x1d, 0x2a, 0xd7, 0x37, 0x0c,
|
|
||||||
+ 0x8c, 0xbc, 0x2a, 0x52),
|
|
||||||
+
|
|
||||||
+ WIRE_SENTINEL()
|
|
||||||
+ };
|
|
||||||
+ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
|
|
||||||
+ dns_rdatatype_rrsig, sizeof(dns_rdata_rrsig_t));
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
ISC_RUN_TEST_IMPL(resinfo) {
|
|
||||||
text_ok_t text_ok[] = {
|
|
||||||
TEXT_VALID_CHANGED("qnamemin exterr=15,16,17 "
|
|
||||||
@@ -3357,6 +3466,7 @@ ISC_TEST_ENTRY(nsec3)
|
|
||||||
ISC_TEST_ENTRY(nxt)
|
|
||||||
ISC_TEST_ENTRY(resinfo)
|
|
||||||
ISC_TEST_ENTRY(rkey)
|
|
||||||
+ISC_TEST_ENTRY(rrsig)
|
|
||||||
ISC_TEST_ENTRY(sshfp)
|
|
||||||
ISC_TEST_ENTRY(wallet)
|
|
||||||
ISC_TEST_ENTRY(wks)
|
|
||||||
--
|
|
||||||
2.55.0
|
|
||||||
|
|
||||||
|
|
@ -1,267 +0,0 @@
|
||||||
From 7a8a1f74c742e98fb5e105b013e7c2bd7af4a76c Mon Sep 17 00:00:00 2001
|
|
||||||
From: Mark Andrews <marka@isc.org>
|
|
||||||
Date: Tue, 14 Apr 2026 15:14:06 +1000
|
|
||||||
Subject: [PATCH] Don't sign out of zone records in dnssec-signzone
|
|
||||||
|
|
||||||
dnssec-signzone was signing extraneous records that were not within
|
|
||||||
the namespace of the zone. This no longer occurs.
|
|
||||||
|
|
||||||
(cherry picked from commit e45c9af7051421fd370f20ba8325199c606223fd)
|
|
||||||
|
|
||||||
Don't sign out of zone records in dnssec-signzone
|
|
||||||
|
|
||||||
dnssec-signzone was signing extraneous records that were not within
|
|
||||||
the namespace of the zone. This no longer occurs.
|
|
||||||
|
|
||||||
(cherry picked from commit e45c9af7051421fd370f20ba8325199c606223fd)
|
|
||||||
(cherry picked from commit 1a4986e2533f87e80eb21da3f06708d335aff1e2)
|
|
||||||
|
|
||||||
Invalid signed wildcard records were being accepted
|
|
||||||
|
|
||||||
An RRSIG whose Labels field indicates fewer labels than its signer
|
|
||||||
name requires was being accepted. When such a record covers a
|
|
||||||
wildcard, the validator reconstructs a wildcard owner name above the
|
|
||||||
signer's zone and caches it as secure. RFC 8198 cache synthesis
|
|
||||||
(synth-from-dnssec) then serves that forged wildcard for unrelated
|
|
||||||
names, poisoning the cache.
|
|
||||||
|
|
||||||
These records are now rejected, both when an RRSIG is parsed and when
|
|
||||||
its signature is verified.
|
|
||||||
|
|
||||||
(cherry picked from commit 084ca5ee10515e461d46b63df9660b8394bc7de9)
|
|
||||||
(cherry picked from commit 15089066b15f826d7487c3d160b5872820f84b83)
|
|
||||||
---
|
|
||||||
bin/dnssec/dnssec-signzone.c | 5 ++++
|
|
||||||
lib/dns/dnssec.c | 43 +++++++++++++++++++++++---------
|
|
||||||
lib/dns/rdata/generic/rrsig_46.c | 37 ++++++++++++++++++++-------
|
|
||||||
3 files changed, 64 insertions(+), 21 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c
|
|
||||||
index 73855e6284..9e3a48a592 100644
|
|
||||||
--- a/bin/dnssec/dnssec-signzone.c
|
|
||||||
+++ b/bin/dnssec/dnssec-signzone.c
|
|
||||||
@@ -1643,6 +1643,11 @@ assignwork(isc_task_t *task, isc_task_t *worker) {
|
|
||||||
dns_db_detachnode(gdb, &node);
|
|
||||||
goto next;
|
|
||||||
}
|
|
||||||
+ if (!dns_name_issubdomain(name, gorigin)) {
|
|
||||||
+ dumpnode(name, node);
|
|
||||||
+ dns_db_detachnode(gdb, &node);
|
|
||||||
+ goto next;
|
|
||||||
+ }
|
|
||||||
/*
|
|
||||||
* Sort the zone data from the glue and out-of-zone data.
|
|
||||||
* For NSEC zones nodes with zone data have NSEC records.
|
|
||||||
diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c
|
|
||||||
index c7e922437c..b12529b5d5 100644
|
|
||||||
--- a/lib/dns/dnssec.c
|
|
||||||
+++ b/lib/dns/dnssec.c
|
|
||||||
@@ -130,11 +130,11 @@ dns_dnssec_keyfromrdata(const dns_name_t *name, const dns_rdata_t *rdata,
|
|
||||||
isc_buffer_t b;
|
|
||||||
isc_region_t r;
|
|
||||||
|
|
||||||
- INSIST(name != NULL);
|
|
||||||
- INSIST(rdata != NULL);
|
|
||||||
- INSIST(mctx != NULL);
|
|
||||||
- INSIST(key != NULL);
|
|
||||||
- INSIST(*key == NULL);
|
|
||||||
+ REQUIRE(name != NULL);
|
|
||||||
+ REQUIRE(rdata != NULL);
|
|
||||||
+ REQUIRE(mctx != NULL);
|
|
||||||
+ REQUIRE(key != NULL);
|
|
||||||
+ REQUIRE(*key == NULL);
|
|
||||||
REQUIRE(rdata->type == dns_rdatatype_key ||
|
|
||||||
rdata->type == dns_rdatatype_dnskey);
|
|
||||||
|
|
||||||
@@ -187,12 +187,14 @@ dns_dnssec_sign(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
|
|
||||||
isc_result_t ret;
|
|
||||||
isc_buffer_t *databuf = NULL;
|
|
||||||
char data[256 + 8];
|
|
||||||
+ unsigned int labels;
|
|
||||||
unsigned int sigsize;
|
|
||||||
dns_fixedname_t fnewname;
|
|
||||||
dns_fixedname_t fsigner;
|
|
||||||
|
|
||||||
REQUIRE(name != NULL);
|
|
||||||
- REQUIRE(dns_name_countlabels(name) <= 255);
|
|
||||||
+ labels = dns_name_countlabels(name);
|
|
||||||
+ REQUIRE(labels <= 255 && labels > 0);
|
|
||||||
REQUIRE(set != NULL);
|
|
||||||
REQUIRE(key != NULL);
|
|
||||||
REQUIRE(inception != NULL);
|
|
||||||
@@ -221,7 +223,7 @@ dns_dnssec_sign(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
|
|
||||||
|
|
||||||
sig.covered = set->type;
|
|
||||||
sig.algorithm = dst_key_alg(key);
|
|
||||||
- sig.labels = dns_name_countlabels(name) - 1;
|
|
||||||
+ sig.labels = labels - 1;
|
|
||||||
if (dns_name_iswildcard(name)) {
|
|
||||||
sig.labels--;
|
|
||||||
}
|
|
||||||
@@ -365,10 +367,13 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
|
|
||||||
isc_result_t ret;
|
|
||||||
unsigned char data[300];
|
|
||||||
dst_context_t *ctx = NULL;
|
|
||||||
- int labels = 0;
|
|
||||||
+ unsigned int labels;
|
|
||||||
+ unsigned int siglabels;
|
|
||||||
bool downcase = false;
|
|
||||||
|
|
||||||
REQUIRE(name != NULL);
|
|
||||||
+ labels = dns_name_countlabels(name);
|
|
||||||
+ REQUIRE(labels > 0);
|
|
||||||
REQUIRE(set != NULL);
|
|
||||||
REQUIRE(key != NULL);
|
|
||||||
REQUIRE(mctx != NULL);
|
|
||||||
@@ -383,6 +388,21 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
|
|
||||||
return DNS_R_SIGINVALID;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ /*
|
|
||||||
+ * The RRSIG labels field can't indicate fewer labels than the
|
|
||||||
+ * signer. Also the labels shouldn't be greater than that of
|
|
||||||
+ * the owner name.
|
|
||||||
+ *
|
|
||||||
+ * sig.labels doesn't include the root label, so add 1 to account
|
|
||||||
+ * for it.
|
|
||||||
+ */
|
|
||||||
+ siglabels = sig.labels + 1;
|
|
||||||
+ if (siglabels < dns_name_countlabels(&sig.signer) || siglabels > labels)
|
|
||||||
+ {
|
|
||||||
+ inc_stat(dns_dnssecstats_fail);
|
|
||||||
+ return DNS_R_SIGINVALID;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
if (isc_serial_lt(sig.timeexpire, sig.timesigned)) {
|
|
||||||
inc_stat(dns_dnssecstats_fail);
|
|
||||||
return DNS_R_SIGINVALID;
|
|
||||||
@@ -449,10 +469,9 @@ again:
|
|
||||||
* If the name is an expanded wildcard, use the wildcard name.
|
|
||||||
*/
|
|
||||||
dns_fixedname_init(&fnewname);
|
|
||||||
- labels = dns_name_countlabels(name) - 1;
|
|
||||||
RUNTIME_CHECK(dns_name_downcase(name, dns_fixedname_name(&fnewname),
|
|
||||||
NULL) == ISC_R_SUCCESS);
|
|
||||||
- if (labels - sig.labels > 0) {
|
|
||||||
+ if (labels > siglabels) {
|
|
||||||
dns_name_split(dns_fixedname_name(&fnewname), sig.labels + 1,
|
|
||||||
NULL, dns_fixedname_name(&fnewname));
|
|
||||||
}
|
|
||||||
@@ -463,7 +482,7 @@ again:
|
|
||||||
* Create an envelope for each rdata: <name|type|class|ttl>.
|
|
||||||
*/
|
|
||||||
isc_buffer_init(&envbuf, data, sizeof(data));
|
|
||||||
- if (labels - sig.labels > 0) {
|
|
||||||
+ if (labels > siglabels) {
|
|
||||||
isc_buffer_putuint8(&envbuf, 1);
|
|
||||||
isc_buffer_putuint8(&envbuf, '*');
|
|
||||||
memmove(data + 2, r.base, r.length);
|
|
||||||
@@ -559,7 +578,7 @@ cleanup_struct:
|
|
||||||
inc_stat(dns_dnssecstats_fail);
|
|
||||||
}
|
|
||||||
|
|
||||||
- if (ret == ISC_R_SUCCESS && labels - sig.labels > 0) {
|
|
||||||
+ if (ret == ISC_R_SUCCESS && labels > siglabels) {
|
|
||||||
if (wild != NULL) {
|
|
||||||
RUNTIME_CHECK(dns_name_concatenate(
|
|
||||||
dns_wildcardname,
|
|
||||||
diff --git a/lib/dns/rdata/generic/rrsig_46.c b/lib/dns/rdata/generic/rrsig_46.c
|
|
||||||
index 10bc039e93..4cf4259c2b 100644
|
|
||||||
--- a/lib/dns/rdata/generic/rrsig_46.c
|
|
||||||
+++ b/lib/dns/rdata/generic/rrsig_46.c
|
|
||||||
@@ -23,12 +23,12 @@
|
|
||||||
static isc_result_t
|
|
||||||
fromtext_rrsig(ARGS_FROMTEXT) {
|
|
||||||
isc_token_t token;
|
|
||||||
- unsigned char c;
|
|
||||||
+ unsigned char alg, labels;
|
|
||||||
long i;
|
|
||||||
dns_rdatatype_t covered;
|
|
||||||
- char *e;
|
|
||||||
+ char *e = NULL;
|
|
||||||
isc_result_t result;
|
|
||||||
- dns_name_t name;
|
|
||||||
+ dns_name_t signer;
|
|
||||||
isc_buffer_t buffer;
|
|
||||||
uint32_t time_signed, time_expire;
|
|
||||||
|
|
||||||
@@ -61,8 +61,8 @@ fromtext_rrsig(ARGS_FROMTEXT) {
|
|
||||||
*/
|
|
||||||
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
|
|
||||||
false));
|
|
||||||
- RETTOK(dns_secalg_fromtext(&c, &token.value.as_textregion));
|
|
||||||
- RETERR(mem_tobuffer(target, &c, 1));
|
|
||||||
+ RETTOK(dns_secalg_fromtext(&alg, &token.value.as_textregion));
|
|
||||||
+ RETERR(mem_tobuffer(target, &alg, 1));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Labels.
|
|
||||||
@@ -72,8 +72,8 @@ fromtext_rrsig(ARGS_FROMTEXT) {
|
|
||||||
if (token.value.as_ulong > 0xffU) {
|
|
||||||
RETTOK(ISC_R_RANGE);
|
|
||||||
}
|
|
||||||
- c = (unsigned char)token.value.as_ulong;
|
|
||||||
- RETERR(mem_tobuffer(target, &c, 1));
|
|
||||||
+ labels = (unsigned char)token.value.as_ulong;
|
|
||||||
+ RETERR(mem_tobuffer(target, &labels, 1));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Original ttl.
|
|
||||||
@@ -144,12 +144,20 @@ fromtext_rrsig(ARGS_FROMTEXT) {
|
|
||||||
*/
|
|
||||||
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
|
|
||||||
false));
|
|
||||||
- dns_name_init(&name, NULL);
|
|
||||||
+ dns_name_init(&signer, NULL);
|
|
||||||
buffer_fromregion(&buffer, &token.value.as_region);
|
|
||||||
if (origin == NULL) {
|
|
||||||
origin = dns_rootname;
|
|
||||||
}
|
|
||||||
- RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
|
|
||||||
+ RETTOK(dns_name_fromtext(&signer, &buffer, origin, options, target));
|
|
||||||
+
|
|
||||||
+ /*
|
|
||||||
+ * (RRSIG labels doesn't include the root label, so add one
|
|
||||||
+ * to normalize it before checking against the signer.)
|
|
||||||
+ */
|
|
||||||
+ if ((unsigned int)(labels + 1) < dns_name_countlabels(&signer)) {
|
|
||||||
+ RETTOK(ISC_R_RANGE);
|
|
||||||
+ }
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Sig.
|
|
||||||
@@ -278,6 +286,7 @@ static isc_result_t
|
|
||||||
fromwire_rrsig(ARGS_FROMWIRE) {
|
|
||||||
isc_region_t sr;
|
|
||||||
dns_name_t name;
|
|
||||||
+ unsigned char labels;
|
|
||||||
|
|
||||||
REQUIRE(type == dns_rdatatype_rrsig);
|
|
||||||
|
|
||||||
@@ -300,6 +309,8 @@ fromwire_rrsig(ARGS_FROMWIRE) {
|
|
||||||
return ISC_R_UNEXPECTEDEND;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ labels = sr.base[3];
|
|
||||||
+
|
|
||||||
isc_buffer_forward(source, 18);
|
|
||||||
RETERR(mem_tobuffer(target, sr.base, 18));
|
|
||||||
|
|
||||||
@@ -309,6 +320,14 @@ fromwire_rrsig(ARGS_FROMWIRE) {
|
|
||||||
dns_name_init(&name, NULL);
|
|
||||||
RETERR(dns_name_fromwire(&name, source, dctx, options, target));
|
|
||||||
|
|
||||||
+ /*
|
|
||||||
+ * (RRSIG labels doesn't include the root label, so add one
|
|
||||||
+ * to normalize it before checking against the signer.)
|
|
||||||
+ */
|
|
||||||
+ if ((unsigned int)(labels + 1) < dns_name_countlabels(&name)) {
|
|
||||||
+ RETERR(DNS_R_FORMERR);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
/*
|
|
||||||
* Sig.
|
|
||||||
*/
|
|
||||||
--
|
|
||||||
2.55.0
|
|
||||||
|
|
||||||
|
|
@ -1,435 +0,0 @@
|
||||||
From a31296b120efc985fb1fc3c932882e965156473b Mon Sep 17 00:00:00 2001
|
|
||||||
From: Colin Vidal <colin@isc.org>
|
|
||||||
Date: Mon, 15 Jun 2026 11:34:08 +0200
|
|
||||||
Subject: [PATCH] Reproducer for #5946 (assertion in some CNAME/DNAME queries)
|
|
||||||
|
|
||||||
Add a system test reproducing the issue reported by #5946, which
|
|
||||||
is also CVE-2026-12617. There are two scenarios:
|
|
||||||
|
|
||||||
- A client send queries for a DNAME and A record to the resolver (ns3),
|
|
||||||
and the authoritative server (ans2) responds positively to the A query
|
|
||||||
but delay the DNAME response and respond later negatively;
|
|
||||||
|
|
||||||
- A client send queries for a CNAME and A record to the resolver (ns3),
|
|
||||||
and the authoritative server (ans2) responds positively to the A query
|
|
||||||
but delay the CNAME response and respond later with a self-referential
|
|
||||||
CNAME.
|
|
||||||
|
|
||||||
The test does not check the results of the queries, however, it expects
|
|
||||||
the resolver to correctly handle those and do not assert.
|
|
||||||
|
|
||||||
(cherry picked from commit e88271f2e584010157b068cc998dd76451273562)
|
|
||||||
(cherry picked from commit bb92832fb6ae899bee7206c2d8966258461c2f71)
|
|
||||||
|
|
||||||
Stabilize timing in the cname_dname_negcache test
|
|
||||||
|
|
||||||
The #5946 reproducer relied on ans2 answering the negative DNAME/CNAME
|
|
||||||
query a fixed second after receiving it, racing that delay against the
|
|
||||||
resolver's per-query timeout. When the timeout fired first -- which
|
|
||||||
happens under load, most notably under ThreadSanitizer, where named is
|
|
||||||
slowed but ans2 (wall-clock) is not -- the resolver dropped the late
|
|
||||||
answer, never processed the negative response, and the watched SOA never
|
|
||||||
appeared, so the test timed out. This made it flaky on the
|
|
||||||
security-bind-9.20 CI. Merely shortening the fixed delay would trade
|
|
||||||
that for the opposite, worse failure: the negative answer arriving
|
|
||||||
before the positive one is cached, silently not exercising the bug.
|
|
||||||
|
|
||||||
Release the negative answer based on the resolver's progress instead of
|
|
||||||
a wall-clock deadline: hold it until ans2 has sent the positive answer
|
|
||||||
(a shared event), then wait a short settle for the resolver to cache it.
|
|
||||||
Both queries traverse the same delegation, so any latency reaching ans2
|
|
||||||
shifts the positive send and the negative fetch's deadline together and
|
|
||||||
cancels out; only the settle, kept well under MINIMUM_QUERY_TIMEOUT
|
|
||||||
(301 ms), has to fit inside the per-query timeout.
|
|
||||||
|
|
||||||
Verified that the stabilized test still triggers the
|
|
||||||
INSIST(namereln == dns_namereln_subdomain) assertion when the resolver
|
|
||||||
fix is reverted.
|
|
||||||
|
|
||||||
Assisted-by: Claude:claude-opus-4-8
|
|
||||||
(cherry picked from commit 738456d91564526e6f15c3858b4c809cd6749e1e)
|
|
||||||
(cherry picked from commit 0c20ee4e8e68999ca617434cde65dd3808f57d8c)
|
|
||||||
|
|
||||||
Split cname_dname_negcache into per-scenario modules
|
|
||||||
|
|
||||||
The DNAME and CNAME scenarios shared a single module, hence a single
|
|
||||||
module-scoped ns3 (the framework sets servers up per module, not per
|
|
||||||
test function). test_dname_negcache cached foo.test. DNAME bar.test.;
|
|
||||||
when test_cname_negcache ran next against the same resolver,
|
|
||||||
cname.foo.test. was DNAME-mapped to cname.bar.test., so the resolver
|
|
||||||
never queried ans2 for the self-referential CNAME and that half of the
|
|
||||||
bug was never exercised. The hardcoded, unanchored "foo.test." watcher
|
|
||||||
still matched test_dname's leftover SOA, so test_cname passed without
|
|
||||||
testing anything -- the CNAME assertion had no coverage.
|
|
||||||
|
|
||||||
Give each scenario its own module so each gets a fresh server set, and
|
|
||||||
anchor the watcher to the queried name so a test cannot pass on an
|
|
||||||
unrelated record.
|
|
||||||
|
|
||||||
With the resolver fix reverted, each module now independently triggers
|
|
||||||
its own assertion:
|
|
||||||
|
|
||||||
DNAME query.c INSIST(namereln == dns_namereln_subdomain)
|
|
||||||
CNAME query.c INSIST(qctx->rdataset == NULL || qctx->qtype == dname)
|
|
||||||
|
|
||||||
Assisted-by: Claude:claude-opus-4-8
|
|
||||||
(cherry picked from commit 3ef0b8d04a1653407cfb9ee88772ae18689b1318)
|
|
||||||
(cherry picked from commit 887124315f03a006c4dc76e48ae3d0d8aac3c407)
|
|
||||||
---
|
|
||||||
.../system/cname_dname_negcache/ans2/ans.py | 148 ++++++++++++++++++
|
|
||||||
.../system/cname_dname_negcache/common.py | 46 ++++++
|
|
||||||
.../cname_dname_negcache/ns1/bar.test.db | 5 +
|
|
||||||
.../cname_dname_negcache/ns1/named.conf.j2 | 24 +++
|
|
||||||
.../system/cname_dname_negcache/ns1/root.db | 6 +
|
|
||||||
.../system/cname_dname_negcache/ns1/test.db | 8 +
|
|
||||||
.../cname_dname_negcache/ns3/named.conf.j2 | 11 ++
|
|
||||||
.../tests_cname_negcache.py | 16 ++
|
|
||||||
.../tests_dname_negcache.py | 16 ++
|
|
||||||
9 files changed, 280 insertions(+)
|
|
||||||
create mode 100644 bin/tests/system/cname_dname_negcache/ans2/ans.py
|
|
||||||
create mode 100644 bin/tests/system/cname_dname_negcache/common.py
|
|
||||||
create mode 100644 bin/tests/system/cname_dname_negcache/ns1/bar.test.db
|
|
||||||
create mode 100644 bin/tests/system/cname_dname_negcache/ns1/named.conf.j2
|
|
||||||
create mode 100644 bin/tests/system/cname_dname_negcache/ns1/root.db
|
|
||||||
create mode 100644 bin/tests/system/cname_dname_negcache/ns1/test.db
|
|
||||||
create mode 100644 bin/tests/system/cname_dname_negcache/ns3/named.conf.j2
|
|
||||||
create mode 100644 bin/tests/system/cname_dname_negcache/tests_cname_negcache.py
|
|
||||||
create mode 100644 bin/tests/system/cname_dname_negcache/tests_dname_negcache.py
|
|
||||||
|
|
||||||
diff --git a/bin/tests/system/cname_dname_negcache/ans2/ans.py b/bin/tests/system/cname_dname_negcache/ans2/ans.py
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..392fe1e088
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/cname_dname_negcache/ans2/ans.py
|
|
||||||
@@ -0,0 +1,148 @@
|
|
||||||
+"""
|
|
||||||
+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 asyncio
|
|
||||||
+
|
|
||||||
+from dns import name, rcode, rdataclass, rdatatype, rrset
|
|
||||||
+
|
|
||||||
+from isctest.asyncserver import (
|
|
||||||
+ AsyncDnsServer,
|
|
||||||
+ DnsResponseSend,
|
|
||||||
+ QnameQtypeHandler,
|
|
||||||
+ QueryContext,
|
|
||||||
+ StaticResponseHandler,
|
|
||||||
+)
|
|
||||||
+
|
|
||||||
+# The attack relies on the resolver caching the positive CNAME/DNAME answer
|
|
||||||
+# *before* it processes the negative answer for the same name. The negative
|
|
||||||
+# answer must therefore be held back until the positive one has been sent, but
|
|
||||||
+# released again while the negative fetch is still waiting for it.
|
|
||||||
+#
|
|
||||||
+# Releasing it at a fixed wall-clock delay (the original approach) is racy: the
|
|
||||||
+# delay must be larger than the time it takes the resolver to cache the
|
|
||||||
+# positive answer, yet smaller than the resolver's per-query timeout. Under
|
|
||||||
+# load -- most notably ThreadSanitizer, which slows down `named` but not this
|
|
||||||
+# (wall-clock) server -- those bounds can be violated in either direction,
|
|
||||||
+# making the test either time out (#5946 CI failures) or, worse, silently stop
|
|
||||||
+# exercising the bug.
|
|
||||||
+#
|
|
||||||
+# Instead, gate the negative answer on an event set right after the positive
|
|
||||||
+# answer is sent. Both queries traverse the same delegation, so any latency in
|
|
||||||
+# reaching this server shifts the positive send and the negative fetch's
|
|
||||||
+# deadline together and cancels out; only the small settle below has to fit
|
|
||||||
+# inside the per-query timeout.
|
|
||||||
+#
|
|
||||||
+# _SETTLE must be longer than the few milliseconds the resolver needs to cache
|
|
||||||
+# the positive answer, and shorter than MINIMUM_QUERY_TIMEOUT (301 ms in
|
|
||||||
+# lib/dns/resolver.c) so the in-flight negative fetch has not given up yet.
|
|
||||||
+_SETTLE = 0.1
|
|
||||||
+
|
|
||||||
+_dname_positive_sent = asyncio.Event()
|
|
||||||
+_cname_positive_sent = asyncio.Event()
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+async def _hold_until_positive_cached(positive_sent: asyncio.Event) -> None:
|
|
||||||
+ await positive_sent.wait()
|
|
||||||
+ await asyncio.sleep(_SETTLE)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def build_rrset(
|
|
||||||
+ qname: name.Name | str,
|
|
||||||
+ rtype: rdatatype.RdataType,
|
|
||||||
+ rdata: str,
|
|
||||||
+ ttl: int = 300,
|
|
||||||
+) -> rrset.RRset:
|
|
||||||
+ return rrset.from_text(qname, ttl, rdataclass.IN, rtype, rdata)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+class FooTestNsHandler(QnameQtypeHandler, StaticResponseHandler):
|
|
||||||
+ qnames = ["foo.test."]
|
|
||||||
+ qtypes = [rdatatype.NS]
|
|
||||||
+ answer = [build_rrset("foo.test.", rdatatype.NS, "ns.foo.test.")]
|
|
||||||
+ additional = [build_rrset("ns.foo.test.", rdatatype.A, "10.53.0.2")]
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+class DelayedDnameNegHandler(QnameQtypeHandler, StaticResponseHandler):
|
|
||||||
+ qnames = ["foo.test."]
|
|
||||||
+ qtypes = [rdatatype.DNAME]
|
|
||||||
+ authority = [
|
|
||||||
+ build_rrset(
|
|
||||||
+ "foo.test.",
|
|
||||||
+ rdatatype.SOA,
|
|
||||||
+ "ns.test. op.ns.test. 2081509183 86400 3600 3600000 300",
|
|
||||||
+ )
|
|
||||||
+ ]
|
|
||||||
+
|
|
||||||
+ async def get_responses(
|
|
||||||
+ self, qctx: QueryContext
|
|
||||||
+ ) -> AsyncGenerator[DnsResponseSend, None]:
|
|
||||||
+ await _hold_until_positive_cached(_dname_positive_sent)
|
|
||||||
+ async for response in super().get_responses(qctx):
|
|
||||||
+ yield response
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+class DnamePosHandler(QnameQtypeHandler, StaticResponseHandler):
|
|
||||||
+ qnames = ["a.foo.test."]
|
|
||||||
+ qtypes = [rdatatype.A]
|
|
||||||
+ answer = [
|
|
||||||
+ build_rrset("foo.test.", rdatatype.DNAME, "bar.test."),
|
|
||||||
+ build_rrset("a.foo.test.", rdatatype.CNAME, "a.bar.test."),
|
|
||||||
+ ]
|
|
||||||
+
|
|
||||||
+ async def get_responses(
|
|
||||||
+ self, qctx: QueryContext
|
|
||||||
+ ) -> AsyncGenerator[DnsResponseSend, None]:
|
|
||||||
+ async for response in super().get_responses(qctx):
|
|
||||||
+ yield response
|
|
||||||
+ _dname_positive_sent.set()
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+class CnameHandler(QnameQtypeHandler):
|
|
||||||
+ qnames = ["cname.foo.test."]
|
|
||||||
+ qtypes = [rdatatype.CNAME, rdatatype.A]
|
|
||||||
+ answer = [build_rrset("cname.foo.test.", rdatatype.CNAME, "cname.foo.test.")]
|
|
||||||
+ authority = [
|
|
||||||
+ build_rrset(
|
|
||||||
+ "cname.foo.test.",
|
|
||||||
+ rdatatype.SOA,
|
|
||||||
+ "ns.test. op.ns.test. 2081509183 86400 3600 3600000 300",
|
|
||||||
+ )
|
|
||||||
+ ]
|
|
||||||
+
|
|
||||||
+ async def get_responses(
|
|
||||||
+ self, qctx: QueryContext
|
|
||||||
+ ) -> AsyncGenerator[DnsResponseSend, None]:
|
|
||||||
+ if qctx.qtype == rdatatype.CNAME:
|
|
||||||
+ await _hold_until_positive_cached(_cname_positive_sent)
|
|
||||||
+ qctx.prepare_new_response(with_zone_data=False)
|
|
||||||
+ qctx.response.authority.extend(self.authority)
|
|
||||||
+ yield DnsResponseSend(qctx.response, authoritative=True)
|
|
||||||
+ else:
|
|
||||||
+ qctx.prepare_new_response(with_zone_data=False)
|
|
||||||
+ qctx.response.answer.extend(self.answer)
|
|
||||||
+ yield DnsResponseSend(qctx.response, authoritative=True)
|
|
||||||
+ _cname_positive_sent.set()
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def main() -> None:
|
|
||||||
+ server = AsyncDnsServer(default_aa=True, default_rcode=rcode.NOERROR)
|
|
||||||
+ server.install_response_handlers(
|
|
||||||
+ FooTestNsHandler(), DelayedDnameNegHandler(), DnamePosHandler(), CnameHandler()
|
|
||||||
+ )
|
|
||||||
+ server.run()
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ main()
|
|
||||||
diff --git a/bin/tests/system/cname_dname_negcache/common.py b/bin/tests/system/cname_dname_negcache/common.py
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..397cfdfa3d
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/cname_dname_negcache/common.py
|
|
||||||
@@ -0,0 +1,46 @@
|
|
||||||
+# 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 os import environ
|
|
||||||
+from re import compile as Re
|
|
||||||
+from re import escape
|
|
||||||
+from socket import AF_INET, SOCK_DGRAM, socket
|
|
||||||
+
|
|
||||||
+import isctest
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def run_attack(ns, name1, type1, name2, type2):
|
|
||||||
+ msg1 = isctest.query.create(name1, type1, cd=True)
|
|
||||||
+ msg2 = isctest.query.create(name2, type2, cd=True)
|
|
||||||
+ port = int(environ["PORT"])
|
|
||||||
+
|
|
||||||
+ with socket(AF_INET, SOCK_DGRAM) as sock:
|
|
||||||
+ # The order the requests go out doesn't matter. What is important is
|
|
||||||
+ # that the first query starts recursion before the second query returns
|
|
||||||
+ # the answer, and the second query returns the answer before the first
|
|
||||||
+ # query returns the answer. (So, when the NOERROR/NODATA comes back from
|
|
||||||
+ # the first query, the cache is queried and we get the positive response
|
|
||||||
+ # cached from the second query attached to the fresp rdataset of the
|
|
||||||
+ # response of the first query.)
|
|
||||||
+ # That ordering is enforced by ans2, which holds back the negative
|
|
||||||
+ # answer to the first query until it has answered the second one (see
|
|
||||||
+ # ans2/ans.py); the resolver must not crash while reconciling them.
|
|
||||||
+ sock.sendto(msg1.to_wire(), (ns.ip, port))
|
|
||||||
+ sock.sendto(msg2.to_wire(), (ns.ip, port))
|
|
||||||
+
|
|
||||||
+ # The second query comes back immediately, the resolver caches the DNAME.
|
|
||||||
+ # The first query comes back shortly after, once ans2 has released the
|
|
||||||
+ # negative answer, and should not crash the server. Wait for the negative
|
|
||||||
+ # SOA for this specific name (not just any foo.test. one) so the test cannot
|
|
||||||
+ # pass on an unrelated record.
|
|
||||||
+ soa = Re(rf"(?<![\w.]){escape(name1)}.*IN\s+SOA\s+ns\.test\.\s+op\.ns\.test\.")
|
|
||||||
+ with ns.watch_log_from_start(timeout=15) as watcher:
|
|
||||||
+ watcher.wait_for_sequence([soa])
|
|
||||||
diff --git a/bin/tests/system/cname_dname_negcache/ns1/bar.test.db b/bin/tests/system/cname_dname_negcache/ns1/bar.test.db
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..840b9c3a2c
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/cname_dname_negcache/ns1/bar.test.db
|
|
||||||
@@ -0,0 +1,5 @@
|
|
||||||
+$TTL 300
|
|
||||||
+bar.test. IN SOA ns.bar.test. hostmaster.bar.test. 1 600 600 1200 600
|
|
||||||
+bar.test. NS ns.bar.test.
|
|
||||||
+ns A 10.53.0.1
|
|
||||||
+a A 10.0.0.1
|
|
||||||
diff --git a/bin/tests/system/cname_dname_negcache/ns1/named.conf.j2 b/bin/tests/system/cname_dname_negcache/ns1/named.conf.j2
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..d72dd1181d
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/cname_dname_negcache/ns1/named.conf.j2
|
|
||||||
@@ -0,0 +1,24 @@
|
|
||||||
+options {
|
|
||||||
+ query-source address @ns.ip@;
|
|
||||||
+ port @PORT@;
|
|
||||||
+ pid-file "named.pid";
|
|
||||||
+ listen-on { @ns.ip@; };
|
|
||||||
+ listen-on-v6 { none; };
|
|
||||||
+ recursion no;
|
|
||||||
+ dnssec-validation no;
|
|
||||||
+};
|
|
||||||
+
|
|
||||||
+zone "." {
|
|
||||||
+ type primary;
|
|
||||||
+ file "root.db";
|
|
||||||
+};
|
|
||||||
+
|
|
||||||
+zone "test." {
|
|
||||||
+ type primary;
|
|
||||||
+ file "test.db";
|
|
||||||
+};
|
|
||||||
+
|
|
||||||
+zone "bar.test." {
|
|
||||||
+ type primary;
|
|
||||||
+ file "bar.test.db";
|
|
||||||
+};
|
|
||||||
diff --git a/bin/tests/system/cname_dname_negcache/ns1/root.db b/bin/tests/system/cname_dname_negcache/ns1/root.db
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..c456c45b9d
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/cname_dname_negcache/ns1/root.db
|
|
||||||
@@ -0,0 +1,6 @@
|
|
||||||
+$TTL 300
|
|
||||||
+. IN SOA ns. hostmaster. 1 600 600 1200 600
|
|
||||||
+. NS a.root-servers.nil.
|
|
||||||
+a.root-servers.nil. A 10.53.0.1
|
|
||||||
+test NS ns.test
|
|
||||||
+ns.test A 10.53.0.1
|
|
||||||
diff --git a/bin/tests/system/cname_dname_negcache/ns1/test.db b/bin/tests/system/cname_dname_negcache/ns1/test.db
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..acb68e00f8
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/cname_dname_negcache/ns1/test.db
|
|
||||||
@@ -0,0 +1,8 @@
|
|
||||||
+$TTL 300
|
|
||||||
+test. IN SOA ns.test. hostmaster.test. 1 600 600 1200 600
|
|
||||||
+test. NS ns.test.
|
|
||||||
+ns A 10.53.0.1
|
|
||||||
+bar NS ns.bar
|
|
||||||
+ns.bar A 10.53.0.1
|
|
||||||
+foo NS ns.foo
|
|
||||||
+ns.foo A 10.53.0.2
|
|
||||||
diff --git a/bin/tests/system/cname_dname_negcache/ns3/named.conf.j2 b/bin/tests/system/cname_dname_negcache/ns3/named.conf.j2
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..197d72756b
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/cname_dname_negcache/ns3/named.conf.j2
|
|
||||||
@@ -0,0 +1,11 @@
|
|
||||||
+options {
|
|
||||||
+ query-source address @ns.ip@;
|
|
||||||
+ port @PORT@;
|
|
||||||
+ pid-file "named.pid";
|
|
||||||
+ listen-on { @ns.ip@; };
|
|
||||||
+ listen-on-v6 { none; };
|
|
||||||
+ recursion yes;
|
|
||||||
+ dnssec-validation no;
|
|
||||||
+};
|
|
||||||
+
|
|
||||||
+{% include "_common/root.hint.conf" %}
|
|
||||||
diff --git a/bin/tests/system/cname_dname_negcache/tests_cname_negcache.py b/bin/tests/system/cname_dname_negcache/tests_cname_negcache.py
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..a546d29109
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/cname_dname_negcache/tests_cname_negcache.py
|
|
||||||
@@ -0,0 +1,16 @@
|
|
||||||
+# 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 cname_dname_negcache.common import run_attack
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def test_cname_negcache(ns3):
|
|
||||||
+ run_attack(ns3, "cname.foo.test.", "CNAME", "cname.foo.test.", "A")
|
|
||||||
diff --git a/bin/tests/system/cname_dname_negcache/tests_dname_negcache.py b/bin/tests/system/cname_dname_negcache/tests_dname_negcache.py
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..41a80b4e05
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/cname_dname_negcache/tests_dname_negcache.py
|
|
||||||
@@ -0,0 +1,16 @@
|
|
||||||
+# 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 cname_dname_negcache.common import run_attack
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def test_dname_negcache(ns3):
|
|
||||||
+ run_attack(ns3, "foo.test.", "DNAME", "a.foo.test.", "A")
|
|
||||||
--
|
|
||||||
2.55.0
|
|
||||||
|
|
||||||
|
|
@ -1,280 +0,0 @@
|
||||||
From 3138886f3767c0a6b933fd147c5465472209bac6 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Colin Vidal <colin@isc.org>
|
|
||||||
Date: Thu, 18 Jun 2026 18:17:05 +0200
|
|
||||||
Subject: [PATCH] Do not assert in some CNAME/DNAME queries
|
|
||||||
|
|
||||||
Fix a `named` crash because of a fail assertion for certains types of
|
|
||||||
CNAME and DNAME queries:
|
|
||||||
|
|
||||||
- If a client queries for a DNAME and A record to the resolver, and the
|
|
||||||
authoritative server responds positively to the A query but delay the
|
|
||||||
DNAME response and respond later negatively;
|
|
||||||
|
|
||||||
- If a client queries for a CNAME and A record to the resolver, and the
|
|
||||||
authoritative server responds positively to the A query but delay the
|
|
||||||
CNAME response and respond later with a self-referential CNAME.
|
|
||||||
|
|
||||||
The first scenario consists of sending two queries: `foo.test./DNAME`
|
|
||||||
and `a.foo.test./A`. The authoritative server delays the answer for
|
|
||||||
`foo.test./DNAME` but immediately answers the DNAME record for the
|
|
||||||
second query: `foo.test. DNAME bar.test.`. The resolver caches it,
|
|
||||||
follows the DNAME, and resolves `a.bar.test./A`. The authoritative
|
|
||||||
server eventually answers negatively for `foo.test./DNAME`
|
|
||||||
(NOERROR/NODATA, with only an SOA in the authority section). The
|
|
||||||
resolver pulls out the previously cached rdataset (because it has a
|
|
||||||
higher trust level than the received negative answer), and wrongly (this
|
|
||||||
is the first bug) sets the result to `DNS_R_DNAME` instead of
|
|
||||||
`ISC_R_SUCCESS`. The code in `ns/query.c` that handles the resolver
|
|
||||||
result interprets this as "this is a non-DNAME query and we got a DNAME
|
|
||||||
rdataset, so follow the chain". It goes into the `query_dname()`
|
|
||||||
function, which asserts that the qname is a subdomain of the owner name
|
|
||||||
in the rdataset. That assertion fails because the qname (`foo.test.`) is
|
|
||||||
exactly equal to the owner name of the DNAME (`foo.test.`), rather than
|
|
||||||
being a subdomain of it. `DNS_R_DNAME` must only be set when the qtype
|
|
||||||
is something other than DNAME and the resolver has obtained a DNAME that
|
|
||||||
needs to be followed.
|
|
||||||
|
|
||||||
The second scenario consists of sending two queries:
|
|
||||||
`cname.foo.test./CNAME` and `cname.foo.test./A`. The authoritative
|
|
||||||
server delays the answer for `cname.foo.test./CNAME` but immediately
|
|
||||||
answers the CNAME record for the second query: `cname.foo.test. CNAME
|
|
||||||
cname.foo.test.`. Note that the CNAME is self-referential. The resolver
|
|
||||||
caches it and sets the result code to `DNS_R_CNAME`. Then `ns/query.c`
|
|
||||||
interprets this as "this is a non-CNAME query and we got a CNAME
|
|
||||||
rdataset, so follow the chain" (which is correct in this case; however,
|
|
||||||
because the CNAME rdataset is self-referential, the resolver responds
|
|
||||||
with SERVFAIL, which is expected). The authoritative server eventually
|
|
||||||
answers negatively for `cname.foo.test./CNAME`. The resolver then pulls
|
|
||||||
out the previously cached CNAME rdataset (obtained from the A answer,
|
|
||||||
even though it was self-referential, the resolver cached it) and wrongly
|
|
||||||
sets the result to `DNS_R_CNAME` (this is the second bug). As noted
|
|
||||||
above, `ns/query.c` interprets this as "this is a non-CNAME query and we
|
|
||||||
got a CNAME rdataset, so follow the chain". The internals here are
|
|
||||||
slightly more subtle: it first goes into `query_cname()` and sets the
|
|
||||||
CNAME rdataset in the message answer section, then restarts the query to
|
|
||||||
follow the CNAME. The restart retrieves the CNAME rdataset from the
|
|
||||||
cache directly (without going to the resolver), and this time the query
|
|
||||||
context result is `ISC_R_SUCCESS` (since it was found) and
|
|
||||||
`qctx->rdataset` points to the same CNAME again (as it is
|
|
||||||
self-referential), so it goes directly into the
|
|
||||||
`query_prepresponse()/query_respond()` flow, which attempts to add the
|
|
||||||
rdataset to the message answer again. However, this fails because the
|
|
||||||
rdataset is already in the message, and the assertion which expects that
|
|
||||||
operation to succeed fails (due to `qctx->rdataset` being set to `NULL`
|
|
||||||
when ownership of the rdataset was transferred). `DNS_R_CNAME` must only
|
|
||||||
be set when the qtype is something other than CNAME and the resolver has
|
|
||||||
obtained a CNAME that needs to be followed.
|
|
||||||
|
|
||||||
In both cases, the correct answer from the resolver should have been
|
|
||||||
`ISC_R_SUCCESS` (instead of respectively `DNS_R_DNAME` and
|
|
||||||
`DNS_R_CNAME`) becuase the rdataset that has been looked up was found.
|
|
||||||
|
|
||||||
(cherry picked from commit 773d46d58c693047a5945c8fe40512edd0ac214e)
|
|
||||||
(cherry picked from commit c740c37689f234e21a9b0ef760471ef2cf1133f5)
|
|
||||||
---
|
|
||||||
lib/dns/resolver.c | 137 ++++++++++++++++++++-------------------------
|
|
||||||
1 file changed, 60 insertions(+), 77 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
|
|
||||||
index edc9c207e1..1f8b5058d1 100644
|
|
||||||
--- a/lib/dns/resolver.c
|
|
||||||
+++ b/lib/dns/resolver.c
|
|
||||||
@@ -692,10 +692,10 @@ fctx_destroy(fetchctx_t *fctx, bool exiting);
|
|
||||||
static void
|
|
||||||
send_shutdown_events(dns_resolver_t *res);
|
|
||||||
static isc_result_t
|
|
||||||
-ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node,
|
|
||||||
- dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl,
|
|
||||||
- dns_ttl_t maxttl, bool optout, bool secure,
|
|
||||||
- dns_rdataset_t *ardataset, isc_result_t *eresultp);
|
|
||||||
+ncache_adderesult(fetchctx_t *fctx, dns_message_t *message, dns_dbnode_t *node,
|
|
||||||
+ dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t maxttl,
|
|
||||||
+ bool optout, bool secure, dns_rdataset_t *ardataset,
|
|
||||||
+ isc_result_t *eresultp);
|
|
||||||
static void
|
|
||||||
validated(isc_task_t *task, isc_event_t *event);
|
|
||||||
static void
|
|
||||||
@@ -5563,6 +5563,46 @@ has_000_label(dns_rdataset_t *nsecset) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
+/*
|
|
||||||
+ * After a (non-error) negative-cache add, 'rdataset' is bound to whatever
|
|
||||||
+ * rdataset the cache authoritatively holds for the queried name and type.
|
|
||||||
+ * Map that to the result code the fetch should report:
|
|
||||||
+ *
|
|
||||||
+ * - A negative cache entry (the one we just added, or a pre-existing one):
|
|
||||||
+ * DNS_R_NCACHENXDOMAIN or DNS_R_NCACHENXRRSET, depending on NXDOMAIN vs
|
|
||||||
+ * NODATA.
|
|
||||||
+ *
|
|
||||||
+ * - A positive rdataset that was already cached at higher trust, which
|
|
||||||
+ * caused our negative entry to be discarded (e.g. a CNAME or DNAME cached
|
|
||||||
+ * by a concurrent query): ISC_R_SUCCESS, because that cached positive
|
|
||||||
+ * answer is what gets returned. Note the specific case for CNAME and
|
|
||||||
+ * DNAME *if* the query type is not the same as the rdataset type. There
|
|
||||||
+ * is a chain to follow *only* if the query type doesn't ask for the CNAME
|
|
||||||
+ * or the DNAME.
|
|
||||||
+ */
|
|
||||||
+static isc_result_t
|
|
||||||
+fctx_setresult(fetchctx_t *fctx, dns_rdataset_t *rdataset) {
|
|
||||||
+ isc_result_t result = ISC_R_SUCCESS;
|
|
||||||
+
|
|
||||||
+ if (NEGATIVE(rdataset)) {
|
|
||||||
+ result = NXDOMAIN(rdataset) ? DNS_R_NCACHENXDOMAIN
|
|
||||||
+ : DNS_R_NCACHENXRRSET;
|
|
||||||
+ } else if (result == ISC_R_SUCCESS && rdataset->type != fctx->type) {
|
|
||||||
+ switch (rdataset->type) {
|
|
||||||
+ case dns_rdatatype_cname:
|
|
||||||
+ result = DNS_R_CNAME;
|
|
||||||
+ break;
|
|
||||||
+ case dns_rdatatype_dname:
|
|
||||||
+ result = DNS_R_DNAME;
|
|
||||||
+ break;
|
|
||||||
+ default:
|
|
||||||
+ break;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ return result;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
/*
|
|
||||||
* The validator has finished.
|
|
||||||
*/
|
|
||||||
@@ -5836,8 +5876,7 @@ validated(isc_task_t *task, isc_event_t *event) {
|
|
||||||
ttl = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
- result = ncache_adderesult(message, fctx->cache, node, covers,
|
|
||||||
- now, fctx->res->view->minncachettl,
|
|
||||||
+ result = ncache_adderesult(fctx, message, node, covers, now,
|
|
||||||
ttl, vevent->optout, vevent->secure,
|
|
||||||
ardataset, &eresult);
|
|
||||||
if (result != ISC_R_SUCCESS) {
|
|
||||||
@@ -6081,23 +6120,7 @@ answer_response:
|
|
||||||
*/
|
|
||||||
INSIST(hevent->rdataset != NULL);
|
|
||||||
if (dns_rdataset_isassociated(hevent->rdataset)) {
|
|
||||||
- if (NEGATIVE(hevent->rdataset)) {
|
|
||||||
- INSIST(eresult == DNS_R_NCACHENXDOMAIN ||
|
|
||||||
- eresult == DNS_R_NCACHENXRRSET);
|
|
||||||
- } else if (eresult == ISC_R_SUCCESS &&
|
|
||||||
- hevent->rdataset->type != fctx->type)
|
|
||||||
- {
|
|
||||||
- switch (hevent->rdataset->type) {
|
|
||||||
- case dns_rdatatype_cname:
|
|
||||||
- eresult = DNS_R_CNAME;
|
|
||||||
- break;
|
|
||||||
- case dns_rdatatype_dname:
|
|
||||||
- eresult = DNS_R_DNAME;
|
|
||||||
- break;
|
|
||||||
- default:
|
|
||||||
- break;
|
|
||||||
- }
|
|
||||||
- }
|
|
||||||
+ eresult = fctx_setresult(fctx, hevent->rdataset);
|
|
||||||
}
|
|
||||||
|
|
||||||
hevent->result = eresult;
|
|
||||||
@@ -6747,24 +6770,7 @@ cache_name(fetchctx_t *fctx, dns_name_t *name, dns_message_t *message,
|
|
||||||
* event->result.
|
|
||||||
*/
|
|
||||||
if (dns_rdataset_isassociated(event->rdataset)) {
|
|
||||||
- if (NEGATIVE(event->rdataset)) {
|
|
||||||
- INSIST(eresult ==
|
|
||||||
- DNS_R_NCACHENXDOMAIN ||
|
|
||||||
- eresult == DNS_R_NCACHENXRRSET);
|
|
||||||
- } else if (eresult == ISC_R_SUCCESS &&
|
|
||||||
- event->rdataset->type != fctx->type)
|
|
||||||
- {
|
|
||||||
- switch (event->rdataset->type) {
|
|
||||||
- case dns_rdatatype_cname:
|
|
||||||
- eresult = DNS_R_CNAME;
|
|
||||||
- break;
|
|
||||||
- case dns_rdatatype_dname:
|
|
||||||
- eresult = DNS_R_DNAME;
|
|
||||||
- break;
|
|
||||||
- default:
|
|
||||||
- break;
|
|
||||||
- }
|
|
||||||
- }
|
|
||||||
+ eresult = fctx_setresult(fctx, event->rdataset);
|
|
||||||
}
|
|
||||||
event->result = eresult;
|
|
||||||
if (adbp != NULL && *adbp != NULL) {
|
|
||||||
@@ -6833,12 +6839,14 @@ cache_message(fetchctx_t *fctx, dns_message_t *message,
|
|
||||||
* eresult.
|
|
||||||
*/
|
|
||||||
static isc_result_t
|
|
||||||
-ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node,
|
|
||||||
- dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl,
|
|
||||||
- dns_ttl_t maxttl, bool optout, bool secure,
|
|
||||||
- dns_rdataset_t *ardataset, isc_result_t *eresultp) {
|
|
||||||
+ncache_adderesult(fetchctx_t *fctx, dns_message_t *message, dns_dbnode_t *node,
|
|
||||||
+ dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t maxttl,
|
|
||||||
+ bool optout, bool secure, dns_rdataset_t *ardataset,
|
|
||||||
+ isc_result_t *eresultp) {
|
|
||||||
isc_result_t result;
|
|
||||||
dns_rdataset_t rdataset;
|
|
||||||
+ dns_db_t *cache = fctx->cache;
|
|
||||||
+ dns_ttl_t minttl = fctx->res->view->minncachettl;
|
|
||||||
|
|
||||||
if (ardataset == NULL) {
|
|
||||||
dns_rdataset_init(&rdataset);
|
|
||||||
@@ -6854,37 +6862,13 @@ ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node,
|
|
||||||
}
|
|
||||||
if (result == DNS_R_UNCHANGED || result == ISC_R_SUCCESS) {
|
|
||||||
/*
|
|
||||||
- * If the cache now contains a negative entry and we
|
|
||||||
- * care about whether it is DNS_R_NCACHENXDOMAIN or
|
|
||||||
- * DNS_R_NCACHENXRRSET then extract it.
|
|
||||||
+ * The cache settled successfully (DNS_R_UNCHANGED means our
|
|
||||||
+ * negative entry was discarded in favour of existing
|
|
||||||
+ * higher-trust data). Either way 'ardataset' is now bound to
|
|
||||||
+ * the rdataset the cache holds for this name and type; derive
|
|
||||||
+ * the result code from it.
|
|
||||||
*/
|
|
||||||
- if (NEGATIVE(ardataset)) {
|
|
||||||
- /*
|
|
||||||
- * The cache data is a negative cache entry.
|
|
||||||
- */
|
|
||||||
- if (NXDOMAIN(ardataset)) {
|
|
||||||
- *eresultp = DNS_R_NCACHENXDOMAIN;
|
|
||||||
- } else {
|
|
||||||
- *eresultp = DNS_R_NCACHENXRRSET;
|
|
||||||
- }
|
|
||||||
- } else {
|
|
||||||
- /*
|
|
||||||
- * The attempt to add a negative cache entry
|
|
||||||
- * was rejected. Set *eresultp to reflect
|
|
||||||
- * the type of the dataset being returned.
|
|
||||||
- */
|
|
||||||
- switch (ardataset->type) {
|
|
||||||
- case dns_rdatatype_cname:
|
|
||||||
- *eresultp = DNS_R_CNAME;
|
|
||||||
- break;
|
|
||||||
- case dns_rdatatype_dname:
|
|
||||||
- *eresultp = DNS_R_DNAME;
|
|
||||||
- break;
|
|
||||||
- default:
|
|
||||||
- *eresultp = ISC_R_SUCCESS;
|
|
||||||
- break;
|
|
||||||
- }
|
|
||||||
- }
|
|
||||||
+ *eresultp = fctx_setresult(fctx, ardataset);
|
|
||||||
result = ISC_R_SUCCESS;
|
|
||||||
}
|
|
||||||
if (ardataset == &rdataset && dns_rdataset_isassociated(ardataset)) {
|
|
||||||
@@ -7029,8 +7013,7 @@ ncache_message(fetchctx_t *fctx, dns_message_t *message,
|
|
||||||
ttl = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
- result = ncache_adderesult(message, fctx->cache, node, covers, now,
|
|
||||||
- fctx->res->view->minncachettl, ttl, false,
|
|
||||||
+ result = ncache_adderesult(fctx, message, node, covers, now, ttl, false,
|
|
||||||
false, ardataset, &eresult);
|
|
||||||
if (result != ISC_R_SUCCESS) {
|
|
||||||
goto unlock;
|
|
||||||
--
|
|
||||||
2.55.0
|
|
||||||
|
|
||||||
|
|
@ -1,416 +0,0 @@
|
||||||
From 89e950d215e9922e5af6e3c69b9d6a8750346bb6 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Alessio Podda <alessio@isc.org>
|
|
||||||
Date: Fri, 12 Jun 2026 11:16:01 +0200
|
|
||||||
Subject: [PATCH] Reproducer for #5985 addnoqname mismatch
|
|
||||||
|
|
||||||
LLM generated.
|
|
||||||
|
|
||||||
(cherry picked from commit 5f4de929b3e4749b6e32c51660be11c47c2514e6)
|
|
||||||
(cherry picked from commit 0cf010c153518f1f9831e201891ecba8d8ba65e1)
|
|
||||||
|
|
||||||
Update reproducer #5985
|
|
||||||
|
|
||||||
Update the llm generated reproducer:
|
|
||||||
- Move server.py into ans/ans1.py
|
|
||||||
- Remove unncessary named.conf configuration options
|
|
||||||
- Add comments describing the steps
|
|
||||||
- Rename system test
|
|
||||||
|
|
||||||
(cherry picked from commit fd539807829dd7d2eb76c8b503083f5d84fec6f0)
|
|
||||||
(cherry picked from commit 6c0e599ea85c0c53a4af09742e64e193da089bb4)
|
|
||||||
---
|
|
||||||
.../dnssec_findnoqname_mismatch/ans1/ans.py | 207 ++++++++++++++++++
|
|
||||||
.../ns2/named.conf.j2 | 33 +++
|
|
||||||
.../tests_findnoqname_mismatch.py | 126 +++++++++++
|
|
||||||
3 files changed, 366 insertions(+)
|
|
||||||
create mode 100644 bin/tests/system/dnssec_findnoqname_mismatch/ans1/ans.py
|
|
||||||
create mode 100644 bin/tests/system/dnssec_findnoqname_mismatch/ns2/named.conf.j2
|
|
||||||
create mode 100644 bin/tests/system/dnssec_findnoqname_mismatch/tests_findnoqname_mismatch.py
|
|
||||||
|
|
||||||
diff --git a/bin/tests/system/dnssec_findnoqname_mismatch/ans1/ans.py b/bin/tests/system/dnssec_findnoqname_mismatch/ans1/ans.py
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..b36fc831c8
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/dnssec_findnoqname_mismatch/ans1/ans.py
|
|
||||||
@@ -0,0 +1,207 @@
|
|
||||||
+#!/usr/bin/python3
|
|
||||||
+
|
|
||||||
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
||||||
+#
|
|
||||||
+# SPDX-License-Identifier: MPL-2.0
|
|
||||||
+
|
|
||||||
+from collections.abc import AsyncGenerator
|
|
||||||
+from dataclasses import dataclass
|
|
||||||
+from datetime import datetime, timedelta, timezone
|
|
||||||
+from pathlib import Path
|
|
||||||
+
|
|
||||||
+import base64
|
|
||||||
+import json
|
|
||||||
+
|
|
||||||
+from cryptography.hazmat.primitives import serialization
|
|
||||||
+
|
|
||||||
+import dns.dnssec
|
|
||||||
+import dns.flags
|
|
||||||
+import dns.message
|
|
||||||
+import dns.name
|
|
||||||
+import dns.rdata
|
|
||||||
+import dns.rdataclass
|
|
||||||
+import dns.rcode
|
|
||||||
+import dns.rdatatype
|
|
||||||
+import dns.rrset
|
|
||||||
+
|
|
||||||
+from isctest.asyncserver import (
|
|
||||||
+ AsyncDnsServer,
|
|
||||||
+ DnsResponseSend,
|
|
||||||
+ QueryContext,
|
|
||||||
+ ResponseHandler,
|
|
||||||
+)
|
|
||||||
+
|
|
||||||
+TTL = 300
|
|
||||||
+ZONE = "f217.test."
|
|
||||||
+CHILD = f"evil.{ZONE}"
|
|
||||||
+ATTACK = f"www.{CHILD}"
|
|
||||||
+NSEC_OWNER = f"00000000.{CHILD}"
|
|
||||||
+NSEC_NEXT = f"zzz.{CHILD}"
|
|
||||||
+FORGED_A = "192.0.2.217"
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+@dataclass(frozen=True)
|
|
||||||
+class Key:
|
|
||||||
+ zone: dns.name.Name
|
|
||||||
+ private_key: object
|
|
||||||
+ dnskey: dns.rdata.Rdata
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def name(text: str) -> dns.name.Name:
|
|
||||||
+ return dns.name.from_text(text)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def load_key() -> Key:
|
|
||||||
+ path = Path(__file__).resolve().parent / "keys.json"
|
|
||||||
+ with path.open(encoding="utf-8") as keys_file:
|
|
||||||
+ raw_key = json.load(keys_file)[ZONE]
|
|
||||||
+
|
|
||||||
+ private_key = serialization.load_pem_private_key(
|
|
||||||
+ raw_key["private_pem"].encode("ascii"),
|
|
||||||
+ password=None,
|
|
||||||
+ )
|
|
||||||
+ dnskey = dns.rdata.from_text(
|
|
||||||
+ dns.rdataclass.IN, dns.rdatatype.DNSKEY, raw_key["dnskey"]
|
|
||||||
+ )
|
|
||||||
+ return Key(name(ZONE), private_key, dnskey)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def rrset(owner: str, rdtype: dns.rdatatype.RdataType, *rdatas: str) -> dns.rrset.RRset:
|
|
||||||
+ return dns.rrset.from_text(owner, TTL, dns.rdataclass.IN, rdtype, *rdatas)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def rrset_from_rdata(owner: str, rdata: dns.rdata.Rdata) -> dns.rrset.RRset:
|
|
||||||
+ return dns.rrset.from_rdata(name(owner), TTL, rdata)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def add_signed(
|
|
||||||
+ section: list[dns.rrset.RRset], covered: dns.rrset.RRset, signer: Key
|
|
||||||
+) -> None:
|
|
||||||
+ rrsig = dns.dnssec.sign(
|
|
||||||
+ covered,
|
|
||||||
+ signer.private_key,
|
|
||||||
+ signer.zone,
|
|
||||||
+ signer.dnskey,
|
|
||||||
+ lifetime=86400,
|
|
||||||
+ verify=True,
|
|
||||||
+ )
|
|
||||||
+ section.append(covered)
|
|
||||||
+ section.append(dns.rrset.from_rdata(covered.name, covered.ttl, rrsig))
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def soa_rrset(zone: str) -> dns.rrset.RRset:
|
|
||||||
+ return rrset(
|
|
||||||
+ zone,
|
|
||||||
+ dns.rdatatype.SOA,
|
|
||||||
+ f"ns.{ZONE} hostmaster.{ZONE} 1 7200 3600 1209600 300",
|
|
||||||
+ )
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def garbage_rrsig(
|
|
||||||
+ owner: str, covered: dns.rdatatype.RdataType, labels: int, signer: str
|
|
||||||
+) -> dns.rrset.RRset:
|
|
||||||
+ now = datetime.now(timezone.utc)
|
|
||||||
+ inception = (now - timedelta(hours=1)).strftime("%Y%m%d%H%M%S")
|
|
||||||
+ expiration = (now + timedelta(days=1)).strftime("%Y%m%d%H%M%S")
|
|
||||||
+ signature = base64.b64encode(bytes(64)).decode("ascii")
|
|
||||||
+ text = (
|
|
||||||
+ f"{dns.rdatatype.to_text(covered)} 13 {labels} {TTL} "
|
|
||||||
+ f"{expiration} {inception} 12345 {signer} {signature}"
|
|
||||||
+ )
|
|
||||||
+ rdata = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.RRSIG, text)
|
|
||||||
+ return dns.rrset.from_rdata(name(owner), TTL, rdata)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def add_ds_denial(response: dns.message.Message, key: Key) -> None:
|
|
||||||
+ add_signed(response.authority, soa_rrset(ZONE), key)
|
|
||||||
+ nsec = rrset(CHILD, dns.rdatatype.NSEC, f"ns.{ZONE} NS RRSIG NSEC")
|
|
||||||
+ add_signed(response.authority, nsec, key)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def add_attack_answer(response: dns.message.Message) -> None:
|
|
||||||
+ """
|
|
||||||
+ Crafted authoritative response to <q>.evil.f217.hack./A
|
|
||||||
+
|
|
||||||
+ ;; ANSWER
|
|
||||||
+ <q>.evil.f217.hack. 300 IN A 192.0.2.217
|
|
||||||
+ <q>.evil.f217.hack. 300 IN RRSIG A 13 1 300 <exp> <inc> 12345 evil.f217.hack. <base64 of 64×0x00>
|
|
||||||
+ ^^^ Labels = 1, qname has 4 labels, wildcard heuristic fires
|
|
||||||
+
|
|
||||||
+ ;; AUTHORITY (single owner, three rdatasets in this wire order)
|
|
||||||
+ 00000000.evil.f217.hack. 300 IN NSEC zzz.evil.f217.hack. A RRSIG NSEC
|
|
||||||
+ 00000000.evil.f217.hack. 300 IN RRSIG NSEC 13 4 300 <exp> <inc> 12345 evil.f217.hack. <base64 of 64×0x00>
|
|
||||||
+ 00000000.evil.f217.hack. 300 IN NSEC3 1 0 0 - VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV A RRSIG
|
|
||||||
+ """
|
|
||||||
+ # A + RRSIG
|
|
||||||
+ response.answer.append(rrset(ATTACK, dns.rdatatype.A, FORGED_A))
|
|
||||||
+ response.answer.append(garbage_rrsig(ATTACK, dns.rdatatype.A, 1, CHILD))
|
|
||||||
+ # NSEC
|
|
||||||
+ nsec = rrset(
|
|
||||||
+ NSEC_OWNER,
|
|
||||||
+ dns.rdatatype.NSEC,
|
|
||||||
+ f"{NSEC_NEXT} A RRSIG NSEC",
|
|
||||||
+ )
|
|
||||||
+ response.authority.append(nsec)
|
|
||||||
+ # RRSIG(NSEC)
|
|
||||||
+ response.authority.append(
|
|
||||||
+ garbage_rrsig(
|
|
||||||
+ NSEC_OWNER,
|
|
||||||
+ dns.rdatatype.NSEC,
|
|
||||||
+ len(name(NSEC_OWNER).labels) - 1,
|
|
||||||
+ CHILD,
|
|
||||||
+ )
|
|
||||||
+ )
|
|
||||||
+ # NSEC3
|
|
||||||
+ nsec3 = rrset(
|
|
||||||
+ NSEC_OWNER,
|
|
||||||
+ dns.rdatatype.NSEC3,
|
|
||||||
+ "1 0 0 - VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV A RRSIG",
|
|
||||||
+ )
|
|
||||||
+ response.authority.append(nsec3)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+class RuntimeCheckHandler(ResponseHandler):
|
|
||||||
+ def __init__(self, key: Key) -> None:
|
|
||||||
+ self.key = key
|
|
||||||
+ self.zone = name(ZONE)
|
|
||||||
+ self.child = name(CHILD)
|
|
||||||
+ self.attack = name(ATTACK)
|
|
||||||
+
|
|
||||||
+ def match(self, qctx: QueryContext) -> bool:
|
|
||||||
+ return qctx.qname.is_subdomain(self.zone)
|
|
||||||
+
|
|
||||||
+ async def get_responses(
|
|
||||||
+ self, qctx: QueryContext
|
|
||||||
+ ) -> AsyncGenerator[DnsResponseSend, None]:
|
|
||||||
+ qctx.prepare_new_response(with_zone_data=False)
|
|
||||||
+ qctx.response.flags |= dns.flags.AA
|
|
||||||
+ qctx.response.set_rcode(dns.rcode.NOERROR)
|
|
||||||
+
|
|
||||||
+ if qctx.qname == self.zone and qctx.qtype == dns.rdatatype.DNSKEY:
|
|
||||||
+ add_signed(
|
|
||||||
+ qctx.response.answer,
|
|
||||||
+ rrset_from_rdata(ZONE, self.key.dnskey),
|
|
||||||
+ self.key,
|
|
||||||
+ )
|
|
||||||
+ elif qctx.qname == self.zone and qctx.qtype == dns.rdatatype.SOA:
|
|
||||||
+ add_signed(qctx.response.answer, soa_rrset(ZONE), self.key)
|
|
||||||
+ elif qctx.qname == self.child and qctx.qtype == dns.rdatatype.DS:
|
|
||||||
+ add_ds_denial(qctx.response, self.key)
|
|
||||||
+ elif qctx.qname == self.child and qctx.qtype == dns.rdatatype.DNSKEY:
|
|
||||||
+ qctx.response.authority.append(soa_rrset(CHILD))
|
|
||||||
+ elif qctx.qname == self.attack and qctx.qtype == dns.rdatatype.A:
|
|
||||||
+ add_attack_answer(qctx.response)
|
|
||||||
+ else:
|
|
||||||
+ add_signed(qctx.response.authority, soa_rrset(ZONE), self.key)
|
|
||||||
+
|
|
||||||
+ yield DnsResponseSend(qctx.response, authoritative=True)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def main() -> None:
|
|
||||||
+ server = AsyncDnsServer(default_aa=True)
|
|
||||||
+ server.install_response_handlers(RuntimeCheckHandler(load_key()))
|
|
||||||
+ server.run()
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+if __name__ == "__main__":
|
|
||||||
+ main()
|
|
||||||
diff --git a/bin/tests/system/dnssec_findnoqname_mismatch/ns2/named.conf.j2 b/bin/tests/system/dnssec_findnoqname_mismatch/ns2/named.conf.j2
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..f4fbd8a617
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/dnssec_findnoqname_mismatch/ns2/named.conf.j2
|
|
||||||
@@ -0,0 +1,33 @@
|
|
||||||
+// validating resolver
|
|
||||||
+
|
|
||||||
+options {
|
|
||||||
+ query-source address 10.53.0.2;
|
|
||||||
+ notify-source 10.53.0.2;
|
|
||||||
+ transfer-source 10.53.0.2;
|
|
||||||
+ port @PORT@;
|
|
||||||
+ pid-file "named.pid";
|
|
||||||
+ listen-on { 10.53.0.2; };
|
|
||||||
+ listen-on-v6 { none; };
|
|
||||||
+ recursion yes;
|
|
||||||
+ dnssec-validation yes;
|
|
||||||
+};
|
|
||||||
+
|
|
||||||
+controls {
|
|
||||||
+ inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
|
|
||||||
+};
|
|
||||||
+
|
|
||||||
+include "../../_common/rndc.key";
|
|
||||||
+
|
|
||||||
+zone "." {
|
|
||||||
+ type hint;
|
|
||||||
+ file "../../_common/root.hint";
|
|
||||||
+};
|
|
||||||
+
|
|
||||||
+zone "f217.test" {
|
|
||||||
+ type static-stub;
|
|
||||||
+ server-addresses { 10.53.0.1; };
|
|
||||||
+};
|
|
||||||
+
|
|
||||||
+trust-anchors {
|
|
||||||
+ f217.test. static-key 257 3 13 "@ZONE_DNSKEY@";
|
|
||||||
+};
|
|
||||||
diff --git a/bin/tests/system/dnssec_findnoqname_mismatch/tests_findnoqname_mismatch.py b/bin/tests/system/dnssec_findnoqname_mismatch/tests_findnoqname_mismatch.py
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..f3e332a360
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/dnssec_findnoqname_mismatch/tests_findnoqname_mismatch.py
|
|
||||||
@@ -0,0 +1,126 @@
|
|
||||||
+#!/usr/bin/python3
|
|
||||||
+
|
|
||||||
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
||||||
+#
|
|
||||||
+# SPDX-License-Identifier: MPL-2.0
|
|
||||||
+
|
|
||||||
+from pathlib import Path
|
|
||||||
+
|
|
||||||
+import json
|
|
||||||
+
|
|
||||||
+from cryptography.hazmat.primitives import serialization
|
|
||||||
+from cryptography.hazmat.primitives.asymmetric import ec
|
|
||||||
+
|
|
||||||
+import dns.dnssec
|
|
||||||
+import dns.name
|
|
||||||
+import dns.rdataclass
|
|
||||||
+import dns.rdatatype
|
|
||||||
+import pytest
|
|
||||||
+
|
|
||||||
+import isctest
|
|
||||||
+import isctest.mark
|
|
||||||
+
|
|
||||||
+ZONE = "f217.test."
|
|
||||||
+CHILD = f"evil.{ZONE}"
|
|
||||||
+ATTACK = f"www.{CHILD}"
|
|
||||||
+NSEC_OWNER = f"00000000.{CHILD}"
|
|
||||||
+FORGED_A = "192.0.2.217"
|
|
||||||
+AUTH = "10.53.0.1"
|
|
||||||
+RESOLVER = "10.53.0.2"
|
|
||||||
+
|
|
||||||
+pytestmark = [
|
|
||||||
+ isctest.mark.with_ecdsa_deterministic,
|
|
||||||
+ pytest.mark.extra_artifacts(
|
|
||||||
+ [
|
|
||||||
+ "ans1/ans.run",
|
|
||||||
+ "ans1/keys.json",
|
|
||||||
+ ]
|
|
||||||
+ ),
|
|
||||||
+]
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def _make_key():
|
|
||||||
+ private_key = ec.generate_private_key(ec.SECP256R1())
|
|
||||||
+ dnskey = dns.dnssec.make_dnskey(
|
|
||||||
+ private_key.public_key(),
|
|
||||||
+ algorithm="ECDSAP256SHA256",
|
|
||||||
+ flags=257,
|
|
||||||
+ )
|
|
||||||
+ private_pem = private_key.private_bytes(
|
|
||||||
+ encoding=serialization.Encoding.PEM,
|
|
||||||
+ format=serialization.PrivateFormat.PKCS8,
|
|
||||||
+ encryption_algorithm=serialization.NoEncryption(),
|
|
||||||
+ ).decode("ascii")
|
|
||||||
+ return {
|
|
||||||
+ "private_pem": private_pem,
|
|
||||||
+ "dnskey": dnskey.to_text(),
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def bootstrap():
|
|
||||||
+ keys = {ZONE: _make_key()}
|
|
||||||
+ Path("ans1/keys.json").write_text(json.dumps(keys, indent=2), encoding="ascii")
|
|
||||||
+ zone_dnskey = "".join(keys[ZONE]["dnskey"].split()[3:])
|
|
||||||
+ return {"ZONE_DNSKEY": zone_dnskey}
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def _query(server, qname, qtype):
|
|
||||||
+ query = isctest.query.create(qname, qtype)
|
|
||||||
+ return isctest.query.tcp(query, server, attempts=1, timeout=5)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def _rrset(response, section, owner, rdtype, covers=None):
|
|
||||||
+ if covers is None:
|
|
||||||
+ return response.get_rrset(
|
|
||||||
+ section, dns.name.from_text(owner), dns.rdataclass.IN, rdtype
|
|
||||||
+ )
|
|
||||||
+ return response.get_rrset(
|
|
||||||
+ section,
|
|
||||||
+ dns.name.from_text(owner),
|
|
||||||
+ dns.rdataclass.IN,
|
|
||||||
+ rdtype,
|
|
||||||
+ covers=covers,
|
|
||||||
+ )
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def _has_a(response, section, owner, address):
|
|
||||||
+ rrset = _rrset(response, section, owner, dns.rdatatype.A)
|
|
||||||
+ return rrset is not None and any(rdata.address == address for rdata in rrset)
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def _check_rrsig(response, section, owner, rdtype, signer, labels=None):
|
|
||||||
+ rrsig = _rrset(response, section, owner, dns.rdatatype.RRSIG, covers=rdtype)
|
|
||||||
+ assert rrsig is not None, response.to_text()
|
|
||||||
+ assert rrsig[0].signer == dns.name.from_text(signer), response.to_text()
|
|
||||||
+ if labels is not None:
|
|
||||||
+ assert rrsig[0].labels == labels, response.to_text()
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def test_malicious_findnoqname_addnoqname_mismatch():
|
|
||||||
+ response = _query(AUTH, ATTACK, "A")
|
|
||||||
+ isctest.check.noerror(response)
|
|
||||||
+ assert _has_a(response, response.answer, ATTACK, FORGED_A), response.to_text()
|
|
||||||
+ _check_rrsig(response, response.answer, ATTACK, dns.rdatatype.A, CHILD, labels=1)
|
|
||||||
+
|
|
||||||
+ # Has NSEC
|
|
||||||
+ assert _rrset(response, response.authority, NSEC_OWNER, dns.rdatatype.NSEC)
|
|
||||||
+ _check_rrsig(response, response.authority, NSEC_OWNER, dns.rdatatype.NSEC, CHILD)
|
|
||||||
+ # Has NSEC3
|
|
||||||
+ assert _rrset(response, response.authority, NSEC_OWNER, dns.rdatatype.NSEC3)
|
|
||||||
+ assert (
|
|
||||||
+ _rrset(
|
|
||||||
+ response,
|
|
||||||
+ response.authority,
|
|
||||||
+ NSEC_OWNER,
|
|
||||||
+ dns.rdatatype.RRSIG,
|
|
||||||
+ covers=dns.rdatatype.NSEC3,
|
|
||||||
+ )
|
|
||||||
+ is None
|
|
||||||
+ )
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def test_resolver_findnoqname_addnoqname_mismatch():
|
|
||||||
+ # Send one trigger query
|
|
||||||
+ _query(RESOLVER, ATTACK, "A")
|
|
||||||
+ response = _query(RESOLVER, ZONE, "SOA")
|
|
||||||
+ isctest.check.noerror(response)
|
|
||||||
--
|
|
||||||
2.55.0
|
|
||||||
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
From 895cac04332d85489ddf881b28e18e9956f6e348 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Evan Hunt <each@isc.org>
|
|
||||||
Date: Wed, 13 May 2026 20:45:57 -0700
|
|
||||||
Subject: [PATCH] dns_rdataset_addnoqname() could find unsigned NSEC/NSEC3
|
|
||||||
|
|
||||||
The dns_rdatalist addnoqname() implementation searches for the first
|
|
||||||
NSEC or NSEC3 record in a message, then for the first RRSIG covering
|
|
||||||
that type in the same message. Previously, if no RRSIG for the type was
|
|
||||||
found, the function accepted the unsigned record. Now, it will instead
|
|
||||||
continue searching until an NSEC or NSEC3 that does have a matching
|
|
||||||
signature is found.
|
|
||||||
|
|
||||||
When this function is called from validated() in resolver.c, a
|
|
||||||
non-success return code is now treated as an error instead of triggering
|
|
||||||
an assertion failure.
|
|
||||||
|
|
||||||
Fixes: isc-projects/bind9#5985
|
|
||||||
(cherry picked from commit 57cba571ee31311e54d8a11cb38094d439f04e09)
|
|
||||||
(cherry picked from commit 48f5aa5fb3746d6194edcc57e8792a8b3cc3b454)
|
|
||||||
---
|
|
||||||
lib/dns/rbtdb.c | 10 +++++++---
|
|
||||||
lib/dns/rdatalist.c | 33 ++++++++++++++++-----------------
|
|
||||||
lib/dns/resolver.c | 4 +++-
|
|
||||||
lib/ns/query.c | 3 +--
|
|
||||||
4 files changed, 27 insertions(+), 23 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c
|
|
||||||
index 0b8547950f..c922df557b 100644
|
|
||||||
--- a/lib/dns/rbtdb.c
|
|
||||||
+++ b/lib/dns/rbtdb.c
|
|
||||||
@@ -6946,7 +6946,7 @@ delegating_type(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
|
|
||||||
static isc_result_t
|
|
||||||
addnoqname(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader,
|
|
||||||
uint32_t maxrrperset, dns_rdataset_t *rdataset) {
|
|
||||||
- struct noqname *noqname;
|
|
||||||
+ struct noqname *noqname = NULL;
|
|
||||||
isc_mem_t *mctx = rbtdb->common.mctx;
|
|
||||||
dns_name_t name;
|
|
||||||
dns_rdataset_t neg, negsig;
|
|
||||||
@@ -6958,7 +6958,9 @@ addnoqname(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader,
|
|
||||||
dns_rdataset_init(&negsig);
|
|
||||||
|
|
||||||
result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig);
|
|
||||||
- RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
||||||
+ if (result != ISC_R_SUCCESS) {
|
|
||||||
+ goto cleanup;
|
|
||||||
+ }
|
|
||||||
|
|
||||||
noqname = isc_mem_get(mctx, sizeof(*noqname));
|
|
||||||
dns_name_init(&noqname->name, NULL);
|
|
||||||
@@ -6984,7 +6986,9 @@ addnoqname(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader,
|
|
||||||
cleanup:
|
|
||||||
dns_rdataset_disassociate(&neg);
|
|
||||||
dns_rdataset_disassociate(&negsig);
|
|
||||||
- free_noqname(mctx, &noqname);
|
|
||||||
+ if (noqname != NULL) {
|
|
||||||
+ free_noqname(mctx, &noqname);
|
|
||||||
+ }
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
diff --git a/lib/dns/rdatalist.c b/lib/dns/rdatalist.c
|
|
||||||
index 98036f9cb3..2cca8d64be 100644
|
|
||||||
--- a/lib/dns/rdatalist.c
|
|
||||||
+++ b/lib/dns/rdatalist.c
|
|
||||||
@@ -192,6 +192,7 @@ isc__rdatalist_addnoqname(dns_rdataset_t *rdataset, const dns_name_t *name) {
|
|
||||||
dns_rdataset_t *neg = NULL;
|
|
||||||
dns_rdataset_t *negsig = NULL;
|
|
||||||
dns_rdataset_t *rdset;
|
|
||||||
+ dns_rdataset_t *sigset;
|
|
||||||
dns_ttl_t ttl;
|
|
||||||
|
|
||||||
REQUIRE(rdataset != NULL);
|
|
||||||
@@ -199,30 +200,27 @@ isc__rdatalist_addnoqname(dns_rdataset_t *rdataset, const dns_name_t *name) {
|
|
||||||
for (rdset = ISC_LIST_HEAD(name->list); rdset != NULL;
|
|
||||||
rdset = ISC_LIST_NEXT(rdset, link))
|
|
||||||
{
|
|
||||||
- if (rdset->rdclass != rdataset->rdclass) {
|
|
||||||
- continue;
|
|
||||||
- }
|
|
||||||
- if (rdset->type == dns_rdatatype_nsec ||
|
|
||||||
- rdset->type == dns_rdatatype_nsec3)
|
|
||||||
+ if (rdset->rdclass != rdataset->rdclass ||
|
|
||||||
+ (rdset->type != dns_rdatatype_nsec &&
|
|
||||||
+ rdset->type != dns_rdatatype_nsec3))
|
|
||||||
{
|
|
||||||
- neg = rdset;
|
|
||||||
+ continue;
|
|
||||||
}
|
|
||||||
- }
|
|
||||||
- if (neg == NULL) {
|
|
||||||
- return ISC_R_NOTFOUND;
|
|
||||||
- }
|
|
||||||
|
|
||||||
- for (rdset = ISC_LIST_HEAD(name->list); rdset != NULL;
|
|
||||||
- rdset = ISC_LIST_NEXT(rdset, link))
|
|
||||||
- {
|
|
||||||
- if (rdset->type == dns_rdatatype_rrsig &&
|
|
||||||
- rdset->covers == neg->type)
|
|
||||||
+ for (sigset = ISC_LIST_HEAD(name->list); sigset != NULL;
|
|
||||||
+ sigset = ISC_LIST_NEXT(sigset, link))
|
|
||||||
{
|
|
||||||
- negsig = rdset;
|
|
||||||
+ if (sigset->type == dns_rdatatype_rrsig &&
|
|
||||||
+ sigset->covers == rdset->type)
|
|
||||||
+ {
|
|
||||||
+ neg = rdset;
|
|
||||||
+ negsig = sigset;
|
|
||||||
+ break;
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- if (negsig == NULL) {
|
|
||||||
+ if (neg == NULL || negsig == NULL) {
|
|
||||||
return ISC_R_NOTFOUND;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
@@ -238,6 +236,7 @@ isc__rdatalist_addnoqname(dns_rdataset_t *rdataset, const dns_name_t *name) {
|
|
||||||
rdataset->ttl = neg->ttl = negsig->ttl = ttl;
|
|
||||||
rdataset->attributes |= DNS_RDATASETATTR_NOQNAME;
|
|
||||||
rdataset->private6 = name;
|
|
||||||
+
|
|
||||||
return ISC_R_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
|
|
||||||
index 1f8b5058d1..059ce53a9e 100644
|
|
||||||
--- a/lib/dns/resolver.c
|
|
||||||
+++ b/lib/dns/resolver.c
|
|
||||||
@@ -5893,7 +5893,9 @@ validated(isc_task_t *task, isc_event_t *event) {
|
|
||||||
result = dns_rdataset_addnoqname(
|
|
||||||
vevent->rdataset,
|
|
||||||
vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF]);
|
|
||||||
- RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
||||||
+ if (result != ISC_R_SUCCESS) {
|
|
||||||
+ goto noanswer_response;
|
|
||||||
+ }
|
|
||||||
INSIST(vevent->sigrdataset != NULL);
|
|
||||||
vevent->sigrdataset->ttl = vevent->rdataset->ttl;
|
|
||||||
if (vevent->proofs[DNS_VALIDATOR_CLOSESTENCLOSER] != NULL) {
|
|
||||||
diff --git a/lib/ns/query.c b/lib/ns/query.c
|
|
||||||
index 3bd7daf79c..2a2ba1daba 100644
|
|
||||||
--- a/lib/ns/query.c
|
|
||||||
+++ b/lib/ns/query.c
|
|
||||||
@@ -7953,8 +7953,7 @@ query_addnoqnameproof(query_ctx_t *qctx) {
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
- result = dns_rdataset_getnoqname(qctx->noqname, fname, neg, negsig);
|
|
||||||
- RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
||||||
+ CHECK(dns_rdataset_getnoqname(qctx->noqname, fname, neg, negsig));
|
|
||||||
|
|
||||||
query_addrrset(qctx, &fname, &neg, &negsig, dbuf,
|
|
||||||
DNS_SECTION_AUTHORITY);
|
|
||||||
--
|
|
||||||
2.55.0
|
|
||||||
|
|
||||||
|
|
@ -1,445 +0,0 @@
|
||||||
From eaa35628f4a201049295a8944f4d28e8a1013199 Mon Sep 17 00:00:00 2001
|
|
||||||
From: =?UTF-8?q?Ayd=C4=B1n=20Mercan?= <aydin@isc.org>
|
|
||||||
Date: Wed, 6 May 2026 16:54:57 +0300
|
|
||||||
Subject: [PATCH] Add system test for out-of-zone nsec dnssec bypass
|
|
||||||
|
|
||||||
A malicious zone with out-of-zone NSEC entries can get a DNSSEC
|
|
||||||
validating resolver's cache to cover the victim zone for non-existence
|
|
||||||
and prevent nameserver queries without DNSSEC failure.
|
|
||||||
|
|
||||||
Test for this case with an `evil.test` zone that tries to cover the
|
|
||||||
`victim.test` zone.
|
|
||||||
|
|
||||||
(cherry picked from commit 654f9773c0af59965c343bdfeb096b3dffe9dd53)
|
|
||||||
(cherry picked from commit c969ad2c17b43dd999e358bfeb280d3df6fab822)
|
|
||||||
---
|
|
||||||
.../system/dnssec_bypass/ns1/named.conf.j2 | 31 ++++
|
|
||||||
bin/tests/system/dnssec_bypass/ns1/root.db | 19 +++
|
|
||||||
bin/tests/system/dnssec_bypass/ns1/test.db | 23 +++
|
|
||||||
.../system/dnssec_bypass/ns2/named.conf.j2 | 26 +++
|
|
||||||
bin/tests/system/dnssec_bypass/ns2/victim.db | 18 +++
|
|
||||||
bin/tests/system/dnssec_bypass/ns3/evil.db | 23 +++
|
|
||||||
.../system/dnssec_bypass/ns3/named.conf.j2 | 26 +++
|
|
||||||
.../system/dnssec_bypass/ns4/named.conf.j2 | 35 ++++
|
|
||||||
.../system/dnssec_bypass/tests_bypass.py | 152 ++++++++++++++++++
|
|
||||||
9 files changed, 353 insertions(+)
|
|
||||||
create mode 100644 bin/tests/system/dnssec_bypass/ns1/named.conf.j2
|
|
||||||
create mode 100644 bin/tests/system/dnssec_bypass/ns1/root.db
|
|
||||||
create mode 100644 bin/tests/system/dnssec_bypass/ns1/test.db
|
|
||||||
create mode 100644 bin/tests/system/dnssec_bypass/ns2/named.conf.j2
|
|
||||||
create mode 100644 bin/tests/system/dnssec_bypass/ns2/victim.db
|
|
||||||
create mode 100644 bin/tests/system/dnssec_bypass/ns3/evil.db
|
|
||||||
create mode 100644 bin/tests/system/dnssec_bypass/ns3/named.conf.j2
|
|
||||||
create mode 100644 bin/tests/system/dnssec_bypass/ns4/named.conf.j2
|
|
||||||
create mode 100644 bin/tests/system/dnssec_bypass/tests_bypass.py
|
|
||||||
|
|
||||||
diff --git a/bin/tests/system/dnssec_bypass/ns1/named.conf.j2 b/bin/tests/system/dnssec_bypass/ns1/named.conf.j2
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..59ced1831a
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/dnssec_bypass/ns1/named.conf.j2
|
|
||||||
@@ -0,0 +1,31 @@
|
|
||||||
+/*
|
|
||||||
+ * 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.
|
|
||||||
+ */
|
|
||||||
+
|
|
||||||
+options {
|
|
||||||
+ port @PORT@;
|
|
||||||
+ pid-file "named.pid";
|
|
||||||
+ listen-on { 10.53.0.1; };
|
|
||||||
+ listen-on-v6 { none; };
|
|
||||||
+ recursion no;
|
|
||||||
+ dnssec-validation no;
|
|
||||||
+};
|
|
||||||
+
|
|
||||||
+zone "test." {
|
|
||||||
+ type primary;
|
|
||||||
+ file "test.db.signed";
|
|
||||||
+};
|
|
||||||
+
|
|
||||||
+zone "." {
|
|
||||||
+ type primary;
|
|
||||||
+ file "root.db.signed";
|
|
||||||
+};
|
|
||||||
diff --git a/bin/tests/system/dnssec_bypass/ns1/root.db b/bin/tests/system/dnssec_bypass/ns1/root.db
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..8d98a0456c
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/dnssec_bypass/ns1/root.db
|
|
||||||
@@ -0,0 +1,19 @@
|
|
||||||
+; 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.
|
|
||||||
+
|
|
||||||
+$TTL 3600
|
|
||||||
+. IN SOA a.nil. a.nil. 1 3600 600 86400 300
|
|
||||||
+. IN NS a.root-servers.nil.
|
|
||||||
+
|
|
||||||
+a.root-servers.nil. IN A 10.53.0.1
|
|
||||||
+
|
|
||||||
+test. IN NS ns1.test.
|
|
||||||
+ns1.test. IN A 10.53.0.1
|
|
||||||
diff --git a/bin/tests/system/dnssec_bypass/ns1/test.db b/bin/tests/system/dnssec_bypass/ns1/test.db
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..6efcd95e42
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/dnssec_bypass/ns1/test.db
|
|
||||||
@@ -0,0 +1,23 @@
|
|
||||||
+; 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.
|
|
||||||
+
|
|
||||||
+$ORIGIN test.
|
|
||||||
+$TTL 3600
|
|
||||||
+
|
|
||||||
+@ IN SOA a a 1 3600 600 86400 300
|
|
||||||
+ IN NS ns1.test.
|
|
||||||
+ns1 IN A 10.53.0.1
|
|
||||||
+
|
|
||||||
+evil IN NS ns1.evil
|
|
||||||
+ns1.evil IN A 10.53.0.3
|
|
||||||
+
|
|
||||||
+victim IN NS ns1.victim
|
|
||||||
+ns1.victim IN A 10.53.0.2
|
|
||||||
diff --git a/bin/tests/system/dnssec_bypass/ns2/named.conf.j2 b/bin/tests/system/dnssec_bypass/ns2/named.conf.j2
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..e81cee7cac
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/dnssec_bypass/ns2/named.conf.j2
|
|
||||||
@@ -0,0 +1,26 @@
|
|
||||||
+/*
|
|
||||||
+ * 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.
|
|
||||||
+ */
|
|
||||||
+
|
|
||||||
+options {
|
|
||||||
+ port @PORT@;
|
|
||||||
+ pid-file "named.pid";
|
|
||||||
+ listen-on { 10.53.0.2; };
|
|
||||||
+ listen-on-v6 { none; };
|
|
||||||
+ recursion no;
|
|
||||||
+ dnssec-validation no;
|
|
||||||
+};
|
|
||||||
+
|
|
||||||
+zone "victim.test." {
|
|
||||||
+ type primary;
|
|
||||||
+ file "victim.db.signed";
|
|
||||||
+};
|
|
||||||
diff --git a/bin/tests/system/dnssec_bypass/ns2/victim.db b/bin/tests/system/dnssec_bypass/ns2/victim.db
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..edcc234322
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/dnssec_bypass/ns2/victim.db
|
|
||||||
@@ -0,0 +1,18 @@
|
|
||||||
+; 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.
|
|
||||||
+
|
|
||||||
+$ORIGIN victim.test.
|
|
||||||
+$TTL 3600
|
|
||||||
+
|
|
||||||
+@ IN SOA ns1 hostmaster 1 3600 600 86400 2147483647
|
|
||||||
+ IN NS ns1
|
|
||||||
+
|
|
||||||
+ns1 IN A 10.53.0.2
|
|
||||||
diff --git a/bin/tests/system/dnssec_bypass/ns3/evil.db b/bin/tests/system/dnssec_bypass/ns3/evil.db
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..618f9d3e85
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/dnssec_bypass/ns3/evil.db
|
|
||||||
@@ -0,0 +1,23 @@
|
|
||||||
+; 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.
|
|
||||||
+
|
|
||||||
+$ORIGIN evil.test.
|
|
||||||
+$TTL 300
|
|
||||||
+
|
|
||||||
+@ IN SOA ns1 hostmaster 1 3600 600 86400 300
|
|
||||||
+ IN NS ns1
|
|
||||||
+; Try to poison the victim zone in a resolver cache.
|
|
||||||
+; If admitted, the aggressive NSEC cache will accept a range such as
|
|
||||||
+; [evil.test, b.victim.test) and will cause the victim nameserver to
|
|
||||||
+; be never queried.
|
|
||||||
+ IN NSEC b.victim.test. NS SOA RRSIG NSEC DNSKEY
|
|
||||||
+
|
|
||||||
+ns1 IN A 10.53.0.3
|
|
||||||
diff --git a/bin/tests/system/dnssec_bypass/ns3/named.conf.j2 b/bin/tests/system/dnssec_bypass/ns3/named.conf.j2
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..17d3e18e4e
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/dnssec_bypass/ns3/named.conf.j2
|
|
||||||
@@ -0,0 +1,26 @@
|
|
||||||
+/*
|
|
||||||
+ * 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.
|
|
||||||
+ */
|
|
||||||
+
|
|
||||||
+options {
|
|
||||||
+ port @PORT@;
|
|
||||||
+ pid-file "named.pid";
|
|
||||||
+ listen-on { 10.53.0.3; };
|
|
||||||
+ listen-on-v6 { none; };
|
|
||||||
+ recursion no;
|
|
||||||
+ dnssec-validation no;
|
|
||||||
+};
|
|
||||||
+
|
|
||||||
+zone "evil.test." {
|
|
||||||
+ type primary;
|
|
||||||
+ file "evil.db.signed";
|
|
||||||
+};
|
|
||||||
diff --git a/bin/tests/system/dnssec_bypass/ns4/named.conf.j2 b/bin/tests/system/dnssec_bypass/ns4/named.conf.j2
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..039695d9b7
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/dnssec_bypass/ns4/named.conf.j2
|
|
||||||
@@ -0,0 +1,35 @@
|
|
||||||
+/*
|
|
||||||
+ * 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.
|
|
||||||
+ */
|
|
||||||
+
|
|
||||||
+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; };
|
|
||||||
+ allow-transfer { any; };
|
|
||||||
+ recursion yes;
|
|
||||||
+ dnssec-validation yes;
|
|
||||||
+ synth-from-dnssec yes;
|
|
||||||
+};
|
|
||||||
+
|
|
||||||
+trust-anchors {
|
|
||||||
+ @root.domain@ @root.type@ @root.contents@;
|
|
||||||
+};
|
|
||||||
+
|
|
||||||
+zone "." {
|
|
||||||
+ type hint;
|
|
||||||
+ file "../../_common/root.hint";
|
|
||||||
+};
|
|
||||||
diff --git a/bin/tests/system/dnssec_bypass/tests_bypass.py b/bin/tests/system/dnssec_bypass/tests_bypass.py
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000000..c41bb7e016
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/bin/tests/system/dnssec_bypass/tests_bypass.py
|
|
||||||
@@ -0,0 +1,152 @@
|
|
||||||
+# 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 datetime import datetime, timedelta, timezone
|
|
||||||
+
|
|
||||||
+import shutil
|
|
||||||
+
|
|
||||||
+from cryptography.hazmat.primitives.asymmetric import ec
|
|
||||||
+
|
|
||||||
+import dns.dnssec
|
|
||||||
+import dns.name
|
|
||||||
+import dns.rdataclass
|
|
||||||
+import dns.rdataset
|
|
||||||
+import dns.rdatatype
|
|
||||||
+import dns.rrset
|
|
||||||
+import dns.zone
|
|
||||||
+
|
|
||||||
+from isctest.run import EnvCmd
|
|
||||||
+
|
|
||||||
+import isctest
|
|
||||||
+
|
|
||||||
+TTL = 3600
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def bootstrap():
|
|
||||||
+ keygen = EnvCmd("KEYGEN", "-q -a ECDSA256")
|
|
||||||
+ signer = EnvCmd("SIGNER", "-S -g -O full")
|
|
||||||
+
|
|
||||||
+ def sign_regular_zone(ns: str, zone: str, database: str) -> isctest.kasp.Key:
|
|
||||||
+ isctest.log.info(f"{zone}: generate keys")
|
|
||||||
+ keygen(zone, cwd=ns).out.strip()
|
|
||||||
+ ksk = keygen(f"-f KSK {zone}", cwd=ns).out.strip()
|
|
||||||
+
|
|
||||||
+ isctest.log.info(f"{zone}: sign zone")
|
|
||||||
+ signer(f"-o {zone} {database}", cwd=ns)
|
|
||||||
+
|
|
||||||
+ if ns != "ns1":
|
|
||||||
+ shutil.copy(f"{ns}/dsset-{zone}", f"ns1/dsset-{zone}")
|
|
||||||
+ shutil.copy(f"{ns}/{ksk}.key", f"ns1/{ksk}.key")
|
|
||||||
+
|
|
||||||
+ return isctest.kasp.Key(ksk, keydir=ns)
|
|
||||||
+
|
|
||||||
+ # dnssec-signzone and `dns.dnssec.sign_zone` correctly disregard the invalid
|
|
||||||
+ # NSEC entries when signing the zone. However, for this test we actualy *want*
|
|
||||||
+ # to serve invalid yet signed zones. To accomplish this we sign the zone and then
|
|
||||||
+ # replace the correct entries with the faulty ones accompanied by its RRSIG.
|
|
||||||
+ #
|
|
||||||
+ # TODO(aydin): move this to `isctest` to sign broken zones
|
|
||||||
+ def sign_rogue_zone(ns: str, zone: str, database: str) -> None:
|
|
||||||
+ # Read zone.
|
|
||||||
+ origin = dns.name.from_text(zone)
|
|
||||||
+ data = dns.zone.from_file(f"{ns}/{database}", origin=origin, relativize=False)
|
|
||||||
+
|
|
||||||
+ # Get key for signing.
|
|
||||||
+ isctest.log.info(f"{zone}: generate keys")
|
|
||||||
+ private_key = ec.generate_private_key(ec.SECP256R1())
|
|
||||||
+ dnskey = dns.dnssec.make_dnskey(
|
|
||||||
+ public_key=private_key.public_key(),
|
|
||||||
+ algorithm=dns.dnssec.Algorithm.ECDSAP256SHA256,
|
|
||||||
+ flags=257,
|
|
||||||
+ )
|
|
||||||
+
|
|
||||||
+ # Sign zone.
|
|
||||||
+ isctest.log.info(f"{zone}: sign zone")
|
|
||||||
+ now = datetime.now(timezone.utc)
|
|
||||||
+ inception = now - timedelta(hours=1)
|
|
||||||
+ expiration = now + timedelta(days=30)
|
|
||||||
+
|
|
||||||
+ for name, node in data.nodes.items():
|
|
||||||
+ owner = name.derelativize(origin)
|
|
||||||
+ rdatasets = list(node.rdatasets)
|
|
||||||
+
|
|
||||||
+ for rdataset in rdatasets:
|
|
||||||
+ rrset = dns.rrset.RRset(owner, rdataset.rdclass, rdataset.rdtype)
|
|
||||||
+ rrset.update(rdataset)
|
|
||||||
+
|
|
||||||
+ rrsig = dns.dnssec.sign(
|
|
||||||
+ rrset=rrset,
|
|
||||||
+ private_key=private_key,
|
|
||||||
+ signer=origin,
|
|
||||||
+ dnskey=dnskey,
|
|
||||||
+ inception=inception,
|
|
||||||
+ expiration=expiration,
|
|
||||||
+ deterministic=False,
|
|
||||||
+ )
|
|
||||||
+
|
|
||||||
+ rdataset = dns.rdataset.Rdataset(rrset.rdclass, dns.rdatatype.RRSIG)
|
|
||||||
+ rdataset.add(rrsig, rrset.ttl)
|
|
||||||
+ node.replace_rdataset(rdataset)
|
|
||||||
+
|
|
||||||
+ # Sign DNSKEY RRset.
|
|
||||||
+ dnskey_rrset = dns.rrset.RRset(origin, dns.rdataclass.IN, dns.rdatatype.DNSKEY)
|
|
||||||
+ dnskey_rrset.add(dnskey, ttl=TTL)
|
|
||||||
+
|
|
||||||
+ apex_node = data.nodes[origin]
|
|
||||||
+ apex_node.replace_rdataset(dnskey_rrset)
|
|
||||||
+
|
|
||||||
+ rrsig = dns.dnssec.sign(
|
|
||||||
+ rrset=dnskey_rrset,
|
|
||||||
+ private_key=private_key,
|
|
||||||
+ signer=origin,
|
|
||||||
+ dnskey=dnskey,
|
|
||||||
+ inception=inception,
|
|
||||||
+ expiration=expiration,
|
|
||||||
+ deterministic=False,
|
|
||||||
+ )
|
|
||||||
+ rdataset = dns.rdataset.Rdataset(rrset.rdclass, dns.rdatatype.RRSIG)
|
|
||||||
+ rdataset.add(rrsig, dnskey_rrset.ttl)
|
|
||||||
+ apex_node.replace_rdataset(rdataset)
|
|
||||||
+
|
|
||||||
+ # Output zone.
|
|
||||||
+ data.to_file(f"{ns}/{database}.signed", relativize=False)
|
|
||||||
+
|
|
||||||
+ # Output DS.
|
|
||||||
+ ds = dns.dnssec.make_ds(name=origin, key=dnskey, algorithm="SHA256")
|
|
||||||
+ with open(f"ns1/dsset-{zone}", "w", encoding="utf-8") as f:
|
|
||||||
+ f.write(f"{zone} {TTL} IN DS {ds.to_text()}\n")
|
|
||||||
+
|
|
||||||
+ sign_rogue_zone("ns3", "evil.test.", "evil.db")
|
|
||||||
+ sign_regular_zone("ns2", "victim.test.", "victim.db")
|
|
||||||
+ sign_regular_zone("ns1", "test.", "test.db")
|
|
||||||
+ root_ksk = sign_regular_zone("ns1", ".", "root.db")
|
|
||||||
+
|
|
||||||
+ return {
|
|
||||||
+ "root": root_ksk.into_ta("static-key"),
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+def test_out_of_zone_nsec(ns4):
|
|
||||||
+ isctest.log.info("trying to poison aggressive nsec cache")
|
|
||||||
+ msg = isctest.query.create("nx.evil.test", "A")
|
|
||||||
+ res = isctest.query.tcp(msg, ns4.ip)
|
|
||||||
+ isctest.check.noadflag(res)
|
|
||||||
+
|
|
||||||
+ isctest.log.info("query victim from recursive")
|
|
||||||
+ msg = isctest.query.create("victim.test", "SOA")
|
|
||||||
+ res = isctest.query.tcp(msg, ns4.ip, attempts=1)
|
|
||||||
+ isctest.check.noerror(res)
|
|
||||||
+ isctest.check.adflag(res)
|
|
||||||
+ isctest.check.rr_count_eq(res.answer, 2)
|
|
||||||
+
|
|
||||||
+ isctest.log.info("checking for query history on victim nameserver")
|
|
||||||
+ with open("ns2/named.run", "r", encoding="utf-8") as f:
|
|
||||||
+ assert "(victim.test): query 'victim.test/SOA/IN' approved" in f.read()
|
|
||||||
--
|
|
||||||
2.55.0
|
|
||||||
|
|
||||||
|
|
@ -1,277 +0,0 @@
|
||||||
From 72967445f37a01d28b4ecb0e8f907e22fddd5087 Mon Sep 17 00:00:00 2001
|
|
||||||
From: =?UTF-8?q?Ayd=C4=B1n=20Mercan?= <aydin@isc.org>
|
|
||||||
Date: Thu, 7 May 2026 18:59:20 +0300
|
|
||||||
Subject: [PATCH] Reject out-of-zone NSEC next owner names
|
|
||||||
|
|
||||||
When verifying DNSSEC records, make sure that a next owner name of
|
|
||||||
an NSEC record is a subdomain of the signer field.
|
|
||||||
|
|
||||||
This follows the specification RFC 4034, section 4.1.1:
|
|
||||||
|
|
||||||
Owner names of RRsets for which the given zone is not authoritative
|
|
||||||
(such as glue records) MUST NOT be listed in the Next Domain Name
|
|
||||||
unless at least one authoritative RRset exists at the same owner
|
|
||||||
name.
|
|
||||||
|
|
||||||
While the above paragraph is intended for glue records, it also
|
|
||||||
applies to out-of-zone data.
|
|
||||||
|
|
||||||
(cherry picked from commit 4065512d25b71605b9502bb69dfb903776d35aa9)
|
|
||||||
(cherry picked from commit 058023c66f11d78590d4aa8c4f98946c4c965e21)
|
|
||||||
|
|
||||||
change dns_nsec_requiredtypespresent to dns_nsec_is_legal
|
|
||||||
|
|
||||||
Change `dns_nsec_requiredtypespresent` to `dns_nsec_is_legal` as a
|
|
||||||
function for checking multiple NSEC validity rules.
|
|
||||||
|
|
||||||
Currently we now additionally check for out-of-zone NSEC entries.
|
|
||||||
|
|
||||||
(cherry picked from commit be2a6a497312469890b552907d039d2de0b44ccc)
|
|
||||||
(cherry picked from commit f751e19a30d107f04c2f644aff9f8dab8fed03ab)
|
|
||||||
---
|
|
||||||
lib/dns/dnssec.c | 13 ++++++++++
|
|
||||||
lib/dns/include/dns/dnssec.h | 6 +++++
|
|
||||||
lib/dns/include/dns/nsec.h | 18 ++++++++++----
|
|
||||||
lib/dns/nsec.c | 17 ++++++++++---
|
|
||||||
lib/dns/resolver.c | 48 ++++++++++++++++++++++++++++++++++--
|
|
||||||
lib/ns/query.c | 6 ++---
|
|
||||||
6 files changed, 94 insertions(+), 14 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c
|
|
||||||
index 9b9b1f2bb2..5acaea9ecb 100644
|
|
||||||
--- a/lib/dns/dnssec.c
|
|
||||||
+++ b/lib/dns/dnssec.c
|
|
||||||
@@ -357,8 +357,10 @@ isc_result_t
|
|
||||||
dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
|
|
||||||
bool ignoretime, unsigned int maxbits, isc_mem_t *mctx,
|
|
||||||
dns_rdata_t *sigrdata, dns_name_t *wild) {
|
|
||||||
+ dns_rdata_nsec_t nsec;
|
|
||||||
dns_rdata_rrsig_t sig;
|
|
||||||
dns_fixedname_t fnewname;
|
|
||||||
+ dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
||||||
isc_region_t r;
|
|
||||||
isc_buffer_t envbuf;
|
|
||||||
dns_rdata_t *rdatas;
|
|
||||||
@@ -464,6 +466,17 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
+ /*
|
|
||||||
+ * Check for out of zone NSEC entries.
|
|
||||||
+ */
|
|
||||||
+ if (set->type == dns_rdatatype_nsec) {
|
|
||||||
+ RETERR(dns_rdataset_first(set));
|
|
||||||
+ dns_rdataset_current(set, &rdata);
|
|
||||||
+ RETERR(dns_rdata_tostruct(&rdata, &nsec, NULL));
|
|
||||||
+ if (!dns_name_issubdomain(&nsec.next, &sig.signer)) {
|
|
||||||
+ return DNS_R_NOVALIDNSEC;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
|
|
||||||
again:
|
|
||||||
ret = dst_context_create(key, mctx, DNS_LOGCATEGORY_DNSSEC, false,
|
|
||||||
diff --git a/lib/dns/include/dns/dnssec.h b/lib/dns/include/dns/dnssec.h
|
|
||||||
index cb8fd9dc20..2be11b9144 100644
|
|
||||||
--- a/lib/dns/include/dns/dnssec.h
|
|
||||||
+++ b/lib/dns/include/dns/dnssec.h
|
|
||||||
@@ -151,6 +151,9 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
|
|
||||||
* this record, as this requires a resolver or database.
|
|
||||||
* If 'ignoretime' is true, temporal validity will not be checked.
|
|
||||||
*
|
|
||||||
+ * If 'set' is of type NSEC, this function also verifies that the
|
|
||||||
+ * Next Name is a subdomain of the Signer's Name from 'sigrdata'.
|
|
||||||
+ *
|
|
||||||
* 'maxbits' specifies the maximum number of rsa exponent bits accepted.
|
|
||||||
*
|
|
||||||
* Requires:
|
|
||||||
@@ -173,6 +176,9 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
|
|
||||||
*\li #DNS_R_KEYUNAUTHORIZED - the key cannot sign this data (either
|
|
||||||
* it is not a zone key or its flags prevent
|
|
||||||
* authentication)
|
|
||||||
+ *
|
|
||||||
+ *\li #DNS_R_NOVALIDNSEC - the NSEC rdata is not valid
|
|
||||||
+ *\li #DNS_R_KEYUNAUTHORIZED - the key cannot sign this data
|
|
||||||
*\li DST_R_*
|
|
||||||
*/
|
|
||||||
|
|
||||||
diff --git a/lib/dns/include/dns/nsec.h b/lib/dns/include/dns/nsec.h
|
|
||||||
index 50df8e45f1..1e71bf14e8 100644
|
|
||||||
--- a/lib/dns/include/dns/nsec.h
|
|
||||||
+++ b/lib/dns/include/dns/nsec.h
|
|
||||||
@@ -119,13 +119,21 @@ dns_nsec_noexistnodata(dns_rdatatype_t type, const dns_name_t *name,
|
|
||||||
*/
|
|
||||||
|
|
||||||
bool
|
|
||||||
-dns_nsec_requiredtypespresent(dns_rdataset_t *rdataset);
|
|
||||||
-/*
|
|
||||||
- * Return true if all the NSEC records in rdataset have both
|
|
||||||
- * NSEC and RRSIG present.
|
|
||||||
+dns_nsec_is_legal(dns_rdataset_t *rdataset, const dns_name_t *name);
|
|
||||||
+/**<
|
|
||||||
+ * \brief
|
|
||||||
+ * Validates a rdataset of type NSEC.
|
|
||||||
*
|
|
||||||
- * Requires:
|
|
||||||
+ * This functions checks for the following in the given rdataset:
|
|
||||||
+ * \li All NSEC records have both NSEC and RRSIG present
|
|
||||||
+ * \li All NSEC entries are under the `name`
|
|
||||||
+ *
|
|
||||||
+ * \par Requires:
|
|
||||||
* \li rdataset to be a NSEC rdataset.
|
|
||||||
+ * \li `name` is a valid dns_name_t
|
|
||||||
+ *
|
|
||||||
+ * \retval true if all the checks pass
|
|
||||||
+ * \retval false otherwise
|
|
||||||
*/
|
|
||||||
|
|
||||||
ISC_LANG_ENDDECLS
|
|
||||||
diff --git a/lib/dns/nsec.c b/lib/dns/nsec.c
|
|
||||||
index 80ee8d7d58..5abcce5f7f 100644
|
|
||||||
--- a/lib/dns/nsec.c
|
|
||||||
+++ b/lib/dns/nsec.c
|
|
||||||
@@ -21,6 +21,7 @@
|
|
||||||
#include <isc/util.h>
|
|
||||||
|
|
||||||
#include <dns/db.h>
|
|
||||||
+#include <dns/name.h>
|
|
||||||
#include <dns/nsec.h>
|
|
||||||
#include <dns/rdata.h>
|
|
||||||
#include <dns/rdatalist.h>
|
|
||||||
@@ -497,8 +498,9 @@ dns_nsec_noexistnodata(dns_rdatatype_t type, const dns_name_t *name,
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
-dns_nsec_requiredtypespresent(dns_rdataset_t *nsecset) {
|
|
||||||
- dns_rdataset_t rdataset;
|
|
||||||
+dns_nsec_is_legal(dns_rdataset_t *nsecset, const dns_name_t *name) {
|
|
||||||
+ dns_rdataset_t rdataset = DNS_RDATASET_INIT;
|
|
||||||
+ dns_rdata_nsec_t nsec;
|
|
||||||
isc_result_t result;
|
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
@@ -513,12 +515,19 @@ dns_nsec_requiredtypespresent(dns_rdataset_t *nsecset) {
|
|
||||||
{
|
|
||||||
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
||||||
dns_rdataset_current(&rdataset, &rdata);
|
|
||||||
- if (!dns_nsec_typepresent(&rdata, dns_rdatatype_nsec) ||
|
|
||||||
- !dns_nsec_typepresent(&rdata, dns_rdatatype_rrsig))
|
|
||||||
+
|
|
||||||
+ /* must never fail */
|
|
||||||
+ result = dns_rdata_tostruct(&rdata, &nsec, NULL);
|
|
||||||
+ INSIST(result == ISC_R_SUCCESS);
|
|
||||||
+
|
|
||||||
+ if (!dns_name_issubdomain(&nsec.next, name) ||
|
|
||||||
+ !dns_nsec_typepresent(&rdata, dns_rdatatype_rrsig) ||
|
|
||||||
+ !dns_nsec_typepresent(&rdata, dns_rdatatype_nsec))
|
|
||||||
{
|
|
||||||
dns_rdataset_disassociate(&rdataset);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
+
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
dns_rdataset_disassociate(&rdataset);
|
|
||||||
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
|
|
||||||
index 059ce53a9e..eac67f7a19 100644
|
|
||||||
--- a/lib/dns/resolver.c
|
|
||||||
+++ b/lib/dns/resolver.c
|
|
||||||
@@ -65,7 +65,9 @@
|
|
||||||
#include <dns/rootns.h>
|
|
||||||
#include <dns/stats.h>
|
|
||||||
#include <dns/tsig.h>
|
|
||||||
+#include <dns/types.h>
|
|
||||||
#include <dns/validator.h>
|
|
||||||
+#include <dns/view.h>
|
|
||||||
#include <dns/zone.h>
|
|
||||||
|
|
||||||
/* Detailed logging of fctx attach/detach */
|
|
||||||
@@ -5603,6 +5605,36 @@ fctx_setresult(fetchctx_t *fctx, dns_rdataset_t *rdataset) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
+static bool
|
|
||||||
+get_and_check_signer_name(dns_name_t *signer, dns_rdataset_t *sigrdataset) {
|
|
||||||
+ dns_rdata_rrsig_t rrsig;
|
|
||||||
+ isc_result_t result;
|
|
||||||
+ dns_rdata_t rdata;
|
|
||||||
+
|
|
||||||
+ if (dns_rdataset_first(sigrdataset) != ISC_R_SUCCESS) {
|
|
||||||
+ return false;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ rdata = (dns_rdata_t)DNS_RDATA_INIT;
|
|
||||||
+ dns_rdataset_current(sigrdataset, &rdata);
|
|
||||||
+ result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
|
|
||||||
+ INSIST(result == ISC_R_SUCCESS);
|
|
||||||
+ dns_name_copy(&rrsig.signer, signer);
|
|
||||||
+
|
|
||||||
+ while (dns_rdataset_next(sigrdataset) == ISC_R_SUCCESS) {
|
|
||||||
+ rdata = (dns_rdata_t)DNS_RDATA_INIT;
|
|
||||||
+ dns_rdataset_current(sigrdataset, &rdata);
|
|
||||||
+ result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
|
|
||||||
+ INSIST(result == ISC_R_SUCCESS);
|
|
||||||
+
|
|
||||||
+ if (!dns_name_equal(signer, &rrsig.signer)) {
|
|
||||||
+ return false;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ return true;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
/*
|
|
||||||
* The validator has finished.
|
|
||||||
*/
|
|
||||||
@@ -5633,6 +5665,8 @@ validated(isc_task_t *task, isc_event_t *event) {
|
|
||||||
dns_fixedname_t fwild;
|
|
||||||
dns_name_t *wild = NULL;
|
|
||||||
dns_message_t *message = NULL;
|
|
||||||
+ dns_fixedname_t fsigner;
|
|
||||||
+ dns_name_t *signer = NULL;
|
|
||||||
|
|
||||||
UNUSED(task); /* for now */
|
|
||||||
|
|
||||||
@@ -6021,10 +6055,20 @@ answer_response:
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
- * Don't cache NSEC if missing NSEC or RRSIG types.
|
|
||||||
+ * Don't cache if all the RRSIGs don't have the same
|
|
||||||
+ * signer.
|
|
||||||
+ */
|
|
||||||
+ signer = dns_fixedname_initname(&fsigner);
|
|
||||||
+ if (!get_and_check_signer_name(signer, sigrdataset)) {
|
|
||||||
+ continue;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /*
|
|
||||||
+ * Don't cache NSEC if missing NSEC or RRSIG
|
|
||||||
+ * types.
|
|
||||||
*/
|
|
||||||
if (rdataset->type == dns_rdatatype_nsec &&
|
|
||||||
- !dns_nsec_requiredtypespresent(rdataset))
|
|
||||||
+ !dns_nsec_is_legal(rdataset, signer))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
diff --git a/lib/ns/query.c b/lib/ns/query.c
|
|
||||||
index 2a2ba1daba..8bbcd7ff30 100644
|
|
||||||
--- a/lib/ns/query.c
|
|
||||||
+++ b/lib/ns/query.c
|
|
||||||
@@ -10370,10 +10370,10 @@ query_coveringnsec(query_ctx_t *qctx) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
- * If NSEC or RRSIG are missing from the type map
|
|
||||||
- * reject the NSEC RRset.
|
|
||||||
+ * Check that the NSEC entry is legal.
|
|
||||||
+ * (NSEC + RRSIG present and the entry isn't out-of-zone)
|
|
||||||
*/
|
|
||||||
- if (!dns_nsec_requiredtypespresent(qctx->rdataset)) {
|
|
||||||
+ if (!dns_nsec_is_legal(qctx->rdataset, signer)) {
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
--
|
|
||||||
2.55.0
|
|
||||||
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
From 12f50726b6bd8f6b3ed6709695e0f6893bc865c6 Mon Sep 17 00:00:00 2001
|
|
||||||
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com>
|
|
||||||
Date: Tue, 16 Sep 2025 11:46:03 +0200
|
|
||||||
Subject: [PATCH] Fix expectations on idna system test
|
|
||||||
|
|
||||||
IDNA tests always redirect output into the file. That means its
|
|
||||||
behaviour has changed and is now processing IDN input by default and
|
|
||||||
just disables IDN output by default.
|
|
||||||
|
|
||||||
New behaviour when redirected is the same as +idnin +noidnout, but does
|
|
||||||
not fail hard on input errors.
|
|
||||||
---
|
|
||||||
bin/tests/system/idna/tests.sh | 12 ++++++------
|
|
||||||
1 file changed, 6 insertions(+), 6 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/bin/tests/system/idna/tests.sh b/bin/tests/system/idna/tests.sh
|
|
||||||
index 398b7e1..37f02b1 100644
|
|
||||||
--- a/bin/tests/system/idna/tests.sh
|
|
||||||
+++ b/bin/tests/system/idna/tests.sh
|
|
||||||
@@ -194,7 +194,7 @@ idna_enabled_test() {
|
|
||||||
# Note that ASCII characters are converted to lower-case.
|
|
||||||
|
|
||||||
text="Checking valid non-ASCII label"
|
|
||||||
- idna_test "$text" "" "München" "M\195\188nchen."
|
|
||||||
+ idna_test "$text" "" "München" "xn--mnchen-3ya."
|
|
||||||
idna_test "$text" "+noidnin +noidnout" "München" "M\195\188nchen."
|
|
||||||
idna_test "$text" "+noidnin +idnout" "München" "M\195\188nchen."
|
|
||||||
idna_test "$text" "+idnin +noidnout" "München" "xn--mnchen-3ya."
|
|
||||||
@@ -218,7 +218,7 @@ idna_enabled_test() {
|
|
||||||
# for the valid U-label.
|
|
||||||
|
|
||||||
text="Checking that non-transitional IDNA processing is used"
|
|
||||||
- idna_test "$text" "" "faß.de" "fa\195\159.de."
|
|
||||||
+ idna_test "$text" "" "faß.de" "xn--fa-hia.de."
|
|
||||||
idna_test "$text" "+noidnin +noidnout" "faß.de" "fa\195\159.de."
|
|
||||||
idna_test "$text" "+noidnin +idnout" "faß.de" "fa\195\159.de."
|
|
||||||
idna_test "$text" "+idnin +noidnout" "faß.de" "xn--fa-hia.de."
|
|
||||||
@@ -228,7 +228,7 @@ idna_enabled_test() {
|
|
||||||
# onto the Greek sigma character ("σ") in IDNA2003.
|
|
||||||
|
|
||||||
text="Second check that non-transitional IDNA processing is used"
|
|
||||||
- idna_test "$text" "" "βόλος.com" "\206\178\207\140\206\187\206\191\207\130.com."
|
|
||||||
+ idna_test "$text" "" "βόλος.com" "xn--nxasmm1c.com."
|
|
||||||
idna_test "$text" "+noidnin +noidnout" "βόλος.com" "\206\178\207\140\206\187\206\191\207\130.com."
|
|
||||||
idna_test "$text" "+noidnin +idnout" "βόλος.com" "\206\178\207\140\206\187\206\191\207\130.com."
|
|
||||||
idna_test "$text" "+idnin +noidnout" "βόλος.com" "xn--nxasmm1c.com."
|
|
||||||
@@ -288,7 +288,7 @@ idna_enabled_test() {
|
|
||||||
idna_test "$text" "" "xn--xx" "xn--xx."
|
|
||||||
idna_test "$text" "+noidnin +noidnout" "xn--xx" "xn--xx."
|
|
||||||
idna_fail "$text" "+noidnin +idnout" "xn--xx"
|
|
||||||
- idna_fail "$text" "+idnin +noidnout" "xn--xx"
|
|
||||||
+ idna_test "$text" "+idnin +noidnout" "xn--xx" "xn--xx."
|
|
||||||
idna_fail "$text" "+idnin +idnout" "xn--xx"
|
|
||||||
|
|
||||||
# Fake A-label - the string does not translate to anything.
|
|
||||||
@@ -297,7 +297,7 @@ idna_enabled_test() {
|
|
||||||
idna_test "$text" "" "xn--ahahah" "xn--ahahah."
|
|
||||||
idna_test "$text" "+noidnin +noidnout" "xn--ahahah" "xn--ahahah."
|
|
||||||
idna_fail "$text" "+noidnin +idnout" "xn--ahahah"
|
|
||||||
- idna_fail "$text" "+idnin +noidnout" "xn--ahahah"
|
|
||||||
+ idna_test "$text" "+idnin +noidnout" "xn--ahahah" "xn--ahahah."
|
|
||||||
idna_fail "$text" "+idnin +idnout" "xn--ahahah"
|
|
||||||
|
|
||||||
# Too long a label. The punycode string is too long (at 64 characters).
|
|
||||||
@@ -324,7 +324,7 @@ idna_enabled_test() {
|
|
||||||
# The +[no]idnout options should not have any effect on the test.
|
|
||||||
|
|
||||||
text="Checking invalid input U-label"
|
|
||||||
- idna_test "$text" "" "√.com" "\226\136\154.com."
|
|
||||||
+ idna_test "$text" "" "√.com" "xn--19g.com."
|
|
||||||
idna_test "$text" "+noidnin +noidnout" "√.com" "\226\136\154.com."
|
|
||||||
idna_test "$text" "+noidnin +idnout" "√.com" "\226\136\154.com."
|
|
||||||
idna_test "$text" "+idnin +noidnout" "√.com" "xn--19g.com."
|
|
||||||
--
|
|
||||||
2.51.0
|
|
||||||
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
From fcc50604359a05e24003f3ff51c3812d8f307814 Mon Sep 17 00:00:00 2001
|
|
||||||
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com>
|
|
||||||
Date: Wed, 6 Nov 2024 21:29:47 +0100
|
|
||||||
Subject: [PATCH] Allow always IDN input in dig
|
|
||||||
MIME-Version: 1.0
|
|
||||||
Content-Type: text/plain; charset=UTF-8
|
|
||||||
Content-Transfer-Encoding: 8bit
|
|
||||||
|
|
||||||
Even when stdout is non-interactive terminal, allow unicode characters
|
|
||||||
to be encoded into ACE form. Still disable IDN output, but unless
|
|
||||||
+noidnin or IDN_DISABLE=1 env is detected, consider input as locale
|
|
||||||
defined name.
|
|
||||||
|
|
||||||
Provides more isolated change, which issue #3527 introduced similar
|
|
||||||
behavior into 9.19 with more changes.
|
|
||||||
|
|
||||||
Ignore input IDN errors when stdout is not terminal
|
|
||||||
|
|
||||||
Attempt to prevent visible regressions when enabling IDN on input
|
|
||||||
always. Instead of new hard failures preventing IDN decoding of input
|
|
||||||
name just use original input.
|
|
||||||
|
|
||||||
Should make the change backward compatible. When on interactive terminal
|
|
||||||
behave the same way as before and emit hard errors. Become more
|
|
||||||
forgiving in scripts where stdout leads to script. Decoding output is
|
|
||||||
not enabled there and if input decoding fails, just use input as it was.
|
|
||||||
|
|
||||||
Change dig manual +idnin
|
|
||||||
|
|
||||||
Note in manual IDN input is always enabled. But it silently ignores
|
|
||||||
errors when stdout is not a terminal to prevent regressions.
|
|
||||||
|
|
||||||
Signed-off-by: Petr Menšík <pemensik@redhat.com>
|
|
||||||
---
|
|
||||||
bin/dig/dig.rst | 5 ++---
|
|
||||||
bin/dig/dighost.c | 16 ++++++++++++----
|
|
||||||
2 files changed, 14 insertions(+), 7 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/bin/dig/dig.rst b/bin/dig/dig.rst
|
|
||||||
index 88b0a40307..e2bf3764d3 100644
|
|
||||||
--- a/bin/dig/dig.rst
|
|
||||||
+++ b/bin/dig/dig.rst
|
|
||||||
@@ -453,9 +453,8 @@ abbreviation is unambiguous; for example, :option:`+cd` is equivalent to
|
|
||||||
This option processes [or does not process] IDN domain names on input. This requires
|
|
||||||
``IDN SUPPORT`` to have been enabled at compile time.
|
|
||||||
|
|
||||||
- The default is to process IDN input when standard output is a tty.
|
|
||||||
- The IDN processing on input is disabled when :program:`dig` output is redirected
|
|
||||||
- to files, pipes, and other non-tty file descriptors.
|
|
||||||
+ The default is to process IDN input. The input IDN processing errors are ignored
|
|
||||||
+ when :program:`dig` output is redirected to files, pipes, and other non-tty file descriptors.
|
|
||||||
|
|
||||||
.. option:: +idnout, +noidnout
|
|
||||||
|
|
||||||
diff --git a/bin/dig/dighost.c b/bin/dig/dighost.c
|
|
||||||
index 0f8ac1335c..1307346192 100644
|
|
||||||
--- a/bin/dig/dighost.c
|
|
||||||
+++ b/bin/dig/dighost.c
|
|
||||||
@@ -604,7 +604,7 @@ dig_lookup_t *
|
|
||||||
make_empty_lookup(void) {
|
|
||||||
dig_lookup_t *looknew;
|
|
||||||
#ifdef HAVE_LIBIDN2
|
|
||||||
- bool idn_allowed = isatty(1) ? (getenv("IDN_DISABLE") == NULL) : false;
|
|
||||||
+ bool idn_allowed = (getenv("IDN_DISABLE") == NULL);
|
|
||||||
#endif /* HAVE_LIBIDN2 */
|
|
||||||
|
|
||||||
debug("make_empty_lookup()");
|
|
||||||
@@ -623,7 +623,7 @@ make_empty_lookup(void) {
|
|
||||||
.badcookie = true,
|
|
||||||
#ifdef HAVE_LIBIDN2
|
|
||||||
.idnin = idn_allowed,
|
|
||||||
- .idnout = idn_allowed,
|
|
||||||
+ .idnout = isatty(1) && idn_allowed,
|
|
||||||
#endif /* HAVE_LIBIDN2 */
|
|
||||||
.udpsize = -1,
|
|
||||||
.edns = -1,
|
|
||||||
@@ -4871,8 +4871,16 @@ idn_locale_to_ace(const char *src, char *dst, size_t dstlen) {
|
|
||||||
res = idn2_to_ascii_lz(src, &ascii_src, IDN2_TRANSITIONAL);
|
|
||||||
}
|
|
||||||
if (res != IDN2_OK) {
|
|
||||||
- fatal("'%s' is not a legal IDNA2008 name (%s), use +noidnin",
|
|
||||||
- src, idn2_strerror(res));
|
|
||||||
+ if (isatty(1)) {
|
|
||||||
+ fatal("'%s' is not a legal IDNA2008 name (%s), use +noidnin",
|
|
||||||
+ src, idn2_strerror(res));
|
|
||||||
+ } else {
|
|
||||||
+ /* In case of non-terminal output silently ignore errors
|
|
||||||
+ * in IDN input decoding. */
|
|
||||||
+ (void)strlcpy(dst, src, dstlen);
|
|
||||||
+ resetlocale(LC_ALL);
|
|
||||||
+ return;
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
--
|
|
||||||
2.50.1
|
|
||||||
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
From 3f686891729c7d39d879e8b5bb1aa17d874d265d Mon Sep 17 00:00:00 2001
|
|
||||||
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com>
|
|
||||||
Date: Thu, 19 Jun 2025 19:51:43 +0200
|
|
||||||
Subject: [PATCH] Limit number of additional records fetched
|
|
||||||
|
|
||||||
Limit number of started fetches for additional zone instead of doing
|
|
||||||
none. Keep limit of NS filled with additional records, but present at
|
|
||||||
least some if possible.
|
|
||||||
|
|
||||||
Might help broken implementations relying on receiving addresses in the
|
|
||||||
response for NS query in authoritative zone.
|
|
||||||
---
|
|
||||||
lib/dns/rdataset.c | 11 ++++++-----
|
|
||||||
1 file changed, 6 insertions(+), 5 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/lib/dns/rdataset.c b/lib/dns/rdataset.c
|
|
||||||
index 532e49a..bfa8e37 100644
|
|
||||||
--- a/lib/dns/rdataset.c
|
|
||||||
+++ b/lib/dns/rdataset.c
|
|
||||||
@@ -581,6 +581,7 @@ dns_rdataset_additionaldata(dns_rdataset_t *rdataset,
|
|
||||||
size_t limit) {
|
|
||||||
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
||||||
isc_result_t result;
|
|
||||||
+ size_t n = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* For each rdata in rdataset, call 'add' for each name and type in the
|
|
||||||
@@ -590,10 +591,6 @@ dns_rdataset_additionaldata(dns_rdataset_t *rdataset,
|
|
||||||
REQUIRE(DNS_RDATASET_VALID(rdataset));
|
|
||||||
REQUIRE((rdataset->attributes & DNS_RDATASETATTR_QUESTION) == 0);
|
|
||||||
|
|
||||||
- if (limit != 0 && dns_rdataset_count(rdataset) > limit) {
|
|
||||||
- return DNS_R_TOOMANYRECORDS;
|
|
||||||
- }
|
|
||||||
-
|
|
||||||
result = dns_rdataset_first(rdataset);
|
|
||||||
if (result != ISC_R_SUCCESS) {
|
|
||||||
return result;
|
|
||||||
@@ -603,7 +600,11 @@ dns_rdataset_additionaldata(dns_rdataset_t *rdataset,
|
|
||||||
dns_rdataset_current(rdataset, &rdata);
|
|
||||||
result = dns_rdata_additionaldata(&rdata, owner_name, add, arg);
|
|
||||||
if (result == ISC_R_SUCCESS) {
|
|
||||||
- result = dns_rdataset_next(rdataset);
|
|
||||||
+ if (limit != 0 && ++n >= limit) {
|
|
||||||
+ result = DNS_R_TOOMANYRECORDS;
|
|
||||||
+ } else {
|
|
||||||
+ result = dns_rdataset_next(rdataset);
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
dns_rdata_reset(&rdata);
|
|
||||||
} while (result == ISC_R_SUCCESS);
|
|
||||||
--
|
|
||||||
2.50.1
|
|
||||||
|
|
||||||
|
|
@ -1,897 +0,0 @@
|
||||||
From 5bd1369eb7781ad2b349b99f783a7ed07fb7d6ac Mon Sep 17 00:00:00 2001
|
|
||||||
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com>
|
|
||||||
Date: Thu, 13 Feb 2025 13:20:28 +0100
|
|
||||||
Subject: [PATCH] Backport OpenSSL 3 provider support
|
|
||||||
|
|
||||||
Use gist of 451edf324281d30fbbe5669223dcea331670847c and
|
|
||||||
5fd6cfc625aa84005618236f4cd699c07367a3dc upstream commits, but do not do
|
|
||||||
significant rebase together. Move engine loading of EVP_PKEY from label to
|
|
||||||
openssl_link and copy provider variant from newer.
|
|
||||||
|
|
||||||
Remove legacy RSA calls from _fromlabel to separate engine handling
|
|
||||||
code. Make rsa_check accepting EVP_PKEY pair only and use conditional
|
|
||||||
compilation to verify them. Move checking of max exponent bits to
|
|
||||||
rsa_check too, because it is done from all usages anyway.
|
|
||||||
|
|
||||||
Use rsa_check_legacy in places where bit checking is not necessary.
|
|
||||||
|
|
||||||
Fix keyfromlabel to not use engine parameter for provider keys
|
|
||||||
|
|
||||||
- Rework key checks to not require 'engine' tag, private key
|
|
||||||
is valid with 'label' tag alone
|
|
||||||
|
|
||||||
- Fix _fromlabel() functions to work with engine == NULL
|
|
||||||
|
|
||||||
- Update dst__openssl_fromlabel_engine() to do provider lookup
|
|
||||||
only when engine is not set
|
|
||||||
|
|
||||||
(cherry picked from commit de486d0ec5d5642ddb1820a1269f5406a2bb1c64)
|
|
||||||
|
|
||||||
Use dst_key_t label to signal isprivate property as a downstream
|
|
||||||
alternative to upstream commit 74361b0b6e5a6b17ebeea6afe1ca990395d7a6dd.
|
|
||||||
That would require additional heavier changes.
|
|
||||||
|
|
||||||
Downstream change:
|
|
||||||
Move RSA bits check to legacy, let it use rsa_check for newer
|
|
||||||
|
|
||||||
rsabigexponent tests got broken by this change.
|
|
||||||
---
|
|
||||||
lib/dns/dst_openssl.h | 4 +
|
|
||||||
lib/dns/dst_parse.c | 21 ++---
|
|
||||||
lib/dns/openssl_link.c | 161 +++++++++++++++++++++++++++-----
|
|
||||||
lib/dns/openssldh_link.c | 5 +
|
|
||||||
lib/dns/opensslecdsa_link.c | 109 +++++++++++-----------
|
|
||||||
lib/dns/openssleddsa_link.c | 40 +++-----
|
|
||||||
lib/dns/opensslrsa_link.c | 181 ++++++++++++++----------------------
|
|
||||||
7 files changed, 296 insertions(+), 225 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/lib/dns/dst_openssl.h b/lib/dns/dst_openssl.h
|
|
||||||
index 819af0f..cd386c0 100644
|
|
||||||
--- a/lib/dns/dst_openssl.h
|
|
||||||
+++ b/lib/dns/dst_openssl.h
|
|
||||||
@@ -64,4 +64,8 @@ ENGINE *
|
|
||||||
dst__openssl_getengine(const char *engine);
|
|
||||||
#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
|
|
||||||
|
|
||||||
+isc_result_t
|
|
||||||
+dst__openssl_fromlabel(int key_base_id, const char *engine, const char *label,
|
|
||||||
+ const char *pin, EVP_PKEY **ppub, EVP_PKEY **ppriv);
|
|
||||||
+
|
|
||||||
ISC_LANG_ENDDECLS
|
|
||||||
diff --git a/lib/dns/dst_parse.c b/lib/dns/dst_parse.c
|
|
||||||
index a353b86..7f3fe51 100644
|
|
||||||
--- a/lib/dns/dst_parse.c
|
|
||||||
+++ b/lib/dns/dst_parse.c
|
|
||||||
@@ -195,10 +195,9 @@ check_rsa(const dst_private_t *priv, bool external) {
|
|
||||||
|
|
||||||
mask = (1ULL << TAG_SHIFT) - 1;
|
|
||||||
|
|
||||||
- if (have[TAG_RSA_ENGINE & mask]) {
|
|
||||||
+ if (have[TAG_RSA_LABEL & mask]) {
|
|
||||||
ok = have[TAG_RSA_MODULUS & mask] &&
|
|
||||||
- have[TAG_RSA_PUBLICEXPONENT & mask] &&
|
|
||||||
- have[TAG_RSA_LABEL & mask];
|
|
||||||
+ have[TAG_RSA_PUBLICEXPONENT & mask];
|
|
||||||
} else {
|
|
||||||
ok = have[TAG_RSA_MODULUS & mask] &&
|
|
||||||
have[TAG_RSA_PUBLICEXPONENT & mask] &&
|
|
||||||
@@ -259,11 +258,9 @@ check_ecdsa(const dst_private_t *priv, bool external) {
|
|
||||||
|
|
||||||
mask = (1ULL << TAG_SHIFT) - 1;
|
|
||||||
|
|
||||||
- if (have[TAG_ECDSA_ENGINE & mask]) {
|
|
||||||
- ok = have[TAG_ECDSA_LABEL & mask];
|
|
||||||
- } else {
|
|
||||||
- ok = have[TAG_ECDSA_PRIVATEKEY & mask];
|
|
||||||
- }
|
|
||||||
+ ok = have[TAG_ECDSA_LABEL & mask] ||
|
|
||||||
+ have[TAG_ECDSA_PRIVATEKEY & mask];
|
|
||||||
+
|
|
||||||
return ok ? 0 : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -295,11 +292,9 @@ check_eddsa(const dst_private_t *priv, bool external) {
|
|
||||||
|
|
||||||
mask = (1ULL << TAG_SHIFT) - 1;
|
|
||||||
|
|
||||||
- if (have[TAG_EDDSA_ENGINE & mask]) {
|
|
||||||
- ok = have[TAG_EDDSA_LABEL & mask];
|
|
||||||
- } else {
|
|
||||||
- ok = have[TAG_EDDSA_PRIVATEKEY & mask];
|
|
||||||
- }
|
|
||||||
+ ok = have[TAG_EDDSA_LABEL & mask] ||
|
|
||||||
+ have[TAG_EDDSA_PRIVATEKEY & mask];
|
|
||||||
+
|
|
||||||
return ok ? 0 : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
diff --git a/lib/dns/openssl_link.c b/lib/dns/openssl_link.c
|
|
||||||
index e3a89f4..2495be4 100644
|
|
||||||
--- a/lib/dns/openssl_link.c
|
|
||||||
+++ b/lib/dns/openssl_link.c
|
|
||||||
@@ -44,6 +44,9 @@
|
|
||||||
#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
|
|
||||||
#include <openssl/engine.h>
|
|
||||||
#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
|
|
||||||
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
|
|
||||||
+#include <openssl/store.h>
|
|
||||||
+#endif
|
|
||||||
|
|
||||||
#include "openssl_shim.h"
|
|
||||||
|
|
||||||
@@ -51,6 +54,12 @@
|
|
||||||
static ENGINE *e = NULL;
|
|
||||||
#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
|
|
||||||
|
|
||||||
+#define DST_RET(a) \
|
|
||||||
+ { \
|
|
||||||
+ result = a; \
|
|
||||||
+ goto cleanup; \
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
static void
|
|
||||||
enable_fips_mode(void) {
|
|
||||||
#ifdef HAVE_FIPS_MODE
|
|
||||||
@@ -70,32 +79,28 @@ enable_fips_mode(void) {
|
|
||||||
|
|
||||||
isc_result_t
|
|
||||||
dst__openssl_init(const char *engine) {
|
|
||||||
- isc_result_t result = ISC_R_SUCCESS;
|
|
||||||
-
|
|
||||||
enable_fips_mode();
|
|
||||||
|
|
||||||
-#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
|
|
||||||
if (engine != NULL && *engine == '\0') {
|
|
||||||
engine = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
- if (engine != NULL) {
|
|
||||||
- e = ENGINE_by_id(engine);
|
|
||||||
- if (e == NULL) {
|
|
||||||
- result = DST_R_NOENGINE;
|
|
||||||
- goto cleanup_rm;
|
|
||||||
- }
|
|
||||||
- if (!ENGINE_init(e)) {
|
|
||||||
- result = DST_R_NOENGINE;
|
|
||||||
- goto cleanup_rm;
|
|
||||||
- }
|
|
||||||
- /* This will init the engine. */
|
|
||||||
- if (!ENGINE_set_default(e, ENGINE_METHOD_ALL)) {
|
|
||||||
- result = DST_R_NOENGINE;
|
|
||||||
- goto cleanup_init;
|
|
||||||
- }
|
|
||||||
+ if (engine == NULL) {
|
|
||||||
+ return (ISC_R_SUCCESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
+#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
|
|
||||||
+ e = ENGINE_by_id(engine);
|
|
||||||
+ if (e == NULL) {
|
|
||||||
+ goto cleanup_rm;
|
|
||||||
+ }
|
|
||||||
+ if (!ENGINE_init(e)) {
|
|
||||||
+ goto cleanup_rm;
|
|
||||||
+ }
|
|
||||||
+ /* This will init the engine. */
|
|
||||||
+ if (!ENGINE_set_default(e, ENGINE_METHOD_ALL)) {
|
|
||||||
+ goto cleanup_init;
|
|
||||||
+ }
|
|
||||||
return ISC_R_SUCCESS;
|
|
||||||
cleanup_init:
|
|
||||||
ENGINE_finish(e);
|
|
||||||
@@ -105,10 +110,8 @@ cleanup_rm:
|
|
||||||
}
|
|
||||||
e = NULL;
|
|
||||||
ERR_clear_error();
|
|
||||||
-#else
|
|
||||||
- UNUSED(engine);
|
|
||||||
#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
|
|
||||||
- return result;
|
|
||||||
+ return (DST_R_NOENGINE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
@@ -220,4 +223,120 @@ dst__openssl_getengine(const char *engine) {
|
|
||||||
}
|
|
||||||
#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
|
|
||||||
|
|
||||||
+static isc_result_t
|
|
||||||
+dst__openssl_fromlabel_engine(int key_base_id, const char *engine,
|
|
||||||
+ const char *label,
|
|
||||||
+ EVP_PKEY **ppub, EVP_PKEY **ppriv) {
|
|
||||||
+#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
|
|
||||||
+ isc_result_t result = ISC_R_SUCCESS;
|
|
||||||
+ ENGINE *e = NULL;
|
|
||||||
+ EVP_PKEY *pkey = NULL, *pubpkey = NULL;
|
|
||||||
+
|
|
||||||
+ UNUSED(key_base_id);
|
|
||||||
+
|
|
||||||
+ e = dst__openssl_getengine(engine);
|
|
||||||
+ if (e == NULL) {
|
|
||||||
+ DST_RET(dst__openssl_toresult(DST_R_NOENGINE));
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ pubpkey = ENGINE_load_public_key(e, label, NULL, NULL);
|
|
||||||
+ if (pubpkey == NULL) {
|
|
||||||
+ DST_RET(dst__openssl_toresult2("ENGINE_load_public_key",
|
|
||||||
+ DST_R_OPENSSLFAILURE));
|
|
||||||
+ }
|
|
||||||
+ if (EVP_PKEY_get_base_id(pubpkey) != key_base_id) {
|
|
||||||
+ DST_RET(DST_R_BADKEYTYPE);
|
|
||||||
+ }
|
|
||||||
+ pkey = ENGINE_load_private_key(e, label, NULL, NULL);
|
|
||||||
+ if (pkey == NULL) {
|
|
||||||
+ DST_RET(dst__openssl_toresult2("ENGINE_load_private_key",
|
|
||||||
+ DST_R_OPENSSLFAILURE));
|
|
||||||
+ }
|
|
||||||
+ if (EVP_PKEY_base_id(pkey) != key_base_id) {
|
|
||||||
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
|
|
||||||
+ }
|
|
||||||
+ *ppub = pubpkey;
|
|
||||||
+ *ppriv = pkey;
|
|
||||||
+cleanup:
|
|
||||||
+ return result;
|
|
||||||
+#else
|
|
||||||
+ UNUSED(key_base_id);
|
|
||||||
+ UNUSED(engine);
|
|
||||||
+ UNUSED(label);
|
|
||||||
+ UNUSED(ppub);
|
|
||||||
+ UNUSED(ppriv);
|
|
||||||
+ return DST_R_NOENGINE;
|
|
||||||
+#endif
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+static isc_result_t
|
|
||||||
+dst__openssl_fromlabel_provider(int key_base_id, const char *label, const char *pin,
|
|
||||||
+ EVP_PKEY **ppub, EVP_PKEY **ppriv) {
|
|
||||||
+ UNUSED(pin);
|
|
||||||
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
|
|
||||||
+ isc_result_t result = DST_R_OPENSSLFAILURE;
|
|
||||||
+ OSSL_STORE_CTX *ctx = NULL;
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+ ctx = OSSL_STORE_open(label, NULL, NULL, NULL, NULL);
|
|
||||||
+ if (!ctx) {
|
|
||||||
+ DST_RET(dst__openssl_toresult2("OSSL_STORE_open_ex",
|
|
||||||
+ DST_R_OPENSSLFAILURE));
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ while (!OSSL_STORE_eof(ctx)) {
|
|
||||||
+ OSSL_STORE_INFO *info = OSSL_STORE_load(ctx);
|
|
||||||
+ if (info == NULL) {
|
|
||||||
+ continue;
|
|
||||||
+ }
|
|
||||||
+ switch (OSSL_STORE_INFO_get_type(info)) {
|
|
||||||
+ case OSSL_STORE_INFO_PKEY:
|
|
||||||
+ if (*ppriv != NULL) {
|
|
||||||
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
|
|
||||||
+ }
|
|
||||||
+ *ppriv = OSSL_STORE_INFO_get1_PKEY(info);
|
|
||||||
+ if (EVP_PKEY_get_base_id(*ppriv) != key_base_id) {
|
|
||||||
+ DST_RET(DST_R_BADKEYTYPE);
|
|
||||||
+ }
|
|
||||||
+ break;
|
|
||||||
+ case OSSL_STORE_INFO_PUBKEY:
|
|
||||||
+ if (*ppub != NULL) {
|
|
||||||
+ DST_RET(DST_R_INVALIDPUBLICKEY);
|
|
||||||
+ }
|
|
||||||
+ *ppub = OSSL_STORE_INFO_get1_PUBKEY(info);
|
|
||||||
+ if (EVP_PKEY_get_base_id(*ppub) != key_base_id) {
|
|
||||||
+ DST_RET(DST_R_BADKEYTYPE);
|
|
||||||
+ }
|
|
||||||
+ break;
|
|
||||||
+ }
|
|
||||||
+ OSSL_STORE_INFO_free(info);
|
|
||||||
+ }
|
|
||||||
+ if (*ppriv != NULL && *ppub != NULL) {
|
|
||||||
+ result = ISC_R_SUCCESS;
|
|
||||||
+ }
|
|
||||||
+cleanup:
|
|
||||||
+ OSSL_STORE_close(ctx);
|
|
||||||
+ return result;
|
|
||||||
+#else
|
|
||||||
+ UNUSED(key_base_id);
|
|
||||||
+ UNUSED(label);
|
|
||||||
+ UNUSED(ppub);
|
|
||||||
+ UNUSED(ppriv);
|
|
||||||
+ return (DST_R_OPENSSLFAILURE);
|
|
||||||
+#endif
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+isc_result_t
|
|
||||||
+dst__openssl_fromlabel(int key_base_id, const char *engine, const char *label,
|
|
||||||
+ const char *pin, EVP_PKEY **ppub, EVP_PKEY **ppriv) {
|
|
||||||
+ if (engine == NULL) {
|
|
||||||
+ return (dst__openssl_fromlabel_provider(key_base_id, label,
|
|
||||||
+ pin, ppub, ppriv));
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ return (dst__openssl_fromlabel_engine(key_base_id, engine, label,
|
|
||||||
+ ppub, ppriv));
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
/*! \file */
|
|
||||||
diff --git a/lib/dns/openssldh_link.c b/lib/dns/openssldh_link.c
|
|
||||||
index a4ba0f7..38345e6 100644
|
|
||||||
--- a/lib/dns/openssldh_link.c
|
|
||||||
+++ b/lib/dns/openssldh_link.c
|
|
||||||
@@ -610,6 +610,11 @@ err:
|
|
||||||
|
|
||||||
static bool
|
|
||||||
openssldh_isprivate(const dst_key_t *key) {
|
|
||||||
+ if (key->label != NULL) {
|
|
||||||
+ /* assume that _fromlabel will not pass without loading private key,
|
|
||||||
+ * but for non-exportable key cannot get d value on the object. */
|
|
||||||
+ return true;
|
|
||||||
+ }
|
|
||||||
#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
|
|
||||||
DH *dh = key->keydata.dh;
|
|
||||||
const BIGNUM *priv_key = NULL;
|
|
||||||
diff --git a/lib/dns/opensslecdsa_link.c b/lib/dns/opensslecdsa_link.c
|
|
||||||
index af45fdc..8b49b5d 100644
|
|
||||||
--- a/lib/dns/opensslecdsa_link.c
|
|
||||||
+++ b/lib/dns/opensslecdsa_link.c
|
|
||||||
@@ -617,6 +617,12 @@ opensslecdsa_isprivate(const dst_key_t *key) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ if (key->label != NULL) {
|
|
||||||
+ /* assume that _fromlabel will not pass without loading private key,
|
|
||||||
+ * but for non-exportable key cannot get d value on the object. */
|
|
||||||
+ return true;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
|
|
||||||
eckey = EVP_PKEY_get1_EC_KEY(pkey);
|
|
||||||
|
|
||||||
@@ -916,7 +922,7 @@ cleanup:
|
|
||||||
|
|
||||||
#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
|
|
||||||
static isc_result_t
|
|
||||||
-ecdsa_check(EC_KEY *eckey, EC_KEY *pubeckey) {
|
|
||||||
+ecdsa_check_legacy(EC_KEY *eckey, EC_KEY *pubeckey) {
|
|
||||||
const EC_POINT *pubkey;
|
|
||||||
|
|
||||||
pubkey = EC_KEY_get0_public_key(eckey);
|
|
||||||
@@ -937,9 +943,42 @@ ecdsa_check(EC_KEY *eckey, EC_KEY *pubeckey) {
|
|
||||||
|
|
||||||
return ISC_R_FAILURE;
|
|
||||||
}
|
|
||||||
+
|
|
||||||
+static isc_result_t
|
|
||||||
+ecdsa_check(EVP_PKEY **pkey, EVP_PKEY *pubpkey, int group_nid) {
|
|
||||||
+ isc_result_t result = ISC_R_FAILURE;
|
|
||||||
+ EC_KEY *eckey;
|
|
||||||
+ EC_KEY *pubeckey;
|
|
||||||
+
|
|
||||||
+ eckey = EVP_PKEY_get1_EC_KEY(*pkey);
|
|
||||||
+ if (eckey == NULL) {
|
|
||||||
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
|
|
||||||
+ }
|
|
||||||
+ if (EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != group_nid) {
|
|
||||||
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ pubeckey = EVP_PKEY_get1_EC_KEY(pubpkey);
|
|
||||||
+ if (pubeckey == NULL) {
|
|
||||||
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
|
|
||||||
+ }
|
|
||||||
+ if (EC_GROUP_get_curve_name(EC_KEY_get0_group(pubeckey)) != group_nid) {
|
|
||||||
+ DST_RET(DST_R_INVALIDPUBLICKEY);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ CHECK(ecdsa_check_legacy(eckey, pubeckey));
|
|
||||||
+cleanup:
|
|
||||||
+ if (pubeckey != NULL) {
|
|
||||||
+ EC_KEY_free(pubeckey);
|
|
||||||
+ }
|
|
||||||
+ if (eckey != NULL) {
|
|
||||||
+ EC_KEY_free(eckey);
|
|
||||||
+ }
|
|
||||||
+ return result;
|
|
||||||
+}
|
|
||||||
#else
|
|
||||||
static isc_result_t
|
|
||||||
-ecdsa_check(EVP_PKEY **pkey, EVP_PKEY *pubpkey) {
|
|
||||||
+ecdsa_check(EVP_PKEY **pkey, EVP_PKEY *pubpkey, int group_nid) {
|
|
||||||
isc_result_t result = ISC_R_FAILURE;
|
|
||||||
int status;
|
|
||||||
size_t pkey_len = 0;
|
|
||||||
@@ -954,6 +993,8 @@ ecdsa_check(EVP_PKEY **pkey, EVP_PKEY *pubpkey) {
|
|
||||||
EVP_PKEY_CTX *ctx = NULL;
|
|
||||||
EVP_PKEY *pkey_new = NULL;
|
|
||||||
|
|
||||||
+ UNUSED(group_nid);
|
|
||||||
+
|
|
||||||
/* Check if `pkey` has a public key. */
|
|
||||||
status = EVP_PKEY_get_octet_string_param(*pkey, OSSL_PKEY_PARAM_PUB_KEY,
|
|
||||||
NULL, 0, &pkey_len);
|
|
||||||
@@ -1267,7 +1308,7 @@ opensslecdsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
|
|
||||||
pubeckey = EVP_PKEY_get1_EC_KEY(pub->keydata.pkey);
|
|
||||||
}
|
|
||||||
|
|
||||||
- if (ecdsa_check(eckey, pubeckey) != ISC_R_SUCCESS) {
|
|
||||||
+ if (ecdsa_check_legacy(eckey, pubeckey) != ISC_R_SUCCESS) {
|
|
||||||
DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1276,7 +1317,7 @@ opensslecdsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (ecdsa_check(&key->keydata.pkey,
|
|
||||||
- pub == NULL ? NULL : pub->keydata.pkey) !=
|
|
||||||
+ pub == NULL ? NULL : pub->keydata.pkey, NID_undef) !=
|
|
||||||
ISC_R_SUCCESS)
|
|
||||||
{
|
|
||||||
DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
|
|
||||||
@@ -1309,11 +1350,7 @@ cleanup:
|
|
||||||
static isc_result_t
|
|
||||||
opensslecdsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
|
|
||||||
const char *pin) {
|
|
||||||
-#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
|
|
||||||
isc_result_t result = ISC_R_SUCCESS;
|
|
||||||
- ENGINE *e;
|
|
||||||
- EC_KEY *eckey = NULL;
|
|
||||||
- EC_KEY *pubeckey = NULL;
|
|
||||||
int group_nid;
|
|
||||||
EVP_PKEY *pkey = NULL;
|
|
||||||
EVP_PKEY *pubpkey = NULL;
|
|
||||||
@@ -1323,13 +1360,9 @@ opensslecdsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
|
|
||||||
|
|
||||||
UNUSED(pin);
|
|
||||||
|
|
||||||
- if (engine == NULL || label == NULL) {
|
|
||||||
+ if (label == NULL) {
|
|
||||||
return DST_R_NOENGINE;
|
|
||||||
}
|
|
||||||
- e = dst__openssl_getengine(engine);
|
|
||||||
- if (e == NULL) {
|
|
||||||
- DST_RET(DST_R_NOENGINE);
|
|
||||||
- }
|
|
||||||
|
|
||||||
if (key->key_alg == DST_ALG_ECDSA256) {
|
|
||||||
group_nid = NID_X9_62_prime256v1;
|
|
||||||
@@ -1337,48 +1370,27 @@ opensslecdsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
|
|
||||||
group_nid = NID_secp384r1;
|
|
||||||
}
|
|
||||||
|
|
||||||
- /* Load private key. */
|
|
||||||
- pkey = ENGINE_load_private_key(e, label, NULL, NULL);
|
|
||||||
- if (pkey == NULL) {
|
|
||||||
- DST_RET(dst__openssl_toresult2("ENGINE_load_private_key",
|
|
||||||
- DST_R_OPENSSLFAILURE));
|
|
||||||
- }
|
|
||||||
+ CHECK(dst__openssl_fromlabel(EVP_PKEY_EC, engine, label, pin,
|
|
||||||
+ &pubpkey, &pkey));
|
|
||||||
+
|
|
||||||
/* Check base id, group nid */
|
|
||||||
if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) {
|
|
||||||
DST_RET(DST_R_INVALIDPRIVATEKEY);
|
|
||||||
}
|
|
||||||
- eckey = EVP_PKEY_get1_EC_KEY(pkey);
|
|
||||||
- if (eckey == NULL) {
|
|
||||||
- DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
|
|
||||||
- }
|
|
||||||
- if (EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != group_nid) {
|
|
||||||
- DST_RET(DST_R_INVALIDPRIVATEKEY);
|
|
||||||
- }
|
|
||||||
-
|
|
||||||
- /* Load public key. */
|
|
||||||
- pubpkey = ENGINE_load_public_key(e, label, NULL, NULL);
|
|
||||||
- if (pubpkey == NULL) {
|
|
||||||
- DST_RET(dst__openssl_toresult2("ENGINE_load_public_key",
|
|
||||||
- DST_R_OPENSSLFAILURE));
|
|
||||||
- }
|
|
||||||
/* Check base id, group nid */
|
|
||||||
if (EVP_PKEY_base_id(pubpkey) != EVP_PKEY_EC) {
|
|
||||||
DST_RET(DST_R_INVALIDPUBLICKEY);
|
|
||||||
}
|
|
||||||
- pubeckey = EVP_PKEY_get1_EC_KEY(pubpkey);
|
|
||||||
- if (pubeckey == NULL) {
|
|
||||||
- DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
|
|
||||||
- }
|
|
||||||
- if (EC_GROUP_get_curve_name(EC_KEY_get0_group(pubeckey)) != group_nid) {
|
|
||||||
- DST_RET(DST_R_INVALIDPUBLICKEY);
|
|
||||||
- }
|
|
||||||
|
|
||||||
- if (ecdsa_check(eckey, pubeckey) != ISC_R_SUCCESS) {
|
|
||||||
+ if (ecdsa_check(&pkey, pubpkey, group_nid) != ISC_R_SUCCESS) {
|
|
||||||
DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
|
|
||||||
}
|
|
||||||
|
|
||||||
+ if (engine != NULL)
|
|
||||||
+ key->engine = isc_mem_strdup(key->mctx, engine);
|
|
||||||
+ else
|
|
||||||
+ key->engine = NULL;
|
|
||||||
key->label = isc_mem_strdup(key->mctx, label);
|
|
||||||
- key->engine = isc_mem_strdup(key->mctx, engine);
|
|
||||||
key->key_size = EVP_PKEY_bits(pkey);
|
|
||||||
key->keydata.pkey = pkey;
|
|
||||||
pkey = NULL;
|
|
||||||
@@ -1390,21 +1402,8 @@ cleanup:
|
|
||||||
if (pkey != NULL) {
|
|
||||||
EVP_PKEY_free(pkey);
|
|
||||||
}
|
|
||||||
- if (pubeckey != NULL) {
|
|
||||||
- EC_KEY_free(pubeckey);
|
|
||||||
- }
|
|
||||||
- if (eckey != NULL) {
|
|
||||||
- EC_KEY_free(eckey);
|
|
||||||
- }
|
|
||||||
|
|
||||||
return result;
|
|
||||||
-#else
|
|
||||||
- UNUSED(key);
|
|
||||||
- UNUSED(engine);
|
|
||||||
- UNUSED(label);
|
|
||||||
- UNUSED(pin);
|
|
||||||
- return DST_R_NOENGINE;
|
|
||||||
-#endif /* !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
|
|
||||||
}
|
|
||||||
|
|
||||||
static dst_func_t opensslecdsa_functions = {
|
|
||||||
diff --git a/lib/dns/openssleddsa_link.c b/lib/dns/openssleddsa_link.c
|
|
||||||
index 6301db4..08d505b 100644
|
|
||||||
--- a/lib/dns/openssleddsa_link.c
|
|
||||||
+++ b/lib/dns/openssleddsa_link.c
|
|
||||||
@@ -362,6 +362,12 @@ openssleddsa_isprivate(const dst_key_t *key) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ if (key->label != NULL) {
|
|
||||||
+ /* assume that _fromlabel will not pass without loading private key,
|
|
||||||
+ * but for non-exportable key cannot get d value on the object. */
|
|
||||||
+ return true;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
/* Must have a buffer to actually check if there is a private key. */
|
|
||||||
if (EVP_PKEY_get_raw_private_key(pkey, buf, &len) == 1) {
|
|
||||||
return true;
|
|
||||||
@@ -591,9 +597,7 @@ cleanup:
|
|
||||||
static isc_result_t
|
|
||||||
openssleddsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
|
|
||||||
const char *pin) {
|
|
||||||
-#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
|
|
||||||
isc_result_t result;
|
|
||||||
- ENGINE *e;
|
|
||||||
EVP_PKEY *pkey = NULL, *pubpkey = NULL;
|
|
||||||
int baseid = EVP_PKEY_NONE;
|
|
||||||
|
|
||||||
@@ -616,28 +620,17 @@ openssleddsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
|
|
||||||
return ISC_R_NOTIMPLEMENTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
- if (engine == NULL) {
|
|
||||||
- return DST_R_NOENGINE;
|
|
||||||
- }
|
|
||||||
- e = dst__openssl_getengine(engine);
|
|
||||||
- if (e == NULL) {
|
|
||||||
- return DST_R_NOENGINE;
|
|
||||||
- }
|
|
||||||
- pkey = ENGINE_load_private_key(e, label, NULL, NULL);
|
|
||||||
- if (pkey == NULL) {
|
|
||||||
- return dst__openssl_toresult2("ENGINE_load_private_key",
|
|
||||||
- ISC_R_NOTFOUND);
|
|
||||||
- }
|
|
||||||
- if (EVP_PKEY_base_id(pkey) != baseid) {
|
|
||||||
- DST_RET(DST_R_INVALIDPRIVATEKEY);
|
|
||||||
- }
|
|
||||||
+ DST_RET(dst__openssl_fromlabel(baseid, engine, label, pin,
|
|
||||||
+ &pubpkey, &pkey));
|
|
||||||
|
|
||||||
- pubpkey = ENGINE_load_public_key(e, label, NULL, NULL);
|
|
||||||
- if (eddsa_check(pkey, pubpkey) != ISC_R_SUCCESS) {
|
|
||||||
+ if (EVP_PKEY_base_id(pkey) != baseid) {
|
|
||||||
DST_RET(DST_R_INVALIDPRIVATEKEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
- key->engine = isc_mem_strdup(key->mctx, engine);
|
|
||||||
+ if (engine != NULL)
|
|
||||||
+ key->engine = isc_mem_strdup(key->mctx, engine);
|
|
||||||
+ else
|
|
||||||
+ key->engine = NULL;
|
|
||||||
key->label = isc_mem_strdup(key->mctx, label);
|
|
||||||
key->key_size = EVP_PKEY_bits(pkey);
|
|
||||||
key->keydata.pkey = pkey;
|
|
||||||
@@ -652,13 +645,6 @@ cleanup:
|
|
||||||
EVP_PKEY_free(pkey);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
-#else /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
|
|
||||||
- UNUSED(key);
|
|
||||||
- UNUSED(engine);
|
|
||||||
- UNUSED(label);
|
|
||||||
- UNUSED(pin);
|
|
||||||
- return DST_R_NOENGINE;
|
|
||||||
-#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
|
|
||||||
}
|
|
||||||
|
|
||||||
static dst_func_t openssleddsa_functions = {
|
|
||||||
diff --git a/lib/dns/opensslrsa_link.c b/lib/dns/opensslrsa_link.c
|
|
||||||
index b92e1bf..12210e8 100644
|
|
||||||
--- a/lib/dns/opensslrsa_link.c
|
|
||||||
+++ b/lib/dns/opensslrsa_link.c
|
|
||||||
@@ -545,6 +545,12 @@ opensslrsa_isprivate(const dst_key_t *key) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ if (key->label != NULL) {
|
|
||||||
+ /* assume that _fromlabel will not pass without loading private key,
|
|
||||||
+ * but for non-exportable key cannot get d value on the object. */
|
|
||||||
+ return true;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
|
|
||||||
rsa = EVP_PKEY_get1_RSA(pkey);
|
|
||||||
INSIST(rsa != NULL);
|
|
||||||
@@ -995,7 +1001,7 @@ cleanup:
|
|
||||||
|
|
||||||
#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
|
|
||||||
static isc_result_t
|
|
||||||
-rsa_check(RSA *rsa, RSA *pub) {
|
|
||||||
+rsa_check_legacy(RSA *rsa, RSA *pub) {
|
|
||||||
const BIGNUM *n1 = NULL, *n2 = NULL;
|
|
||||||
const BIGNUM *e1 = NULL, *e2 = NULL;
|
|
||||||
BIGNUM *n = NULL, *e = NULL;
|
|
||||||
@@ -1050,6 +1056,46 @@ rsa_check(RSA *rsa, RSA *pub) {
|
|
||||||
|
|
||||||
return ISC_R_SUCCESS;
|
|
||||||
}
|
|
||||||
+
|
|
||||||
+static isc_result_t
|
|
||||||
+rsa_check(EVP_PKEY *pkey, EVP_PKEY *pubpkey) {
|
|
||||||
+ isc_result_t ret = ISC_R_FAILURE;
|
|
||||||
+ RSA *rsa = NULL, *pubrsa = NULL;
|
|
||||||
+ const BIGNUM *ex = NULL;
|
|
||||||
+
|
|
||||||
+ pubrsa = EVP_PKEY_get1_RSA(pubpkey);
|
|
||||||
+ if (pubrsa == NULL) {
|
|
||||||
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ rsa = EVP_PKEY_get1_RSA(pkey);
|
|
||||||
+ if (rsa == NULL) {
|
|
||||||
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ ret = rsa_check_legacy(rsa, pubrsa);
|
|
||||||
+ if (ret != ISC_R_SUCCESS) {
|
|
||||||
+ DST_RET(ret);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ RSA_get0_key(rsa, NULL, &ex, NULL);
|
|
||||||
+
|
|
||||||
+ if (ex == NULL) {
|
|
||||||
+ DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
|
|
||||||
+ }
|
|
||||||
+ if (BN_num_bits(ex) > RSA_MAX_PUBEXP_BITS) {
|
|
||||||
+ DST_RET(ISC_R_RANGE);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+err:
|
|
||||||
+ if (rsa != NULL) {
|
|
||||||
+ RSA_free(rsa);
|
|
||||||
+ }
|
|
||||||
+ if (pubrsa != NULL) {
|
|
||||||
+ RSA_free(pubrsa);
|
|
||||||
+ }
|
|
||||||
+ return ret;
|
|
||||||
+}
|
|
||||||
#else
|
|
||||||
static isc_result_t
|
|
||||||
rsa_check(EVP_PKEY *pkey, EVP_PKEY *pubpkey) {
|
|
||||||
@@ -1097,6 +1143,10 @@ rsa_check(EVP_PKEY *pkey, EVP_PKEY *pubpkey) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+ if (BN_num_bits(e1) > RSA_MAX_PUBEXP_BITS) {
|
|
||||||
+ DST_RET(ISC_R_RANGE);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
if (EVP_PKEY_eq(pkey, pubpkey) == 1) {
|
|
||||||
DST_RET(ISC_R_SUCCESS);
|
|
||||||
}
|
|
||||||
@@ -1119,6 +1169,10 @@ cleanup:
|
|
||||||
}
|
|
||||||
#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
|
|
||||||
|
|
||||||
+static isc_result_t
|
|
||||||
+opensslrsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
|
|
||||||
+ const char *pin);
|
|
||||||
+
|
|
||||||
static isc_result_t
|
|
||||||
opensslrsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
|
|
||||||
dst_private_t priv;
|
|
||||||
@@ -1131,12 +1185,8 @@ opensslrsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
|
|
||||||
OSSL_PARAM *params = NULL;
|
|
||||||
EVP_PKEY_CTX *ctx = NULL;
|
|
||||||
#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
|
|
||||||
-#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
|
|
||||||
- const BIGNUM *ex = NULL;
|
|
||||||
- ENGINE *ep = NULL;
|
|
||||||
- const char *engine = NULL;
|
|
||||||
-#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
|
|
||||||
isc_mem_t *mctx = NULL;
|
|
||||||
+ const char *engine = NULL;
|
|
||||||
const char *label = NULL;
|
|
||||||
EVP_PKEY *pkey = NULL;
|
|
||||||
BIGNUM *n = NULL, *e = NULL, *d = NULL;
|
|
||||||
@@ -1190,46 +1240,7 @@ opensslrsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
|
|
||||||
* See if we can fetch it.
|
|
||||||
*/
|
|
||||||
if (label != NULL) {
|
|
||||||
-#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
|
|
||||||
- if (engine == NULL) {
|
|
||||||
- DST_RET(DST_R_NOENGINE);
|
|
||||||
- }
|
|
||||||
- ep = dst__openssl_getengine(engine);
|
|
||||||
- if (ep == NULL) {
|
|
||||||
- DST_RET(dst__openssl_toresult(DST_R_NOENGINE));
|
|
||||||
- }
|
|
||||||
- pkey = ENGINE_load_private_key(ep, label, NULL, NULL);
|
|
||||||
- if (pkey == NULL) {
|
|
||||||
- DST_RET(dst__openssl_toresult2("ENGINE_load_private_"
|
|
||||||
- "key",
|
|
||||||
- ISC_R_NOTFOUND));
|
|
||||||
- }
|
|
||||||
- key->engine = isc_mem_strdup(key->mctx, engine);
|
|
||||||
- key->label = isc_mem_strdup(key->mctx, label);
|
|
||||||
-
|
|
||||||
- rsa = EVP_PKEY_get1_RSA(pkey);
|
|
||||||
- if (rsa == NULL) {
|
|
||||||
- DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
|
|
||||||
- }
|
|
||||||
- if (rsa_check(rsa, pubrsa) != ISC_R_SUCCESS) {
|
|
||||||
- DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
|
|
||||||
- }
|
|
||||||
- RSA_get0_key(rsa, NULL, &ex, NULL);
|
|
||||||
-
|
|
||||||
- if (ex == NULL) {
|
|
||||||
- DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
|
|
||||||
- }
|
|
||||||
- if (BN_num_bits(ex) > RSA_MAX_PUBEXP_BITS) {
|
|
||||||
- DST_RET(ISC_R_RANGE);
|
|
||||||
- }
|
|
||||||
-
|
|
||||||
- key->key_size = EVP_PKEY_bits(pkey);
|
|
||||||
- key->keydata.pkey = pkey;
|
|
||||||
- pkey = NULL;
|
|
||||||
- DST_RET(ISC_R_SUCCESS);
|
|
||||||
-#else /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
|
|
||||||
- DST_RET(DST_R_NOENGINE);
|
|
||||||
-#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
|
|
||||||
+ DST_RET(opensslrsa_fromlabel(key, engine, label, NULL));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < priv.nelements; i++) {
|
|
||||||
@@ -1318,9 +1329,14 @@ opensslrsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
|
|
||||||
BN_clear_free(iqmp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
- if (rsa_check(rsa, pubrsa) != ISC_R_SUCCESS) {
|
|
||||||
+ if (rsa_check_legacy(rsa, pubrsa) != ISC_R_SUCCESS) {
|
|
||||||
DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
|
|
||||||
}
|
|
||||||
+
|
|
||||||
+ if (BN_num_bits(e) > RSA_MAX_PUBEXP_BITS) {
|
|
||||||
+ DST_RET(ISC_R_RANGE);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
#else
|
|
||||||
bld = OSSL_PARAM_BLD_new();
|
|
||||||
if (bld == NULL) {
|
|
||||||
@@ -1387,17 +1403,9 @@ opensslrsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
|
|
||||||
DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
|
|
||||||
}
|
|
||||||
|
|
||||||
- if (rsa_check(pkey, pub != NULL ? pub->keydata.pkey : NULL) !=
|
|
||||||
- ISC_R_SUCCESS)
|
|
||||||
- {
|
|
||||||
- DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
|
|
||||||
- }
|
|
||||||
+ CHECK(rsa_check(pkey, pub != NULL ? pub->keydata.pkey : NULL));
|
|
||||||
#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
|
|
||||||
|
|
||||||
- if (BN_num_bits(e) > RSA_MAX_PUBEXP_BITS) {
|
|
||||||
- DST_RET(ISC_R_RANGE);
|
|
||||||
- }
|
|
||||||
-
|
|
||||||
key->key_size = BN_num_bits(n);
|
|
||||||
key->keydata.pkey = pkey;
|
|
||||||
pkey = NULL;
|
|
||||||
@@ -1461,69 +1469,31 @@ cleanup:
|
|
||||||
static isc_result_t
|
|
||||||
opensslrsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
|
|
||||||
const char *pin) {
|
|
||||||
-#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
|
|
||||||
- ENGINE *e = NULL;
|
|
||||||
isc_result_t result = ISC_R_SUCCESS;
|
|
||||||
EVP_PKEY *pkey = NULL, *pubpkey = NULL;
|
|
||||||
- RSA *rsa = NULL, *pubrsa = NULL;
|
|
||||||
- const BIGNUM *ex = NULL;
|
|
||||||
|
|
||||||
UNUSED(pin);
|
|
||||||
|
|
||||||
- if (engine == NULL) {
|
|
||||||
- DST_RET(DST_R_NOENGINE);
|
|
||||||
- }
|
|
||||||
- e = dst__openssl_getengine(engine);
|
|
||||||
- if (e == NULL) {
|
|
||||||
- DST_RET(dst__openssl_toresult(DST_R_NOENGINE));
|
|
||||||
- }
|
|
||||||
+ CHECK(dst__openssl_fromlabel(EVP_PKEY_RSA, engine, label, pin,
|
|
||||||
+ &pubpkey, &pkey));
|
|
||||||
+ CHECK(rsa_check(pkey, pubpkey));
|
|
||||||
|
|
||||||
- pubpkey = ENGINE_load_public_key(e, label, NULL, NULL);
|
|
||||||
- if (pubpkey == NULL) {
|
|
||||||
- DST_RET(dst__openssl_toresult2("ENGINE_load_public_key",
|
|
||||||
- DST_R_OPENSSLFAILURE));
|
|
||||||
- }
|
|
||||||
- pubrsa = EVP_PKEY_get1_RSA(pubpkey);
|
|
||||||
- if (pubrsa == NULL) {
|
|
||||||
- DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
|
|
||||||
- }
|
|
||||||
-
|
|
||||||
- pkey = ENGINE_load_private_key(e, label, NULL, NULL);
|
|
||||||
if (pkey == NULL) {
|
|
||||||
- DST_RET(dst__openssl_toresult2("ENGINE_load_private_key",
|
|
||||||
+ DST_RET(dst__openssl_toresult2("dst__openssl_fromlabel",
|
|
||||||
DST_R_OPENSSLFAILURE));
|
|
||||||
}
|
|
||||||
|
|
||||||
- key->engine = isc_mem_strdup(key->mctx, engine);
|
|
||||||
+ if (engine != NULL)
|
|
||||||
+ key->engine = isc_mem_strdup(key->mctx, engine);
|
|
||||||
+ else
|
|
||||||
+ key->engine = NULL;
|
|
||||||
key->label = isc_mem_strdup(key->mctx, label);
|
|
||||||
|
|
||||||
- rsa = EVP_PKEY_get1_RSA(pkey);
|
|
||||||
- if (rsa == NULL) {
|
|
||||||
- DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
|
|
||||||
- }
|
|
||||||
- if (rsa_check(rsa, pubrsa) != ISC_R_SUCCESS) {
|
|
||||||
- DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
|
|
||||||
- }
|
|
||||||
- RSA_get0_key(rsa, NULL, &ex, NULL);
|
|
||||||
-
|
|
||||||
- if (ex == NULL) {
|
|
||||||
- DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
|
|
||||||
- }
|
|
||||||
- if (BN_num_bits(ex) > RSA_MAX_PUBEXP_BITS) {
|
|
||||||
- DST_RET(ISC_R_RANGE);
|
|
||||||
- }
|
|
||||||
-
|
|
||||||
key->key_size = EVP_PKEY_bits(pkey);
|
|
||||||
key->keydata.pkey = pkey;
|
|
||||||
pkey = NULL;
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
- if (rsa != NULL) {
|
|
||||||
- RSA_free(rsa);
|
|
||||||
- }
|
|
||||||
- if (pubrsa != NULL) {
|
|
||||||
- RSA_free(pubrsa);
|
|
||||||
- }
|
|
||||||
if (pkey != NULL) {
|
|
||||||
EVP_PKEY_free(pkey);
|
|
||||||
}
|
|
||||||
@@ -1531,13 +1501,6 @@ cleanup:
|
|
||||||
EVP_PKEY_free(pubpkey);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
-#else /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
|
|
||||||
- UNUSED(key);
|
|
||||||
- UNUSED(engine);
|
|
||||||
- UNUSED(label);
|
|
||||||
- UNUSED(pin);
|
|
||||||
- return DST_R_NOENGINE;
|
|
||||||
-#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
|
|
||||||
}
|
|
||||||
|
|
||||||
static dst_func_t opensslrsa_functions = {
|
|
||||||
--
|
|
||||||
2.52.0
|
|
||||||
|
|
||||||
41
bind-9.18-tests-rbtdb_test-i686.patch
Normal file
41
bind-9.18-tests-rbtdb_test-i686.patch
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
From 37643c9689c2dee85b453879f7145bb393b99dfa Mon Sep 17 00:00:00 2001
|
||||||
|
From: Petr Mensik <pemensik@redhat.com>
|
||||||
|
Date: Fri, 22 Sep 2023 19:54:36 +0200
|
||||||
|
Subject: [PATCH] Skip failing tests on 32 platform
|
||||||
|
|
||||||
|
Reported to fail also on 32 bit arm, but skip it only on current builds.
|
||||||
|
---
|
||||||
|
tests/dns/rbtdb_test.c | 4 ++++
|
||||||
|
1 file changed, 4 insertions(+)
|
||||||
|
|
||||||
|
diff --git a/tests/dns/rbtdb_test.c b/tests/dns/rbtdb_test.c
|
||||||
|
index 59a5786..384a26a 100644
|
||||||
|
--- a/tests/dns/rbtdb_test.c
|
||||||
|
+++ b/tests/dns/rbtdb_test.c
|
||||||
|
@@ -326,6 +326,7 @@ ISC_RUN_TEST_IMPL(overmempurge_bigrdata) {
|
||||||
|
isc_mem_destroy(&mctx2);
|
||||||
|
}
|
||||||
|
|
||||||
|
+#ifndef i386
|
||||||
|
ISC_RUN_TEST_IMPL(overmempurge_longname) {
|
||||||
|
size_t maxcache = 2097152U; /* 2MB - same as DNS_CACHE_MINSIZE */
|
||||||
|
size_t hiwater = maxcache - (maxcache >> 3); /* borrowed from cache.c */
|
||||||
|
@@ -370,12 +371,15 @@ ISC_RUN_TEST_IMPL(overmempurge_longname) {
|
||||||
|
dns_db_detach(&db);
|
||||||
|
isc_mem_destroy(&mctx2);
|
||||||
|
}
|
||||||
|
+#endif
|
||||||
|
|
||||||
|
ISC_TEST_LIST_START
|
||||||
|
ISC_TEST_ENTRY(ownercase)
|
||||||
|
ISC_TEST_ENTRY(setownercase)
|
||||||
|
ISC_TEST_ENTRY(overmempurge_bigrdata)
|
||||||
|
+#ifndef i386
|
||||||
|
ISC_TEST_ENTRY(overmempurge_longname)
|
||||||
|
+#endif
|
||||||
|
ISC_TEST_LIST_END
|
||||||
|
|
||||||
|
ISC_TEST_MAIN
|
||||||
|
--
|
||||||
|
2.41.0
|
||||||
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
From c5c756ce2ac4c1563d024428e148ca27c7721f71 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Aram Sargsyan <aram@isc.org>
|
|
||||||
Date: Wed, 21 Sep 2022 15:05:11 +0000
|
|
||||||
Subject: [PATCH 2/3] Document nsupdate options related to DoT
|
|
||||||
|
|
||||||
Add documentation for the newly implemented DoT feature of the
|
|
||||||
nsupdate program.
|
|
||||||
|
|
||||||
(cherry picked from commit bd8299d7b501234263a6aee98049f879b1c700b7)
|
|
||||||
---
|
|
||||||
bin/nsupdate/nsupdate.rst | 48 ++++++++++++++++++++++++++++++++++++++-
|
|
||||||
1 file changed, 47 insertions(+), 1 deletion(-)
|
|
||||||
|
|
||||||
diff --git a/bin/nsupdate/nsupdate.rst b/bin/nsupdate/nsupdate.rst
|
|
||||||
index 81bb4815cf4..f1ab5c76fa7 100644
|
|
||||||
--- a/bin/nsupdate/nsupdate.rst
|
|
||||||
+++ b/bin/nsupdate/nsupdate.rst
|
|
||||||
@@ -19,7 +19,7 @@ nsupdate - dynamic DNS update utility
|
|
||||||
Synopsis
|
|
||||||
~~~~~~~~
|
|
||||||
|
|
||||||
-:program:`nsupdate` [**-d**] [**-D**] [**-i**] [**-L** level] [ [**-g**] | [**-o**] | [**-l**] | [**-y** [hmac:]keyname:secret] | [**-k** keyfile] ] [**-t** timeout] [**-u** udptimeout] [**-r** udpretries] [**-v**] [**-T**] [**-P**] [**-V**] [ [**-4**] | [**-6**] ] [filename]
|
|
||||||
+:program:`nsupdate` [**-d**] [**-D**] [**-i**] [**-L** level] [ [**-g**] | [**-o**] | [**-l**] | [**-y** [hmac:]keyname:secret] | [**-k** keyfile] ] [ [**-S**] [**-K** tlskeyfile] [**-E** tlscertfile] [**-A** tlscafile] [**-H** tlshostname] [-O] ] [**-t** timeout] [**-u** udptimeout] [**-r** udpretries] [**-v**] [**-T**] [**-P**] [**-V**] [ [**-4**] | [**-6**] ] [filename]
|
|
||||||
|
|
||||||
Description
|
|
||||||
~~~~~~~~~~~
|
|
||||||
@@ -71,6 +71,15 @@ Options
|
|
||||||
|
|
||||||
This option sets use of IPv6 only.
|
|
||||||
|
|
||||||
+.. option:: -A tlscafile
|
|
||||||
+
|
|
||||||
+ This option specifies the file of the certificate authorities (CA) certificates
|
|
||||||
+ (in PEM format) in order to verify the remote server TLS certificate when
|
|
||||||
+ using DNS-over-TLS (DoT), to achieve Strict or Mutual TLS. When used, it will
|
|
||||||
+ override the certificates from the global certificates store, which are
|
|
||||||
+ otherwise used by default when :option:`-S` is enabled. This option can not
|
|
||||||
+ be used in conjuction with :option:`-O`, and it implies :option:`-S`.
|
|
||||||
+
|
|
||||||
.. option:: -C
|
|
||||||
|
|
||||||
Overrides the default `resolv.conf` file. This is only intended for testing.
|
|
||||||
@@ -84,10 +93,23 @@ Options
|
|
||||||
|
|
||||||
This option sets extra debug mode.
|
|
||||||
|
|
||||||
+.. option:: -E tlscertfile
|
|
||||||
+
|
|
||||||
+ This option sets the certificate(s) file for authentication for the
|
|
||||||
+ DNS-over-TLS (DoT) transport to the remote server. The certificate
|
|
||||||
+ chain file is expected to be in PEM format. This option implies :option:`-S`,
|
|
||||||
+ and can only be used with :option:`-K`.
|
|
||||||
+
|
|
||||||
.. option:: -g
|
|
||||||
|
|
||||||
This option enables standard GSS-TSIG mode.
|
|
||||||
|
|
||||||
+.. option:: -H tlshostname
|
|
||||||
+
|
|
||||||
+ This option makes :program:`nsupdate` use the provided hostname during remote
|
|
||||||
+ server TLS certificate verification. Otherwise, the DNS server name
|
|
||||||
+ is used. This option implies :option:`-S`.
|
|
||||||
+
|
|
||||||
.. option:: -i
|
|
||||||
|
|
||||||
This option forces interactive mode, even when standard input is not a terminal.
|
|
||||||
@@ -104,6 +126,13 @@ Options
|
|
||||||
key used to authenticate Dynamic DNS update requests. In this case,
|
|
||||||
the key specified is not an HMAC-MD5 key.
|
|
||||||
|
|
||||||
+.. option:: -K tlskeyfile
|
|
||||||
+
|
|
||||||
+ This option sets the key file for authenticated encryption for the
|
|
||||||
+ DNS-over-TLS (DoT) transport with the remote server. The private key file is
|
|
||||||
+ expected to be in PEM format. This option implies :option:`-S`, and can only
|
|
||||||
+ be used with :option:`-E`.
|
|
||||||
+
|
|
||||||
.. option:: -l
|
|
||||||
|
|
||||||
This option sets local-host only mode, which sets the server address to localhost
|
|
||||||
@@ -123,6 +152,14 @@ Options
|
|
||||||
This option enables a non-standards-compliant variant of GSS-TSIG
|
|
||||||
used by Windows 2000.
|
|
||||||
|
|
||||||
+.. option:: -O
|
|
||||||
+
|
|
||||||
+ This option enables Opportunistic TLS. When used, the remote peer's TLS
|
|
||||||
+ certificate will not be verified. This option should be used for debugging
|
|
||||||
+ purposes only, and it is not recommended to use it in production. This
|
|
||||||
+ option can not be used in conjuction with :option:`-A`, and it implies
|
|
||||||
+ :option:`-S`.
|
|
||||||
+
|
|
||||||
.. option:: -p port
|
|
||||||
|
|
||||||
This option sets the port to use for connections to a name server. The default is
|
|
||||||
@@ -138,6 +175,15 @@ Options
|
|
||||||
This option sets the number of UDP retries. The default is 3. If zero, only one update
|
|
||||||
request is made.
|
|
||||||
|
|
||||||
+.. option:: -S
|
|
||||||
+
|
|
||||||
+ This option indicates whether to use DNS-over-TLS (DoT) when querying
|
|
||||||
+ name servers specified by ``server servername port`` syntax in the input
|
|
||||||
+ file, and the primary server discovered through a SOA request. When the
|
|
||||||
+ :option:`-K` and :option:`-E` options are used, then the specified TLS
|
|
||||||
+ client certificate and private key pair are used for authentication
|
|
||||||
+ (Mutual TLS). This option implies :option:`-v`.
|
|
||||||
+
|
|
||||||
.. option:: -t timeout
|
|
||||||
|
|
||||||
This option sets the maximum time an update request can take before it is aborted. The
|
|
||||||
--
|
|
||||||
2.47.0
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,45 +0,0 @@
|
||||||
From 32f203d4e3c711cde5b1546a054be42b16436822 Mon Sep 17 00:00:00 2001
|
|
||||||
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com>
|
|
||||||
Date: Fri, 17 Jul 2026 19:42:39 +0200
|
|
||||||
Subject: [PATCH] Support program suffixes of tsig-confgen and ddns-confgen
|
|
||||||
|
|
||||||
Suffixes different than .exe are used on Fedora. But those commands
|
|
||||||
require exact names only. Allow switching between two variants only from
|
|
||||||
prefix. That should work on all platforms. It should support also names
|
|
||||||
like tsig-confgen-9.18 or tsig-confgen-9.20.
|
|
||||||
|
|
||||||
The same case applies to named-checkzone and named-compilezone.
|
|
||||||
---
|
|
||||||
bin/check/named-checkzone.c | 2 +-
|
|
||||||
bin/confgen/tsig-keygen.c | 2 +-
|
|
||||||
2 files changed, 2 insertions(+), 2 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/bin/check/named-checkzone.c b/bin/check/named-checkzone.c
|
|
||||||
index ae8feafc8c..5a1f712463 100644
|
|
||||||
--- a/bin/check/named-checkzone.c
|
|
||||||
+++ b/bin/check/named-checkzone.c
|
|
||||||
@@ -137,7 +137,7 @@ main(int argc, char **argv) {
|
|
||||||
}
|
|
||||||
|
|
||||||
#define PROGCMP(X) \
|
|
||||||
- (strcasecmp(prog_name, X) == 0 || strcasecmp(prog_name, X ".exe") == 0)
|
|
||||||
+ (strncasecmp(prog_name, X, strlen(X)) == 0)
|
|
||||||
|
|
||||||
if (PROGCMP("named-checkzone")) {
|
|
||||||
progmode = progmode_check;
|
|
||||||
diff --git a/bin/confgen/tsig-keygen.c b/bin/confgen/tsig-keygen.c
|
|
||||||
index f0d2f9a245..9b13312b5b 100644
|
|
||||||
--- a/bin/confgen/tsig-keygen.c
|
|
||||||
+++ b/bin/confgen/tsig-keygen.c
|
|
||||||
@@ -113,7 +113,7 @@ main(int argc, char **argv) {
|
|
||||||
}
|
|
||||||
|
|
||||||
#define PROGCMP(X) \
|
|
||||||
- (strcasecmp(progname, X) == 0 || strcasecmp(progname, X ".exe") == 0)
|
|
||||||
+ (strncasecmp(progname, X, strlen(X)) == 0)
|
|
||||||
|
|
||||||
if (PROGCMP("tsig-keygen")) {
|
|
||||||
progmode = progmode_keygen;
|
|
||||||
--
|
|
||||||
2.54.0
|
|
||||||
|
|
||||||
|
|
@ -1,28 +1,17 @@
|
||||||
From 13348a5fc64387bf53ef450688e181100d0ceddb Mon Sep 17 00:00:00 2001
|
|
||||||
From: Petr Mensik <pemensik@redhat.com>
|
|
||||||
Date: Thu, 12 Dec 2024 15:56:13 +0100
|
|
||||||
Subject: [PATCH] Harden named service build flags
|
|
||||||
|
|
||||||
---
|
|
||||||
bin/named/Makefile.am | 5 ++++-
|
|
||||||
1 file changed, 4 insertions(+), 1 deletion(-)
|
|
||||||
|
|
||||||
diff --git a/bin/named/Makefile.am b/bin/named/Makefile.am
|
diff --git a/bin/named/Makefile.am b/bin/named/Makefile.am
|
||||||
index 57a023b..b832e9c 100644
|
index 57a023b..085f2f7 100644
|
||||||
--- a/bin/named/Makefile.am
|
--- a/bin/named/Makefile.am
|
||||||
+++ b/bin/named/Makefile.am
|
+++ b/bin/named/Makefile.am
|
||||||
@@ -33,7 +33,10 @@ endif HAVE_LIBXML2
|
@@ -32,9 +32,12 @@ AM_CPPFLAGS += \
|
||||||
|
endif HAVE_LIBXML2
|
||||||
|
|
||||||
AM_CPPFLAGS += \
|
AM_CPPFLAGS += \
|
||||||
|
+ -fpie \
|
||||||
-DNAMED_LOCALSTATEDIR=\"${localstatedir}\" \
|
-DNAMED_LOCALSTATEDIR=\"${localstatedir}\" \
|
||||||
- -DNAMED_SYSCONFDIR=\"${sysconfdir}\"
|
-DNAMED_SYSCONFDIR=\"${sysconfdir}\"
|
||||||
+ -DNAMED_SYSCONFDIR=\"${sysconfdir}\" \
|
|
||||||
+ -fpie
|
|
||||||
+
|
|
||||||
+AM_LDFLAGS += -pie -Wl,-z,relro,-z,now,-z,nodlopen,-z,noexecstack
|
|
||||||
|
|
||||||
|
+AM_LDFLAGS += -pie -Wl,-z,relro,-z,now,-z,nodlopen,-z,noexecstack
|
||||||
|
+
|
||||||
sbin_PROGRAMS = named
|
sbin_PROGRAMS = named
|
||||||
|
|
||||||
--
|
nodist_named_SOURCES = xsl.c
|
||||||
2.47.1
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
# vim: ft=conf:
|
|
||||||
# TODO: these definitions are in different form in rpm spec %files chroot section
|
|
||||||
# find a way to have it defined only once
|
|
||||||
#defattr(0664,root,named,-)
|
|
||||||
c /var/named/chroot/dev/null 0664 root named - 1:3
|
|
||||||
c /var/named/chroot/dev/random 0664 root named - 1:8
|
|
||||||
c /var/named/chroot/dev/urandom 0664 root named - 1:9
|
|
||||||
c /var/named/chroot/dev/zero 0664 root named - 1:5
|
|
||||||
#defattr(0640,root,named,0750)
|
|
||||||
d /var/named/chroot 0750 root named -
|
|
||||||
d /var/named/chroot/dev 0750 root named -
|
|
||||||
d /var/named/chroot/etc 0750 root named -
|
|
||||||
d /var/named/chroot/etc/named 0750 root named -
|
|
||||||
d /var/named/chroot/etc/pki 0750 root named -
|
|
||||||
d /var/named/chroot/etc/pki/dnssec-keys 0750 root named -
|
|
||||||
d /var/named/chroot/etc/crypto-policies 0750 root named -
|
|
||||||
d /var/named/chroot/etc/crypto-policies/back-ends 0750 root named -
|
|
||||||
d /var/named/chroot/var 0750 root named -
|
|
||||||
d /var/named/chroot/run 0750 root named -
|
|
||||||
#defattr(-,root,root,-)
|
|
||||||
d /var/named/chroot/usr - root root -
|
|
||||||
d /var/named/chroot/usr/lib64 - root root -
|
|
||||||
d /var/named/chroot/usr/lib64/bind - root root -
|
|
||||||
d /var/named/chroot/usr/lib64/named - root root -
|
|
||||||
d /var/named/chroot/usr/share/GeoIP - root root -
|
|
||||||
d /var/named/chroot/usr/share/named - root root -
|
|
||||||
d /var/named/chroot/proc - root root -
|
|
||||||
d /var/named/chroot/proc/sys - root root -
|
|
||||||
d /var/named/chroot/proc/sys/net - root root -
|
|
||||||
d /var/named/chroot/proc/sys/net/ipv4 - root root -
|
|
||||||
#defattr(0660,root,named,01770)
|
|
||||||
d /var/named/chroot/var/named 01770 root named -
|
|
||||||
#defattr(0660,named,named,0770)
|
|
||||||
d /var/named/chroot/var/tmp 0770 named named -
|
|
||||||
d /var/named/chroot/var/log 0770 named named -
|
|
||||||
#defattr(-,named,named,-)
|
|
||||||
d /var/named/chroot/run/named - named named -
|
|
||||||
L /var/named/chroot/var/run - named named - ../run
|
|
||||||
|
|
@ -1,10 +1 @@
|
||||||
# vim: ft=conf:
|
|
||||||
d /run/named 0755 named named -
|
d /run/named 0755 named named -
|
||||||
d /var/named 01770 root named -
|
|
||||||
d /var/named/slaves 0770 named named -
|
|
||||||
d /var/named/data 0770 named named -
|
|
||||||
d /var/named/dynamic 0770 named named -
|
|
||||||
L /var/named/named.ca 0640 named named - ../../../etc/named.ca
|
|
||||||
L /var/named/named.localhost 0640 named named - ../../../usr/share/named/named.localhost
|
|
||||||
L /var/named/named.loopback 0640 named named - ../../../usr/share/named/named.loopback
|
|
||||||
L /var/named/named.empty 0640 named named - ../../../usr/share/named/named.empty
|
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,5 @@ After=named-setup-rndc.service
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
RemainAfterExit=yes
|
RemainAfterExit=yes
|
||||||
ExecStart=/usr/libexec/%{name}/setup-named-chroot.sh /var/named/chroot on /etc/named-chroot.files
|
ExecStart=/usr/libexec/setup-named-chroot.sh /var/named/chroot on /etc/named-chroot.files
|
||||||
ExecStop=/usr/libexec/%{name}/setup-named-chroot.sh /var/named/chroot off /etc/named-chroot.files
|
ExecStop=/usr/libexec/setup-named-chroot.sh /var/named/chroot off /etc/named-chroot.files
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
# if they are missing or empty in target directory.
|
# if they are missing or empty in target directory.
|
||||||
/etc/localtime
|
/etc/localtime
|
||||||
/etc/named.root.key
|
/etc/named.root.key
|
||||||
/etc/named.ca
|
|
||||||
/etc/named.conf
|
/etc/named.conf
|
||||||
/etc/named.rfc1912.zones
|
/etc/named.rfc1912.zones
|
||||||
/etc/rndc.conf
|
/etc/rndc.conf
|
||||||
|
|
@ -18,7 +17,6 @@
|
||||||
/usr/lib64/bind
|
/usr/lib64/bind
|
||||||
/usr/lib/bind
|
/usr/lib/bind
|
||||||
/usr/share/GeoIP
|
/usr/share/GeoIP
|
||||||
/usr/share/named
|
|
||||||
/run/named
|
/run/named
|
||||||
/proc/sys/net/ipv4/ip_local_port_range
|
/proc/sys/net/ipv4/ip_local_port_range
|
||||||
# Warning: the order is important
|
# Warning: the order is important
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,17 @@ After=network.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=forking
|
Type=forking
|
||||||
Environment=OPTIONS=
|
|
||||||
Environment=NAMEDCONF=/etc/named.conf
|
Environment=NAMEDCONF=/etc/named.conf
|
||||||
EnvironmentFile=-/etc/sysconfig/named
|
EnvironmentFile=-/etc/sysconfig/named
|
||||||
Environment=KRB5_KTNAME=/etc/named.keytab
|
Environment=KRB5_KTNAME=/etc/named.keytab
|
||||||
PIDFile=/var/named/chroot/run/named/named.pid
|
PIDFile=/var/named/chroot/run/named/named.pid
|
||||||
|
|
||||||
ExecStartPre=/bin/bash -c 'if [ ! "$DISABLE_ZONE_CHECKING" == "yes" ]; then %{_bindir}/named-checkconf%{program_suffix} -t /var/named/chroot -z "$NAMEDCONF"; else echo "Checking of zone files is disabled"; fi'
|
ExecStartPre=/bin/bash -c 'if [ ! "$DISABLE_ZONE_CHECKING" == "yes" ]; then /usr/bin/named-checkconf -t /var/named/chroot -z "$NAMEDCONF"; else echo "Checking of zone files is disabled"; fi'
|
||||||
ExecStart=%{_sbindir}/named%{program_suffix} -u named -c ${NAMEDCONF} -t /var/named/chroot $OPTIONS
|
ExecStart=/usr/sbin/named -u named -c ${NAMEDCONF} -t /var/named/chroot $OPTIONS
|
||||||
|
|
||||||
ExecReload=/bin/sh -c 'if %{_sbindir}/rndc%{program_suffix} null > /dev/null 2>&1; then %{_sbindir}/rndc%{program_suffix} reload; else %{_bindir}/kill -HUP $MAINPID; fi'
|
ExecReload=/bin/sh -c 'if /usr/sbin/rndc null > /dev/null 2>&1; then /usr/sbin/rndc reload; else /bin/kill -HUP $MAINPID; fi'
|
||||||
|
|
||||||
ExecStop=/bin/sh -c '%{_sbindir}/rndc%{program_suffix} stop > /dev/null 2>&1 || %{_bindir}/kill -TERM $MAINPID'
|
ExecStop=/bin/sh -c '/usr/sbin/rndc stop > /dev/null 2>&1 || /bin/kill -TERM $MAINPID'
|
||||||
|
|
||||||
PrivateTmp=false
|
PrivateTmp=false
|
||||||
Restart=on-abnormal
|
Restart=on-abnormal
|
||||||
|
|
@ -4,4 +4,4 @@ Description=Generate rndc key for BIND (DNS)
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
|
|
||||||
ExecStart=/usr/libexec/%{name}/generate-rndc-key.sh
|
ExecStart=/usr/libexec/generate-rndc-key.sh
|
||||||
|
|
@ -51,7 +51,7 @@ logging {
|
||||||
|
|
||||||
zone "." IN {
|
zone "." IN {
|
||||||
type hint;
|
type hint;
|
||||||
file "/etc/named.ca";
|
file "named.ca";
|
||||||
};
|
};
|
||||||
|
|
||||||
include "/etc/named.rfc1912.zones";
|
include "/etc/named.rfc1912.zones";
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@
|
||||||
missingok
|
missingok
|
||||||
su named named
|
su named named
|
||||||
create 0644 named named
|
create 0644 named named
|
||||||
notifempty
|
|
||||||
postrotate
|
postrotate
|
||||||
/usr/bin/systemctl reload named.service > /dev/null 2>&1 || true
|
/usr/bin/systemctl reload named.service > /dev/null 2>&1 || true
|
||||||
/usr/bin/systemctl reload named-chroot.service > /dev/null 2>&1 || true
|
/usr/bin/systemctl reload named-chroot.service > /dev/null 2>&1 || true
|
||||||
|
/usr/bin/systemctl reload named-sdb.service > /dev/null 2>&1 || true
|
||||||
|
/usr/bin/systemctl reload named-sdb-chroot.service > /dev/null 2>&1 || true
|
||||||
|
/usr/bin/systemctl reload named-pkcs11.service > /dev/null 2>&1 || true
|
||||||
endscript
|
endscript
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
// named.rfc1912.zones:
|
// named.rfc1912.zones:
|
||||||
// vim: ft=named:
|
|
||||||
//
|
//
|
||||||
// Provided by Red Hat caching-nameserver package
|
// Provided by Red Hat caching-nameserver package
|
||||||
//
|
//
|
||||||
|
|
@ -17,30 +16,30 @@
|
||||||
|
|
||||||
zone "localhost.localdomain" IN {
|
zone "localhost.localdomain" IN {
|
||||||
type primary;
|
type primary;
|
||||||
file "/usr/share/named/named.localhost";
|
file "named.localhost";
|
||||||
allow-update { none; };
|
allow-update { none; };
|
||||||
};
|
};
|
||||||
|
|
||||||
zone "localhost" IN {
|
zone "localhost" IN {
|
||||||
type primary;
|
type primary;
|
||||||
file "/usr/share/named/named.localhost";
|
file "named.localhost";
|
||||||
allow-update { none; };
|
allow-update { none; };
|
||||||
};
|
};
|
||||||
|
|
||||||
zone "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa" IN {
|
zone "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa" IN {
|
||||||
type primary;
|
type primary;
|
||||||
file "/usr/share/named/named.loopback";
|
file "named.loopback";
|
||||||
allow-update { none; };
|
allow-update { none; };
|
||||||
};
|
};
|
||||||
|
|
||||||
zone "1.0.0.127.in-addr.arpa" IN {
|
zone "1.0.0.127.in-addr.arpa" IN {
|
||||||
type primary;
|
type primary;
|
||||||
file "/usr/share/named/named.loopback";
|
file "named.loopback";
|
||||||
allow-update { none; };
|
allow-update { none; };
|
||||||
};
|
};
|
||||||
|
|
||||||
zone "0.in-addr.arpa" IN {
|
zone "0.in-addr.arpa" IN {
|
||||||
type primary;
|
type primary;
|
||||||
file "/usr/share/named/named.empty";
|
file "named.empty";
|
||||||
allow-update { none; };
|
allow-update { none; };
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@
|
||||||
; on server FTP.INTERNIC.NET
|
; on server FTP.INTERNIC.NET
|
||||||
; -OR- RS.INTERNIC.NET
|
; -OR- RS.INTERNIC.NET
|
||||||
;
|
;
|
||||||
; last update: December 20, 2023
|
; last update: June 24, 2021
|
||||||
; related version of root zone: 2023122001
|
; related version of root zone: 2021062401
|
||||||
;
|
;
|
||||||
; FORMERLY NS.INTERNIC.NET
|
; FORMERLY NS.INTERNIC.NET
|
||||||
;
|
;
|
||||||
|
|
@ -21,8 +21,8 @@ A.ROOT-SERVERS.NET. 3600000 AAAA 2001:503:ba3e::2:30
|
||||||
; FORMERLY NS1.ISI.EDU
|
; FORMERLY NS1.ISI.EDU
|
||||||
;
|
;
|
||||||
. 3600000 NS B.ROOT-SERVERS.NET.
|
. 3600000 NS B.ROOT-SERVERS.NET.
|
||||||
B.ROOT-SERVERS.NET. 3600000 A 170.247.170.2
|
B.ROOT-SERVERS.NET. 3600000 A 199.9.14.201
|
||||||
B.ROOT-SERVERS.NET. 3600000 AAAA 2801:1b8:10::b
|
B.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:200::b
|
||||||
;
|
;
|
||||||
; FORMERLY C.PSI.NET
|
; FORMERLY C.PSI.NET
|
||||||
;
|
;
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,4 @@ trust-anchors {
|
||||||
# managed key database will be trusted and maintained
|
# managed key database will be trusted and maintained
|
||||||
# automatically.
|
# automatically.
|
||||||
. initial-ds 20326 8 2 "E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D";
|
. initial-ds 20326 8 2 "E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D";
|
||||||
# This key (38696) will be pre-published in the root zone in 2025
|
|
||||||
# and is scheduled to begin signing in late 2026. At that time,
|
|
||||||
# servers which were already using the old key (20326) should roll
|
|
||||||
# seamlessly to this new one via RFC 5011 rollover.
|
|
||||||
. initial-ds 38696 8 2 "683D2D0ACB8C9B712A1948B27F741219298D0A450D612C483AF444A4C0FB2B16";
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
6
named.rwtab
Normal file
6
named.rwtab
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
dirs /var/named
|
||||||
|
|
||||||
|
files /var/named/named.ca
|
||||||
|
files /var/named/named.empty
|
||||||
|
files /var/named/named.localhost
|
||||||
|
files /var/named/named.loopback
|
||||||
26
named.service
Normal file
26
named.service
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Berkeley Internet Name Domain (DNS)
|
||||||
|
Wants=nss-lookup.target
|
||||||
|
Wants=named-setup-rndc.service
|
||||||
|
Before=nss-lookup.target
|
||||||
|
After=named-setup-rndc.service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=forking
|
||||||
|
Environment=NAMEDCONF=/etc/named.conf
|
||||||
|
EnvironmentFile=-/etc/sysconfig/named
|
||||||
|
Environment=KRB5_KTNAME=/etc/named.keytab
|
||||||
|
PIDFile=/run/named/named.pid
|
||||||
|
|
||||||
|
ExecStartPre=/bin/bash -c 'if [ ! "$DISABLE_ZONE_CHECKING" == "yes" ]; then /usr/bin/named-checkconf -z "$NAMEDCONF"; else echo "Checking of zone files is disabled"; fi'
|
||||||
|
ExecStart=/usr/sbin/named -u named -c ${NAMEDCONF} $OPTIONS
|
||||||
|
ExecReload=/bin/sh -c 'if /usr/sbin/rndc null > /dev/null 2>&1; then /usr/sbin/rndc reload; else /bin/kill -HUP $MAINPID; fi'
|
||||||
|
|
||||||
|
ExecStop=/bin/sh -c '/usr/sbin/rndc stop > /dev/null 2>&1 || /bin/kill -TERM $MAINPID'
|
||||||
|
|
||||||
|
PrivateTmp=true
|
||||||
|
Restart=on-abnormal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
[Unit]
|
|
||||||
Description=Berkeley Internet Name Domain (DNS)
|
|
||||||
Wants=nss-lookup.target
|
|
||||||
Wants=named-setup-rndc.service
|
|
||||||
Before=nss-lookup.target
|
|
||||||
After=named-setup-rndc.service
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=forking
|
|
||||||
Environment=OPTIONS=
|
|
||||||
Environment=NAMEDCONF=/etc/named.conf
|
|
||||||
EnvironmentFile=-/etc/sysconfig/named
|
|
||||||
Environment=KRB5_KTNAME=/etc/named.keytab
|
|
||||||
PIDFile=/run/named/named.pid
|
|
||||||
|
|
||||||
ExecStartPre=/bin/bash -c 'if [ ! "$DISABLE_ZONE_CHECKING" == "yes" ]; then %{_sbindir}/named-checkconf%{program_suffix} -z "$NAMEDCONF"; else echo "Checking of zone files is disabled"; fi'
|
|
||||||
ExecStart=%{_sbindir}/named%{program_suffix} -u named -c ${NAMEDCONF} $OPTIONS
|
|
||||||
|
|
||||||
ExecReload=/bin/sh -c 'if %{_sbindir}/rndc%{program_suffix} null > /dev/null 2>&1; then %{_sbindir}/rndc%{program_suffix} reload; else %{_bindir}/kill -HUP $MAINPID; fi'
|
|
||||||
|
|
||||||
ExecStop=/bin/sh -c '%{_sbindir}/rndc%{program_suffix} stop > /dev/null 2>&1 || %{_bindir}/kill -TERM $MAINPID'
|
|
||||||
|
|
||||||
PrivateTmp=true
|
|
||||||
Restart=on-abnormal
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
#Type Name ID GECOS Home directory Shell
|
|
||||||
u named 25 "Named" /var/named /sbin/nologin
|
|
||||||
g named 25
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
summary: Test plan with all beakerlib tests
|
summary: Test plan with all beakerlib tests
|
||||||
environment+:
|
|
||||||
PACKAGE: bind
|
|
||||||
discover:
|
discover:
|
||||||
how: fmf
|
how: fmf
|
||||||
url: https://gitlab.com/redhat/centos-stream/tests/bind.git
|
url: https://src.fedoraproject.org/tests/bind.git
|
||||||
execute:
|
execute:
|
||||||
how: tmt
|
how: tmt
|
||||||
context:
|
|
||||||
component: bind
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
summary: Public (Fedora) Tier1 beakerlib tests
|
summary: Public (Fedora) Tier1 beakerlib tests
|
||||||
environment+:
|
|
||||||
PACKAGE: bind
|
|
||||||
discover:
|
discover:
|
||||||
how: fmf
|
how: fmf
|
||||||
url: https://gitlab.com/redhat/centos-stream/tests/bind.git
|
url: https://src.fedoraproject.org/tests/bind.git
|
||||||
filter: 'tier: 1'
|
filter: 'tier: 1'
|
||||||
execute:
|
execute:
|
||||||
how: tmt
|
how: tmt
|
||||||
context:
|
|
||||||
component: bind
|
|
||||||
|
|
|
||||||
4
sources
4
sources
|
|
@ -1,2 +1,2 @@
|
||||||
SHA512 (bind-9.18.50.tar.xz) = 5ae70c95f19868f41fd609bf34a908c38769401ee5b89e4782b94081032a579c00ba9010b2feb2789a95cfd12289cfe250e6488b3b47ff666bfa8cd7edf74d43
|
SHA512 (bind-9.18.19.tar.xz) = 51af9a246f23afc9ac9a1ef2d793bc91f43fe835b6c4101ad557799ee3aa4253bd12b2f12d9d101c1ce616e2a852a42c5567b031adaaaf06677fcc11c98cf393
|
||||||
SHA512 (bind-9.18.50.tar.xz.asc) = 41df6df786cd33055a3fd6632ffd2968d6600e314140c547bba3452ec9f7726f44df27c3bd53a5a937ad11407d7391641e0b7b1aff7ebf87f25c65e2a76707b6
|
SHA512 (bind-9.18.19.tar.xz.asc) = 3903ff0cf9af09c111572677eb9b2707aa253cf59ed22e67c7a2272ab10087ef8a2bb54b15286f86035e59a5bef02fee01e17cef71c5f8f7a38ce8ec7e1b01a3
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1 @@
|
||||||
. 3600 IN DNSKEY 257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTOiW1vkIbzxeF3+/4RgWOq7HrxRixHlFlExOLAJr5emLvN7SWXgnLh4+B5xQlNVz8Og8kvArMtNROxVQuCaSnIDdD5LKyWbRd2n9WGe2R8PzgCmr3EgVLrjyBxWezF0jLHwVN8efS3rCj/EWgvIWgb9tarpVUDK/b58Da+sqqls3eNbuv7pr+eoZG+SrDK6nWeL3c6H5Apxz7LjVc1uTIdsIXxuOLYA4/ilBmSVIzuDWfdRUfhHdY6+cn8HFRm+2hM8AnXGXws9555KrUB5qihylGa8subX2Nn6UwNR1AkUTV74bU=
|
. 3600 IN DNSKEY 257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTOiW1vkIbzxeF3+/4RgWOq7HrxRixHlFlExOLAJr5emLvN7SWXgnLh4+B5xQlNVz8Og8kvArMtNROxVQuCaSnIDdD5LKyWbRd2n9WGe2R8PzgCmr3EgVLrjyBxWezF0jLHwVN8efS3rCj/EWgvIWgb9tarpVUDK/b58Da+sqqls3eNbuv7pr+eoZG+SrDK6nWeL3c6H5Apxz7LjVc1uTIdsIXxuOLYA4/ilBmSVIzuDWfdRUfhHdY6+cn8HFRm+2hM8AnXGXws9555KrUB5qihylGa8subX2Nn6UwNR1AkUTV74bU=
|
||||||
. 3600 IN DNSKEY 257 3 8 AwEAAa96jeuknZlaeSrvyAJj6ZHv28hhOKkx3rLGXVaC6rXTsDc449/cidltpkyGwCJNnOAlFNKF2jBosZBU5eeHspaQWOmOElZsjICMQMC3aeHbGiShvZsx4wMYSjH8e7Vrhbu6irwCzVBApESjbUdpWWmEnhathWu1jo+siFUiRAAxm9qyJNg/wOZqqzL/dL/q8PkcRU5oUKEpUge71M3ej2/7CPqpdVwuMoTvoB+ZOT4YeGyxMvHmbrxlFzGOHOijtzN+u1TQNatX2XBuzZNQ1K+s2CXkPIZo7s6JgZyvaBevYtxPvYLw4z9mR7K2vaF18UYH9Z9GNUUeayffKC73PYc=
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue